All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.trinidadinternal.config.GlobalConfiguratorImpl Maven / Gradle / Ivy

The newest version!
/*
 *  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.trinidadinternal.config;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import java.util.concurrent.locks.ReentrantLock;

import javax.faces.context.ExternalContext;

import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.http.HttpServletRequest;

import org.apache.myfaces.trinidad.config.Configurator;
import org.apache.myfaces.trinidad.context.ExternalContextDecorator;
import org.apache.myfaces.trinidad.context.RequestContext;
import org.apache.myfaces.trinidad.context.RequestContextFactory;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.skin.SkinFactory;
import org.apache.myfaces.trinidad.util.ClassLoaderUtils;
import org.apache.myfaces.trinidad.util.ComponentReference;
import org.apache.myfaces.trinidad.util.ExternalContextUtils;
import org.apache.myfaces.trinidad.util.RequestStateMap;
import org.apache.myfaces.trinidad.util.RequestType;
import org.apache.myfaces.trinidadinternal.context.RequestContextFactoryImpl;
import org.apache.myfaces.trinidadinternal.context.external.ServletCookieMap;
import org.apache.myfaces.trinidadinternal.context.external.ServletRequestHeaderMap;
import org.apache.myfaces.trinidadinternal.context.external.ServletRequestHeaderValuesMap;
import org.apache.myfaces.trinidadinternal.context.external.ServletRequestMap;
import org.apache.myfaces.trinidadinternal.context.external.ServletRequestParameterMap;
import org.apache.myfaces.trinidadinternal.context.external.ServletRequestParameterValuesMap;
import org.apache.myfaces.trinidadinternal.skin.SkinFactoryImpl;
import org.apache.myfaces.trinidadinternal.skin.SkinUtils;

/**
 * This is the implementation of the Trinidad's Global configurator. It provides the entry point for
 * all other configurators. This class is responsible for enforcing the contract outlined by the
 * Configurator abstract class, but allows a more "relaxed" implementation of the APIs called for by
 * the Configurator class, making it more convenient to use ConfiguratorServices from within the
 * Trinidad renderkit. Where appropriate, these differences will be documented for the benifit of
 * the Trindad developer.
 *
 * @see org.apache.myfaces.trinidad.config.Configurator
 * @version $Revision$ $Date$
 */
