Java tutorial
// 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; } }