blob: 678a2196dba918a6706a9167eedb83300297b96c [file] [log] [blame]
/*
* 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.webapp;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.application.ResourceDependencies;
import javax.faces.application.ResourceDependency;
import javax.faces.component.FacesComponent;
import javax.faces.component.UIComponent;
import javax.faces.component.behavior.FacesBehavior;
import javax.faces.context.ExternalContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;
import javax.faces.event.ListenerFor;
import javax.faces.event.ListenersFor;
import javax.faces.event.NamedEvent;
import javax.faces.model.FacesDataModel;
import javax.faces.render.FacesBehaviorRenderer;
import javax.faces.render.FacesRenderer;
import javax.faces.render.Renderer;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.Validator;
import javax.faces.webapp.FacesServlet;
import javax.servlet.Servlet;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.annotation.HandlesTypes;
import org.apache.myfaces.config.MyfacesConfig;
import org.apache.myfaces.context.servlet.StartupServletExternalContextImpl;
import org.apache.myfaces.spi.FacesConfigResourceProvider;
import org.apache.myfaces.spi.FacesConfigResourceProviderFactory;
import org.apache.myfaces.util.lang.ClassUtils;
/**
* This class is called by any Java EE 6 complaint container at startup.
* It checks if the current webapp is a JSF-webapp by checking if some of
* the JSF related annotations are specified in the webapp classpath or if
* the faces-config.xml file is present. If so, the listener checks if
* the FacesServlet has already been defined in web.xml and if not, it adds
* the FacesServlet with the mappings (/faces/*, *.jsf, *.faces) dynamically.
*
* @author Jakob Korherr (latest modification by $Author$)
* @version $Revision$ $Date$
*/
@HandlesTypes({
FacesBehavior.class,
FacesBehaviorRenderer.class,
FacesComponent.class,
FacesConverter.class,
FacesRenderer.class,
FacesValidator.class,
FacesDataModel.class,
ListenerFor.class,
ListenersFor.class,
NamedEvent.class,
ResourceDependencies.class,
ResourceDependency.class,
UIComponent.class,
Converter.class,
Renderer.class,
Validator.class
})
public class MyFacesContainerInitializer implements ServletContainerInitializer
{
/**
* If the servlet mapping for the FacesServlet is added dynamically, Boolean.TRUE
* is stored under this key in the ServletContext.
*/
public static final String FACES_SERVLET_ADDED_ATTRIBUTE = "org.apache.myfaces.DYNAMICALLY_ADDED_FACES_SERVLET";
/**
* If the servlet mapping for the FacesServlet is found on the ServletContext, Boolean.TRUE
* is stored under this key in the ServletContext.
*/
public static final String FACES_SERVLET_FOUND = "org.apache.myfaces.FACES_SERVLET_FOUND";
private static final String FACES_CONFIG_RESOURCE = "/WEB-INF/faces-config.xml";
private static final Logger log = Logger.getLogger(MyFacesContainerInitializer.class.getName());
private static final String[] FACES_SERVLET_MAPPINGS = { "/faces/*", "*.jsf", "*.faces" };
private static final String[] FACES_SERVLET_FULL_MAPPINGS = { "/faces/*", "*.jsf", "*.faces", "*.xhtml" };
private static final String FACES_SERVLET_NAME = "FacesServlet";
private static final Class<? extends Servlet> FACES_SERVLET_CLASS = FacesServlet.class;
private static final Class<?> DELEGATED_FACES_SERVLET_CLASS = DelegatedFacesServlet.class;
@Override
public void onStartup(Set<Class<?>> clazzes, ServletContext servletContext) throws ServletException
{
log.log(Level.INFO, "Using " + this.getClass().getName());
MyFacesHttpSessionListener httpSessionListener = new MyFacesHttpSessionListener();
servletContext.addListener(httpSessionListener);
// Publishes the MyFacesHttpSessionListener instance into the servletContext.
// This allows the FacesConfigurator to access the instance and to set the
// correct ManagedBeanDestroyer instance on it.
servletContext.setAttribute(MyFacesHttpSessionListener.APPLICATION_MAP_KEY, httpSessionListener);
boolean startDireclty = shouldStartupRegardless(servletContext);
if (startDireclty)
{
// if the INITIALIZE_ALWAYS_STANDALONE param was set to true,
// we do not want to have the FacesServlet being added, we simply
// do no extra configuration in here.
return;
}
// Check for one or more of this conditions:
// 1. A faces-config.xml file is found in WEB-INF
// 2. A faces-config.xml file is found in the META-INF directory of a jar in the application's classpath.
// 3. A filename ending in .faces-config.xml is found in the META-INF directory of a jar in the
// application's classpath.
// 4. The javax.faces.CONFIG_FILES context param is declared in web.xml or web-fragment.xml.
// 5. The Set of classes passed to the onStartup() method of the ServletContainerInitializer
// implementation is not empty.
if ((clazzes != null && !clazzes.isEmpty()) || isFacesConfigPresent(servletContext))
{
// look for the FacesServlet
Map<String, ? extends ServletRegistration> servlets = servletContext.getServletRegistrations();
for (Map.Entry<String, ? extends ServletRegistration> servletEntry : servlets.entrySet())
{
String className = servletEntry.getValue().getClassName();
if (FACES_SERVLET_CLASS.getName().equals(className) || isDelegatedFacesServlet(className))
{
// we found a FacesServlet; set an attribute for use during initialization
servletContext.setAttribute(FACES_SERVLET_FOUND, Boolean.TRUE);
return;
}
}
// the FacesServlet is not installed yet - install it
ServletRegistration.Dynamic servlet = servletContext.addServlet(FACES_SERVLET_NAME, FACES_SERVLET_CLASS);
//try to add typical JSF mappings
String[] mappings = isAutomaticXhtmlMappingDisabled(servletContext) ?
FACES_SERVLET_MAPPINGS : FACES_SERVLET_FULL_MAPPINGS;
Set<String> conflictMappings = servlet.addMapping(mappings);
if (conflictMappings != null && !conflictMappings.isEmpty())
{
//at least one of the attempted mappings is in use, remove and try again
Set<String> newMappings = new HashSet<>(Arrays.asList(mappings));
newMappings.removeAll(conflictMappings);
mappings = newMappings.toArray(new String[newMappings.size()]);
servlet.addMapping(mappings);
}
if (mappings != null && mappings.length > 0)
{
// at least one mapping was added
// now we have to set a field in the ServletContext to indicate that we have
// added the mapping dynamically, because MyFaces just parsed the web.xml to
// find mappings and thus it would abort initializing
servletContext.setAttribute(FACES_SERVLET_ADDED_ATTRIBUTE, Boolean.TRUE);
// add a log message
log.log(Level.INFO, "Added FacesServlet with mappings=" + Arrays.toString(mappings));
}
}
}
/**
* Checks if the <code>INITIALIZE_ALWAYS_STANDALONE</code> flag is ture in <code>web.xml</code>.
* If the flag is true, this means we should not add the FacesServlet, instead we want to
* init MyFaces regardless...
*/
private boolean shouldStartupRegardless(ServletContext servletContext)
{
try
{
String standaloneStartup = servletContext.getInitParameter(MyfacesConfig.INITIALIZE_ALWAYS_STANDALONE);
return "true".equalsIgnoreCase(standaloneStartup);
}
catch (Exception e)
{
return false;
}
}
/**
* Checks if the <code>INITIALIZE_SCAN_JARS_FOR_FACES_CONFIG</code> flag is true in <code>web.xml</code>.
* If the flag is true, this means we should scan app jars for *.faces-config.xml before adding
* any FacesServlet; in false, we skip that scan for performance.
*/
private boolean shouldSkipJarFacesConfigScan(ServletContext servletContext)
{
try
{
String skipJarScan = servletContext.getInitParameter(MyfacesConfig.INITIALIZE_SKIP_JAR_FACES_CONFIG_SCAN);
if (skipJarScan == null)
{
skipJarScan = System.getProperty(MyfacesConfig.INITIALIZE_SKIP_JAR_FACES_CONFIG_SCAN);
}
return "true".equalsIgnoreCase(skipJarScan);
}
catch (Exception e)
{
return false;
}
}
private boolean isAutomaticXhtmlMappingDisabled(ServletContext servletContext)
{
try
{
String xhtmlMappingDisabled = servletContext.getInitParameter(
FacesServlet.DISABLE_FACESSERVLET_TO_XHTML_PARAM_NAME);
if (xhtmlMappingDisabled == null)
{
xhtmlMappingDisabled = "false";
}
return "true".equalsIgnoreCase(xhtmlMappingDisabled);
}
catch (Exception e)
{
return false;
}
}
/**
* Checks if /WEB-INF/faces-config.xml is present.
* @return
*/
private boolean isFacesConfigPresent(ServletContext servletContext)
{
try
{
// 1. A faces-config.xml file is found in WEB-INF
if (servletContext.getResource(FACES_CONFIG_RESOURCE) != null)
{
return true;
}
// 4. The javax.faces.CONFIG_FILES context param is declared in web.xml or web-fragment.xml.
// check for alternate faces-config files specified by javax.faces.CONFIG_FILES
String configFilesAttrValue = servletContext.getInitParameter(FacesServlet.CONFIG_FILES_ATTR);
if (configFilesAttrValue != null)
{
String[] configFiles = configFilesAttrValue.split(",");
for (String file : configFiles)
{
if (servletContext.getResource(file.trim()) != null)
{
return true;
}
}
}
// Skip this scan - for performance - if INITIALIZE_SKIP_JAR_FACES_CONFIG_SCAN is set to true
// 2. A faces-config.xml file is found in the META-INF directory of a jar in the
// application's classpath.
// 3. A filename ending in .faces-config.xml is found in the META-INF directory of a jar in
// the application's classpath.
// To do this properly it is necessary to use some SPI interfaces MyFaces already has, to
// deal with OSGi and other
// environments properly.
if (!shouldSkipJarFacesConfigScan(servletContext))
{
ExternalContext externalContext = new StartupServletExternalContextImpl(servletContext, true);
FacesConfigResourceProviderFactory factory = FacesConfigResourceProviderFactory.
getFacesConfigResourceProviderFactory(externalContext);
FacesConfigResourceProvider provider = factory.createFacesConfigResourceProvider(externalContext);
Collection<URL> metaInfFacesConfigUrls = provider.getMetaInfConfigurationResources(externalContext);
if (metaInfFacesConfigUrls != null && !metaInfFacesConfigUrls.isEmpty())
{
return true;
}
}
return false;
}
catch (Exception e)
{
return false;
}
}
/**
* Checks if the class represented by className implements DelegatedFacesServlet.
* @param className
* @return
*/
private boolean isDelegatedFacesServlet(String className)
{
if (className == null)
{
// The class name can be null if this is e.g., a JSP mapped to
// a servlet.
return false;
}
try
{
Class<?> clazz = ClassUtils.classForName(className);
return DELEGATED_FACES_SERVLET_CLASS.isAssignableFrom(clazz);
}
catch (ClassNotFoundException cnfe)
{
return false;
}
}
}