Created site resource loader which loads resources based on the site
identified from an http request. Also created a unit test driver for the loader. git-svn-id: https://samskivert.googlecode.com/svn/trunk@418 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
@@ -0,0 +1,294 @@
|
|||||||
|
//
|
||||||
|
// $Id: SiteResourceLoader.java,v 1.1 2001/11/05 09:12:31 mdb Exp $
|
||||||
|
//
|
||||||
|
// samskivert library - useful routines for java programs
|
||||||
|
// Copyright (C) 2001 Michael Bayne
|
||||||
|
//
|
||||||
|
// 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.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import com.samskivert.Log;
|
||||||
|
import com.samskivert.util.HashIntMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, first from a site-specific jar file and if that doesn't
|
||||||
|
* contain an overriding resource, it loads the resource via the servlet
|
||||||
|
* context.
|
||||||
|
*
|
||||||
|
* <p> 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
|
||||||
|
* <code>/usr/share/java/webapps/site-data</code> and the site identifier
|
||||||
|
* returns <code>samskivert</code> as the site identifier for a particular
|
||||||
|
* request, site-specific resources will be loaded from
|
||||||
|
* <code>/usr/share/java/webapps/site-data/samskivert.jar</code>.
|
||||||
|
*
|
||||||
|
* <p> Also note that if the site identifier returns {@link
|
||||||
|
* SiteIdentifier#DEFAULT_SITE_ID} as the site for a particular request,
|
||||||
|
* no site-specific jar file will be searched and the default version of
|
||||||
|
* the resource will assumed to have been provided by the webapp itself
|
||||||
|
* and be available via the servlet context.
|
||||||
|
*/
|
||||||
|
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 context the servlet context from which site-agnostic
|
||||||
|
* resources will be loaded and from which configuration information
|
||||||
|
* will be obtained.
|
||||||
|
*/
|
||||||
|
public SiteResourceLoader (
|
||||||
|
SiteIdentifier siteIdent, ServletContext context)
|
||||||
|
{
|
||||||
|
// keep this stuff around
|
||||||
|
_siteIdent = siteIdent;
|
||||||
|
_context = context;
|
||||||
|
|
||||||
|
// obtain the path to the site-specific jar files
|
||||||
|
_jarPath = context.getInitParameter(SITE_JAR_PATH);
|
||||||
|
if (_jarPath == null) {
|
||||||
|
// use the default
|
||||||
|
_jarPath = DEFAULT_SITE_JAR_PATH;
|
||||||
|
// and complain about it
|
||||||
|
Log.warning("Site resource loader not configured with a " +
|
||||||
|
"site-specific jar path. Will use default " +
|
||||||
|
"[path=" + _jarPath + "], but you should provide " +
|
||||||
|
"a proper value via a servlet context init " +
|
||||||
|
"parameter named '" + SITE_JAR_PATH + "' to " +
|
||||||
|
"quiet this warning message.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the specific resource, from the site-specific jar file if one
|
||||||
|
* exists and contains the specified resource, or via the servlet
|
||||||
|
* context. If no resource exists with that path in either location,
|
||||||
|
* 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
|
||||||
|
{
|
||||||
|
InputStream stream = null;
|
||||||
|
|
||||||
|
// figure out which site in which we're interested
|
||||||
|
int siteId = _siteIdent.identifySite(req);
|
||||||
|
|
||||||
|
// if we identified a non-default site, we first look for a
|
||||||
|
// site-specific resource
|
||||||
|
if (siteId != SiteIdentifier.DEFAULT_SITE_ID) {
|
||||||
|
// and fetch the site specific resource
|
||||||
|
stream = getSiteResourceAsStream(siteId, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we didn't find a site-specific resource (or didn't look for
|
||||||
|
// one because we're loading for the default site), we need to
|
||||||
|
// attempt to load the resource from the servlet context
|
||||||
|
if (stream == null) {
|
||||||
|
stream = _context.getResourceAsStream(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// that's all she wrote
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the resource with the specified path from the site-specific
|
||||||
|
* jar file associated with the specified site.
|
||||||
|
*/
|
||||||
|
protected InputStream getSiteResourceAsStream (int siteId, String path)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
Object lock = null;
|
||||||
|
|
||||||
|
// Log.info("Loading site resource [siteId=" + siteId +
|
||||||
|
// ", path=" + path + "].");
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now synchronize on the lock to ensure that only one thread per
|
||||||
|
// site is concurrently executing
|
||||||
|
synchronized (lock) {
|
||||||
|
// look up the site resource bundle for this site
|
||||||
|
SiteResourceBundle bundle = (SiteResourceBundle)
|
||||||
|
_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/** The last modified time of the jar file at the time that we
|
||||||
|
* opened it for reading. */
|
||||||
|
public long lastModified;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reopens our site-specific jar file if it has been modified
|
||||||
|
* since it was last opened.
|
||||||
|
*/
|
||||||
|
protected void refreshJarFile ()
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
// 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("Opening site-specific jar file " +
|
||||||
|
"[path=" + file + "].");
|
||||||
|
|
||||||
|
// and open a new one
|
||||||
|
jarFile = new JarFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The site identifier we use to identify requests. */
|
||||||
|
protected SiteIdentifier _siteIdent;
|
||||||
|
|
||||||
|
/** The servlet context via which we load resources. */
|
||||||
|
protected ServletContext _context;
|
||||||
|
|
||||||
|
/** The path to our site-specific jar files. */
|
||||||
|
protected String _jarPath;
|
||||||
|
|
||||||
|
/** We synchronize on a per-site basis. */
|
||||||
|
protected HashIntMap _locks = new HashIntMap();
|
||||||
|
|
||||||
|
/** The table of site-specific jar file information. */
|
||||||
|
protected HashIntMap _bundles = new HashIntMap();
|
||||||
|
|
||||||
|
/** The servlet context init parameter name that is used to load our
|
||||||
|
* site-specific jar path configuration parameter. */
|
||||||
|
protected static final String SITE_JAR_PATH = "siteJarPath";
|
||||||
|
|
||||||
|
/** The default path to the site-specific jar files. This won't be
|
||||||
|
* used without logging a complaint first. */
|
||||||
|
protected static final String DEFAULT_SITE_JAR_PATH =
|
||||||
|
"/usr/share/java/webapps/site-data";
|
||||||
|
|
||||||
|
/** The file extension to be appended to the string site identifier to
|
||||||
|
* obtain the file name of the site-specific jar file. */
|
||||||
|
protected static final String JAR_EXTENSION = ".jar";
|
||||||
|
}
|
||||||
+193
@@ -0,0 +1,193 @@
|
|||||||
|
//
|
||||||
|
// $Id: SiteResourceLoaderTest.java,v 1.1 2001/11/05 09:12:31 mdb Exp $
|
||||||
|
//
|
||||||
|
// samskivert library - useful routines for java programs
|
||||||
|
// Copyright (C) 2001 Michael Bayne
|
||||||
|
//
|
||||||
|
// 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.ByteArrayOutputStream;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
import javax.servlet.RequestDispatcher;
|
||||||
|
import javax.servlet.Servlet;
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import junit.framework.Test;
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.apache.commons.util.StreamUtils;
|
||||||
|
|
||||||
|
import com.samskivert.Log;
|
||||||
|
import com.samskivert.test.TestUtil;
|
||||||
|
|
||||||
|
public class SiteResourceLoaderTest extends TestCase
|
||||||
|
{
|
||||||
|
public SiteResourceLoaderTest ()
|
||||||
|
{
|
||||||
|
super(SiteResourceLoaderTest.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runTest ()
|
||||||
|
{
|
||||||
|
// we need to fake a couple of things to get the test to work
|
||||||
|
TestSiteIdentifier ident = new TestSiteIdentifier();
|
||||||
|
ServletContext context = new TestServletContext();
|
||||||
|
|
||||||
|
// now create a resource loader and load up some resources
|
||||||
|
SiteResourceLoader loader = new SiteResourceLoader(ident, context);
|
||||||
|
|
||||||
|
try {
|
||||||
|
testResourceLoader(loader, "defaultout.txt");
|
||||||
|
|
||||||
|
ident.setSiteId(SITE1_ID);
|
||||||
|
testResourceLoader(loader, "site1out.txt");
|
||||||
|
|
||||||
|
ident.setSiteId(SITE2_ID);
|
||||||
|
testResourceLoader(loader, "site2out.txt");
|
||||||
|
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
fail("Error testing resource loader: " + ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void testResourceLoader (SiteResourceLoader loader,
|
||||||
|
String compareFile)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
StringBuffer gen = new StringBuffer();
|
||||||
|
appendResource(gen, loader, "/header.txt");
|
||||||
|
appendResource(gen, loader, "/body.txt");
|
||||||
|
appendResource(gen, loader, "/footer.txt");
|
||||||
|
|
||||||
|
StringBuffer cmp = new StringBuffer();
|
||||||
|
compareFile = "servlet/srl/" + compareFile;
|
||||||
|
InputStream cin = TestUtil.getResourceAsStream(compareFile);
|
||||||
|
if (cin == null) {
|
||||||
|
throw new IOException("Unable to load " + compareFile);
|
||||||
|
}
|
||||||
|
cmp.append(StreamUtils.streamAsString(cin));
|
||||||
|
|
||||||
|
// Log.info("Loaded resources [cmp=" + compareFile + "]: " + gen);
|
||||||
|
|
||||||
|
// now make sure the strings match
|
||||||
|
assertEquals("Testing " + compareFile,
|
||||||
|
cmp.toString(), gen.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void appendResource (
|
||||||
|
StringBuffer buffer, SiteResourceLoader loader, String path)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
InputStream rin = loader.getResourceAsStream(null, path);
|
||||||
|
if (rin == null) {
|
||||||
|
throw new IOException("Unable to load " + path);
|
||||||
|
}
|
||||||
|
buffer.append(StreamUtils.streamAsString(rin));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Test suite ()
|
||||||
|
{
|
||||||
|
return new SiteResourceLoaderTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestServletContext implements ServletContext
|
||||||
|
{
|
||||||
|
public InputStream getResourceAsStream (String path)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
String rpath = "servlet/srl/default/" + path;
|
||||||
|
rpath = TestUtil.getResourcePath(rpath);
|
||||||
|
return new FileInputStream(rpath);
|
||||||
|
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Log.warning("Unable to access servlet resource " +
|
||||||
|
"[path=" + path + ", error=" + ioe + "].");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInitParameter (String s)
|
||||||
|
{
|
||||||
|
if (s.equals("siteJarPath")) {
|
||||||
|
return TestUtil.getResourcePath("servlet/srl");
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServletContext getContext (String s) { return null; }
|
||||||
|
public int getMajorVersion () { return 1; }
|
||||||
|
public int getMinorVersion () { return 1; }
|
||||||
|
public String getMimeType (String s) { return null; }
|
||||||
|
public URL getResource (String s)
|
||||||
|
throws MalformedURLException { return null; }
|
||||||
|
public RequestDispatcher getRequestDispatcher (String s)
|
||||||
|
{ return null; }
|
||||||
|
public RequestDispatcher getNamedDispatcher (String s)
|
||||||
|
{ return null; }
|
||||||
|
public Servlet getServlet (String s)
|
||||||
|
throws ServletException { return null; }
|
||||||
|
public Enumeration getServlets () { return null; }
|
||||||
|
public Enumeration getServletNames () { return null; }
|
||||||
|
public void log (String s) { }
|
||||||
|
public void log (Exception e, String s) { }
|
||||||
|
public void log (String s, Throwable t) { }
|
||||||
|
public String getRealPath (String s) { return null; }
|
||||||
|
public String getServerInfo () { return null; }
|
||||||
|
public Enumeration getInitParameterNames () { return null; }
|
||||||
|
public Object getAttribute (String s) { return null; }
|
||||||
|
public Enumeration getAttributeNames () { return null; }
|
||||||
|
public void setAttribute (String s, Object o) { }
|
||||||
|
public void removeAttribute (String s) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestSiteIdentifier implements SiteIdentifier
|
||||||
|
{
|
||||||
|
public void setSiteId (int siteId)
|
||||||
|
{
|
||||||
|
_siteId = siteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int identifySite (HttpServletRequest req)
|
||||||
|
{
|
||||||
|
return _siteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSiteString (int siteId)
|
||||||
|
{
|
||||||
|
// we are required to map site ids to strings, however
|
||||||
|
switch (siteId) {
|
||||||
|
case SITE1_ID: return "site1";
|
||||||
|
case SITE2_ID: return "site2";
|
||||||
|
default: return DEFAULT_SITE_STRING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int _siteId = SiteIdentifier.DEFAULT_SITE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final int SITE1_ID = 1;
|
||||||
|
protected static final int SITE2_ID = 2;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user