/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.myfaces.orchestra.lib.jsf;

import java.util.Map;

import javax.faces.FacesException;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.orchestra.CoreConfig;
import org.apache.myfaces.orchestra.conversation.ConversationContext;
import org.apache.myfaces.orchestra.conversation.ConversationManager;

/**
 * RequestHandler that ensures that only one thread is processing
 * each ConversationContext at a time.
 *
 * @since 1.1
 */
class ContextLockRequestHandler implements RequestHandler
{
    private Log log = LogFactory.getLog(ContextLockRequestHandler.class);
    private ConversationContext context;
    private boolean lockAcquired = false;

    public void init(FacesContext facesContext) throws FacesException
    {
        if (getSerializeRequests(facesContext))
        {
            // Fetch the ConversationManager instance for the current HttpSession.
            //
            // We do not want to create a ConversationManager instance if one does not exist; that would force an
            // HttpSession to be created in webapps where Orchestra usage only occurs in a small part of the webapp;
            // if the user doesn't visit that part of the app we should not force a session and ConversationManager
            // to be created until they do need it.
            //
            // We also should avoid creating an HttpSession unless one exists (and creating a ConversationManager
            // instance requires a session). This is particularly useful for applications that have cookies turned
            // off (ie use a jsessionid value encoded in the url). In this case, weblets requests will not have
            // the jsessionid but do trigger the creation of a FacesContext, and therefore run this code. If we
            // create a session here, then we will create a separate session for each and every weblets resource
            // request - and they will live until the webapp session timeout expires. Bad. Very bad.
            //
            // Note that if the request being processed includes any code that uses FacesContext.responseWriter
            // then that invokes the ConversationRequestParameterProvider. And that always writes out a contextId
            // which in turn requires creating a ConversationManager. But there are value requests that run the
            //
            // Note that ConversationManager.getInstance requires the FrameworkAdapter to be initialized. 
            ConversationManager manager = ConversationManager.getInstance(false);
            if (manager != null)
            {
                // Fetch a context for this request if one already exists, and lock it
                // so that concurrent requests that affect this context block until
                // this request is complete. Not doing so can cause races for resources
                // within the current context, such as beans or PersistenceContexts.
                //
                // But if the request did not explicitly specify a contextId then we
                // do NOT create a new context at this point. Doing so would create
                // contexts for things like Weblet resource requests, and that context
                // would then just hang around unused until it times out! 
                //
                // Note that a request that does not explicitly specify a contextId
                // might have one created for it later in the request, eg when an
                // orchestra-scoped bean is accessed. However this is not a race
                // condition because nothing else can refer to that newly created
                // id until the response for this request has been sent back to the
                // client browser.
                context = manager.getCurrentRootConversationContext();
                if (context != null)
                {
                    try
                    {
                        if (log.isDebugEnabled())
                        {
                            log.debug("Locking context " + context.getId());
                        }
                        context.lockInterruptablyForCurrentThread();
                        lockAcquired = true;
                    }
                    catch(InterruptedException e)
                    {
                        throw new FacesException(e);
                    }
                }
                else
                {
                    log.debug("No conversation context specified for this request");
                }
            }
            else
            {
                log.debug("No conversation manager exists for this request");
            }
        }
    }

    public void deinit() throws FacesException
    {
        if (context != null)
        {
            if (lockAcquired == true)
            {
                if (log.isDebugEnabled())
                {
                    log.debug("Unlocking context " + context.getId());
                }
                context.unlockForCurrentThread();
            }
            else
            {
                log.debug(
                  "Odd situation: lock never acquired. Perhaps InterruptedException occurred"
                  + " while waiting to get the context lock?");
            }
        }
    }

    private boolean getSerializeRequests(FacesContext facesContext)
    {
        ExternalContext ec = facesContext.getExternalContext();

        // Check for deprecated setting via the OrchestraServletFilter.
        Map reqScope = ec.getRequestMap();
        Boolean serializeRequests = (Boolean) reqScope.get(CoreConfig.SERIALIZE_REQUESTS);
        if (serializeRequests != null)
        {
            return serializeRequests.booleanValue();
        }

        // Check for the normal global init param; true unless "false" is specified
        String value = ec.getInitParameter(CoreConfig.SERIALIZE_REQUESTS);
        return !"false".equals(value); // NON-NLS
    }
}

