All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.richfaces.resource.ResourceHandlerImpl Maven / Gradle / Ivy

There is a newer version: 4.5.17.Final
Show newest version
/*
 * 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