/* * 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.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; import java.text.MessageFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import javax.faces.FacesException; import javax.faces.application.Resource; import javax.faces.context.FacesContext; import org.richfaces.application.Uptime; import org.richfaces.log.Logger; import org.richfaces.log.RichfacesLogger; import org.richfaces.application.ServiceTracker; /** * @author Nick Belaevski * @since 4.0 */ public abstract class AbstractBaseResource extends Resource { public static final String URL_PROTOCOL = "jsfresource"; private static final Logger LOGGER = RichfacesLogger.RESOURCE.getLogger(); private Date lastModified = null; protected AbstractBaseResource() { super(); } protected int getContentLength(FacesContext context) { return -1; } /** * TODO optimize/review? * * @return Returns the lastModified. */ protected Date getLastModified(FacesContext context) { if (lastModified == null) { lastModified = getLastModifiedBySource(); } // TODO - originally lastModified was set during resource creation. // as resources can be managed beans this approach does not seem good if (lastModified == null) { lastModified = ServiceTracker.getService(Uptime.class).getStartTime(); if (LOGGER.isDebugEnabled()) { LOGGER .debug(MessageFormat.format( "Using resource handler start time as last modified date: {0,date,dd MMM yyyy HH:mm:ss zzz}", lastModified)); } } return lastModified; } private Date getLastModifiedBySource() { ClassLoader classLoader = getClassLoader(); if (classLoader == null) { return null; } URL classResource = classLoader.getResource(getClass().getName().replace('.', '/') + ".class"); if (classResource != null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug(MessageFormat.format("Located source for the resource class: {0}", classResource)); } try { URLConnection connection = classResource.openConnection(); connection.setUseCaches(false); long classLastModifiedDate = connection.getLastModified(); if (classLastModifiedDate > 0) { lastModified = new Date(classLastModifiedDate); if (LOGGER.isDebugEnabled()) { LOGGER.debug(MessageFormat.format("Last source modification date is: {0,date}", lastModified)); } } } catch (IOException e) { LOGGER.error(e.getMessage(), e); } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Source for the resource class has not been located"); } } return null; } protected ClassLoader getClassLoader() { Class<? extends AbstractBaseResource> thisClass = getClass(); ClassLoader classLoader = thisClass.getClassLoader(); if (classLoader == null) { classLoader = ClassLoader.getSystemClassLoader(); } return classLoader; } private String getResourceVersion() { if (this instanceof VersionedResource) { return ((VersionedResource) this).getVersion(); } return null; } @Override public String getRequestPath() { FacesContext context = FacesContext.getCurrentInstance(); ResourceCodec resourceCodec = ServiceTracker.getService(context, ResourceCodec.class); String libraryName = getLibraryName(); String resourceName = getResourceName(); Object resourceData = ResourceUtils.saveResourceState(context, this); String resourceVersion = getResourceVersion(); String resourceUri = resourceCodec.encodeResourceRequestPath(context, libraryName, resourceName, resourceData, resourceVersion); resourceUri = resourceCodec.encodeJSFMapping(context, resourceUri); if (LOGGER.isDebugEnabled()) { LOGGER.debug(MessageFormat.format("Request path for {0} resource is: {1}", String.valueOf(resourceName), String.valueOf(resourceUri))); } return resourceUri; } boolean isResourceRequest() { FacesContext facesContext = FacesContext.getCurrentInstance(); return facesContext.getApplication().getResourceHandler().isResourceRequest(facesContext); } long getCurrentTime() { return System.currentTimeMillis(); } protected void addNoCacheResponseHeaders(FacesContext facesContext, Map<String, String> headers) { headers.put("Expires", "0"); headers.put("Cache-Control", "max-age=0, no-store, no-cache"); headers.put("Pragma", "no-cache"); } protected void addCacheControlResponseHeaders(FacesContext facesContext, Map<String, String> headers) { addNoCacheResponseHeaders(facesContext, headers); } @Override public Map<String, String> getResponseHeaders() { Map<String, String> headers = new HashMap<String, String>(); FacesContext facesContext = FacesContext.getCurrentInstance(); if (isResourceRequest()) { int contentLength = getContentLength(facesContext); if (contentLength >= 0) { headers.put("Content-Length", String.valueOf(contentLength)); } String contentType = getContentType(); if (contentType != null) { headers.put("Content-Type", contentType); } Date lastModified = getLastModified(facesContext); if (lastModified != null) { headers.put("Last-Modified", ResourceUtils.formatHttpDate(lastModified)); } headers.put("Date", ResourceUtils.formatHttpDate(getCurrentTime())); addCacheControlResponseHeaders(facesContext, headers); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Created set of response headers"); // TODO - security - can we log header values? for (Entry<String, String> entry : headers.entrySet()) { LOGGER.debug(MessageFormat.format("\t{0}={1}", entry.getKey(), entry.getValue())); } } } return headers; } @Override public URL getURL() { try { return new URL(URL_PROTOCOL, null, -1, getResourceName(), new MyURLStreamHandler()); } catch (MalformedURLException e) { throw new FacesException(e.getLocalizedMessage(), e); } } @Override public boolean userAgentNeedsUpdate(FacesContext context) { return true; } private class MyURLConnection extends URLConnection { MyURLConnection(URL u) { super(u); } @Override public void connect() throws IOException { } @Override public int getContentLength() { FacesContext facesContext = FacesContext.getCurrentInstance(); return AbstractBaseResource.this.getContentLength(facesContext); } @Override public String getContentType() { return AbstractBaseResource.this.getContentType(); } @Override public long getExpiration() { return 0; } @Override public long getLastModified() { FacesContext facesContext = FacesContext.getCurrentInstance(); Date date = AbstractBaseResource.this.getLastModified(facesContext); if (date != null) { return date.getTime(); } return 0; } @Override public InputStream getInputStream() throws IOException { return AbstractBaseResource.this.getInputStream(); } } private class MyURLStreamHandler extends URLStreamHandler { @Override protected URLConnection openConnection(URL u) throws IOException { return new MyURLConnection(u); } } }