org.apache.myfaces.renderkit.html.util.MyFacesResourceLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.myfaces.renderkit.html.util.MyFacesResourceLoader.java

Source

/*
 * 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.renderkit.html.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.shared_tomahawk.util.ClassUtils;

import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.ResourceBundle;
import java.net.HttpURLConnection;

/**
 * A ResourceLoader capable of fetching resources from the classpath,
 * but only for classes under package org.apache.myfaces.custom.
 * <p>
 * The URI is expected to contain two pieces of information: the
 * tomahawk class the resource is associated with, and a relative path
 * from that class to the resource.
 *
 * @author Mathias Broekelmann (latest modification by $Author$)
 * @version $Revision$ $Date$
 */
public class MyFacesResourceLoader implements ResourceLoader {
    protected static final Log log = LogFactory.getLog(MyFacesResourceLoader.class);

    static final String ORG_APACHE_MYFACES_CUSTOM = "org.apache.myfaces.custom";

    private static long lastModified = 0;

    /**
     * Get the last-modified time of the resource.
     * <p>
     * Unfortunately this is not possible with files inside jars. Instead, the
     * MyFaces build process ensures that there is a file AddResource.properties
     * which has the datestamp of the time the build process was run. This method
     * simply gets that value and returns it.
     * <p>
     * Note that this method is not related to the generation of "cache key"
     * values by the AddResource class, nor does it affect the caching behaviour
     * of web browsers. This value simply goes into the http headers as the
     * last-modified time of the specified resource.
     */
    private static long getLastModified() {
        if (lastModified == 0) {
            final String format = "yyyy-MM-dd HH:mm:ss Z"; // Must match the one used in the build file
            final String bundleName = AddResource.class.getName();
            ResourceBundle resources = ResourceBundle.getBundle(bundleName);
            String sLastModified = resources.getString("lastModified");
            try {
                lastModified = new SimpleDateFormat(format).parse(sLastModified).getTime();
            } catch (ParseException e) {
                lastModified = new Date().getTime();
                log.warn("Unparsable lastModified : " + sLastModified);
            }
        }

        return lastModified;
    }

