/*
* 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.commons.resourcehandler;
import java.util.Map;
import javax.faces.application.ProjectStage;
import javax.faces.application.Resource;
import javax.faces.application.ResourceHandler;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.webapp.FacesServlet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
import org.apache.myfaces.commons.resourcehandler.application.FacesServletMapping;
import org.apache.myfaces.commons.resourcehandler.config.MyFacesResourceHandlerConfigParser;
import org.apache.myfaces.commons.resourcehandler.config.element.MyFacesResourcesConfig;
import org.apache.myfaces.commons.resourcehandler.resource.BaseResourceHandlerSupport;
import org.apache.myfaces.commons.resourcehandler.resource.ClassLoaderResourceLoader;
import org.apache.myfaces.commons.resourcehandler.resource.ExternalContextResourceLoader;
import org.apache.myfaces.commons.resourcehandler.resource.ResourceLoader;
import org.apache.myfaces.commons.resourcehandler.resource.ResourceMeta;
import org.apache.myfaces.commons.resourcehandler.webapp.config.WebConfigProvider;
import org.apache.myfaces.commons.resourcehandler.webapp.config.WebConfigProviderFactory;
import org.apache.myfaces.commons.resourcehandler.webapp.config.WebRegistration;
import org.apache.myfaces.commons.resourcehandler.webapp.config.element.ServletRegistration;
import org.apache.myfaces.commons.util.ClassUtils;
import org.apache.myfaces.commons.util.StringUtils;
import org.apache.myfaces.commons.util.WebConfigParamUtils;
public class ExtendedDefaultResourceHandlerSupport extends BaseResourceHandlerSupport
{
protected static final String CACHED_SERVLET_MAPPING =
ExtendedDefaultResourceHandlerSupport.class.getName() + ".CACHED_SERVLET_MAPPING";
/**
* Enable or disable gzip compressions for resources served by this extended resource handler. By default is disabled (false).
*/
@JSFWebConfigParam(defaultValue="false")
public static final String INIT_PARAM_GZIP_RESOURCES_ENABLED = "org.apache.myfaces.commons.GZIP_RESOURCES_ENABLED";
/**
* Indicate the suffix used to recognize resources that should be compressed. By default is ".css .js".
*/
@JSFWebConfigParam(defaultValue=".css, .js")
public static final String INIT_PARAM_GZIP_RESOURCES_SUFFIX = "org.apache.myfaces.commons.GZIP_RESOURCES_SUFFIX";
public static final String INIT_PARAM_GZIP_RESOURCES_EXTENSIONS_DEFAULT = ".css .js";
/**
* Indicate if gzipped files are stored on a temporal directory to serve them later. By default is true. If this is
* disable, the files are compressed when they are served.
*/
@JSFWebConfigParam(defaultValue="true")
public static final String INIT_PARAM_CACHE_DISK_GZIP_RESOURCES = "org.apache.myfaces.commons.CACHE_DISK_GZIP_RESOURCES";
/**
* Indicate the prefix that is added to each resource path that is used later to check if the request is a resource request.
*
* By default is /javax.faces.resource
*/
@JSFWebConfigParam(defaultValue="/javax.faces.resource")
public static final String INIT_PARAM_EXTENDED_RESOURCE_IDENTIFIER = "org.apache.myfaces.commons.EXTENDED_RESOURCE_IDENTIFIER";
private static final String INIT_PARAM_DELEGATE_FACES_SERVLET = "org.apache.myfaces.DELEGATE_FACES_SERVLET";
private static Class DELEGATE_FACES_SERVLET_INTERFACE_CLASS = null;
static
{
try
{
DELEGATE_FACES_SERVLET_INTERFACE_CLASS = ClassUtils.classForName("org.apache.myfaces.shared_impl.webapp.webxml.DelegatedFacesServlet");
}
catch (ClassNotFoundException e)
{
}
}
/**
* Accept-Encoding HTTP header field.
*/
private static final String ACCEPT_ENCODING_HEADER = "Accept-Encoding";
private ResourceLoader[] _resourceLoaders;
private final boolean _gzipResourcesEnabled;
private final String[] _gzipResourcesSuffix;
private final boolean _cacheDiskGzipResources;
private final boolean _developmentStage;
private MyFacesResourcesConfig _config;
private WebConfigProvider _webConfigProvider;
private String _resourceIdentifier;
public ExtendedDefaultResourceHandlerSupport()
{
super();
FacesContext context = FacesContext.getCurrentInstance();
_gzipResourcesEnabled = WebConfigParamUtils.getBooleanInitParameter(context.getExternalContext(),
INIT_PARAM_GZIP_RESOURCES_ENABLED, false);
_gzipResourcesSuffix = StringUtils.splitShortString(
WebConfigParamUtils.getStringInitParameter(context.getExternalContext(),
INIT_PARAM_GZIP_RESOURCES_SUFFIX, INIT_PARAM_GZIP_RESOURCES_EXTENSIONS_DEFAULT), ' ');
_cacheDiskGzipResources = WebConfigParamUtils.getBooleanInitParameter(context.getExternalContext(),
INIT_PARAM_CACHE_DISK_GZIP_RESOURCES, true);
_developmentStage = context.isProjectStage(ProjectStage.Development);
_resourceIdentifier = WebConfigParamUtils.getStringInitParameter(context.getExternalContext(),
INIT_PARAM_EXTENDED_RESOURCE_IDENTIFIER, ResourceHandler.RESOURCE_IDENTIFIER);
// parse the config
MyFacesResourceHandlerConfigParser configParser = new MyFacesResourceHandlerConfigParser();
_config = configParser.parse(FacesContext.getCurrentInstance());
_webConfigProvider = WebConfigProviderFactory.getFacesConfigResourceProviderFactory(context).
createWebConfigProvider(context);
_webConfigProvider.init(context);
// GZIPResourceLoader does some file operations to clear the cache, so this call must be done
// to ensure only one thread is cleaning it up (at setup time)
getResourceLoaders();
}
public MyFacesResourcesConfig getMyFacesResourcesConfig()
{
return _config;
}
public String[] getGzipResourcesSuffixes()
{
return _gzipResourcesSuffix;
}
public boolean isGzipResourcesEnabled()
{
return _gzipResourcesEnabled;
}
public boolean isCacheDiskGzipResources()
{
return _cacheDiskGzipResources;
}
public boolean isCompressable(ResourceMeta resourceMeta)
{
if (getGzipResourcesSuffixes() != null)
{
boolean compressable = false;
for (int i = 0; i < getGzipResourcesSuffixes().length; i++)
{
if (getGzipResourcesSuffixes()[i] != null &&
getGzipResourcesSuffixes()[i].length() > 0 &&
resourceMeta.getResourceName().endsWith(getGzipResourcesSuffixes()[i]))
{
compressable = true;
break;
}
}
return compressable;
}
else
{
return true;
}
}
public boolean isCompressable(Resource resource)
{
if (getGzipResourcesSuffixes() != null)
{
boolean compressable = false;
for (int i = 0; i < getGzipResourcesSuffixes().length; i++)
{
if (getGzipResourcesSuffixes()[i] != null &&
getGzipResourcesSuffixes()[i].length() > 0 &&
resource.getResourceName().endsWith(getGzipResourcesSuffixes()[i]))
{
compressable = true;
break;
}
}
return compressable;
}
else
{
return true;
}
}
public boolean userAgentSupportsCompression(FacesContext facesContext)
{
String acceptEncodingHeader = facesContext.getExternalContext()
.getRequestHeaderMap().get(ACCEPT_ENCODING_HEADER);
return ResourceUtils.isGZIPEncodingAccepted(acceptEncodingHeader);
}
public String calculateResourceBasePath(FacesContext facesContext)
{
ExternalContext externalContext = facesContext.getExternalContext();
String resourceBasePath = null;
//Calculate the mapping from the current request information
FacesServletMapping mapping = calculateFacesServletMapping(
externalContext.getRequestServletPath(),
externalContext.getRequestPathInfo());
//FacesServletMapping mapping = getFacesServletMapping(facesContext);
if (mapping != null)
{
if (mapping.isExtensionMapping())
{
// Mapping using a suffix. In this case we have to strip
// the suffix. If we have a url like:
// http://localhost:8080/testjsf20/javax.faces.resource/imagen.jpg.jsf?ln=dojo
//
// The servlet path is /javax.faces.resource/imagen.jpg.jsf
//
// For obtain the resource name we have to remove the .jsf suffix and
// the prefix ResourceHandler.RESOURCE_IDENTIFIER
resourceBasePath = externalContext.getRequestServletPath();
int stripPoint = resourceBasePath.lastIndexOf('.');
if (stripPoint > 0)
{
resourceBasePath = resourceBasePath.substring(0, stripPoint);
}
}
else
{
// Mapping using prefix. In this case we have to strip
// the prefix used for mapping. If we have a url like:
// http://localhost:8080/testjsf20/faces/javax.faces.resource/imagen.jpg?ln=dojo
//
// The servlet path is /faces
// and the path info is /javax.faces.resource/imagen.jpg
//
// For obtain the resource name we have to remove the /faces prefix and
// then the prefix ResourceHandler.RESOURCE_IDENTIFIER
resourceBasePath = externalContext.getRequestPathInfo();
}
return resourceBasePath;
}
else
{
//If no mapping is detected, just return the
//information follows the servlet path but before
//the query string
return externalContext.getRequestPathInfo();
}
}
protected FacesServletMapping getFacesServletMapping(FacesContext context)
{
Map<Object, Object> attributes = context.getAttributes();
// Has the mapping already been determined during this request?
FacesServletMapping mapping = (FacesServletMapping) attributes.get(CACHED_SERVLET_MAPPING);
if (mapping == null)
{
ExternalContext externalContext = context.getExternalContext();
FacesServletMapping calculatedMapping = calculateFacesServletMapping(
externalContext.getRequestServletPath(),
externalContext.getRequestPathInfo());
if (!calculatedMapping.isPrefixMapping())
{
// Scan the current configuration if there is a FacesServlet and if that so,
// retrieve the first prefix mapping and use it.
getWebConfigProvider().update(context);
WebRegistration webRegistration = getWebConfigProvider().getWebRegistration(context);
String prefix = getFacesServletPrefixMapping(context, webRegistration);
if (prefix != null)
{
mapping = FacesServletMapping.createPrefixMapping(prefix);
}
else
{
mapping = calculatedMapping;
}
}
else
{
mapping = calculatedMapping;
}
attributes.put(CACHED_SERVLET_MAPPING, mapping);
}
return mapping;
}
private String getFacesServletPrefixMapping(FacesContext context, WebRegistration webRegistration)
{
String prefix = null;
String delegateFacesServlet = WebConfigParamUtils.getStringInitParameter(context.getExternalContext(),
INIT_PARAM_DELEGATE_FACES_SERVLET);
for (Map.Entry<String, ? extends ServletRegistration> entry : webRegistration.getServletRegistrations().entrySet())
{
ServletRegistration registration = entry.getValue();
boolean facesServlet = false;
if (FacesServlet.class.getName().equals(registration.getClassName()))
{
facesServlet = true;
}
else if (delegateFacesServlet != null && delegateFacesServlet.equals(registration.getClassName()))
{
facesServlet = true;
}
else
{
if (DELEGATE_FACES_SERVLET_INTERFACE_CLASS != null)
{
try
{
Class servletClass = ClassUtils.classForName(registration.getClassName());
if (DELEGATE_FACES_SERVLET_INTERFACE_CLASS.isAssignableFrom(servletClass));
{
facesServlet = true;
}
}
catch (ClassNotFoundException e)
{
Log log = LogFactory.getLog(ExtendedDefaultResourceHandlerSupport.class);
if (log.isTraceEnabled())
{
log.trace("cannot load servlet class to detect if is a FacesServlet or DelegateFacesServlet", e);
}
}
}
}
if (facesServlet)
{
for (String urlPattern : registration.getMappings())
{
String extension = urlPattern != null && urlPattern.startsWith("*.") ? urlPattern.substring(urlPattern
.indexOf('.')) : null;
if (extension == null)
{
int index = urlPattern.indexOf("/*");
if (index != -1)
{
prefix = urlPattern.substring(0, urlPattern.indexOf("/*"));
}
else
{
prefix = urlPattern;
}
}
else
{
prefix = null;
}
if (prefix != null)
{
return prefix;
}
}
}
}
return prefix;
}
/**
* Return the resource loaders used. Note this loaders should return ExtendedResourceMeta instances.
*/
public ResourceLoader[] getResourceLoaders()
{
if (_resourceLoaders == null)
{
// we should serve a compressed version of the resource, if
// - ProjectStage != Development
// - a compressed version is available (created in constructor)
// - the user agent supports compresssion
if (/*!_developmentStage && */isGzipResourcesEnabled() && isCacheDiskGzipResources())
{
_resourceLoaders = new ResourceLoader[] {
new GZIPResourceLoader(new ExtendedResourceLoaderWrapper(new ExternalContextResourceLoader("/resources")), this),
new GZIPResourceLoader(new ExtendedResourceLoaderWrapper(new ClassLoaderResourceLoader("META-INF/resources")), this)
};
}
else
{
_resourceLoaders = new ResourceLoader[] {
new ExtendedResourceLoaderWrapper(new ExternalContextResourceLoader("/resources")),
new ExtendedResourceLoaderWrapper(new ClassLoaderResourceLoader("META-INF/resources"))
};
}
}
return _resourceLoaders;
}
public WebConfigProvider getWebConfigProvider()
{
return _webConfigProvider;
}
@Override
public String getResourceIdentifier()
{
return _resourceIdentifier;
}
}