blob: 1f0e96645f4053c846a313856e372c6119beda13 [file] [log] [blame]
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 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 com.sun.faces.config;
import com.sun.faces.RIConstants;
import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.ValidateFacesConfigFiles;
import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.DisableFaceletJSFViewHandler;
import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.EnableThreading;
import com.sun.faces.spi.ConfigurationResourceProvider;
import com.sun.faces.spi.ConfigurationResourceProviderFactory;
import com.sun.faces.spi.AnnotationProvider;
import com.sun.faces.spi.AnnotationProviderFactory;
import com.sun.faces.spi.HighAvailabilityEnabler;
import static com.sun.faces.spi.ConfigurationResourceProviderFactory.ProviderType.*;
import com.sun.faces.config.configprovider.MetaInfFacesConfigResourceProvider;
import com.sun.faces.config.configprovider.MojarraFacesConfigResourceProvider;
import com.sun.faces.config.configprovider.WebFacesConfigResourceProvider;
import com.sun.faces.config.configprovider.MetaInfFaceletTaglibraryConfigProvider;
import com.sun.faces.config.configprovider.WebFaceletTaglibResourceProvider;
import com.sun.faces.config.processor.ApplicationConfigProcessor;
import com.sun.faces.config.processor.BehaviorConfigProcessor;
import com.sun.faces.config.processor.ComponentConfigProcessor;
import com.sun.faces.config.processor.ConfigProcessor;
import com.sun.faces.config.processor.ConverterConfigProcessor;
import com.sun.faces.config.processor.FactoryConfigProcessor;
import com.sun.faces.config.processor.LifecycleConfigProcessor;
import com.sun.faces.config.processor.ManagedBeanConfigProcessor;
import com.sun.faces.config.processor.NavigationConfigProcessor;
import com.sun.faces.config.processor.RenderKitConfigProcessor;
import com.sun.faces.config.processor.ValidatorConfigProcessor;
import com.sun.faces.config.processor.FaceletTaglibConfigProcessor;
import com.sun.faces.config.processor.FacesConfigExtensionProcessor;
import com.sun.faces.spi.InjectionProvider;
import com.sun.faces.spi.InjectionProviderFactory;
import com.sun.faces.util.FacesLogger;
import com.sun.faces.util.Timer;
import com.sun.faces.util.Util;
import org.xml.sax.InputSource;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.event.PostConstructApplicationEvent;
import javax.faces.application.Application;
import javax.faces.context.FacesContext;
import javax.servlet.ServletContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
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.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.lang.annotation.Annotation;
import java.net.URI;
import java.util.Iterator;
import org.w3c.dom.*;
import org.xml.sax.SAXParseException;
/**
* <p>
* This class manages the initialization of each web application that uses
* JSF.
* </p>
*/
public class ConfigManager {
private static final String SERVLET_CONTEXT_INITIALISED = "com.sap.core.context.initialized";
private static final Logger LOGGER = FacesLogger.CONFIG.getLogger();
private static final Pattern JAR_PATTERN = Pattern.compile("(.*/(\\S*\\.jar)).*(/faces-config.xml|/*.\\.faces-config.xml)");
/**
* <p>
* A List of resource providers that search for faces-config documents.
* By default, this contains a provider for the Mojarra, and two other
* providers to satisfy the requirements of the specification.
* </p>
*/
private static final List<ConfigurationResourceProvider> FACES_CONFIG_RESOURCE_PROVIDERS;
/**
* <p>
* A List of resource providers that search for faces-config documents.
* By default, this contains a provider for the Mojarra, and one other
* providers to satisfy the requirements of the specification.
* </p>
*/
private static final List<ConfigurationResourceProvider> FACELET_TAGLIBRARY_RESOURCE_PROVIDERS;
/**
* <p>
* The <code>ConfigManager</code> will multithread the calls to the
* <code>ConfigurationResourceProvider</code>s as well as any calls
* to parse a resources into a DOM. By default, we'll use only 5 threads
* per web application.
* </p>
*/
private static final int NUMBER_OF_TASK_THREADS = 5;
/**
* <p>
* There is only once instance of <code>ConfigManager</code>.
* <p>
*/
private static final ConfigManager CONFIG_MANAGER = new ConfigManager();
/**
* The application-scoped key under which the Future responsible for annotation
* scanning is associated with.
*/
private static final String ANNOTATIONS_SCAN_TASK_KEY =
ConfigManager.class.getName() + "_ANNOTATION_SCAN_TASK";
/**
* The initialization time FacesContext scoped key under which the
* InjectionProvider is stored.
*/
public static final String INJECTION_PROVIDER_KEY =
ConfigManager.class.getName() + "_INJECTION_PROVIDER_TASK";
/**
* Name of the attribute added by ParseTask to indicate a
* {@link Document} instance as a representation of
* <code>/WEB-INF/faces-config.xml</code>.
*/
public static final String WEB_INF_MARKER = "com.sun.faces.webinf";
/**
* <p>
* Contains each <code>ServletContext</code> that we've initialized.
* The <code>ServletContext</code> will be removed when the application
* is destroyed.
* </p>
*/
@SuppressWarnings({"CollectionWithoutInitialCapacity"})
private List<ServletContext> initializedContexts =
new CopyOnWriteArrayList<ServletContext>();
/**
* <p>
* The chain of {@link ConfigProcessor} instances to processing of
* faces-config documents.
* </p>
*/
private static final ConfigProcessor FACES_CONFIG_PROCESSOR_CHAIN;
/**
* <p>
* The chain of {@link ConfigProcessor} instances to processing of
* facelet-taglib documents.
* </p>
*/
private static final ConfigProcessor FACELET_TAGLIB_CONFIG_PROCESSOR_CHAIN;
/**
* Stylesheet to convert 1.0 and 1.1 based faces-config documents
* to our private 1.1 schema for validation.
*/
private static final String FACES_TO_1_1_PRIVATE_XSL =
"/com/sun/faces/jsf1_0-1_1toSchema.xsl";
/**
* Stylesheet to convert 1.0 facelet-taglib documents
* from 1.0 to 2.0 for schema validation purposes.
*/
private static final String FACELETS_TO_2_0_XSL =
"/com/sun/faces/facelets1_0-2_0toSchema.xsl";
private static final String FACES_CONFIG_1_X_DEFAULT_NS =
"http://java.sun.com/JSF/Configuration";
private static final String FACELETS_1_0_DEFAULT_NS =
"http://java.sun.com/JSF/Facelet";
static {
// initialize the resource providers for faces-config documents
List<ConfigurationResourceProvider> facesConfigProviders =
new ArrayList<ConfigurationResourceProvider>(3);
facesConfigProviders.add(new MojarraFacesConfigResourceProvider());
facesConfigProviders.add(new MetaInfFacesConfigResourceProvider());
facesConfigProviders.add(new WebFacesConfigResourceProvider());
FACES_CONFIG_RESOURCE_PROVIDERS = Collections.unmodifiableList(facesConfigProviders);
// initialize the resource providers for facelet-taglib documents
List<ConfigurationResourceProvider> faceletTaglibProviders =
new ArrayList<ConfigurationResourceProvider>(3);
faceletTaglibProviders.add(new MetaInfFaceletTaglibraryConfigProvider());
faceletTaglibProviders.add(new WebFaceletTaglibResourceProvider());
FACELET_TAGLIBRARY_RESOURCE_PROVIDERS = Collections.unmodifiableList(faceletTaglibProviders);
// initialize the config processors for faces-config documents
ConfigProcessor[] configProcessors = {
new FactoryConfigProcessor(),
new LifecycleConfigProcessor(),
new ApplicationConfigProcessor(),
new ComponentConfigProcessor(),
new ConverterConfigProcessor(),
new ValidatorConfigProcessor(),
new ManagedBeanConfigProcessor(),
new RenderKitConfigProcessor(),
new NavigationConfigProcessor(),
new BehaviorConfigProcessor(),
new FacesConfigExtensionProcessor()
};
for (int i = 0; i < configProcessors.length; i++) {
ConfigProcessor p = configProcessors[i];
if ((i + 1) < configProcessors.length) {
p.setNext(configProcessors[i + 1]);
}
}
FACES_CONFIG_PROCESSOR_CHAIN = configProcessors[0];
// initialize the config processor for facelet-taglib documents
FACELET_TAGLIB_CONFIG_PROCESSOR_CHAIN = new FaceletTaglibConfigProcessor();
}
// ---------------------------------------------------------- Public Methods
/**
* @return a <code>ConfigManager</code> instance
*/
public static ConfigManager getInstance() {
return CONFIG_MANAGER;
}
/**
* <p>
* This method bootstraps JSF based on the parsed configuration resources.
* </p>
*
* @param sc the <code>ServletContext</code> for the application that
* requires initialization
*/
public void initialize(ServletContext sc) {
if (!hasBeenInitialized(sc)) {
initializedContexts.add(sc);
sc.setAttribute(SERVLET_CONTEXT_INITIALISED, true);
ExecutorService executor = null;
try {
WebConfiguration webConfig = WebConfiguration.getInstance(sc);
boolean validating = webConfig.isOptionEnabled(ValidateFacesConfigFiles);
if (useThreads(sc)) {
executor = createExecutorService();
}
DocumentInfo[] facesDocuments =
getConfigDocuments(sc,
getFacesConfigResourceProviders(),
executor,
validating);
FacesConfigInfo webInfFacesConfigInfo =
new FacesConfigInfo(facesDocuments[facesDocuments.length - 1]);
facesDocuments = sortDocuments(facesDocuments, webInfFacesConfigInfo);
InitFacesContext context = (InitFacesContext) FacesContext.getCurrentInstance();
InjectionProvider containerConnector =
InjectionProviderFactory.createInstance(context.getExternalContext());
context.getAttributes().put(INJECTION_PROVIDER_KEY, containerConnector);
boolean isFaceletsDisabled =
isFaceletsDisabled(webConfig, webInfFacesConfigInfo);
if (!webInfFacesConfigInfo.isWebInfFacesConfig() || !webInfFacesConfigInfo.isMetadataComplete()) {
// execute the Task responsible for finding annotation classes
ProvideMetadataToAnnotationScanTask taskMetadata = new ProvideMetadataToAnnotationScanTask(facesDocuments, containerConnector);
Future<Map<Class<? extends Annotation>,Set<Class<?>>>> annotationScan;
if (executor != null) {
annotationScan = executor.submit(new AnnotationScanTask(sc, context, taskMetadata));
pushTaskToContext(sc, annotationScan);
} else {
annotationScan =
new FutureTask<Map<Class<? extends Annotation>,Set<Class<?>>>>(new AnnotationScanTask(sc, context, taskMetadata));
((FutureTask) annotationScan).run();
}
pushTaskToContext(sc, annotationScan);
}
//see if the app is running in a HA enabled env
if (containerConnector instanceof HighAvailabilityEnabler) {
((HighAvailabilityEnabler)containerConnector).enableHighAvailability(sc);
}
// process the ordered documents
FACES_CONFIG_PROCESSOR_CHAIN.process(sc, facesDocuments);
if (!isFaceletsDisabled) {
FACELET_TAGLIB_CONFIG_PROCESSOR_CHAIN.process(
sc, getConfigDocuments(sc,
getFaceletConfigResourceProviders(),
executor,
validating));
}
publishPostConfigEvent();
} catch (Exception e) {
// clear out any configured factories
releaseFactories();
Throwable t = e;
if (!(e instanceof ConfigurationException)) {
t = new ConfigurationException("CONFIGURATION FAILED! " + t.getMessage(), t);
}
throw (ConfigurationException)t;
} finally {
if (executor != null) {
executor.shutdown();
}
sc.removeAttribute(ANNOTATIONS_SCAN_TASK_KEY);
}
}
}
/**
* <p>
* This method will remove any information about the application.
* </p>
* @param sc the <code>ServletContext</code> for the application that
* needs to be removed
*/
public void destroy(ServletContext sc) {
releaseFactories();
initializedContexts.remove(sc);
sc.removeAttribute(SERVLET_CONTEXT_INITIALISED);
}
/**
* @param sc the <code>ServletContext</code> for the application in question
* @return <code>true</code> if this application has already been initialized,
* otherwise returns </code>fase</code>
*/
public boolean hasBeenInitialized(ServletContext sc) {
if (sc.getAttribute(SERVLET_CONTEXT_INITIALISED) == null) {
return initializedContexts.contains(sc);
} else {
sc.removeAttribute(SERVLET_CONTEXT_INITIALISED);
return true;
}
}
/**
* @return the results of the annotation scan task
*/
public static Map<Class<? extends Annotation>,Set<Class<?>>> getAnnotatedClasses(FacesContext ctx) {
Map<String, Object> appMap =
ctx.getExternalContext().getApplicationMap();
//noinspection unchecked
Future<Map<Class<? extends Annotation>,Set<Class<?>>>> scanTask =
(Future<Map<Class<? extends Annotation>,Set<Class<?>>>>) appMap.get(ANNOTATIONS_SCAN_TASK_KEY);
try {
return ((scanTask != null)
? scanTask.get()
: Collections.<Class<? extends Annotation>,Set<Class<?>>>emptyMap());
} catch (Exception e) {
throw new FacesException(e);
}
}
// --------------------------------------------------------- Private Methods
private static boolean useThreads(ServletContext ctx) {
WebConfiguration config = WebConfiguration.getInstance(ctx);
return config.isOptionEnabled(EnableThreading);
}
private List<ConfigurationResourceProvider> getFacesConfigResourceProviders() {
return getConfigurationResourceProviders(FACES_CONFIG_RESOURCE_PROVIDERS,
FacesConfig);
}
private List<ConfigurationResourceProvider> getFaceletConfigResourceProviders() {
return getConfigurationResourceProviders(FACELET_TAGLIBRARY_RESOURCE_PROVIDERS,
FaceletConfig);
}
private List<ConfigurationResourceProvider> getConfigurationResourceProviders(List<ConfigurationResourceProvider> defaultProviders,
ConfigurationResourceProviderFactory.ProviderType providerType) {
ConfigurationResourceProvider[] custom =
ConfigurationResourceProviderFactory.createProviders(providerType);
if (custom.length == 0) {
return defaultProviders;
} else {
List<ConfigurationResourceProvider> list = new ArrayList<ConfigurationResourceProvider>();
list.addAll(defaultProviders);
// insert the custom providers after the META-INF providers and
// before those that scan /WEB-INF
list.addAll((defaultProviders.size() - 1), Arrays.asList(custom));
return Collections.unmodifiableList(list);
}
}
/**
* <p>
* Sort the <code>faces-config</code> documents found on the classpath
* and those specified by the <code>javax.faces.CONFIG_FILES</code> context
* init parameter.
* </p>
*
* @param facesDocuments an array of <em>all</em> <code>faces-config</code>
* documents
* @param webInfFacesConfig FacesConfigInfo representing the WEB-INF/faces-config.xml
* for this app
*
* @return the sorted documents
*/
private DocumentInfo[] sortDocuments(DocumentInfo[] facesDocuments,
FacesConfigInfo webInfFacesConfig) {
int len = (webInfFacesConfig.isWebInfFacesConfig()
? facesDocuments.length - 1
: facesDocuments.length);
List<String> absoluteOrdering = webInfFacesConfig.getAbsoluteOrdering();
if (len > 1) {
List<DocumentOrderingWrapper> list =
new ArrayList<DocumentOrderingWrapper>();
for (int i = 1; i < len; i++) {
list.add(new DocumentOrderingWrapper(facesDocuments[i]));
}
DocumentOrderingWrapper[] ordering =
list.toArray(new DocumentOrderingWrapper[list.size()]);
if (absoluteOrdering == null) {
DocumentOrderingWrapper.sort(ordering);
// sorting complete, now update the appropriate locations within
// the original array with the sorted documentation.
for (int i = 1; i < len; i++) {
facesDocuments[i] = ordering[i - 1].getDocument();
}
return facesDocuments;
} else {
DocumentOrderingWrapper[] result =
DocumentOrderingWrapper.sort(ordering, absoluteOrdering);
DocumentInfo[] ret = new DocumentInfo[((webInfFacesConfig.isWebInfFacesConfig()) ? (result.length + 2) : (result.length + 1))];
for (int i = 1; i < len; i++) {
ret[i] = result[i - 1].getDocument();
}
// add the impl specific config file
ret[0] = facesDocuments[0];
// add the WEB-INF if necessary
if (webInfFacesConfig.isWebInfFacesConfig()) {
ret[ret.length - 1] = facesDocuments[facesDocuments.length - 1];
}
return ret;
}
}
return facesDocuments;
}
/**
* <p>
* Utility method to check if JSF 2.0 Facelets should be disabled.
* If it's not explicitly disabled by the context init parameter, then
* check the version of the WEB-INF/faces-config.xml document. If the version
* is less than 2.0, then override the default value for the context init
* parameter so that other parts of the system that use that config option
* will know it has been disabled.
* </p>
*
* <p>
* NOTE: Since this method overrides a configuration value, it should
* be called before *any* document parsing is performed the configuration
* value may be queried by the <code>ConfigParser</code>s.
* </p>
*
* @param webconfig configuration for this application
* @param facesConfigInfo object representing WEB-INF/faces-config.xml
* @return <code>true</code> if Facelets should be disabled
*/
private boolean isFaceletsDisabled(WebConfiguration webconfig,
FacesConfigInfo facesConfigInfo) {
boolean isFaceletsDisabled = webconfig.isOptionEnabled(DisableFaceletJSFViewHandler);
if (!isFaceletsDisabled) {
// if not explicitly disabled, make a sanity check against
// /WEB-INF/faces-config.xml
isFaceletsDisabled = !facesConfigInfo.isVersionGreaterOrEqual(2.0);
webconfig.overrideContextInitParameter(DisableFaceletJSFViewHandler, isFaceletsDisabled);
}
return isFaceletsDisabled;
}
/**
* Push the provided <code>Future</code> to the specified <code>ServletContext</code>.
*/
private void pushTaskToContext(ServletContext sc,
Future<Map<Class<? extends Annotation>,Set<Class<?>>>> scanTask) {
sc.setAttribute(ANNOTATIONS_SCAN_TASK_KEY, scanTask);
}
/**
* Publishes a {@link javax.faces.event.PostConstructApplicationEvent} event for the current
* {@link Application} instance.
*/
private void publishPostConfigEvent() {
FacesContext ctx = FacesContext.getCurrentInstance();
Application app = ctx.getApplication();
app.publishEvent(ctx,
PostConstructApplicationEvent.class,
Application.class,
app);
}
/**
* <p>
* Obtains an array of <code>Document</code>s to be processed
* by {@link ConfigManager#FACES_CONFIG_PROCESSOR_CHAIN}.
* </p>
*
* @param sc the <code>ServletContext</code> for the application to be
* processed
* @param providers <code>List</code> of <code>ConfigurationResourceProvider</code>
* instances that provide the URL of the documents to parse.
* @param executor the <code>ExecutorService</code> used to dispatch parse
* request to
* @param validating flag indicating whether or not the documents
* should be validated
* @return an array of <code>DocumentInfo</code>s
*/
private static DocumentInfo[] getConfigDocuments(ServletContext sc,
List<ConfigurationResourceProvider> providers,
ExecutorService executor,
boolean validating) {
List<FutureTask<Collection<URI>>> urlTasks =
new ArrayList<FutureTask<Collection<URI>>>(providers.size());
for (ConfigurationResourceProvider p : providers) {
FutureTask<Collection<URI>> t =
new FutureTask<Collection<URI>>(new URITask(p, sc));
urlTasks.add(t);
if (executor != null) {
executor.execute(t);
} else {
t.run();
}
}
List<FutureTask<DocumentInfo>> docTasks =
new ArrayList<FutureTask<DocumentInfo>>(providers.size() << 1);
for (FutureTask<Collection<URI>> t : urlTasks) {
try {
Collection<URI> l = t.get();
for (URI u : l) {
FutureTask<DocumentInfo> d =
new FutureTask<DocumentInfo>(new ParseTask(validating, u));
docTasks.add(d);
if (executor != null) {
executor.execute(d);
} else {
d.run();
}
}
} catch (InterruptedException ignored) {
} catch (Exception e) {
throw new ConfigurationException(e);
}
}
List<DocumentInfo> docs = new ArrayList<DocumentInfo>(docTasks.size());
for (FutureTask<DocumentInfo> t : docTasks) {
try {
docs.add(t.get());
} catch (ExecutionException e) {
throw new ConfigurationException(e);
} catch (InterruptedException ignored) { }
}
return docs.toArray(new DocumentInfo[docs.size()]);
}
/**
* Create a new <code>ExecutorService</code> with
* {@link #NUMBER_OF_TASK_THREADS} threads.
*/
private static ExecutorService createExecutorService() {
int tc = Runtime.getRuntime().availableProcessors();
if (tc > NUMBER_OF_TASK_THREADS) {
tc = NUMBER_OF_TASK_THREADS;
}
return Executors.newFixedThreadPool(tc);
}
/**
* @param throwable Throwable
* @return the root cause of this error
*/
private Throwable unwind(Throwable throwable) {
Throwable t = null;
if (throwable != null) {
t = unwind(throwable.getCause());
if (t == null) {
t = throwable;
}
}
return t;
}
/**
* Calls through to {@link javax.faces.FactoryFinder#releaseFactories()}
* ignoring any exceptions.
*/
private void releaseFactories() {
try {
FactoryFinder.releaseFactories();
} catch (FacesException ignored) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE,
"Exception thrown from FactoryFinder.releaseFactories()",
ignored);
}
}
}
// ----------------------------------------------------------- Inner Classes
private static final class ProvideMetadataToAnnotationScanTask {
DocumentInfo [] documentInfos;
InjectionProvider containerConnector;
Set<URI> uris = null;
Set<String> jarNames = null;
private ProvideMetadataToAnnotationScanTask(DocumentInfo [] documentInfos,
InjectionProvider containerConnector) {
this.documentInfos = documentInfos;
this.containerConnector = containerConnector;
}
private void initializeIvars() {
if (null != uris || null != jarNames) {
assert(null != uris && null != jarNames);
return;
}
uris = new HashSet<URI>(documentInfos.length);
jarNames = new HashSet<String>(documentInfos.length);
for (DocumentInfo docInfo : documentInfos) {
URI sourceURI = docInfo.getSourceURI();
Matcher m = JAR_PATTERN.matcher(sourceURI.toString());
if (m.matches()) {
String jarName = m.group(2);
if (!jarNames.contains(jarName)) {
FacesConfigInfo configInfo = new FacesConfigInfo(docInfo);
if (!configInfo.isMetadataComplete()) {
uris.add(sourceURI);
jarNames.add(jarName);
}
}
}
}
}
private Set<URI> getAnnotationScanURIs() {
initializeIvars();
return uris;
}
private Set<String> getJarNames() {
initializeIvars();
return jarNames;
}
private com.sun.faces.spi.AnnotationScanner getAnnotationScanner() {
com.sun.faces.spi.AnnotationScanner result = null;
if (this.containerConnector instanceof com.sun.faces.spi.AnnotationScanner) {
result = (com.sun.faces.spi.AnnotationScanner) this.containerConnector;
}
return result;
}
}
/**
* Scans the class files within a web application returning a <code>Set</code>
* of classes that have been annotated with a standard Faces annotation.
*/
private static class AnnotationScanTask implements Callable<Map<Class<? extends Annotation>,Set<Class<?>>>> {
private ServletContext sc;
private InitFacesContext facesContext;
private AnnotationProvider provider;
private ProvideMetadataToAnnotationScanTask metadataGetter;
// -------------------------------------------------------- Constructors
public AnnotationScanTask(ServletContext sc, InitFacesContext facesContext, ProvideMetadataToAnnotationScanTask metadataGetter) {
this.facesContext = facesContext;
this.provider = AnnotationProviderFactory.createAnnotationProvider(sc);
this.metadataGetter = metadataGetter;
}
// ----------------------------------------------- Methods from Callable
public Map<Class<? extends Annotation>,Set<Class<?>>> call() throws Exception {
Timer t = Timer.getInstance();
if (t != null) {
t.startTiming();
}
// We are executing on a different thread.
facesContext.callSetCurrentInstance();
Set<URI> scanUris = null;
com.sun.faces.spi.AnnotationScanner annotationScanner =
metadataGetter.getAnnotationScanner();
// This is where we discover what kind of InjectionProvider
// we have.
if (provider instanceof DelegatingAnnotationProvider &&
null != annotationScanner) {
// This InjectionProvider is capable of annotation scanning *and*
// injection.
((DelegatingAnnotationProvider)provider).setAnnotationScanner(annotationScanner,
metadataGetter.getJarNames());
scanUris = Collections.emptySet();
} else {
// This InjectionProvider is capable of annotation scanning only
scanUris = metadataGetter.getAnnotationScanURIs();
}
//AnnotationScanner scanner = new AnnotationScanner(sc);
Map<Class<? extends Annotation>,Set<Class<?>>> annotatedClasses =
provider.getAnnotatedClasses(scanUris);
if (t != null) {
t.stopTiming();
t.logResult("Configuration annotation scan complete.");
}
return annotatedClasses;
}
} // END AnnotationScanTask
/**
* <p>
* This <code>Callable</code> will be used by {@link ConfigManager#getConfigDocuments(javax.servlet.ServletContext, java.util.List, java.util.concurrent.ExecutorService, boolean)}.
* It represents a single configuration resource to be parsed into a DOM.
* </p>
*/
private static class ParseTask implements Callable<DocumentInfo> {
private static final String JAVAEE_SCHEMA_DEFAULT_NS =
"http://java.sun.com/xml/ns/javaee";
private static final String EMPTY_FACES_CONFIG =
"com/sun/faces/empty-faces-config.xml";
private URI documentURI;
private DocumentBuilderFactory factory;
private boolean validating;
// -------------------------------------------------------- Constructors
/**
* <p>
* Constructs a new ParseTask instance
* </p>
*
* @param validating whether or not we're validating
* @param documentURI a URL to the configuration resource to be parsed
* @throws Exception general error
*/
public ParseTask(boolean validating, URI documentURI)
throws Exception {
this.documentURI = documentURI;
this.validating = validating;
}
// ----------------------------------------------- Methods from Callable
/**
* @return the result of the parse operation (a DOM)
* @throws Exception if an error occurs during the parsing process
*/
public DocumentInfo call() throws Exception {
try {
Timer timer = Timer.getInstance();
if (timer != null) {
timer.startTiming();
}
Document d = getDocument();
if (timer != null) {
timer.stopTiming();
timer.logResult("Parse " + documentURI.toURL().toExternalForm());
}
return new DocumentInfo(d, documentURI);
} catch (Exception e) {
throw new ConfigurationException(MessageFormat.format(
"Unable to parse document ''{0}'': {1}",
documentURI.toURL().toExternalForm(),
e.getMessage()), e);
}
}
// ----------------------------------------------------- Private Methods
/**
* @return <code>Document</code> based on <code>documentURI</code>.
* @throws Exception if an error occurs during the process of building a
* <code>Document</code>
*/
private Document getDocument() throws Exception {
Document returnDoc;
DocumentBuilder db = getNonValidatingBuilder();
URL documentURL = documentURI.toURL();
InputSource is = new InputSource(getInputStream(documentURL));
is.setSystemId(documentURI.toURL().toExternalForm());
Document doc = null;
try {
doc = db.parse(is);
} catch (SAXParseException spe) {
// [mojarra-1693]
// Test if this is a zero length or whitespace only faces-config.xml file.
// If so, just make an empty Document
InputStream stream = is.getByteStream();
stream.close();
is = new InputSource(getInputStream(documentURL));
stream = is.getByteStream();
if (streamIsZeroLengthOrEmpty(stream) &&
documentURL.toExternalForm().endsWith("faces-config.xml")) {
ClassLoader loader = this.getClass().getClassLoader();
is = new InputSource(getInputStream(loader.getResource(EMPTY_FACES_CONFIG)));
doc = db.parse(is);
}
}
String documentNS = doc.getDocumentElement().getNamespaceURI();
if (validating && documentNS != null) {
DOMSource domSource
= new DOMSource(doc, documentURL.toExternalForm());
/*
* If the Document in question is 1.2 (i.e. it has a namespace matching
* JAVAEE_SCHEMA_DEFAULT_NS, then perform validation using the cached schema
* and return. Otherwise we assume a 1.0 or 1.1 faces-config in which case
* we need to transform it to reference a special 1.1 schema before validating.
*/
Node documentElement = ((Document) domSource.getNode()).getDocumentElement();
if (JAVAEE_SCHEMA_DEFAULT_NS.equals(documentNS)) {
Attr version = (Attr)
documentElement.getAttributes().getNamedItem("version");
DbfFactory.FacesSchema schema;
if (version != null) {
String versionStr = version.getValue();
if ("2.0".equals(versionStr)) {
if ("facelet-taglib".equals(documentElement.getLocalName())) {
schema = DbfFactory.FacesSchema.FACELET_TAGLIB_20;
} else {
schema = DbfFactory.FacesSchema.FACES_20;
}
} else if ("2.1".equals(versionStr)) {
if ("facelet-taglib".equals(documentElement.getLocalName())) {
schema = DbfFactory.FacesSchema.FACELET_TAGLIB_20;
} else {
schema = DbfFactory.FacesSchema.FACES_21;
}
} else if ("1.2".equals(versionStr)) {
schema = DbfFactory.FacesSchema.FACES_12;
} else {
throw new ConfigurationException("Unknown Schema version: " + versionStr);
}
DocumentBuilder builder = getBuilderForSchema(schema);
if (builder.isValidating()) {
builder.getSchema().newValidator().validate(domSource);
returnDoc = ((Document) domSource.getNode());
} else {
returnDoc = ((Document) domSource.getNode());
}
} else {
// this shouldn't happen, but...
throw new ConfigurationException("No document version available.");
}
} else {
DOMResult domResult = new DOMResult();
Transformer transformer = getTransformer(documentNS);
transformer.transform(domSource, domResult);
// copy the source document URI to the transformed result
// so that processes that need to build URLs relative to the
// document will work as expected.
((Document) domResult.getNode())
.setDocumentURI(((Document) domSource
.getNode()).getDocumentURI());
DbfFactory.FacesSchema schemaToApply;
if (FACES_CONFIG_1_X_DEFAULT_NS.equals(documentNS)) {
schemaToApply = DbfFactory.FacesSchema.FACES_11;
} else if (FACELETS_1_0_DEFAULT_NS.equals(documentNS)) {
schemaToApply = DbfFactory.FacesSchema.FACELET_TAGLIB_20;
} else {
throw new IllegalStateException();
}
DocumentBuilder builder = getBuilderForSchema(schemaToApply);
if (builder.isValidating()) {
builder.getSchema().newValidator().validate(new DOMSource(domResult.getNode()));
returnDoc = (Document) domResult.getNode();
} else {
returnDoc = (Document) domResult.getNode();
}
}
} else {
returnDoc = doc;
}
// mark this document as the parsed representation of the
// WEB-INF/faces-config.xml. This is used later in the configuration
// processing.
if (documentURL.toExternalForm().contains("/WEB-INF/faces-config.xml")) {
Attr webInf = returnDoc.createAttribute(WEB_INF_MARKER);
webInf.setValue("true");
returnDoc.getDocumentElement().getAttributes().setNamedItem(webInf);
}
return returnDoc;
}
private boolean streamIsZeroLengthOrEmpty(InputStream is) throws IOException {
boolean isZeroLengthOrEmpty = (0 == is.available());
final int size = 1024;
byte[] b = new byte[size];
String s;
while (!isZeroLengthOrEmpty && -1 != is.read(b, 0, size)) {
s = (new String(b, RIConstants.CHAR_ENCODING)).trim();
isZeroLengthOrEmpty = 0 == s.length();
b[0] = 0;
for (int i = 1; i < size; i += i) {
System.arraycopy(b, 0, b, i, ((size - i) < i) ? (size - i) : i);
}
}
return isZeroLengthOrEmpty;
}
/**
* Obtain a <code>Transformer</code> using the style sheet
* referenced by the <code>XSL</code> constant.
*
* @return a new Tranformer instance
* @throws Exception if a Tranformer instance could not be created
*/
private static Transformer getTransformer(String documentNS)
throws Exception {
TransformerFactory factory = Util.createTransformerFactory();
String xslToApply;
if (FACES_CONFIG_1_X_DEFAULT_NS.equals(documentNS)) {
xslToApply = FACES_TO_1_1_PRIVATE_XSL;
} else if (FACELETS_1_0_DEFAULT_NS.equals(documentNS)) {
xslToApply = FACELETS_TO_2_0_XSL;
} else {
throw new IllegalStateException();
}
return factory
.newTransformer(new StreamSource(getInputStream(ConfigManager
.class.getResource(xslToApply))));
}
/**
* @return an <code>InputStream</code> to the resource referred to by
* <code>url</code>
* @param url source <code>URL</code>
* @throws IOException if an error occurs
*/
private static InputStream getInputStream(URL url) throws IOException {
URLConnection conn = url.openConnection();
conn.setUseCaches(false);
return new BufferedInputStream(conn.getInputStream());
}
private DocumentBuilder getNonValidatingBuilder() throws Exception {
DocumentBuilderFactory tFactory = DbfFactory.getFactory();
tFactory.setValidating(false);
DocumentBuilder tBuilder = tFactory.newDocumentBuilder();
tBuilder.setEntityResolver(DbfFactory.FACES_ENTITY_RESOLVER);
tBuilder.setErrorHandler(DbfFactory.FACES_ERROR_HANDLER);
return tBuilder;
}
private DocumentBuilder getBuilderForSchema(DbfFactory.FacesSchema schema)
throws Exception {
schema.initSchema();
this.factory = DbfFactory.getFactory();
try {
factory.setSchema(schema.getSchema());
} catch (UnsupportedOperationException upe) {
return getNonValidatingBuilder();
}
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(DbfFactory.FACES_ENTITY_RESOLVER);
builder.setErrorHandler(DbfFactory.FACES_ERROR_HANDLER);
return builder;
}
} // END ParseTask
/**
* <p>
* This <code>Callable</code> will be used by {@link ConfigManager#getConfigDocuments(javax.servlet.ServletContext, java.util.List, java.util.concurrent.ExecutorService, boolean)}.
* It represents one or more URLs to configuration resources that require
* processing.
* </p>
*/
private static class URITask implements Callable<Collection<URI>> {
private ConfigurationResourceProvider provider;
private ServletContext sc;
// -------------------------------------------------------- Constructors
/**
* Constructs a new <code>URITask</code> instance.
* @param provider the <code>ConfigurationResourceProvider</code> from
* which zero or more <code>URL</code>s will be returned
* @param sc the <code>ServletContext</code> of the current application
*/
public URITask(ConfigurationResourceProvider provider,
ServletContext sc) {
this.provider = provider;
this.sc = sc;
}
// ----------------------------------------------- Methods from Callable
/**
* @return zero or more <code>URL</code> instances
* @throws Exception if an Exception is thrown by the underlying
* <code>ConfigurationResourceProvider</code>
*/
public Collection<URI> call() throws Exception {
Collection untypedCollection = provider.getResources(sc);
Iterator untypedCollectionIterator = untypedCollection.iterator();
Collection<URI> result = Collections.emptyList();
if (untypedCollectionIterator.hasNext()) {
Object cur = untypedCollectionIterator.next();
// account for older versions of the provider that return Collection<URL>.
if (cur instanceof URL) {
result = new ArrayList<URI>(untypedCollection.size());
result.add(new URI(((URL)cur).toExternalForm()));
while (untypedCollectionIterator.hasNext()) {
cur = untypedCollectionIterator.next();
result.add(new URI(((URL)cur).toExternalForm()));
}
} else {
result = (Collection<URI>) untypedCollection;
}
}
return result;
}
} // END URITask
}