    /**
     * Given a URI of form "{partial.class.name}/{resourceName}", locate the
     * specified file within the current classpath and write it to the
     * response object.
     * <p>
     * The partial class name has "org.apache.myfaces.custom." prepended
     * to it to form the fully qualified classname. This class object is
     * loaded, and Class.getResourceAsStream is called on it, passing
     * a uri of "resource/" + {resourceName}.
     * <p>
     * The data written to the response stream includes http headers
     * which define the mime content-type; this is deduced from the
     * filename suffix of the resource.
     * <p>
     * @see org.apache.myfaces.renderkit.html.util.ResourceLoader#serveResource(javax.servlet.ServletContext,
     *     javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.String)
     */
    public void serveResource(ServletContext context, HttpServletRequest request, HttpServletResponse response,
            String resourceUri) throws IOException {
        String[] uriParts = resourceUri.split("/", 2);

        String component = uriParts[0];
        if (component == null || component.trim().length() == 0) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid request");
            log.error("Could not find parameter for component to load a resource.");
            return;
        }
        Class componentClass;
        String className = ORG_APACHE_MYFACES_CUSTOM + "." + component;
        try {
            componentClass = loadComponentClass(className);
        } catch (ClassNotFoundException e) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
            log.error("Could not find the class for component " + className + " to load a resource.");
            return;
        }
        String resource = uriParts[1];
        if (resource == null || resource.trim().length() == 0) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "No resource defined");
            log.error("No resource defined component class " + className);
            return;
        }

        InputStream is = null;

        try {
            ResourceProvider resourceProvider;
            if (ResourceProvider.class.isAssignableFrom(componentClass)) {
                try {
                    resourceProvider = (ResourceProvider) componentClass.newInstance();
                } catch (InstantiationException e) {
                    response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                            "Unable to instantiate resource provider for resource " + resource + " for component "
                                    + component);
                    log.error("Unable to instantiate resource provider for resource " + resource + " for component "
                            + component, e);
                    return;
                } catch (IllegalAccessException e) {
                    response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                            "Unable to instantiate resource provider for resource " + resource + " for component "
                                    + component);
                    log.error("Unable to instantiate resource provider for resource " + resource + " for component "
                            + component, e);
                    return;
                }
            } else {
                resourceProvider = new DefaultResourceProvider(componentClass);
            }

            if (!resourceProvider.exists(context, resource)) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND,
                        "Unable to find resource " + resource + " for component " + component
                                + ". Check that this file is available " + "in the classpath in sub-directory "
                                + "/resource of the package-directory.");
                log.error("Unable to find resource " + resource + " for component " + component
                        + ". Check that this file is available " + "in the classpath in sub-directory "
                        + "/resource of the package-directory.");
            } else {
                // URLConnection con = url.openConnection();

                long lastModified = resourceProvider.getLastModified(context, resource);
                if (lastModified < 1) {
                    // fallback
                    lastModified = getLastModified();
                }

                long browserDate = request.getDateHeader("If-Modified-Since");
                if (browserDate > -1) {
                    // normalize to seconds - this should work with any os
                    lastModified = (lastModified / 1000) * 1000;
                    browserDate = (browserDate / 1000) * 1000;

                    if (lastModified == browserDate) {
                        // the browser already has the correct version

                        response.setStatus(HttpURLConnection.HTTP_NOT_MODIFIED);
                        return;
                    }
                }

                int contentLength = resourceProvider.getContentLength(context, resource);
                String contentEncoding = resourceProvider.getEncoding(context, resource);

                is = resourceProvider.getInputStream(context, resource);

                defineContentHeaders(request, response, resource, contentLength, contentEncoding);
                defineCaching(request, response, resource, lastModified);
                writeResource(request, response, is);
            }
        } finally {
            // nothing to do here..
        }
    }

    /**
     * Copy the content of the specified input stream to the servlet response.
     */
    protected void writeResource(HttpServletRequest request, HttpServletResponse response, InputStream in)
            throws IOException {
        ServletOutputStream out = response.getOutputStream();
        try {
            byte[] buffer = new byte[1024];
            for (int size = in.read(buffer); size != -1; size = in.read(buffer)) {
                out.write(buffer, 0, size);
            }
            out.flush();
        } catch (IOException e) {
            // This happens sometimes with Microsft Internet Explorer. It would
            // appear (guess) that when javascript creates multiple dom nodes
            // referring to the same remote resource then IE stupidly opens 
            // multiple sockets and requests that resource multiple times. But
            // when the first request completes, it then realises its stupidity
            // and forcibly closes all the other sockets. But here we are trying
            // to service those requests, and so get a "broken pipe" failure 
            // on write. The only thing to do here is to silently ignore the issue,
            // ie suppress the exception. Note that it is also possible for the
            // above code to succeed (ie this exception clause is not run) but
            // for a later flush to get the "broken pipe"; this is either due
            // just to timing, or possibly IE is closing sockets after receiving
            // a complete file for some types (gif?) rather than waiting for the
            // server to close it. We throw a special exception here to inform
            // callers that they should NOT flush anything - though that is
            // dangerous no matter what IOException subclass is thrown.
            log.debug("Unable to send resource data to client", e);
            throw new ResourceLoader.ClosedSocketException();
        }
    }

    /**
     * Output http headers telling the browser (and possibly intermediate caches) how
     * to cache this data.
     * <p>
     * The expiry time in this header info is set to 7 days. This is not a problem as
     * the overall URI contains a "cache key" that changes whenever the webapp is
     * redeployed (see AddResource.getCacheKey), meaning that all browsers will
     * effectively reload files on webapp redeploy.
     */
    protected void defineCaching(HttpServletRequest request, HttpServletResponse response, String resource,
            long lastModified) {
        response.setDateHeader("Last-Modified", lastModified);

        Calendar expires = Calendar.getInstance();
        expires.add(Calendar.DAY_OF_YEAR, 7);
        response.setDateHeader("Expires", expires.getTimeInMillis());

        //12 hours: 43200 = 60s * 60 * 12
        response.setHeader("Cache-Control", "max-age=43200");
        response.setHeader("Pragma", "");
    }

    /**
     * Output http headers indicating the mime-type of the content being served.
     * The mime-type output is determined by the resource filename suffix.
     */
    protected void defineContentHeaders(HttpServletRequest request, HttpServletResponse response, String resource,
            int contentLength, String contentEncoding) {
        String charset = "";
        if (contentEncoding != null) {
            charset = "; charset=" + contentEncoding;
        }
        if (contentLength > -1) {
            response.setContentLength(contentLength);
        }

        if (resource.endsWith(".js"))
            response.setContentType(
                    org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT + charset);
        else if (resource.endsWith(".css"))
            response.setContentType(
                    org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.STYLE_TYPE_TEXT_CSS + charset);
        else if (resource.endsWith(".gif"))
            response.setContentType("image/gif");
        else if (resource.endsWith(".png"))
            response.setContentType("image/png");
        else if (resource.endsWith(".jpg") || resource.endsWith(".jpeg"))
            response.setContentType("image/jpeg");
        else if (resource.endsWith(".xml") || resource.endsWith(".xsl"))
            response.setContentType("text/xml"); // XSL has to be served as XML.
    }

    protected Class loadComponentClass(String componentClass) throws ClassNotFoundException {
        return ClassUtils.classForName(componentClass);
    }

    // NOTE: This method is not being used. Perhaps it can be removed?
    protected void validateCustomComponent(Class myfacesCustomComponent) {
        if (!myfacesCustomComponent.getName().startsWith(ORG_APACHE_MYFACES_CUSTOM + ".")) {
            throw new IllegalArgumentException(
                    "expected a myfaces custom component class in package " + ORG_APACHE_MYFACES_CUSTOM);
        }
    }
}