/*
* 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.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPOutputStream;
import javax.faces.FacesException;
import javax.faces.application.ProjectStage;
import javax.faces.application.Resource;
import javax.faces.application.ResourceHandler;
import javax.faces.application.ResourceHandlerWrapper;
import javax.faces.application.ResourceWrapper;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletResponse;
import org.apache.myfaces.commons.resourcehandler.config.element.Library;
import org.apache.myfaces.commons.resourcehandler.resource.ResourceHandlerCache;
import org.apache.myfaces.commons.resourcehandler.resource.ResourceHandlerCache.ResourceValue;
import org.apache.myfaces.commons.resourcehandler.resource.ResourceLoader;
import org.apache.myfaces.commons.resourcehandler.resource.ResourceMeta;
import org.apache.myfaces.commons.util.ClassUtils;
import org.apache.myfaces.commons.util.ExternalContextUtils;
import org.apache.myfaces.commons.util.RequestType;
import org.apache.myfaces.commons.util.StringUtils;
/**
*
* @author Leonardo Uribe
*
*/
public class ExtendedResourceHandlerImpl extends ResourceHandlerWrapper
{
private static final String IS_RESOURCE_REQUEST = "org.apache.myfaces.commons.IS_RESOURCE_REQUEST";
private static final String RESOURCE_LOCALE = "org.apache.myfaces.commons.RESOURCE_LOCALE";
private ExtendedDefaultResourceHandlerSupport _resourceHandlerSupport;
private ResourceHandlerCache _resourceHandlerCache;
//private static final Log log = LogFactory.getLog(ResourceHandlerImpl.class);
private static final Logger log = Logger.getLogger(ExtendedResourceHandlerImpl.class.getName());
private static final int _BUFFER_SIZE = 2048;
private ResourceHandler _delegate;
//private volatile Boolean filterOn;
public ExtendedResourceHandlerImpl(ResourceHandler delegate)
{
this._delegate = delegate;
//Eager initialization
_resourceHandlerSupport = new ExtendedDefaultResourceHandlerSupport();
}
/*
@Override
public Resource createResource(String resourceName)
{
return createResource(resourceName, null);
}*/
@Override
public Resource createResource(String resourceName, String libraryName)
{
if (getResourceHandlerSupport().getMyFacesResourcesConfig().getLibrary(libraryName) != null)
{
return createResource(resourceName, libraryName, null);
}
else
{
return super.createResource(resourceName, libraryName);
}
}
@Override
public Resource createResource(String resourceName, String libraryName,
String contentType)
{
if (getResourceHandlerSupport().getMyFacesResourcesConfig().getLibrary(libraryName) != null)
{
return defaultCreateResource(resourceName, libraryName, contentType);
}
else
{
return super.createResource(resourceName, libraryName, contentType);
}
}
private Resource defaultCreateResource(String resourceName, String expectedLibraryName,
String contentType)
{
Resource resource = null;
FacesContext facesContext = FacesContext.getCurrentInstance();
if (contentType == null)
{
//Resolve contentType using ExternalContext.getMimeType
contentType = facesContext.getExternalContext().getMimeType(resourceName);
}
String localePrefix = (String) facesContext.getAttributes().get(RESOURCE_LOCALE);
if (localePrefix == null)
{
localePrefix = getLocalePrefixForLocateResource();
}
//Calculate the real libraryName
String redirectedLibraryName = resolveLibraryName(expectedLibraryName);
// check cache
if(getResourceLoaderCache().containsResource(resourceName, redirectedLibraryName, contentType, localePrefix))
{
ResourceValue resourceValue = getResourceLoaderCache().getResource(
resourceName, redirectedLibraryName, contentType, localePrefix);
//resource = new ResourceImpl(resourceValue.getResourceMeta(), resourceValue.getResourceLoader(),
// getResourceHandlerSupport(), contentType);
resource = new ExtendedResourceImpl((ExtendedResourceMeta) resourceValue.getResourceMeta(), resourceValue.getResourceLoader(),
getResourceHandlerSupport(), contentType, localePrefix, redirectedLibraryName.equals(expectedLibraryName) ? null : expectedLibraryName);
}
else
{
for (ResourceLoader loader : getResourceHandlerSupport().getResourceLoaders())
{
ResourceMeta resourceMeta = deriveResourceMeta(loader, resourceName, redirectedLibraryName, localePrefix);
if (resourceMeta != null)
{
//resource = new ResourceImpl(resourceMeta, loader, getResourceHandlerSupport(), contentType);
resource = new ExtendedResourceImpl((ExtendedResourceMeta) resourceMeta, loader, getResourceHandlerSupport(), contentType, localePrefix,
redirectedLibraryName.equals(expectedLibraryName) ? null : expectedLibraryName);
// cache it
getResourceLoaderCache().putResource(resourceName, redirectedLibraryName, contentType,
localePrefix, resourceMeta, loader);
break;
}
}
}
return resource;
}
public String resolveLibraryName(String libraryName)
{
String finalLibraryName = libraryName;
Library library = null;
boolean resolved = false;
do
{
library = getResourceHandlerSupport().getMyFacesResourcesConfig().getLibrary(finalLibraryName);
if (library != null)
{
if (library.getRedirectName() != null && library.getRedirectName().length() > 0)
{
finalLibraryName = library.getRedirectName();
}
else
{
//No redirect, so this is the real instance
resolved = true;
}
}
} while (library != null && !resolved);
return finalLibraryName;
}
/**
* This method try to create a ResourceMeta for a specific resource
* loader. If no library, or resource is found, just return null,
* so the algorithm in createResource can continue checking with the
* next registered ResourceLoader.
*/
protected ExtendedResourceMeta deriveResourceMeta(ResourceLoader resourceLoader,
String resourceName, String libraryName, String localePrefix)
{
String resourceVersion = null;
String libraryVersion = null;
ExtendedResourceMeta resourceId = null;
//1. Try to locate resource in a localized path
if (localePrefix != null)
{
if (null != libraryName)
{
String pathToLib = localePrefix + '/' + libraryName;
libraryVersion = resourceLoader.getLibraryVersion(pathToLib);
if (null != libraryVersion)
{
String pathToResource = localePrefix + '/'
+ libraryName + '/' + libraryVersion + '/'
+ resourceName;
resourceVersion = resourceLoader
.getResourceVersion(pathToResource);
}
else
{
String pathToResource = localePrefix + '/'
+ libraryName + '/' + resourceName;
resourceVersion = resourceLoader
.getResourceVersion(pathToResource);
}
if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
{
resourceId = (ExtendedResourceMeta) resourceLoader.createResourceMeta(localePrefix, libraryName,
libraryVersion, resourceName, resourceVersion);
}
}
else
{
resourceVersion = resourceLoader
.getResourceVersion(localePrefix + '/'+ resourceName);
if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
{
resourceId = (ExtendedResourceMeta) resourceLoader.createResourceMeta(localePrefix, null, null,
resourceName, resourceVersion);
}
}
if (resourceId != null)
{
if (!resourceLoader.resourceExists(resourceId))
{
resourceId = null;
}
}
}
//2. Try to localize resource in a non localized path
if (resourceId == null)
{
if (null != libraryName)
{
libraryVersion = resourceLoader.getLibraryVersion(libraryName);
if (null != libraryVersion)
{
String pathToResource = (libraryName + '/' + libraryVersion
+ '/' + resourceName);
resourceVersion = resourceLoader
.getResourceVersion(pathToResource);
}
else
{
String pathToResource = (libraryName + '/'
+ resourceName);
resourceVersion = resourceLoader
.getResourceVersion(pathToResource);
}
if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
{
resourceId = (ExtendedResourceMeta) resourceLoader.createResourceMeta(null, libraryName,
libraryVersion, resourceName, resourceVersion);
}
}
else
{
resourceVersion = resourceLoader
.getResourceVersion(resourceName);
if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
{
resourceId = (ExtendedResourceMeta) resourceLoader.createResourceMeta(null, null, null,
resourceName, resourceVersion);
}
}
if (resourceId != null)
{
if (!resourceLoader.resourceExists(resourceId))
{
resourceId = null;
}
}
}
return resourceId;
}
/**
* Handle the resource request, writing in the output.
*
* This method implements an algorithm semantically identical to
* the one described on the javadoc of ResourceHandler.handleResourceRequest
*/
@Override
public void handleResourceRequest(FacesContext facesContext) throws IOException
{
// Only if filter is on
//if (!isFilterOn())
//{
// super.handleResourceRequest(facesContext);
//}
// And this is a request handled from the filter first!
//if (!facesContext.getAttributes().containsKey(ResourceHandlerFilter.RESOURCE_HANDLER_FILTER_REQUEST))
//{
// super.handleResourceRequest(facesContext);
//}
try
{
String resourceBasePath = getResourceHandlerSupport()
.calculateResourceBasePath(facesContext);
if (resourceBasePath == null)
{
// No base name could be calculated, so no further
//advance could be done here. HttpServletResponse.SC_NOT_FOUND
//cannot be returned since we cannot extract the
//resource base name
super.handleResourceRequest(facesContext);
return;
}
// We neet to get an instance of HttpServletResponse, but sometimes
// the response object is wrapped by several instances of
// ServletResponseWrapper (like ResponseSwitch).
// Since we are handling a resource, we can expect to get an
// HttpServletResponse.
if (!RequestType.SERVLET.equals(ExternalContextUtils.getRequestType(facesContext.getExternalContext())))
{
throw new IllegalStateException("Could not obtain an instance of HttpServletResponse.");
}
Object response = facesContext.getExternalContext().getResponse();
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
if (httpServletResponse == null)
{
throw new IllegalStateException("Could not obtain an instance of HttpServletResponse.");
}
if (isResourceIdentifierExcluded(facesContext, resourceBasePath))
{
httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
String resourceName = null;
String libraryName = null;
String requestedLocalePrefix = null;
if (resourceBasePath.startsWith(getResourceHandlerSupport().getResourceIdentifier()))
{
//resourceName = resourceBasePath
// .substring(ResourceHandler.RESOURCE_IDENTIFIER.length() + 1);
resourceName = resourceBasePath
.substring(getResourceHandlerSupport().getResourceIdentifier().length());
if (resourceName.startsWith("/$"))
{
//Extract locale prefix, libraryName and resourceName
int from = 3;
int to = resourceName.indexOf('/',3);
requestedLocalePrefix = resourceName.substring(from,to);
from = to+1;
to = resourceName.indexOf('/', from);
libraryName = resourceName.substring(from, to);
resourceName = resourceName.substring(to+1);
}
else
{
//No special identifier used, delegate to default algorithm
resourceName = null;
//Try to get the library name using the standard form
//libraryName = facesContext.getExternalContext()
// .getRequestParameterMap().get("ln");
}
}
else
{
//Does not have the conditions for be a resource call, let the base one
//return not found
super.handleResourceRequest(facesContext);
return;
}
//Only resources with resourceName and advanced libraryName are handled by this handler
if (resourceName == null)
{
super.handleResourceRequest(facesContext);
return;
}
else if (libraryName == null)
{
super.handleResourceRequest(facesContext);
return;
}
else if (libraryName != null && getResourceHandlerSupport().getMyFacesResourcesConfig().getLibrary(libraryName) == null)
{
super.handleResourceRequest(facesContext);
return;
}
if (requestedLocalePrefix != null)
{
facesContext.getAttributes().put(RESOURCE_LOCALE, requestedLocalePrefix);
}
Resource resource = null;
//if (libraryName != null)
//{
//log.info("libraryName=" + libraryName);
resource = facesContext.getApplication().getResourceHandler().createResource(resourceName, libraryName);
//}
//else
//{
// resource = facesContext.getApplication().getResourceHandler().createResource(resourceName);
//}
if (resource == null)
{
httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
if (!resource.userAgentNeedsUpdate(facesContext))
{
httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
httpServletResponse.setContentType(_getContentType(resource, facesContext.getExternalContext()));
Map<String, String> headers = resource.getResponseHeaders();
for (Map.Entry<String, String> entry : headers.entrySet())
{
httpServletResponse.setHeader(entry.getKey(), entry.getValue());
}
//serve up the bytes (taken from trinidad ResourceServlet)
try
{
// 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
// and if there is no caching on disk, do compression here!
if (!facesContext.isProjectStage(ProjectStage.Development) &&
getResourceHandlerSupport().isGzipResourcesEnabled() &&
!getResourceHandlerSupport().isCacheDiskGzipResources() &&
getResourceHandlerSupport().userAgentSupportsCompression(facesContext) &&
getResourceHandlerSupport().isCompressable(resource))
{
InputStream in = resource.getInputStream();
OutputStream out = new GZIPOutputStream(httpServletResponse.getOutputStream(), _BUFFER_SIZE);
byte[] buffer = new byte[_BUFFER_SIZE];
try
{
int count = pipeBytes(in, out, buffer);
//set the content lenght
httpServletResponse.setContentLength(count);
}
finally
{
try
{
in.close();
}
finally
{
out.close();
}
}
}
else
{
InputStream in = resource.getInputStream();
OutputStream out = httpServletResponse.getOutputStream();
byte[] buffer = new byte[_BUFFER_SIZE];
try
{
int count = pipeBytes(in, out, buffer);
//set the content lenght
httpServletResponse.setContentLength(count);
}
finally
{
try
{
in.close();
}
finally
{
out.close();
}
}
}
}
catch (IOException e)
{
//TODO: Log using a localized message (which one?)
if (log.isLoggable(Level.SEVERE))
log.severe("Error trying to load resource " + resourceName
+ " with library " + libraryName + " :"
+ e.getMessage());
httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
catch (Throwable ex)
{
// handle the Throwable accordingly. Maybe generate an error page.
// FIXME we are creating a html error page for a non html request here
// shouln't we do something better? -=Jakob Korherr=-
throw new FacesException(ex);
//ErrorPageWriter.handleThrowable(facesContext, ex);
}
}
/**
* Reads the specified input stream into the provided byte array storage and
* writes it to the output stream.
*/
private static int pipeBytes(InputStream in, OutputStream out, byte[] buffer)
throws IOException
{
int count = 0;
int length;
while ((length = (in.read(buffer))) >= 0)
{
out.write(buffer, 0, length);
count += length;
}
return count;
}
@Override
public boolean isResourceRequest(FacesContext facesContext)
{
// Since this method could be called many times we save it
//on request map so the first time is calculated it remains
//alive until the end of the request
Boolean value = (Boolean) facesContext.getAttributes().get(IS_RESOURCE_REQUEST);
if (value != null && value)
{
//return the saved value
return value;
}
else
{
String resourceBasePath = getResourceHandlerSupport()
.calculateResourceBasePath(facesContext);
if (resourceBasePath != null
&& resourceBasePath.startsWith(getResourceHandlerSupport().getResourceIdentifier()))
{
facesContext.getAttributes().put(IS_RESOURCE_REQUEST, Boolean.TRUE);
return true;
}
else
{
value = super.isResourceRequest(facesContext);
facesContext.getAttributes().put(IS_RESOURCE_REQUEST, value);
return value;
}
}
}
protected String getLocalePrefixForLocateResource()
{
String localePrefix = null;
FacesContext context = FacesContext.getCurrentInstance();
String bundleName = context.getApplication().getMessageBundle();
if (null != bundleName)
{
Locale locale = context.getApplication().getViewHandler()
.calculateLocale(context);
ResourceBundle bundle = ResourceBundle
.getBundle(bundleName, locale, ClassUtils.getContextClassLoader());
if (bundle != null)
{
try
{
localePrefix = bundle.getString(ResourceHandler.LOCALE_PREFIX);
}
catch (MissingResourceException e)
{
// Ignore it and return null
}
}
}
return localePrefix;
}
protected boolean isResourceIdentifierExcluded(FacesContext context,
String resourceIdentifier)
{
String value = context.getExternalContext().getInitParameter(
RESOURCE_EXCLUDES_PARAM_NAME);
if (value == null)
{
value = RESOURCE_EXCLUDES_DEFAULT_VALUE;
}
//TODO: optimize this code
String[] extensions = StringUtils.splitShortString(value, ' ');
for (int i = 0; i < extensions.length; i++)
{
if (resourceIdentifier.endsWith(extensions[i]))
{
return true;
}
}
return false;
}
/**
* Check if a library exists or not. This is done delegating
* to each ResourceLoader used, because each one has a different
* prefix and way to load resources.
*
*/
@Override
public boolean libraryExists(String libraryName)
{
if (getResourceHandlerSupport().getMyFacesResourcesConfig().getLibrary(libraryName) != null)
{
String localePrefix = getLocalePrefixForLocateResource();
String pathToLib = null;
if (localePrefix != null)
{
//Check with locale
pathToLib = localePrefix + '/' + libraryName;
for (ResourceLoader loader : getResourceHandlerSupport()
.getResourceLoaders())
{
if (loader.libraryExists(pathToLib))
{
return true;
}
}
}
//Check without locale
for (ResourceLoader loader : getResourceHandlerSupport()
.getResourceLoaders())
{
if (loader.libraryExists(libraryName))
{
return true;
}
}
}
else
{
super.libraryExists(libraryName);
}
return false;
}
/**
* @param resourceHandlerSupport
* the resourceHandlerSupport to set
*/
public void setResourceHandlerSupport(
ExtendedDefaultResourceHandlerSupport resourceHandlerSupport)
{
_resourceHandlerSupport = resourceHandlerSupport;
}
/**
* @return the resourceHandlerSupport
*/
protected ExtendedDefaultResourceHandlerSupport getResourceHandlerSupport()
{
return _resourceHandlerSupport;
}
private ResourceHandlerCache getResourceLoaderCache()
{
if (_resourceHandlerCache == null)
_resourceHandlerCache = new ResourceHandlerCache();
return _resourceHandlerCache;
}
private String _getContentType(Resource resource, ExternalContext externalContext)
{
String contentType = resource.getContentType();
// the resource does not provide a content-type --> determine it via mime-type
if (contentType == null || contentType.length() == 0)
{
String resourceName = getWrappedResourceName(resource);
if (resourceName != null)
{
contentType = externalContext.getMimeType(resourceName);
}
}
return contentType;
}
/**
* Recursively unwarp the resource until we find the real resourceName
* This is needed because the JSF2 specced ResourceWrapper doesn't override
* the getResourceName() method :(
* @param resource
* @return the first non-null resourceName or <code>null</code> if none set
*/
private String getWrappedResourceName(Resource resource)
{
String resourceName = resource.getResourceName();
if (resourceName != null)
{
return resourceName;
}
if (resource instanceof ResourceWrapper)
{
return getWrappedResourceName(((ResourceWrapper) resource).getWrapped());
}
return null;
}
@Override
public ResourceHandler getWrapped()
{
return _delegate;
}
}