de.fhg.igd.vaadin.util.servlets.OSGiResourcesServlet.java Source code

Java tutorial

Introduction

Here is the source code for de.fhg.igd.vaadin.util.servlets.OSGiResourcesServlet.java

Source

// This file is part of Vaadin Utils
//
// Copyright (c) 2012 Fraunhofer IGD
//
// MongoMVCC 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 3 of the
// License, or (at your option) any later version.
//
// MongoMVCC 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 Vaadin Utils. If not, see <http://www.gnu.org/licenses/>.
package de.fhg.igd.vaadin.util.servlets;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Set;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.slf4j.bridge.SLF4JBridgeHandler;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.HttpHeaders;

/**
 * 
 * TODO: implement caching headers
 * 
 * @author Robert Gregor
 */
public class OSGiResourcesServlet extends HttpServlet implements BundleListener {

    private static final String GZIP_ENCODING = "gzip";

    private static final String EMPTY_STRING = "";

    private static final long serialVersionUID = -6812445506450686633L;

    private static final XLogger log = XLoggerFactory.getXLogger(OSGiResourcesServlet.class);

    private Integer bufferSize;

    private Integer resourceCacheTime;

    private String pathPrefix;

    /**
     * Base path to look for resources in the lookup path. 
     */
    private String lookupBasePath;

    private BundleContext bctx;

    private String[] lookupBundleNames;

    private Set<Bundle> lookupBundles;

    private Boolean usePrecompressedResources;

    /**
     * Checks if the browser has an up to date cached version of requested
     * resource. Currently the check is performed using the "If-Modified-Since"
     * header.
     * 
     * @param request
     *            The HttpServletRequest from the browser.
     * @param resourceLastModifiedTimestamp
     *            The timestamp when the resource was last modified. 0 if the
     *            last modification time is unknown.
     * @return true if the If-Modified-Since header tells the cached version in
     *         the browser is up to date, false otherwise.
     */
    private boolean browserHasNewestVersion(HttpServletRequest request, long resourceLastModifiedTimestamp) {
        if (resourceLastModifiedTimestamp < 1)
            return false;
        try {
            final long headerIfModifiedSince = request.getDateHeader("If-Modified-Since");

            if (headerIfModifiedSince >= resourceLastModifiedTimestamp)
                return true;
        } catch (final Exception e) {
            /* NOP */
        }
        return false;
    }

    @Override
    public void bundleChanged(BundleEvent event) {
        if (lookupBundles != null && getLookupBundles().contains(event.getBundle()))
            lookupBundles = null;
    }

    @Override
    public void destroy() {
        bctx.removeBundleListener(this);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String resourcePath = req.getServletPath() + req.getPathInfo();

        int index = resourcePath.indexOf(getPathPrefix());
        if (index >= 0) {
            resourcePath = resourcePath.substring(index);
        }

        log.debug("resolving {} :", resourcePath);

        // try to load from local WebContent directory
        URL resourceUrl = usePrecompressedResources() ? lookupPrecompressedFileResource(resourcePath, req, resp)
                : lookupFileResource(resourcePath, req, resp);

        log.trace("...in ServletContext: {}", resourceUrl);

        // if this failed, try to load from lookupBundles
        if (null == resourceUrl) {
            for (final Bundle b : getLookupBundles()) {
                resourceUrl = b.getResource(getLookupBasePath() + resourcePath);
                log.trace("...in Bundle {}: {}", b, resourceUrl);
                if (null != resourceUrl)
                    break;
            }
            // if this failed, we fail
            if (null == resourceUrl) {
                log.error("could not resolve resource: {}", resourcePath);
                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
                return;
            }
        }

        final String mimetype = getServletContext().getMimeType(resourcePath);
        if (mimetype != null)
            resp.setContentType(mimetype);

        // Find the modification timestamp
        long lastModifiedTime = 0;
        try {
            lastModifiedTime = resourceUrl.openConnection().getLastModified();
            // Remove milliseconds to avoid comparison problems (milliseconds
            // are not returned by the browser in the "If-Modified-Since"
            // header).
            lastModifiedTime = lastModifiedTime - lastModifiedTime % 1000;

            if (browserHasNewestVersion(req, lastModifiedTime)) {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                return;
            }
        } catch (final Exception e) {
            // Failed to find out last modified timestamp. Continue without it.
            log.trace("Failed to determine last modified timestamp: {} Continuing without it.", e);
        }

        // Provide modification timestamp to the browser if it is known.
        if (lastModifiedTime > 0) {
            resp.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModifiedTime);
            /*
             * FIXME should be configurable by setting the resourceCacheTime
             * servlet parameter in web.xml
             */
            resp.setHeader(HttpHeaders.CACHE_CONTROL, "max-age: " + String.valueOf(getResourceCacheTime()));
        }