public final class GlobalConfiguratorImpl
  extends Configurator
{
  /**
   * Returns a GlobalConfigurator instance for the current context's class loader. The
   * GlobalConfigurator is responsible for enforcing the contract on the other methods of this
   * class. This means that if {@link #init(ExternalContext)} is called multiple times, the global
   * configurator will call all subordinate configurators only once.
   *
   * Likewise, the GlobalConfigurator will return exceptions when the contract is expressly violated
   * (like if {@link #getExternalContext(ExternalContext)} is called before a {{@link #beginRequest(ExternalContext)}.
   *
   * @return a GlobalConfigurator or null is one was unable to be obtained.
   */
  static public final GlobalConfiguratorImpl getInstance()
  {
    final ClassLoader loader = Thread.currentThread().getContextClassLoader();

    if (loader == null)
    {
      _LOG.severe("CANNOT_FIND_CONTEXT_CLASS_LOADER");
    }
    else
    {
      synchronized (_CONFIGURATORS)
      {
        GlobalConfiguratorImpl config = _CONFIGURATORS.get(loader);
        if (config == null)
        {
          try
          {
            config = new GlobalConfiguratorImpl();
            _CONFIGURATORS.put(loader, config);
          }
          catch (final RuntimeException e)
          {
            // OC4J was not reporting these errors properly:
            _LOG.severe(e);
            throw e;
          }
          _LOG.fine("GlobalConfigurator has been created.");
        }
        return config;
      }
    }
    return null;
  }

  /**
   * Returns true if the request has not been stated for the current "virtual"
   * request.  In the servlet environment this will be true after
   * {@link #beginRequest(ExternalContext)} is executed and before
   * {@link #endRequest(ExternalContext)} is executed.  This will generally
   * happen once per request.  In the Portlet Environment, the request must be
   * be started and ended at the beginning and end of both the actionRequest
   * and the RenderRequest.
   *
   * @param ec
   * @return
   */
  static public boolean isRequestStarted(ExternalContext ec)
  {
    return (RequestStateMap.getInstance(ec).get(_REQUEST_TYPE) != null);
  }

  /**
   * Returns "true" if the services should be considered enabled or disabled.
   *
   * @param ec
   * @return
   */
  static private final boolean _isDisabled(final ExternalContext ec)
  {
    final Boolean inRequest = (Boolean) RequestStateMap.getInstance(ec).get(_IN_REQUEST);

    if (inRequest == null)
    {
      return isConfiguratorServiceDisabled(ec);
    }
    else
    {
      final boolean disabled = inRequest.booleanValue();
      if (disabled != isConfiguratorServiceDisabled(ec))
      {
        _LOG.warning("Configurator services were disabled after beginRequest was executed.  Cannot disable configurator services");
      }

      return disabled;
    }
  }

  /**
   * Private default constructor. Right now this class is not serializable. If serialization is
   * required, we may wish to make this public. We really don't want people using this though.
   */
  private GlobalConfiguratorImpl()
  {
  }

  /**
   * Executes the beginRequest methods of all of the configurator services. This method will also
   * initizlize the configurator if it has not already been initialized, so there may be no need to
   * call the {@link #init(ExternalContext)} method directly.
   *
   * This method also ensures that the requestContext is attached before the beginRequest methods
   * are called, so there is no reason to initialize the request context before calling this method.
   * In portal environments, it is important to execute this method once for each Portlet action and
   * render request so that the requestContext may be properly initialized even though the
   * underlying services will be called only once per physical request.
   *
   * @param ec the externalContext to use to begin the request.
   *
   * @see Configurator#beginRequest(javax.faces.context.ExternalContext)
   */
  @SuppressWarnings("unchecked") // TODO: remove this for Faces 1.2
  @Override
  public void beginRequest(ExternalContext ec)
  {
    // asserts for debug which disappear in production
    assert ec != null;
    RequestStateMap state = RequestStateMap.getInstance(ec);
    RequestType requestType = (RequestType) state.get(_REQUEST_TYPE);

    // Do per-virtual request stuff
    if (requestType == null)
    {
      // RequestType may change in a portal environment. Make sure it's set up to enforce the
      // contracts
      requestType = ExternalContextUtils.getRequestType(ec);
      RequestStateMap.getInstance(ec).put(_REQUEST_TYPE, requestType);

      // By contract, Configurators beginRequest is only called once per physical request.
      // The globalConfigurator may be called multiple times, however, so we need to enforce
      // the contract.
      if (!_isDisabled(ec))
      {
        // If this hasn't been initialized then please initialize
        if (!_initialized.get())
        {
          init(ec);
        }

        _attachRequestContext(ec);

        if (state.get(_IN_REQUEST) == null)
        {
          _startConfiguratorServiceRequest(ec);
        }
      }
      else
      {
        _LOG.fine("GlobalConfigurator: Configurators have been disabled for this request.");
      }
    }
    else if (!requestType.equals(ExternalContextUtils.getRequestType(ec)))
    {
      // This will happen if the actionRequest was not ended before dispatching to the render
      // request
      throw new IllegalStateException("The previous action request was not ended.");
    }
    else
    {
      _LOG.fine("BeginRequest called multiple times for this request");
    }
  }

  /**
   * Cleans up the current configurator. This will execute the destroy method on all of the
   * configurator services. Generally this will be called by Trinidad's context listener when the
   * context is destroyed, but it may be used manually to allow the configurator to be
   * re-initialized.
   *
   * Calling this method while the configurator is not initialized will not re-execute the destroy
   * methods on the services.
   *
   * @see org.apache.myfaces.trinidad.config.Configurator#destroy()
   */
  @Override
  public void destroy()
  {
    
    if (_initialized.get())
    { 
      try
      {
        //Forces atomic operations with init.  If we are in the middle of an init or another destroy, we'll
        //wait on this lock until our operations are complete.  We then have to recheck our initialized state.
        
        _initLock.lock();
        if(_initialized.get())
        {
          for (final Configurator config: _services)
          {
            try
            {
              config.destroy();
            }
            catch (final Throwable t)
            {
              // we always want to continue to destroy things, so log errors and continue
              _LOG.severe(t);
            }
          }
          _services = null;
          _initialized.set(false);
        }
      }
      finally
      {
        //release any managed threadlocals that may have been used durring destroy
        _releaseManagedThreadLocals();
        _initLock.unlock();
      }
    }
  }

  /**
   * Ends the currently begun request. It is important to note that this should be executed only
   * once per physical request.
   *
   * @see org.apache.myfaces.trinidad.config.Configurator#endRequest(javax.faces.context.ExternalContext)
   */
  @Override
  public void endRequest(ExternalContext ec)
  {
    RequestStateMap state = RequestStateMap.getInstance(ec);

    // do per virtual-request stuff
    if (state.get(_REQUEST_TYPE) != null)
    {
      if (!_isDisabled(ec))
      {
        try
        {
          //Only end services at the end of a writable response.  This will
          //generally be RENDER, RESOURCE, and SERVLET.
          if (ExternalContextUtils.isResponseWritable(ec))
          {
            _endConfiguratorServiceRequest(ec);
          }
        }
        finally
        {
          state.saveState(ec);
          _releaseRequestContext(ec);
        }
      }
      state.remove(_REQUEST_TYPE);
    }
  }

  /**
   * Returns an externalContext for this configurator and all of the configurator services. If this
   * method is executed before {@link #beginRequest(ExternalContext)} then this method will call
   * beginRequest(). It is important to note, however, that even though beginRequest does not need
   * to be explicitly called, {{@link #endRequest(ExternalContext)} does need to be called when the
   * request has completed or the contract to the configurators will be broken.
   *
   * @param ec
   *          the ExternalContext object that should be wrapped.
   *
   * @return a decorated ExternalContext object
   */
  @Override
  public ExternalContext getExternalContext(ExternalContext ec)
  {
    RequestStateMap state = RequestStateMap.getInstance(ec);
    RequestType type = (RequestType) state.get(_REQUEST_TYPE);

    if (type == null)
    {
      beginRequest(ec);
      type = (RequestType) state.get(_REQUEST_TYPE);
    }
    else if (!ExternalContextUtils.getRequestType(ec).equals(type))
    {
      throw new IllegalStateException("The expected request type is not the same as the current request type.");
    }

    if (!_isDisabled(ec))
    {
      if (!type.isPortlet() && _isSetRequestBugPresent(ec))
      {
        //This handles bug 493 against the JSF-RI 1.2_03 and earlier.  If the bug
        //is present in the current system, add a wrapper to fix it

        //TODO sobryan this is somewhat inefficient so should be removed when we
        //are no longer dependant on JSF1.2_03 or earlier.  Still, we only wrap
        //when we have to so it should be no biggy under normal circumstances.
        ec = new ClearRequestExternalContext(ec);
      }

      // Wrap ExternalContexts
      for (Configurator config: _services)
      {
        ec = config.getExternalContext(ec);
      }
    }

    return ec;
  }

  /**
   * Initializes the global configurator and the configurator services. This method need not be
   * called directly as it will be called from {@link #beginRequest(ExternalContext)} if needed. It
   * is also possible to execute this method more then once, although if initialization has already
   * happened then a call to this method will not do anything. To re-initialize this class, call
   * {@link #destroy()} first and then call this method.
   *
   * @param ec
   *          the externalContext needed to initialize this class
   *
   * @see org.apache.myfaces.trinidad.config.Configurator#init(javax.faces.context.ExternalContext)
   */
  @Override
  public void init(ExternalContext ec)
  {
    assert ec != null;
    
    if (!_initialized.get())
    {
      try
      {
        //For thread saftey.  It is possible for two threads to enter this code at the same time.  When
        //the do the second one will wait at the lock until initialization is complete.  Then the AtomicBoolean
        //is checked again for validity.
        _initLock.lock();
        //Check the AtomicBoolean for a change
        if(!_initialized.get())
        {
          _services = ClassLoaderUtils.getServices(Configurator.class.getName());
  
          // Create a new RequestContextFactory is needed
          if (RequestContextFactory.getFactory() == null)
          {
            RequestContextFactory.setFactory(new RequestContextFactoryImpl());
          }
  
          // Create a new SkinFactory if needed.
          if (SkinFactory.getFactory() == null)
          {
            SkinFactory.setFactory(new SkinFactoryImpl());
          }
  
          // register the base skins
          SkinUtils.registerBaseSkins();
  
          for (final Configurator config: _services)
          {
            config.init(ec);
          }
  
          // after the 'services' filters are initialized, then register
          // the skin extensions found in trinidad-skins.xml. This
          // gives a chance to the 'services' filters to create more base
          // skins that the skins in trinidad-skins.xml can extend.
          SkinUtils.registerSkinExtensions(ec);
          _initialized.set(true);
        }
      }
      finally
      {
        //Do cleanup of anything which may have use the thread local manager during
        //init.
        _releaseManagedThreadLocals();
        _initLock.unlock();
      }
    }
    else
    {
      _LOG.warning("CONFIGURATOR_SERVICES_INITIALIZED");
    }
  }

  /**
   * Hackily called by the ThreadLocalResetter to register itself so that the
   * GlobalConfiguratorImpl can tell the ThreadLocalResetter to clean up the
   * ThreadLocals at the appropriate time.
   */
  void __setThreadLocalResetter(ThreadLocalResetter resetter)
  {
    if (resetter == null)
      throw new NullPointerException();

    _threadResetter.set(resetter);
  }

  /**
   * @param ec
   * @return
   */
  @SuppressWarnings("unchecked")
  private void _attachRequestContext(ExternalContext ec)
  {
    // If someone didn't release the RequestContext on an earlier request,
    // then it'd still be around, and trying to create a new one
    // would trigger an exception. We don't want to take down
    // this thread for all eternity, so clean up after poorly-behaved code.
    RequestContext context = RequestContext.getCurrentInstance();
    if (context != null)
    {
      if (_LOG.isWarning())
      {
        _LOG.warning("REQUESTCONTEXT_NOT_PROPERLY_RELEASED");
      }
      _releaseRequestContext(ec);
    }

    // See if we've got a cached RequestContext instance; if so,
    // reattach it
    Object cachedRequestContext = RequestStateMap.getInstance(ec).get(_REQUEST_CONTEXT);

    // Catch both the null scenario and the
    // RequestContext-from-a-different-classloader scenario
    if (cachedRequestContext instanceof RequestContext)
    {
      context = (RequestContext) cachedRequestContext;
      context.attach();
    }
    else
    {
      RequestContextFactory factory = RequestContextFactory.getFactory();
      assert factory != null;
      context = factory.createContext(ec);
      RequestStateMap.getInstance(ec).put(_REQUEST_CONTEXT, context);
    }
  }

  private void _releaseRequestContext(ExternalContext ec)
  {
    RequestContext context = RequestContext.getCurrentInstance();
    if (context != null)
    {
      // ensure that any deferred ComponentReferences are initialized
      _finishComponentReferenceInitialization(ec);

      context.release();
      _releaseManagedThreadLocals();

      assert RequestContext.getCurrentInstance() == null;
    }
  }

  /**
   * Ensure that any ThreadLocals initialized during this request are cleared
   */
  private void _releaseManagedThreadLocals()
  {
    ThreadLocalResetter resetter = _threadResetter.get();

    if (resetter != null)
    {
      resetter.__removeThreadLocals();
    }
  }

  /**
   * Ensure that all DeferredComponentReferences are fully initialized before the
   * request completes
   */
  private void _finishComponentReferenceInitialization(ExternalContext ec)
  {
    Map requestMap = ec.getRequestMap();
    
    Collection> initializeList = (Collection>)
                                             requestMap.get(_FINISH_INITIALIZATION_LIST_KEY);
    
    if ((initializeList != null) && !initializeList.isEmpty())
    {
      for (ComponentReference reference : initializeList)
      {
        reference.ensureInitialization();
      }
      
      // we've initialized everything, so we're done
      initializeList.clear();
    }
  }

  private void _endConfiguratorServiceRequest(final ExternalContext ec)
  {
    // Physical request has now ended
    // Clear the in-request flag
    RequestStateMap.getInstance(ec).remove(_IN_REQUEST);
    if (_services != null)
    {
      for (Configurator config: _services)
      {
        try
        {
          config.endRequest(ec);
        }
        catch (Throwable t)
        {
          _LOG.severe(t);
        }
      }
    }
  }

  @SuppressWarnings("unchecked")
  private void _startConfiguratorServiceRequest(final ExternalContext ec)
  {
    // Physical request has now begun
    final boolean disabled = isConfiguratorServiceDisabled(ec);

    // Tell whether the services were disabled when the requests had begun
    RequestStateMap.getInstance(ec).put(_IN_REQUEST, disabled);

    // If this hasn't been initialized then please initialize
    for (Configurator config: _services)
    {
      try
      {
        config.beginRequest(ec);
      }
      catch (Throwable t)
      {
        _LOG.severe(t);
      }
    }
  }

  static private boolean _isSetRequestBugPresent(ExternalContext ec)
  {
    // This first check is here in order to skip synchronization until
    // absolutely necessary.
    if (!_sSetRequestBugTested)
    {
      synchronized (GlobalConfiguratorImpl.class)
      {
        //This second check is here in case a couple of things enter before the
        //boolean is set.  This is only an exception case and will make it so
        //the initialization code runs only once.
        if (!_sSetRequestBugTested)
        {
          ServletRequest orig = (ServletRequest) ec.getRequest();
          // Call getInitParameterMap() up front
          ec.getInitParameterMap();

          ec.setRequest(new TestRequest(orig));

          _sHasSetRequestBug = !TestRequest.isTestParamPresent(ec);
          _sSetRequestBugTested = true;

          ec.setRequest(orig);
        }
      }
    }

    return _sHasSetRequestBug;
  }

  // This handles an issue with the ExternalContext object prior to
  // JSF1.2_04.

  static private class ClearRequestExternalContext
    extends ExternalContextDecorator
  {
    private ExternalContext _ec;
    private Map _requestCookieMap;
    private Map _requestHeaderMap;
    private Map _requestHeaderValuesMap;
    private Map _requestMap;
    private Map _requestParameterMap;
    private Map _requestParameterValuesMap;

    public ClearRequestExternalContext(ExternalContext ec)
    {
      _ec = ec;
    }

    @Override
    protected ExternalContext getExternalContext()
    {
      return _ec;
    }

    @Override
    public void setRequest(Object request)
    {
      super.setRequest(request);

      // And clear out any of the cached maps, since we should
      // go back and look in the map
      _requestCookieMap = null;
      _requestHeaderMap = null;
      _requestHeaderValuesMap = null;
      _requestMap = null;
      _requestParameterMap = null;
      _requestParameterValuesMap = null;
    }

    @Override
    public Map getRequestCookieMap()
    {
      _checkRequest();
      if (_requestCookieMap == null)
      {

        _requestCookieMap = new ServletCookieMap(_getHttpServletRequest());
      }
      return _requestCookieMap;
    }

    @Override
    public Map getRequestHeaderMap()
    {
      if (_requestHeaderMap == null)
      {
        _requestHeaderMap = new ServletRequestHeaderMap(_getHttpServletRequest());
      }
      return _requestHeaderMap;
    }

    @Override
    public Map getRequestHeaderValuesMap()
    {
      if (_requestHeaderValuesMap == null)
      {
        _requestHeaderValuesMap = new ServletRequestHeaderValuesMap(_getHttpServletRequest());
      }
      return _requestHeaderValuesMap;
    }

    @Override
    public Map getRequestMap()
    {
      _checkRequest();
      if (_requestMap == null)
      {
        _requestMap = new ServletRequestMap((ServletRequest) getRequest());
      }
      return _requestMap;
    }

    @Override
    public Map getRequestParameterMap()
    {
      _checkRequest();
      if (_requestParameterMap == null)
      {
        _requestParameterMap = new ServletRequestParameterMap((ServletRequest) getRequest());
      }
      return _requestParameterMap;
    }

    @Override
    public Map getRequestParameterValuesMap()
    {
      _checkRequest();
      if (_requestParameterValuesMap == null)
      {
        _requestParameterValuesMap =
            new ServletRequestParameterValuesMap((ServletRequest) getRequest());
      }
      return _requestParameterValuesMap;
    }

    private void _checkRequest()
    {
      if (super.getRequest() == null)
      {
        throw new UnsupportedOperationException("Request is null on this context.");
      }
    }

    private HttpServletRequest _getHttpServletRequest()
    {
      _checkRequest();
      if (!(getRequest() instanceof HttpServletRequest))
      {
        throw new IllegalArgumentException("Only HttpServletRequest supported");
      }

      return (HttpServletRequest) getRequest();
    }
  }


  private static volatile boolean _sSetRequestBugTested = false;
  private static boolean _sHasSetRequestBug = false;
  
  private final ReentrantLock _initLock = new ReentrantLock();

  private AtomicBoolean _initialized = new AtomicBoolean(false);
  private List _services;
  static private final Map _CONFIGURATORS =
    new HashMap();
  static private final String _IN_REQUEST =
    GlobalConfiguratorImpl.class.getName() + ".IN_REQUEST";
  static private final String _REQUEST_CONTEXT =
    GlobalConfiguratorImpl.class.getName() + ".REQUEST_CONTEXT";
  static private final String _REQUEST_TYPE =
    GlobalConfiguratorImpl.class.getName() + ".REQUEST_TYPE";

  static private class TestRequest
    extends ServletRequestWrapper
  {
    public TestRequest(ServletRequest request)
    {
      super(request);
    }

    @Override
    public String getParameter(String string)
    {
      if (_TEST_PARAM.equals(string))
      {
        return "passed";
      }

      return super.getParameter(string);
    }

    static public final boolean isTestParamPresent(ExternalContext ec)
    {
      return RequestStateMap.getInstance(ec).get(_TEST_PARAM) != null;
    }

    static private String _TEST_PARAM = TestRequest.class.getName() + ".TEST_PARAM";
  }

  // skanky duplication of key from ComponentReference Class
  private static final String _FINISH_INITIALIZATION_LIST_KEY = ComponentReference.class.getName() +
                                                                "#FINISH_INITIALIZATION";

  // hacky reference to the ThreadLocalResetter used to clean up request-scoped
  // ThreadLocals
  private AtomicReference _threadResetter =
    new AtomicReference();

  static private final TrinidadLogger _LOG =
    TrinidadLogger.createTrinidadLogger(GlobalConfiguratorImpl.class);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy