/* vim: set ts=2 et sw=2 cindent fo=qroca: */ package com.globant.katari.jsmodule.view; import javax.servlet.ServletConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.commons.lang.Validate; import org.apache.commons.lang.StringUtils; import java.io.IOException; import java.io.InputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import javax.servlet.ServletException; import java.util.Enumeration; import java.net.URL; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import com.globant.katari.core.web.BaseStaticContentServlet; import com.globant.katari.jsmodule.domain.BundleCache; /** Servlet that serves static content (gif, png, css, js) from the classpath. * * Static content is packaged in jar files with an additional metadata file * that defines which classpath entries are served from that jar. The metadata * file is META-INF/katari-resource-set. This resource is a property file of * the form: * * staticContentPath=com/globant/katari/jsmodule/view * * mimeType.js=text/javascript * * mimeType.gif=image/gif * * debugPrefix=../katari-jsmodule/src/test/resources * * This servlet serves resources of the form * [context-path]/[servlet-path]/[package]/file.ext. * * The [package] part must start with one of the paths that are being served * from this servlet, as specified by the property staticContentPath in some * katari-resource-set file. * * If the requested file is in package /com/globant/katari/jsmodule/bundle, * this servlet assumes it is a bundle. See ResolveDependenciesCommand. * * As a future enhacement, this servlet will be able to bundle and compress css * files. */ public class ContentModuleServlet extends BaseStaticContentServlet { /** The serialization version number. * * This number must change every time a new serialization incompatible change * is introduced in the class. */ private static final long serialVersionUID = 1; /** Default path for the cached bundled files. * * The default value is "/com/globant/katari/jsmodule/bundle/". */ public static final String BUNDLE_PATH = "/com/globant/katari/jsmodule/bundle/"; /** Content type for ".js" files. * * The value is "text/javascript". */ private static final String JS_CONTENT_TYPE = "text/javascript"; /** The class logger. */ private static Logger log = LoggerFactory.getLogger( ContentModuleServlet.class); /** The resource sets, keyed by base path. */ private ResourceSets resourceSets = new ResourceSets(); /** Contains the cache of the bundled files, never null. */ private BundleCache cache; /** Makes a new instance of the {@link ContentModuleServlet} with the given * {@link BundleCache}. * * @param theCache The {@link BundleCache} contains the cache of the bundled * files. Cannot be null. */ public ContentModuleServlet(final BundleCache theCache) { Validate.notNull(theCache, "The Bundle Cache cannot be null."); cache = theCache; } /** Initializes the servlet. * * It sets the default packages for static resources. * * @param config The servlet configuration, ignored in this implementation. * * @throws ServletException in case of error. */ /* @Override public void init(final ServletConfig config) throws ServletException { log.trace("Entering init"); super.init(config); String name = "META-INF/katari-resource-set"; Enumeration<URL> resourceUrls; try { resourceUrls = getClass().getClassLoader().getResources(name); } catch (IOException e) { throw new ServletException("Error loading " + name, e); } while(resourceUrls.hasMoreElements()) { URL url = resourceUrls.nextElement(); log.debug("Found resource set {}.", url); ResourceSet resourceSet = new ResourceSet(url); resourceSets.put(resourceSet.getBasePath(), resourceSet); } log.trace("Leaving init"); } */ /** {@inheritDoc} */ @Override protected String getContentType(final String path) { log.trace("Entering getContentType('{}')", path); if (path.contains(BUNDLE_PATH)) { // We only support javascript. String bundleName = path.substring(path.lastIndexOf("/") + 1); if (StringUtils.isNotEmpty(bundleName)) { log.trace("Leaving getContentType with {}", JS_CONTENT_TYPE); return JS_CONTENT_TYPE; } } else { String contentType = resourceSets.getContentType(path); log.trace("Leaving getContentType with {}", contentType); return contentType; } log.trace("Leaving getContentType with null"); return null; } /** {@inheritDoc} */ @Override protected InputStream findInputStream(final String path) throws IOException { log.trace("Entering findInputStream('{}')", path); Validate.notNull(path, "The resource path cannot be null"); if (path.contains(BUNDLE_PATH)) { String bundleName = path.substring(path.lastIndexOf("/") + 1); String bundleContent = cache.findContent(bundleName); if (StringUtils.isNotEmpty(bundleName)) { log.trace("Leaving findInputStream with a file resource"); return new ByteArrayInputStream(bundleContent.getBytes("UTF-8")); } } else { ResourceSet set = resourceSets.find(path); if (set != null) { if (isInDebugMode()) { String filePath = buildPath(set.getDebugPrefix(), path); log.debug("In debug mode, looking for file {}", filePath); File file = new File(filePath); if (file.exists()) { log.trace("Leaving findInputStream with a file resource"); return new FileInputStream(file); } } // The resource path must not start with '/', or it will fail under // certain containers. String resource; if (path.startsWith("/")) { resource = path.substring(1); } else { resource = path; } log.debug("Looking for resource {}", resource); return getResourceAsStream(resource); } } log.trace("Leaving findInputStream"); return null; } /** Obtains the resourceSet that matches the provided path. * * @param path The path that we are searching for. It cannot be null. * * @return the resource set, or null if the path cannot be served by any * resource set. */ /* private ResourceSet findResourceSet(final String path) { log.trace("Entering findResourceSet {}", path); Validate.notNull(path, "the path cannot be null"); SortedMap<String, ResourceSet> subset = resourceSets.headMap(path); if (subset.size() != 0) { String basePath = subset.lastKey(); if (path.startsWith(basePath)) { log.trace("Leaving findResourceSet with {}", basePath); return resourceSets.get(basePath); } } log.trace("Leaving findResourceSet with null"); return null; } */ }