/* * 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.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 static com.sun.faces.util.Util.getContextClassLoader2; import static com.sun.faces.util.Util.isAnyNull; import static com.sun.faces.util.Util.isOneOf; import static java.text.MessageFormat.format; import static java.util.Arrays.binarySearch; import static java.util.Arrays.sort; import static java.util.logging.Level.SEVERE; import static javax.faces.FactoryFinder.APPLICATION_FACTORY; import static javax.faces.FactoryFinder.CLIENT_WINDOW_FACTORY; import static javax.faces.FactoryFinder.EXCEPTION_HANDLER_FACTORY; import static javax.faces.FactoryFinder.EXTERNAL_CONTEXT_FACTORY; import static javax.faces.FactoryFinder.FACELET_CACHE_FACTORY; import static javax.faces.FactoryFinder.FACES_CONTEXT_FACTORY; import static javax.faces.FactoryFinder.FACTORIES_CACHE; import static javax.faces.FactoryFinder.FLASH_FACTORY; import static javax.faces.FactoryFinder.FLOW_HANDLER_FACTORY; import static javax.faces.FactoryFinder.LIFECYCLE_FACTORY; import static javax.faces.FactoryFinder.PARTIAL_VIEW_CONTEXT_FACTORY; import static javax.faces.FactoryFinder.RENDER_KIT_FACTORY; import static javax.faces.FactoryFinder.SEARCH_EXPRESSION_CONTEXT_FACTORY; import static javax.faces.FactoryFinder.TAG_HANDLER_DELEGATE_FACTORY; import static javax.faces.FactoryFinder.VIEW_DECLARATION_LANGUAGE_FACTORY; import static javax.faces.FactoryFinder.VISIT_CONTEXT_FACTORY; import static javax.faces.ServletContextFacesContextFactory.SERVLET_CONTEXT_FINDER_NAME; import static javax.faces.ServletContextFacesContextFactory.SERVLET_CONTEXT_FINDER_REMOVAL_NAME; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; 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.Map.Entry; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Logger; import javax.faces.context.FacesContext; import com.sun.faces.spi.InjectionProvider; final class FactoryFinderInstance { private static final String INJECTION_PROVIDER_KEY = FactoryFinder.class.getPackage().getName() + "INJECTION_PROVIDER_KEY"; private final Map<String, Object> factories; private final Map<String, List<String>> savedFactoryNames; private final ReentrantReadWriteLock lock; private ServletContextFacesContextFactory servletContextFinder; /** * <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<>(); buildUpFactoryClasses.put(APPLICATION_FACTORY, javax.faces.application.ApplicationFactory.class); buildUpFactoryClasses.put(VISIT_CONTEXT_FACTORY, javax.faces.component.visit.VisitContextFactory.class); buildUpFactoryClasses.put(EXCEPTION_HANDLER_FACTORY, javax.faces.context.ExceptionHandlerFactory.class); buildUpFactoryClasses.put(EXTERNAL_CONTEXT_FACTORY, javax.faces.context.ExternalContextFactory.class); buildUpFactoryClasses.put(FACES_CONTEXT_FACTORY, javax.faces.context.FacesContextFactory.class); buildUpFactoryClasses.put(FLASH_FACTORY, javax.faces.context.FlashFactory.class); buildUpFactoryClasses.put(PARTIAL_VIEW_CONTEXT_FACTORY, javax.faces.context.PartialViewContextFactory.class); buildUpFactoryClasses.put(LIFECYCLE_FACTORY, javax.faces.lifecycle.LifecycleFactory.class); buildUpFactoryClasses.put(CLIENT_WINDOW_FACTORY, javax.faces.lifecycle.ClientWindowFactory.class); buildUpFactoryClasses.put(RENDER_KIT_FACTORY, javax.faces.render.RenderKitFactory.class); buildUpFactoryClasses.put(VIEW_DECLARATION_LANGUAGE_FACTORY, javax.faces.view.ViewDeclarationLanguageFactory.class); buildUpFactoryClasses.put(FACELET_CACHE_FACTORY, javax.faces.view.facelets.FaceletCacheFactory.class); buildUpFactoryClasses.put(TAG_HANDLER_DELEGATE_FACTORY, javax.faces.view.facelets.TagHandlerDelegateFactory.class); buildUpFactoryClasses.put(FLOW_HANDLER_FACTORY, javax.faces.flow.FlowHandlerFactory.class); buildUpFactoryClasses.put(SEARCH_EXPRESSION_CONTEXT_FACTORY, javax.faces.component.search.SearchExpressionContextFactory.class); FACTORY_CLASSES = Collections.unmodifiableMap(buildUpFactoryClasses); FACTORY_NAMES = new String[] { APPLICATION_FACTORY, VISIT_CONTEXT_FACTORY, EXCEPTION_HANDLER_FACTORY, EXTERNAL_CONTEXT_FACTORY, FACES_CONTEXT_FACTORY, FLASH_FACTORY, FLOW_HANDLER_FACTORY, PARTIAL_VIEW_CONTEXT_FACTORY, CLIENT_WINDOW_FACTORY, LIFECYCLE_FACTORY, RENDER_KIT_FACTORY, VIEW_DECLARATION_LANGUAGE_FACTORY, FACELET_CACHE_FACTORY, TAG_HANDLER_DELEGATE_FACTORY, SEARCH_EXPRESSION_CONTEXT_FACTORY }; // Optimize performance of validateFactoryName sort(FACTORY_NAMES); LOGGER = Logger.getLogger("javax.faces", "javax.faces.LogStrings"); } // -------------------------------------------------------- Constructors FactoryFinderInstance() { lock = new ReentrantReadWriteLock(true); factories = new HashMap<>(); savedFactoryNames = new HashMap<>(); 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<>(); savedFactoryNames = new HashMap<>(); factories.putAll(toCopy.savedFactoryNames); copyInjectionProviderFromFacesContext(); servletContextFinder = new ServletContextFacesContextFactory(); } // ------------------------------------------------------ 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 (provider != null) { lock.writeLock().lock(); try { for (Entry<String, Object> 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()) && curFactory != null && !(curFactory instanceof String)) { try { provider.invokePreDestroy(curFactory); } catch (Exception ex) { logPreDestroyFail(entry.getValue(), ex); } } } } finally { factories.clear(); lock.writeLock().unlock(); } } else { LOGGER.log(SEVERE, "Unable to call @PreDestroy annotated methods because no InjectionProvider can be found. Does this container implement the Mojarra Injection SPI?"); } } InjectionProvider getInjectionProvider() { return (InjectionProvider) factories.get(INJECTION_PROVIDER_KEY); } void clearInjectionProvider() { factories.remove(INJECTION_PROVIDER_KEY); } @SuppressWarnings("unchecked") Object getFactory(String factoryName) { validateFactoryName(factoryName); if (factoryName.equals(SERVLET_CONTEXT_FINDER_NAME)) { return servletContextFinder; } if (factoryName.equals(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<String>((List<String>) factoryOrList)); Object factory = getImplementationInstance(getContextClassLoader2(), factoryName, (List<String>) factoryOrList); if (factory == null) { logNoFactory(factoryName); factory = FACTORIES_CACHE.getFallbackFactory(this, factoryName); notNullFactory(factoryName, factory); } // Record and return the new instance factories.put(factoryName, factory); return factory; } finally { lock.writeLock().unlock(); } } // -------------------------------------------------------- Private methods private void copyInjectionProviderFromFacesContext() { InjectionProvider injectionProvider = null; FacesContext context = FacesContext.getCurrentInstance(); if (context != null) { injectionProvider = (InjectionProvider) context.getAttributes().get("com.sun.faces.config.ConfigManager_INJECTION_PROVIDER_TASK"); } if (injectionProvider != null) { factories.put(INJECTION_PROVIDER_KEY, injectionProvider); } else { LOGGER.log(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<String> implementations) throws FacesException { Object implementation = null; String curImplClass; int len; // step 1. if (implementations != null && (1 < (len = implementations.size()) || 1 == len)) { curImplClass = 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-argument ctor. implementation = getImplGivenPreviousImpl(classLoader, factoryName, curImplClass, null); } // step 2. List<String> fromServices = getImplNameFromServices(classLoader, factoryName); if (fromServices != null) { for (String name : fromServices) { implementation = getImplGivenPreviousImpl(classLoader, factoryName, name, implementation); } } // step 3. if (implementations != null) { for (len = (implementations.size() - 1); 0 <= len; len--) { curImplClass = implementations.remove(len); implementation = getImplGivenPreviousImpl(classLoader, factoryName, curImplClass, implementation); } } return implementation; } /** * <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> implementationNames = new ArrayList<>(); String resourceName = "META-INF/services/" + factoryName; try { Enumeration<URL> resources = classLoader.getResources(resourceName); while (resources.hasMoreElements()) { URL url = resources.nextElement(); URLConnection connection = url.openConnection(); connection.setUseCaches(false); try (InputStream stream = connection.getInputStream()) { if (stream != null) { implementationNames.add(readLineFromStream(stream)); } } } } catch (IOException | SecurityException e) { if (LOGGER.isLoggable(SEVERE)) { LOGGER.log(SEVERE, e.toString(), e); } } return implementationNames; } /** * <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) { Object implementation = null; Class<?> factoryClass = getFactoryClass(factoryName); if (!isAnyNull(previousImpl, factoryClass)) { // We have a previousImpl AND the appropriate one argument ctor. try { implementation = Class.forName(implName, false, classLoader) .getConstructor(factoryClass) .newInstance(previousImpl); } catch (NoSuchMethodException nsme) { // fall through to "zero-arg-ctor" case factoryClass = null; } catch (ClassNotFoundException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new FacesException(implName, e); } } if (isAnyNull(previousImpl, factoryClass)) { // We have either no previousImpl or no appropriate one argument ctor. try { // Since this is the hard coded implementation default, there is no preceding implementation, // so don't bother with a non-zero-argument ctor. implementation = Class.forName(implName, false, classLoader) .newInstance(); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { throw new FacesException(implName, e); } } injectImplementation(implName, implementation); return implementation; } /** * @return the <code>java.lang.Class</code> for the argument factory. */ private Class<?> getFactoryClass(String factoryClassName) { return FACTORY_CLASSES.get(factoryClassName); } private String readLineFromStream(InputStream stream) throws IOException { // Deal with systems whose native encoding is possibly // different from the way that the services entry was created try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"))) { return 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. try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { return reader.readLine(); } } } private void injectImplementation(String implementationName, Object implementation) { if (implementation != null) { InjectionProvider provider = getInjectionProvider(); if (provider != null) { try { provider.inject(implementation); provider.invokePostConstruct(implementation); } catch (Exception e) { throw new FacesException(implementationName, e); } } else { LOGGER.log(SEVERE, "Unable to inject {0} because no InjectionProvider can be found. Does this container implement the Mojarra Injection SPI?", implementation); } } } private void logNoFactory(String factoryName) { if (LOGGER.isLoggable(SEVERE)) { LOGGER.log(SEVERE, format(LOGGER.getResourceBundle().getString("severe.no_factory"), factoryName)); } } private void logPreDestroyFail(Object factory, Exception ex) { if (LOGGER.isLoggable(SEVERE)) { LOGGER.log(SEVERE, format("Unable to invoke @PreDestroy annotated methods on {0}.", factory), ex); } } private void notNullFactory(String factoryName, Object factory) { if (factory == null) { throw new IllegalStateException( format(LOGGER.getResourceBundle().getString("severe.no_factory_backup_failed"), factoryName)); } } private void validateFactoryName(String factoryName) { if (factoryName == null) { throw new NullPointerException(); } if (isOneOf(factoryName, SERVLET_CONTEXT_FINDER_NAME, SERVLET_CONTEXT_FINDER_REMOVAL_NAME)) { return; } if (binarySearch(FACTORY_NAMES, factoryName) < 0) { throw new IllegalArgumentException(factoryName); } } }