/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2012 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package javax.faces; import com.sun.faces.spi.InjectionProvider; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.net.URL; import java.net.URLConnection; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; import java.util.logging.Logger; import javax.faces.context.FacesContext; final class FactoryFinderInstance { private final Map<String, Object> factories; private final Map<String, List<String>> savedFactoryNames; private final ReentrantReadWriteLock lock; private ServletContextFacesContextFactory servletContextFinder; private static final String INJECTION_PROVIDER_KEY = FactoryFinder.class.getPackage().getName() + "INJECTION_PROVIDER_KEY"; /** * <p>The set of JavaServer Faces factory classes for which the factory * discovery mechanism is supported. The entries in this list must be * alphabetically ordered according to the entire string of the * *value* of each of the literals, not just * the last part of the literal!</p> */ private static final String[] FACTORY_NAMES; /** * <p>Map of Class instances for the our factory names.</p> */ private final static Map<String, Class> FACTORY_CLASSES; private static final Logger LOGGER; static { Map<String, Class> buildUpFactoryClasses; buildUpFactoryClasses = new HashMap<String, Class>(); buildUpFactoryClasses.put(FactoryFinder.APPLICATION_FACTORY, javax.faces.application.ApplicationFactory.class); buildUpFactoryClasses.put(FactoryFinder.VISIT_CONTEXT_FACTORY, javax.faces.component.visit.VisitContextFactory.class); buildUpFactoryClasses.put(FactoryFinder.EXCEPTION_HANDLER_FACTORY, javax.faces.context.ExceptionHandlerFactory.class); buildUpFactoryClasses.put(FactoryFinder.EXTERNAL_CONTEXT_FACTORY, javax.faces.context.ExternalContextFactory.class); buildUpFactoryClasses.put(FactoryFinder.FACES_CONTEXT_FACTORY, javax.faces.context.FacesContextFactory.class); buildUpFactoryClasses.put(FactoryFinder.FLASH_FACTORY, javax.faces.context.FlashFactory.class); buildUpFactoryClasses.put(FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY, javax.faces.context.PartialViewContextFactory.class); buildUpFactoryClasses.put(FactoryFinder.LIFECYCLE_FACTORY, javax.faces.lifecycle.LifecycleFactory.class); buildUpFactoryClasses.put(FactoryFinder.CLIENT_WINDOW_FACTORY, javax.faces.lifecycle.ClientWindowFactory.class); buildUpFactoryClasses.put(FactoryFinder.RENDER_KIT_FACTORY, javax.faces.render.RenderKitFactory.class); buildUpFactoryClasses.put(FactoryFinder.VIEW_DECLARATION_LANGUAGE_FACTORY, javax.faces.view.ViewDeclarationLanguageFactory.class); buildUpFactoryClasses.put(FactoryFinder.FACELET_CACHE_FACTORY, javax.faces.view.facelets.FaceletCacheFactory.class); buildUpFactoryClasses.put(FactoryFinder.TAG_HANDLER_DELEGATE_FACTORY, javax.faces.view.facelets.TagHandlerDelegateFactory.class); buildUpFactoryClasses.put(FactoryFinder.FLOW_HANDLER_FACTORY, javax.faces.flow.FlowHandlerFactory.class); FACTORY_CLASSES = Collections.unmodifiableMap(buildUpFactoryClasses); FACTORY_NAMES = new String [] { FactoryFinder.APPLICATION_FACTORY, FactoryFinder.VISIT_CONTEXT_FACTORY, FactoryFinder.EXCEPTION_HANDLER_FACTORY, FactoryFinder.EXTERNAL_CONTEXT_FACTORY, FactoryFinder.FACES_CONTEXT_FACTORY, FactoryFinder.FLASH_FACTORY, FactoryFinder.FLOW_HANDLER_FACTORY, FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY, FactoryFinder.CLIENT_WINDOW_FACTORY, FactoryFinder.LIFECYCLE_FACTORY, FactoryFinder.RENDER_KIT_FACTORY, FactoryFinder.VIEW_DECLARATION_LANGUAGE_FACTORY, FactoryFinder.FACELET_CACHE_FACTORY, FactoryFinder.TAG_HANDLER_DELEGATE_FACTORY }; // Optimize performance of validateFactoryName Arrays.sort(FACTORY_NAMES); LOGGER = Logger.getLogger("javax.faces", "javax.faces.LogStrings"); } // -------------------------------------------------------- Consturctors FactoryFinderInstance() { lock = new ReentrantReadWriteLock(true); factories = new HashMap<String, Object>(); savedFactoryNames = new HashMap<String, List<String>>(); for (String name : FACTORY_NAMES) { factories.put(name, new ArrayList(4)); // NOPMD } copyInjectionProviderFromFacesContext(); servletContextFinder = new ServletContextFacesContextFactory(); } FactoryFinderInstance(FactoryFinderInstance toCopy) { lock = new ReentrantReadWriteLock(true); factories = new HashMap<String, Object>(); savedFactoryNames = new HashMap<String, List<String>>(); factories.putAll(toCopy.savedFactoryNames); copyInjectionProviderFromFacesContext(); servletContextFinder = new ServletContextFacesContextFactory(); } private void copyInjectionProviderFromFacesContext() { InjectionProvider injectionProvider = null; FacesContext context = FacesContext.getCurrentInstance(); if (null != context) { injectionProvider = (InjectionProvider) context.getAttributes().get("com.sun.faces.config.ConfigManager_INJECTION_PROVIDER_TASK"); } if (null != injectionProvider) { factories.put(INJECTION_PROVIDER_KEY, injectionProvider); } else { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Unable to obtain InjectionProvider from init time FacesContext. Does this container implement the Mojarra Injection SPI?"); } } } /** * <p>Load and return an instance of the specified implementation * class using the following algorithm.</p> * <p/> * <ol> * <p/> * <li><p>If the argument <code>implementations</code> list has * more than one element, or exactly one element, interpret the * last element in the list to be the fully qualified class name of * a class implementing <code>factoryName</code>. Instantiate that * class and save it for return. If the * <code>implementations</code> list has only one element, skip * this step.</p></li> * <p/> * <li><p>Look for a resource called * <code>/META-INF/services/<factoryName></code>. If found, * interpret it as a properties file, and read out the first entry. * Interpret the first entry as a fully qualify class name of a * class that implements <code>factoryName</code>. If we have an * instantiated factory from the previous step <em>and</em> the * implementing class has a one arg constructor of the type for * <code>factoryName</code>, instantiate it, passing the * instantiated factory from the previous step. If there is no one * arg constructor, just instantiate the zero arg constructor. Save * the newly instantiated factory for return, replacing the * instantiated factory from the previous step.</p></li> * <p/> * <li><p>Treat each remaining element in the * <code>implementations</code> list as a fully qualified class name * of a class implementing <code>factoryName</code>. If the currentKeyrent element has a one arg constructor of the type for <code>factoryName</code>, instantiate it, passing the * instantiated factory from the previous or step iteration. If * there is no one arg constructor, just instantiate the zero arg * constructor, replacing the instantiated factory from the previous * step or iteration.</p></li> * <p/> * <li><p>Return the saved factory</p></li> * <p/> * </ol> * * @param classLoader Class loader for the web application that will * be loading the implementation class * @param implementations A List of implementations for a given * factory class. * @throws FacesException if the specified implementation class * cannot be loaded * @throws FacesException if an instance of the specified implementation * class cannot be instantiated */ private Object getImplementationInstance(ClassLoader classLoader, String factoryName, List implementations) throws FacesException { Object result = null; String curImplClass; int len; // step 1. if (null != implementations && (1 < (len = implementations.size()) || 1 == len)) { curImplClass = (String) implementations.remove(len - 1); // since this is the hard coded implementation default, // there is no preceding implementation, so don't bother // with a non-zero-arg ctor. result = getImplGivenPreviousImpl(classLoader, factoryName, curImplClass, null); } // step 2. List<String> fromServices = getImplNameFromServices(classLoader, factoryName); if (fromServices != null) { for (String name : fromServices) { result = getImplGivenPreviousImpl(classLoader, factoryName, name, result); } } // step 3. if (null != implementations) { for (len = (implementations.size() - 1); 0 <= len; len--) { curImplClass = (String) implementations.remove(len); result = getImplGivenPreviousImpl(classLoader, factoryName, curImplClass, result); } } return result; } /** * <p>Perform the logic to get the implementation class for the * second step of {@link FactoryFinder#getImplementationInstance(ClassLoader, String, java.util.List)}.</p> */ private List<String> getImplNameFromServices(ClassLoader classLoader, String factoryName) { // Check for a services definition List<String> result = null; String resourceName = "META-INF/services/" + factoryName; InputStream stream; BufferedReader reader = null; try { Enumeration<URL> e = classLoader.getResources(resourceName); while (e.hasMoreElements()) { URL url = e.nextElement(); URLConnection conn = url.openConnection(); conn.setUseCaches(false); stream = conn.getInputStream(); if (stream != null) { // Deal with systems whose native encoding is possibly // different from the way that the services entry was created try { reader = new BufferedReader(new InputStreamReader(stream, "UTF-8")); if (result == null) { result = new ArrayList<String>(3); } result.add(reader.readLine()); } catch (UnsupportedEncodingException uee) { // The DM_DEFAULT_ENCODING warning is acceptable here // because we explicitly *want* to use the Java runtime's // default encoding. reader = new BufferedReader(new InputStreamReader(stream)); } finally { if (reader != null) { reader.close(); reader = null; } if (stream != null) { stream.close(); //noinspection UnusedAssignment stream = null; } } } } } catch (IOException e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, e.toString(), e); } } catch (SecurityException e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, e.toString(), e); } } return result; } /** * <p>Implement the decorator pattern for the factory * implementation.</p> * <p/> * <p>If <code>previousImpl</code> is non-<code>null</code> and the * class named by the argument <code>implName</code> has a one arg * contstructor of type <code>factoryName</code>, instantiate it, * passing previousImpl to the constructor.</p> * <p/> * <p>Otherwise, we just instantiate and return * <code>implName</code>.</p> * * @param classLoader the ClassLoader from which to load the class * @param factoryName the fully qualified class name of the factory. * @param implName the fully qualified class name of a class that * implements the factory. * @param previousImpl if non-<code>null</code>, the factory * instance to be passed to the constructor of the new factory. */ private Object getImplGivenPreviousImpl(ClassLoader classLoader, String factoryName, String implName, Object previousImpl) { Class clazz; Class factoryClass = null; Class[] getCtorArg; Object[] newInstanceArgs = new Object[1]; Constructor ctor; Object result = null; // if we have a previousImpl and the appropriate one arg ctor. if ((null != previousImpl) && (null != (factoryClass = getFactoryClass(factoryName)))) { try { clazz = Class.forName(implName, false, classLoader); getCtorArg = new Class[1]; getCtorArg[0] = factoryClass; ctor = clazz.getConstructor(getCtorArg); newInstanceArgs[0] = previousImpl; result = ctor.newInstance(newInstanceArgs); } catch (NoSuchMethodException nsme) { // fall through to "zero-arg-ctor" case factoryClass = null; } catch (Exception e) { throw new FacesException(implName, e); } } if (null == previousImpl || null == factoryClass) { // we have either no previousImpl or no appropriate one arg // ctor. try { clazz = Class.forName(implName, false, classLoader); // since this is the hard coded implementation default, // there is no preceding implementation, so don't bother // with a non-zero-arg ctor. result = clazz.newInstance(); } catch (Exception e) { throw new FacesException(implName, e); } } if (null != result) { InjectionProvider provider = getInjectionProvider(); if (null != provider) { try { provider.inject(result); provider.invokePostConstruct(result); } catch (Exception e) { throw new FacesException(implName, e); } } else { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Unable to inject {0} because no InjectionProvider can be found. Does this container implement the Mojarra Injection SPI?", result); } } } return result; } /** * @return the <code>java.lang.Class</code> for the argument * factory. */ private Class getFactoryClass(String factoryClassName) { return FACTORY_CLASSES.get(factoryClassName); } // ------------------------------------------------------ Package Private Methods Collection<Object> getFactories() { return factories.values(); } void addFactory(String factoryName, String implementation) { validateFactoryName(factoryName); Object result = factories.get(factoryName); lock.writeLock().lock(); try { if (result instanceof List) { TypedCollections.dynamicallyCastList((List) result, String.class).add(0, implementation); } } finally { lock.writeLock().unlock(); } } void releaseFactories() { InjectionProvider provider = getInjectionProvider(); if (null != provider) { lock.writeLock().lock(); try { for (Map.Entry entry : factories.entrySet()) { Object curFactory = entry.getValue(); // If the current entry is not the injectionProvider itself // and the current entry has a non-null value // and the value is not a string... if (!INJECTION_PROVIDER_KEY.equals(entry.getKey()) && null != curFactory && !(curFactory instanceof String)) { try { provider.invokePreDestroy(curFactory); } catch (Exception ex) { if (LOGGER.isLoggable(Level.SEVERE)) { String message = MessageFormat.format("Unable to invoke @PreDestroy annotated methods on {0}.", entry.getValue()); LOGGER.log(Level.SEVERE, message, ex); } } } } } finally { factories.clear(); lock.writeLock().unlock(); } } else { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Unable to call @PreDestroy annotated methods because no InjectionProvider can be found. Does this container implement the Mojarra Injection SPI?"); } } } InjectionProvider getInjectionProvider() { InjectionProvider result = (InjectionProvider) factories.get(INJECTION_PROVIDER_KEY); return result; } void clearInjectionProvider() { factories.remove(INJECTION_PROVIDER_KEY); } Object getFactory(String factoryName) { validateFactoryName(factoryName); if (factoryName.equals(ServletContextFacesContextFactory.SERVLET_CONTEXT_FINDER_NAME)) { return servletContextFinder; } else if (factoryName.equals(ServletContextFacesContextFactory.SERVLET_CONTEXT_FINDER_REMOVAL_NAME)) { try { lock.writeLock().lock(); servletContextFinder = null; return null; } finally { lock.writeLock().unlock(); } } Object factoryOrList; lock.readLock().lock(); try { factoryOrList = factories.get(factoryName); if (!(factoryOrList instanceof List)) { return factoryOrList; } } finally { lock.readLock().unlock(); } // factory hasn't been constructed lock.writeLock().lock(); try { // double check the current value. Another thread // may have completed the initialization by the time // this thread entered this block factoryOrList = factories.get(factoryName); if (!(factoryOrList instanceof List)) { return factoryOrList; } savedFactoryNames.put(factoryName, new ArrayList((List) factoryOrList)); ClassLoader cl = getClassLoader(); Object factory = getImplementationInstance(cl, factoryName, (List) factoryOrList); if (factory == null) { ResourceBundle rb = LOGGER.getResourceBundle(); String message = rb.getString("severe.no_factory"); message = MessageFormat.format(message, factoryName); if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, message); } factory = FactoryFinder.FACTORIES_CACHE.getFallbackFactory(this, factoryName); if (null == factory) { message = rb.getString("severe.no_factory_backup_failed"); message = MessageFormat.format(message, factoryName); throw new IllegalStateException(message); } } // Record and return the new instance factories.put(factoryName, factory); return factory; } finally { lock.writeLock().unlock(); } } private ClassLoader getClassLoader() throws FacesException { // J2EE 1.3 (and later) containers are required to make the // web application class loader visible through the context // class loader of the current thread. ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) { throw new FacesException("getContextClassLoader"); } return (cl); } private void validateFactoryName(String factoryName) { if (factoryName == null) { throw new NullPointerException(); } if (factoryName.equals(ServletContextFacesContextFactory.SERVLET_CONTEXT_FINDER_NAME) || factoryName.equals(ServletContextFacesContextFactory.SERVLET_CONTEXT_FINDER_REMOVAL_NAME)) { return; } if (Arrays.binarySearch(FACTORY_NAMES, factoryName) < 0) { throw new IllegalArgumentException(factoryName); } } } // END FactoryFinderInstance