de.betterform.agent.web.resources.ResourceServlet.java Source code

Java tutorial

Introduction

Here is the source code for de.betterform.agent.web.resources.ResourceServlet.java

Source

/*
 * Copyright (c) 2012. betterFORM Project - http://www.betterform.de
 * Licensed under the terms of BSD License
 */

package de.betterform.agent.web.resources;

import de.betterform.agent.web.WebFactory;
import de.betterform.agent.web.resources.stream.DefaultResourceStreamer;
import de.betterform.agent.web.resources.stream.ResourceStreamer;
import de.betterform.xml.config.XFormsConfigException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * ResourceServlet is responsible for streaming resources like css, script, images and etc to the client.
 * Streaming is done via ResourceStreamers and resources are forced to be cached indefinitely using convenient response headers.
 */
public class ResourceServlet extends HttpServlet {

    private static final Log LOG = LogFactory.getLog(ResourceServlet.class);
    private static Map<String, String> mimeTypes;
    private List<ResourceStreamer> resourceStreamers;
    private boolean caching;
    private boolean exploded = false;
    private long oneYear = 31363200000L;

    /**
     * RESOURCE_FOLDER refers to the location in the classpath where resources are found.
     */
    public final static String RESOURCE_FOLDER = "/META-INF/resources";

    /**
     * RESOURCE_PATTERN is the string used in URLs requesting resources. This value is hardcoded for now - meaning
     * that all requests to internal betterFORM resources like CSS, images, scripts and XSLTs have to use
     * 'bfResources'.
     *
     * Example:
     * http://somehost.com/betterform/bfResources/images/image.gif"
     * will try to load an image 'image.gif' from /META-INF/resources/images/image.gif
     *
     */
    public final static String RESOURCE_PATTERN = "bfResources";
    private long lastModified = 0;

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        if ("false".equals(config.getInitParameter("caching"))) {
            caching = false;
            if (LOG.isTraceEnabled()) {
                LOG.trace("Caching of Resources is disabled");
            }
        } else {
            caching = true;

            if (LOG.isTraceEnabled()) {
                LOG.trace("Caching of Resources is enabled - resources are loaded from classpath");
            }
        }
        this.lastModified = getLastModifiedValue();
        String path = null;
        try {
            path = WebFactory.getRealPath("WEB-INF/classes/META-INF/resources", config.getServletContext());
        } catch (XFormsConfigException e) {
            throw new ServletException(e);
        }
        if (path != null && new File(path).exists()) {
            exploded = true;
        }