        final InputStream in = resourceUrl.openStream();
        final OutputStream out = resp.getOutputStream();

        final byte[] buffer = new byte[getBufferSize()];
        int read = 0;
        while (-1 != (read = in.read(buffer)))
            out.write(buffer, 0, read);
    }

    protected int getBufferSize() {
        if (bufferSize == null)
            bufferSize = 1024 * getIntInitParamter("bufferSize", 128);
        return bufferSize;
    }

    private Bundle getBundle(String bundleName) {
        log.debug("looking up bundle with symbolic name: " + bundleName);
        for (final Bundle bundle : bctx.getBundles())
            if (bundle.getSymbolicName().equals(bundleName))
                return bundle;
        log.error("could not find bundle with symbolic name: " + bundleName);
        return null;
    }

    protected Integer getIntInitParamter(String name, Integer defaultValue) {
        try {
            return Integer.valueOf(getInitParameter(name));
        } catch (final Exception e) {
            log.warn("invalid init parameter \"{}\" using default value: {}", name, defaultValue);
            return defaultValue;
        }
    }

    protected String[] getLookupBundleNames() {
        if (lookupBundleNames == null)
            lookupBundleNames = getInitParameter("lookupBundleNames").replaceAll("[\\s]+", EMPTY_STRING)
                    .split("[,;]");
        return lookupBundleNames;
    }

    protected Set<Bundle> getLookupBundles() {
        if (lookupBundles == null) {
            final ImmutableSet.Builder<Bundle> b = ImmutableSet.builder();
            for (final String name : getLookupBundleNames()) {
                final Bundle bundle = getBundle(name);
                if (bundle != null)
                    b.add(bundle);
            }
            lookupBundles = b.build();
        }
        return lookupBundles;
    }

    protected String getPathPrefix() {
        if (pathPrefix == null) {
            pathPrefix = getInitParameter("pathPrefix");
            if (pathPrefix == null) {
                log.warn("no path prefix present, setting to empty String");
                pathPrefix = EMPTY_STRING;
            }
        }
        return pathPrefix;
    }

    protected String getLookupBasePath() {
        if (lookupBasePath == null) {
            lookupBasePath = getInitParameter("lookupBase");
            if (lookupBasePath == null) {
                lookupBasePath = EMPTY_STRING;
            }
        }
        return lookupBasePath;
    }

    protected int getResourceCacheTime() {
        if (resourceCacheTime == null)
            resourceCacheTime = getIntInitParamter("resourceCacheTime", 3600);
        return resourceCacheTime;
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        // route java.util.Loggers to slf4j
        SLF4JBridgeHandler.install();
        super.init(config);
        bctx = (BundleContext) getServletContext().getAttribute("osgi-bundlecontext");
        if (bctx == null)
            throw new ServletException(
                    "Could not obtain OSGi BundleContext from ServletContext make sure this servlet is running in a WAB compatible OSGi Environment");
        bctx.addBundleListener(this);
    }

    private URL lookupFileResource(String resourcePath, HttpServletRequest req, HttpServletResponse resp)
            throws MalformedURLException {
        return getServletContext().getResource(resourcePath);
    }

    /**
     * 
     * @param resourcePath
     * @param req
     * @param resp
     * @return
     * @throws MalformedURLException 
     */
    private URL lookupPrecompressedFileResource(String resourcePath, HttpServletRequest req,
            HttpServletResponse resp) throws MalformedURLException {

        final String acceptedEncodings = req.getHeader(HttpHeaders.ACCEPT_ENCODING);
        if (acceptedEncodings != null && acceptedEncodings.contains(GZIP_ENCODING)) {
            final URL precompressedResourceURL = lookupFileResource(resourcePath + ".gz", req, resp);
            if (precompressedResourceURL != null) {
                resp.setHeader(HttpHeaders.CONTENT_ENCODING, GZIP_ENCODING);
                log.trace("...using precompressed Resource from servlet context: {}", precompressedResourceURL);
                return precompressedResourceURL;
            }
        }
        // final URL resourcePath.
        return lookupFileResource(resourcePath, req, resp);
    }

    protected Boolean usePrecompressedResources() {
        if (usePrecompressedResources == null) {
            final String usePrecompressed = getInitParameter("usePrecompressedResources");
            usePrecompressedResources = Strings.isNullOrEmpty(usePrecompressed) ? Boolean.TRUE
                    : Boolean.valueOf(usePrecompressed);
        }
        return usePrecompressedResources;
    }
}