
com.samskivert.servlet.SiteResourceLoader Maven / Gradle / Ivy
Show all versions of samskivert Show documentation
//
// $Id$
//
// samskivert library - useful routines for java programs
// Copyright (C) 2001-2011 Michael Bayne, et al.
//
// This library 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 2.1 of the License, or
// (at your option) any later version.
//
// This library 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 this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.samskivert.servlet;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.servlet.http.HttpServletRequest;
import com.samskivert.util.HashIntMap;
import static com.samskivert.Log.log;
/**
* Web applications may wish to load resources in such a way that the site
* on which they are running is allowed to override a resource with a
* site-specific version (a header, footer or navigation template for
* example). The site resource loader provides this capability by loading
* resources from a site-specific jar file.
*
* The site resource loader must be configured with the path to the
* site-specific jar files, the names of which are dictated by the string
* identifiers returned by the {@link SiteIdentifier} provided to the
* resource loader at construct time. For example, if the configuration
* dictates that site-specific jar files are located in
* /usr/share/java/webapps/site-data
and the site identifier
* returns samskivert
as the site identifier for a particular
* request, site-specific resources will be loaded from
* /usr/share/java/webapps/site-data/samskivert.jar
.
*/
public class SiteResourceLoader
{
/**
* Constructs a new resource loader.
*
* @param siteIdent the site identifier to be used to identify which
* site through which a request was made when loading resources.
* @param siteJarPath the path to the site-specific jar files.
*/
public SiteResourceLoader (
SiteIdentifier siteIdent, String siteJarPath)
{
// keep this stuff around
_siteIdent = siteIdent;
_jarPath = siteJarPath;
}
/**
* Loads the specific resource, from the site-specific jar file if one
* exists and contains the specified resource. If no resource exists
* with that path, null will be returned.
*
* @param req the http request for which we are loading a resource
* (this will be used to determine for which site the resource will be
* loaded).
* @param path the path to the desired resource.
*
* @return an input stream via which the resource can be read or null
* if no resource could be located with the specified path.
*
* @exception IOException thrown if an I/O error occurs while loading
* a resource.
*/
public InputStream getResourceAsStream (
HttpServletRequest req, String path)
throws IOException
{
return getResourceAsStream(_siteIdent.identifySite(req), path);
}
/**
* Loads the specific resource, from the site-specific jar file if one
* exists and contains the specified resource. If no resource exists
* with that path, null will be returned.
*
* @param siteId the unique identifer for the site for which we are
* loading the resource.
* @param path the path to the desired resource.
*
* @return an input stream via which the resource can be read or null
* if no resource could be located with the specified path.
*
* @exception IOException thrown if an I/O error occurs while loading
* a resource.
*/
public InputStream getResourceAsStream (int siteId, String path)
throws IOException
{
// Log.info("Loading site resource [siteId=" + siteId +
// ", path=" + path + "].");
// synchronize on the lock to ensure that only one thread per site
// is concurrently executing
synchronized (getLock(siteId)) {
SiteResourceBundle bundle = getBundle(siteId);
// make sure the path has no leading slash
if (path.startsWith("/")) {
path = path.substring(1);
}
// obtain our resource from the bundle
return bundle.getResourceAsStream(path);
}
}
/**
* Returns the last modification time of the site-specific jar file
* for the specified site.
*
* @exception IOException thrown if an error occurs accessing the
* site-specific jar file (like it doesn't exist).
*/
public long getLastModified (int siteId)
throws IOException
{
// synchronize on the lock to ensure that only one thread per site
// is concurrently executing
synchronized (getLock(siteId)) {
return getBundle(siteId).getLastModified();
}
}
/**
* Returns a class loader that loads resources from the site-specific
* jar file for the specified site. If no site-specific jar file
* exists for the specified site, null will be returned.
*/
public ClassLoader getSiteClassLoader (int siteId)
throws IOException
{
// synchronize on the lock to ensure that only one thread per site
// is concurrently executing
synchronized (getLock(siteId)) {
// see if we've already got one
ClassLoader loader = _loaders.get(siteId);
// create one if we've not
if (loader == null) {
final SiteResourceBundle bundle = getBundle(siteId);
if (bundle == null) {
// no bundle... no classloader.
return null;
}
loader = AccessController.doPrivileged(new PrivilegedAction() {
public SiteClassLoader run () {
return new SiteClassLoader(bundle);
}
});
_loaders.put(siteId, loader);
}
return loader;
}
}
@Override
public String toString ()
{
return "[jarPath=" + _jarPath + "]";
}
/**
* We synchronize on a per-site basis, but we use a separate lock
* object for each site so that the process of loading a bundle for
* the first time does not require blocking access to resources from
* other sites.
*/
protected Object getLock (int siteId)
{
Object lock = null;
synchronized (_locks) {
lock = _locks.get(siteId);
// create a lock object if we haven't one already
if (lock == null) {
_locks.put(siteId, lock = new Object());
}
}
return lock;
}
/**
* Obtains the site-specific jar file for the specified site. This
* should only be called when the lock for this site is held.
*/
protected SiteResourceBundle getBundle (int siteId)
throws IOException
{
// look up the site resource bundle for this site
SiteResourceBundle bundle = _bundles.get(siteId);
// if we haven't got one, create one
if (bundle == null) {
// obtain the string identifier for this site
String ident = _siteIdent.getSiteString(siteId);
// compose that with the jar file directory to obtain the
// path to the site-specific jar file
File file = new File(_jarPath, ident + JAR_EXTENSION);
// create a handle for this site-specific jar file
bundle = new SiteResourceBundle(file);
// cache our new bundle
_bundles.put(siteId, bundle);
}
return bundle;
}
/**
* Encapsulates the information we need to load data from a site
* resource bundle as well as to determine whether the loaded bundle
* is up to date.
*/
public static class SiteResourceBundle
{
/** The object through which we load resources from the
* site-specific jar file. */
public JarFile jarFile;
/** A handle on the site-specific jar file. */
public File file;
/**
* Constructs a new site resource bundle. The associated jar file
* will be opened the first time a resource is read.
*/
public SiteResourceBundle (File file)
throws IOException
{
this.file = file;
}
/**
* Fetches the specified resource from our site-specific jar file.
* The last modified time of the underlying jar file may be
* checked to determine whether or not it needs to be reloaded.
*
* @return an input stream via which the resource can be read or
* null if no resource exists with the specified path.
*/
public InputStream getResourceAsStream (String path)
throws IOException
{
// open or reopen our underlying jar file as necessary
refreshJarFile();
// now load up the resource
JarEntry entry = jarFile.getJarEntry(path);
return (entry == null) ? null : jarFile.getInputStream(entry);
}
/**
* Returns the last modified time of the underlying jar file.
*/
public long getLastModified ()
throws IOException
{
// open or reopen our underlying jar file as necessary
refreshJarFile();
return _lastModified;
}
@Override public String toString ()
{
return "[bundle=" + file + "]";
}
/**
* Reopens our site-specific jar file if it has been modified
* since it was last opened.
*/
protected void refreshJarFile ()
throws IOException
{
// ensure that the file exists
if (!file.exists()) {
String errmsg = "No site-specific jar file " +
"[path=" + file.getPath() + "].";
throw new FileNotFoundException(errmsg);
}
// determine whether or not we need to create a new jarfile
// instance
if (file.lastModified() > _lastModified) {
// make a note of the last modified time
_lastModified = file.lastModified();
// close our old jar file if we've got one
if (jarFile != null) {
jarFile.close();
}
log.info("Opened site bundle", "path", file.getPath());
// and open a new one
jarFile = new JarFile(file);
}
}
/** The last modified time of the jar file at the time that we
* opened it for reading. */
protected long _lastModified;
}
protected static class SiteClassLoader extends ClassLoader
{
public SiteClassLoader (SiteResourceBundle bundle)
{
_bundle = bundle;
}
@Override public InputStream getResourceAsStream (String path)
{
try {
return _bundle.getResourceAsStream(path);
} catch (IOException ioe) {
log.warning("Error loading resource from jarfile", "bundle", _bundle, "path", path,
"error", ioe);
return null;
}
}
@Override public String toString ()
{
return _bundle.toString();
}
protected SiteResourceBundle _bundle;
}
/** The site identifier we use to identify requests. */
protected SiteIdentifier _siteIdent;
/** The path to our site-specific jar files. */
protected String _jarPath;
/** We synchronize on a per-site basis. */
protected HashIntMap