        initMimeTypes();
        initResourceStreamers();
    }

    // todo: shouldn't we move these definitions to web.xml and use servletContext.getMimeType?
    private void initMimeTypes() {
        mimeTypes = new HashMap<String, String>();
        mimeTypes.put("css", "text/css");
        mimeTypes.put("js", "text/javascript");
        mimeTypes.put("jpg", "image/jpeg");
        mimeTypes.put("jpeg", "image/jpeg");
        mimeTypes.put("png", "image/png");
        mimeTypes.put("gif", "image/gif");
        mimeTypes.put("gif", "image/gif");
        mimeTypes.put("html", "text/html");
        mimeTypes.put("swf", "application/x-shockwave-flash");
        mimeTypes.put("xsl", "application/xml+xslt");
    }

    private void initResourceStreamers() {
        resourceStreamers = new ArrayList<ResourceStreamer>();
        resourceStreamers.add(new DefaultResourceStreamer());
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String requestUri = req.getRequestURI();
        String resourcePath = RESOURCE_FOLDER + getResourcePath(requestUri);
        URL url = ResourceServlet.class.getResource(resourcePath);
        if (LOG.isTraceEnabled()) {
            LOG.trace("Request URI: " + requestUri);
            LOG.trace("resource fpath: " + resourcePath);
        }

        if (url == null) {
            boolean error = true;

            if (requestUri.endsWith(".js")) {
                //try optimized version first
                if (requestUri.contains("scripts/betterform/betterform-")) {
                    if (ResourceServlet.class.getResource(resourcePath) == null) {
                        resourcePath = resourcePath.replace("betterform-", "BfRequired");
                        if (ResourceServlet.class.getResource(resourcePath) != null) {
                            error = false;
                        }
                    }
                }
            }

            if (error) {
                if (LOG.isWarnEnabled()) {
                    LOG.warn("Resource " + resourcePath + " not found");
                }
                resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Resource " + resourcePath + " not found");
                return;
            }

        }

        if (LOG.isTraceEnabled()) {
            LOG.trace("Streaming resource " + resourcePath);
        }

        InputStream inputStream = null;

        try {
            if (exploded) {
                String path = ResourceServlet.class.getResource(resourcePath).getPath();
                inputStream = new FileInputStream(new File(path));
                if (LOG.isTraceEnabled()) {
                    LOG.trace("loading reources form file: " + path);
                }
            } else {
                inputStream = ResourceServlet.class.getResourceAsStream(resourcePath);
            }

            String mimeType = getResourceContentType(resourcePath);
            if (mimeType == null) {
                mimeType = getServletContext().getMimeType(resourcePath);
            }

            if (mimeType == null) {
                if (LOG.isTraceEnabled()) {
                    LOG.trace("MimeType for " + resourcePath + " not found. Sending 'not found' response");
                }
                resp.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE,
                        "MimeType for " + resourcePath + " not found. Sending 'not found' response");
                return;
            }

            resp.setContentType(mimeType);
            resp.setStatus(HttpServletResponse.SC_OK);
            setCaching(req, resp);
            streamResource(req, resp, mimeType, inputStream);

            if (LOG.isTraceEnabled()) {
                LOG.trace("Resource " + resourcePath + " streamed succesfully");
            }
        } catch (Exception exception) {
            LOG.error("Error in streaming resource " + resourcePath + ". Exception is " + exception.getMessage());
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }

            resp.getOutputStream().flush();
            resp.getOutputStream().close();
        }
    }

    private void streamResource(HttpServletRequest req, HttpServletResponse resp, String mimeType,
            InputStream inputStream) throws IOException {
        for (ResourceStreamer streamer : resourceStreamers) {
            if (streamer.isAppropriateStreamer(mimeType))
                streamer.stream(req, resp, inputStream);
        }
    }

    /**
     * set the caching headers for the resource response. Caching can be disabled by adding and init-param
     * of 'caching' with value 'false' to web.xml
     *
     * @param request  the http servlet request
     * @param response the http servlet response
     */
    protected void setCaching(HttpServletRequest request, HttpServletResponse response) {
        long now = System.currentTimeMillis();
        if (caching) {
            response.setHeader("Cache-Control", "max-age=3600, public");
            response.setDateHeader("Date", now);
            response.setDateHeader("Expires", now + this.oneYear);
            response.setDateHeader("Last-Modified", this.getLastModifiedValue());
        } else {
            // Set to expire far in the past.
            response.setHeader("Expires", "Sat, 6 May 1995 12:00:00 GMT");
            // Set standard HTTP/1.1 no-cache headers.
            response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
            // Set IE extended HTTP/1.1 no-cache headers (use addHeader).
            response.addHeader("Cache-Control", "post-check=0, pre-check=0");
            // Set standard HTTP/1.0 no-cache header.
            response.setHeader("Pragma", "no-cache");
        }
    }

    protected String getResourcePath(String requestURI) {
        int jsessionidIndex = requestURI.toLowerCase().indexOf(";jsessionid");
        if (jsessionidIndex != -1) {
            requestURI = requestURI.substring(0, jsessionidIndex);
        }
        int patternIndex = requestURI.indexOf(RESOURCE_PATTERN);
        return requestURI.substring(patternIndex + RESOURCE_PATTERN.length(), requestURI.length());
    }

    protected String getResourceContentType(String resourcePath) {
        String resourceFileExtension = getResourceFileExtension(resourcePath);
        return mimeTypes.get(resourceFileExtension);
    }

    protected String getResourceFileExtension(String resourcePath) {
        String parsed[] = resourcePath.split("\\.");

        return parsed[parsed.length - 1];
    }

    private long getLastModifiedValue() {
        if (this.lastModified == 0) {
            long bfTimestamp;
            try {
                String path = WebFactory.getRealPath("/WEB-INF/betterform-version.info", this.getServletContext());
                StringBuilder versionInfo = new StringBuilder();
                String NL = System.getProperty("line.separator");
                Scanner scanner = new Scanner(new FileInputStream(path), "UTF-8");
                try {
                    while (scanner.hasNextLine()) {
                        versionInfo.append(scanner.nextLine() + NL);
                    }
                } finally {
                    scanner.close();
                }
                if (LOG.isDebugEnabled()) {
                    LOG.debug("VersionInfo: " + versionInfo);
                }
                // String APP_NAME = APP_INFO.substring(0, APP_INFO.indexOf(" "));
                // String APP_VERSION = APP_INFO.substring(APP_INFO.indexOf(" ") + 1, APP_INFO.indexOf("-") - 1);
                String timestamp = versionInfo.substring(versionInfo.indexOf("Timestamp:") + 10,
                        versionInfo.length());
                String df = "yyyy-MM-dd HH:mm:ss";
                SimpleDateFormat sdf = new SimpleDateFormat(df);
                Date date = sdf.parse(timestamp);
                bfTimestamp = date.getTime();
            } catch (Exception e) {
                LOG.error("Error setting HTTP Header 'Last Modified', could not parse the given date.");
                bfTimestamp = 0;
            }
            this.lastModified = bfTimestamp;
        }
        return lastModified;
    }

    protected long getLastModified(HttpServletRequest req) {
        return this.getLastModifiedValue();
    }
}