
org.richfaces.resource.ResourceHandlerImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of richfaces-core Show documentation
Show all versions of richfaces-core Show documentation
The RichFaces core framework.
/*
* JBoss, Home of Professional Open Source
* Copyright 2013, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.richfaces.resource;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import javax.faces.application.ProjectStage;
import javax.faces.application.Resource;
import javax.faces.application.ResourceHandler;
import javax.faces.application.ResourceHandlerWrapper;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletResponse;
import org.richfaces.cache.Cache;
import org.richfaces.log.Logger;
import org.richfaces.log.RichfacesLogger;
import org.richfaces.renderkit.html.ResourceLibraryRenderer;
import org.richfaces.application.ServiceTracker;
import org.richfaces.util.RequestStateManager.BooleanRequestStateVariable;
/**
* RichFaces-specific {@link ResourceHandler}.
*
* It adds support for:
*
*
* - ECSS files handling
* - cacheable resources
*
*
* It delegates to {@link ResourceFactory} for creating resources.
*
* @author Nick Belaevski
* @since 4.0
*/
// TODO extract caching
public class ResourceHandlerImpl extends ResourceHandlerWrapper {
public static final String RICHFACES_RESOURCE_IDENTIFIER = "/rfRes/";
public static final String RESOURCE_CACHE_NAME = "org.richfaces.ResourcesCache";
public static final String HANDLER_START_TIME_ATTRIBUTE = ResourceHandlerImpl.class.getName() + ":StartTime";
private static final Logger LOGGER = RichfacesLogger.RESOURCE.getLogger();
private ResourceFactory resourceFactory;
private ResourceHandler defaultHandler;
public ResourceHandlerImpl(ResourceHandler defaultHandler) {
this.defaultHandler = defaultHandler;
this.resourceFactory = new ResourceFactoryImpl(defaultHandler);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(MessageFormat.format("Instance of {0} resource handler created", getClass().getName()));
}
}
/*
* (non-Javadoc)
* @see javax.faces.application.ResourceHandlerWrapper#isResourceRequest(javax.faces.context.FacesContext)
*/
@Override
public boolean isResourceRequest(FacesContext context) {
return isThisHandlerResourceRequest(context) || defaultHandler.isResourceRequest(context);
}
/*
* (non-Javadoc)
* @see javax.faces.application.ResourceHandlerWrapper#handleResourceRequest(javax.faces.context.FacesContext)
*/
@Override
public void handleResourceRequest(FacesContext context) throws IOException {
if (isThisHandlerResourceRequest(context)) {
ResourceCodec resourceCodec = ServiceTracker.getService(context, ResourceCodec.class);
String resourcePath = getResourcePathFromRequest(context);
assert (resourcePath != null) && (resourcePath.length() != 0);
ResourceRequestData data = resourceCodec.decodeResource(context, resourcePath);
assert (data != null);
Cache cache = ServiceTracker.getService(context, Cache.class);
Resource resource = lookupInCache(cache, data.getResourceKey());
if (resource == null) {
resource = resourceFactory.createResource(context, data);
}
if (resource == null) {
sendResourceNotFound(context);
return;
}
if (resource instanceof CacheableResource) {
CacheableResource cacheableResource = (CacheableResource) resource;
if (cacheableResource.isCacheable(context)) {
// TODO - we could move this part of code to ConcurrentMap so that
// only single thread does resource put
CachedResourceImpl cachedResource = new CachedResourceImpl();
cachedResource.initialize(resource);
// someone may provided this resource for us
// while we were reading it, check once again
resource = lookupInCache(cache, data.getResourceKey());
if (resource == null) {
// don't cache it on Development stage
if (!ProjectStage.Development.equals(context.getApplication().getProjectStage())) {
Date cacheExpirationDate = cachedResource.getExpired(context);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(new MessageFormat(
"Storing {0} resource in cache until {1,date,dd MMM yyyy HH:mm:ss zzz}", Locale.US)
.format(new Object[] { data.getResourceKey(), cacheExpirationDate }));
}
cache.put(data.getResourceKey(), cachedResource, cacheExpirationDate);
}
resource = cachedResource;
}
}
}
if (resource.userAgentNeedsUpdate(context)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("User agent needs resource update, encoding resource");
}
ExternalContext externalContext = context.getExternalContext();
Map headers = resource.getResponseHeaders();
for (Entry headerEntry : headers.entrySet()) {
String headerName = headerEntry.getKey();
String headerValue = headerEntry.getValue();
// TODO should external context handles this itself?
if ("content-length".equals(headerName.toLowerCase(Locale.US))) {
try {
externalContext.setResponseContentLength(Integer.parseInt(headerValue));
} catch (NumberFormatException e) {
// TODO: handle exception
}
} else {
externalContext.setResponseHeader(headerName, headerValue);
}
}
// TODO null content type?
String contentType = resource.getContentType();
if (contentType != null) {
externalContext.setResponseContentType(contentType);
}
if (resource instanceof ContentProducerResource) {
ContentProducerResource contentProducerResource = (ContentProducerResource) resource;
contentProducerResource.encode(context);
} else {
// TODO setup output buffer size according to configuration parameter
InputStream is = resource.getInputStream();
OutputStream os = externalContext.getResponseOutputStream();
try {
ResourceUtils.copyStreamContent(is, os);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(e.getMessage(), e);
}
}
}
// TODO flush resource
// TODO dispose resource
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Resource succesfully encoded");
}
} else {
sendNotModified(context);
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Passing request to the next resource handler in chain");
}
defaultHandler.handleResourceRequest(context);
}
}
protected boolean isThisHandlerResourceRequest(FacesContext context) {
Boolean resourceRequest = BooleanRequestStateVariable.ResourceRequest.get(context);
if (resourceRequest == null) {
String resourcePath = getResourcePathFromRequest(context);
// TODO handle exclusions
resourceRequest = (resourcePath != null) && (resourcePath.length() > 0);
BooleanRequestStateVariable.ResourceRequest.set(context, resourceRequest);
if (LOGGER.isDebugEnabled() && resourceRequest) {
LOGGER.debug(MessageFormat.format("Resource request detected: {0}", resourcePath));
}
}
return resourceRequest;
}
private Resource lookupInCache(Cache cache, String resourceKey) {
if (cache == null) {
LOGGER.debug("No cache was provided");
return null;
}
Resource resource = (Resource) cache.get(resourceKey);
if (LOGGER.isDebugEnabled()) {
if (resource == null) {
LOGGER.debug("Resource was not located in cache");
} else {
LOGGER.debug("Resource was located in cache");
}
}
return resource;
}
/*
* (non-Javadoc)
* @see javax.faces.application.ResourceHandlerWrapper#createResource(java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public Resource createResource(String resourceName, String libraryName, String contentType) {
Resource resource = resourceFactory.createResource(resourceName, libraryName, contentType);
if (resource == null) {
resource = defaultHandler.createResource(resourceName, libraryName, contentType);
}
return resource;
}
/*
* (non-Javadoc)
* @see javax.faces.application.ResourceHandlerWrapper#createResource(java.lang.String, java.lang.String)
*/
@Override
public Resource createResource(String resourceName, String libraryName) {
return createResource(resourceName, libraryName, null);
}
/*
* (non-Javadoc)
* @see javax.faces.application.ResourceHandlerWrapper#createResource(java.lang.String)
*/
@Override
public Resource createResource(String resourceName) {
return createResource(resourceName, null, null);
}
/*
* (non-Javadoc)
* @see javax.faces.application.ResourceHandlerWrapper#getRendererTypeForResourceName(java.lang.String)
*/
@Override
public String getRendererTypeForResourceName(String resourceName) {
if (resourceName.endsWith(".ecss")) {
return "javax.faces.resource.Stylesheet";
}
if (resourceName.endsWith(ResourceLibraryRenderer.RESOURCE_LIBRARY_EXTENSION)) {
return ResourceLibraryRenderer.RENDERER_TYPE;
}
return defaultHandler.getRendererTypeForResourceName(resourceName);
}
/*
* (non-Javadoc)
* @see javax.faces.application.ResourceHandlerWrapper#libraryExists(java.lang.String)
*/
@Override
public boolean libraryExists(String libraryName) {
return defaultHandler.libraryExists(libraryName);
}
/*
* (non-Javadoc)
* @see javax.faces.application.ResourceHandlerWrapper#getWrapped()
*/
@Override
public ResourceHandler getWrapped() {
return defaultHandler;
}
private static String getResourcePathFromRequest(FacesContext context) {
String resourceName = ResourceUtils.decodeResourceURL(context);
if (resourceName != null) {
if (resourceName.startsWith(RICHFACES_RESOURCE_IDENTIFIER)) {
return resourceName.substring(RICHFACES_RESOURCE_IDENTIFIER.length());
} else {
return null;
}
} else {
LOGGER.warn("Resource key not found" + resourceName);
return null;
}
}
private static void sendNotModified(FacesContext context) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("User agent has actual resource copy - sending 304 status code");
}
// TODO send cacheable resource headers (ETag + LastModified)?
context.getExternalContext().setResponseStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
private static void sendResourceNotFound(FacesContext context) {
context.getExternalContext().setResponseStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy