Revamped site-specific resource loading to work without requiring a modified

version of Velocity. We still maintain a modified version because we want to be
able to access object fields directly instead of requiring getter/setter
methods, but with the other requirement gone normal people can compile and use
Samskivert with the stock Velocity distribution and not have to worry. It also
makes it vastly easier for us to keep up with the latest version of Velocity.


git-svn-id: https://samskivert.googlecode.com/svn/trunk@1739 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2005-12-09 01:13:04 +00:00
parent acbe7b16d1
commit d78f6b6021
8 changed files with 131 additions and 287 deletions
Binary file not shown.
@@ -241,6 +241,7 @@ public class DispatcherServlet extends VelocityServlet
Log.info("Velocity loading templates from site loader.");
props.setProperty(RuntimeSingleton.RESOURCE_MANAGER_CLASS,
SiteResourceManager.class.getName());
_usingSiteLoading = true;
} else {
// otherwise use a servlet context resource loader
Log.info("Velocity loading templates from servlet context.");
@@ -287,7 +288,9 @@ public class DispatcherServlet extends VelocityServlet
// context
SiteIdentifier ident = _app.getSiteIdentifier();
int siteId = ident.identifySite(req);
ctx.put("__siteid__", new Integer(siteId));
if (_usingSiteLoading) {
ctx.put("__siteid__", new Integer(siteId));
}
// put the context path in the context as well to make it easier
// to construct full paths
@@ -434,13 +437,14 @@ public class DispatcherServlet extends VelocityServlet
protected Template selectTemplate (int siteId, InvocationContext ctx)
throws ResourceNotFoundException, ParseErrorException, Exception
{
// create a site resource key based on the template path and the
// id of the site through which this request was made
String path = ctx.getRequest().getServletPath();
SiteResourceKey key = new SiteResourceKey(siteId, path);
// Log.info("Loading template [key=" + key + "].");
return RuntimeSingleton.getRuntimeServices().getTemplate(key);
if (_usingSiteLoading) {
// if we're using site resource loading, we need to prefix the path
// with the site identifier
path = siteId + ":" + path;
}
// Log.info("Loading template [path=" + path + "].");
return RuntimeSingleton.getRuntimeServices().getTemplate(path);
}
/**
@@ -493,6 +497,9 @@ public class DispatcherServlet extends VelocityServlet
/** The character set in which serve our responses. */
protected String _charset;
/** Set to true if we're using the {@link SiteResourceLoader}. */
protected boolean _usingSiteLoading;
/** This is the key used in the context for error messages. */
protected static final String ERROR_KEY = "error";
@@ -72,13 +72,13 @@ public class ImportDirective extends Directive
MethodInvocationException
{
// make sure an argument was supplied to the directive
if (node.jjtGetChild(0) == null) {
if (node.getChild(0) == null) {
rsvc.error("#import() error : null argument");
return false;
}
// make sure that argument has a value
Object value = node.jjtGetChild(0).value(context);
Object value = node.getChild(0).value(context);
if (value == null) {
rsvc.error("#import() error : null argument");
return false;
@@ -109,27 +109,29 @@ public class ImportDirective extends Directive
rsvc.getProperty(RuntimeConstants.INPUT_ENCODING);
}
// construct the template key based on the desired path and the
// site information in the current context. the siteid was shoved
// into the context by the dispatcher servlet. this is a hack, i
// know, but i couldn't convince the velocity peeps that we should
// have access to a request context in places like this
// adjust the template path with the site information in the current
// context if available. the siteid is shoved into the context by the
// dispatcher servlet when we're using the site resource loader. this
// is a hack, i know, but i couldn't convince the velocity peeps that
// we should have access to a request context in places like this
Object siteIdVal = context.get("__siteid__");
int siteId = SiteIdentifier.DEFAULT_SITE_ID;
try {
siteId = ((Integer)siteIdVal).intValue();
} catch (Exception e) {
rsvc.error("#import() error: No siteId information in context.");
if (siteIdVal != null) {
int siteId = SiteIdentifier.DEFAULT_SITE_ID;
try {
siteId = ((Integer)siteIdVal).intValue();
} catch (Exception e) {
rsvc.error("#import() error: No siteId information in context.");
}
path = siteId + ":" + path;
}
Object tkey = new SiteResourceKey(siteId, path);
// locate the requested template
Template t = null;
try {
t = rsvc.getTemplate(tkey, encoding);
t = rsvc.getTemplate(path, encoding);
} catch (ResourceNotFoundException rnfe) {
rsvc.error("#import(): cannot find template '" + tkey +
rsvc.error("#import(): cannot find template '" + path +
"', called from template " +
context.getCurrentTemplateName() +
" at (" + getLine() + ", " + getColumn() + ")");
@@ -137,13 +139,13 @@ public class ImportDirective extends Directive
} catch (ParseErrorException pee) {
rsvc.error("#import(): syntax error in #import()-ed template '" +
tkey + "', called from template " +
path + "', called from template " +
context.getCurrentTemplateName() +
" at (" + getLine() + ", " + getColumn() + ")");
throw pee;
} catch (Exception e) {
rsvc.error("#import(): Error [path=" + tkey +
rsvc.error("#import(): Error [path=" + path +
", error=" + e + "].");
return false;
}
@@ -65,14 +65,14 @@ public class ServletContextResourceManager extends ResourceManagerImpl
}
protected Resource loadResource(
Object resourceKey, int resourceType, String encoding)
String resourceName, int resourceType, String encoding)
throws ResourceNotFoundException, ParseErrorException, Exception
{
// create a blank new resource
Resource resource =
ResourceFactory.getResource(resourceKey, resourceType);
ResourceFactory.getResource(resourceName, resourceType);
resource.setRuntimeServices(rsvc);
resource.setKey(resourceKey);
resource.setName(resourceName);
resource.setEncoding(encoding);
resource.setResourceLoader(_contextLoader);
@@ -63,29 +63,24 @@ public class SiteJarResourceLoader extends ResourceLoader
* Returns the input stream that can be used to load the named
* resource.
*
* @param resourceKey the locator key for the resource to be loaded.
* @param path the path for the resource to be loaded.
*
* @return an input stream that can be used to read the resource.
*
* @exception ResourceNotFoundException if the resource was not found.
*/
public InputStream getResourceStream (Object resourceKey)
public InputStream getResourceStream (String path)
throws ResourceNotFoundException
{
SiteResourceKey skey = castKey(resourceKey);
if (skey == null) {
String errmsg = "Cannot use SiteResourceLoader without " +
"loading resources with SiteResourceKey instances.";
throw new ResourceNotFoundException(errmsg);
}
SiteKey skey = new SiteKey(path);
// load it on up
try {
InputStream stream = _loader.getResourceAsStream(
skey.getSiteId(), skey.getPath());
skey.siteId, skey.path);
if (stream == null) {
String errmsg = "Unable to load resource via " +
"site-specific jar file [key=" + skey + "].";
"site-specific jar file [path=" + path + "].";
throw new ResourceNotFoundException(errmsg);
}
return stream;
@@ -97,33 +92,27 @@ public class SiteJarResourceLoader extends ResourceLoader
/**
* Things won't ever be modified when loaded from the servlet context
* because they came from the webapp .war file and if that is
* reloaded, everything will be thrown away and started afresh.
* because they came from the webapp .war file and if that is reloaded,
* everything will be thrown away and started afresh.
*/
public boolean isSourceModified (Resource resource)
{
SiteResourceKey skey = castKey(resource.getKey());
if (skey == null) {
// return false if we were supplied with a bogus key
return false;
}
SiteKey skey = new SiteKey(resource.getName());
// if the resource is for the default site, it is never considered
// to be modified
int siteId = skey.getSiteId();
if (siteId == SiteIdentifier.DEFAULT_SITE_ID) {
// if the resource is for the default site, it is never considered to
// be modified
if (skey.siteId == SiteIdentifier.DEFAULT_SITE_ID) {
return false;
} else {
// otherwise compare the last modified time of the loaded
// resource with the last modified time of the associated
// site-specific jar file
// otherwise compare the last modified time of the loaded resource
// with that of the associated site-specific jar file
try {
return (resource.getLastModified() <
_loader.getLastModified(siteId));
_loader.getLastModified(skey.siteId));
} catch (IOException ioe) {
Log.warning("Failure obtaining last modified time of " +
"site-specific jar file [siteId=" + siteId +
"site-specific jar file [siteId=" + skey.siteId +
", error=" + ioe + "].");
return false;
}
@@ -132,49 +121,33 @@ public class SiteJarResourceLoader extends ResourceLoader
/**
* Things won't ever be modified when loaded from the servlet context
* because they came from the webapp .war file and if that is
* reloaded, everything will be thrown away and started afresh. So we
* can punt here and return zero.
* because they came from the webapp .war file and if that is reloaded,
* everything will be thrown away and started afresh. So we can punt here
* and return zero.
*/
public long getLastModified (Resource resource)
{
SiteResourceKey skey = castKey(resource.getKey());
if (skey == null) {
// return 0 if we were supplied with a bogus key
return 0;
}
SiteKey skey = new SiteKey(resource.getName());
// if the resource is for the default site, it is never considered
// to be modified
int siteId = skey.getSiteId();
if (siteId == SiteIdentifier.DEFAULT_SITE_ID) {
// if the resource is for the default site, it is never considered to
// be modified
if (skey.siteId == SiteIdentifier.DEFAULT_SITE_ID) {
return 0;
} else {
// otherwise return the last modified time of the associated
// site-specific jar file
try {
return _loader.getLastModified(siteId);
return _loader.getLastModified(skey.siteId);
} catch (IOException ioe) {
Log.warning("Failure obtaining last modified time of " +
"site-specific jar file [siteId=" + siteId +
"site-specific jar file [siteId=" + skey.siteId +
", error=" + ioe + "].");
return 0;
}
}
}
/**
* Casts the supplied resource key to a {@link SiteResourceKey} and
* returns it iff it is an instance of such. Returns null otherwise.
*/
protected SiteResourceKey castKey (Object resourceKey)
{
return (resourceKey instanceof SiteResourceKey) ?
(SiteResourceKey)resourceKey : null;
}
/** A reference to the site resource loader through which we'll load
* things. */
/** The site resource loader through which we'll load things. */
protected SiteResourceLoader _loader;
}
@@ -0,0 +1,49 @@
//
// samskivert library - useful routines for java programs
// Copyright (C) 2001-2004 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.velocity;
import com.samskivert.Log;
/**
* Decodes a compound Velocity resource name plus site identifier.
*/
public class SiteKey
{
/** The site identifier associated with this path or -1 if no site
* identifier was specified in the path. */
public int siteId = -1;
/** The resource path. */
public String path;
public SiteKey (String path)
{
int cidx = path.indexOf(":");
if (cidx == -1) {
this.path = path;
} else {
try {
siteId = Integer.parseInt(path.substring(0, cidx));
} catch (Exception e) {
Log.warning("Invalid site path [path=" + path + "].");
}
this.path = path.substring(cidx+1);
}
}
}
@@ -1,180 +0,0 @@
//
// $Id: SiteResourceKey.java,v 1.1 2001/11/06 04:49:32 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.velocity;
import com.samskivert.servlet.SiteIdentifier;
import com.samskivert.util.IntListUtil;
import com.samskivert.util.StringUtil;
/**
* The site resource key is used as a key for resources that can be used
* for one or more sites. A resource always has the same path, but can be
* valid for a single site, or multiple sites and the set of sites for
* which a resource is valid can change without impacting the key's hash
* value.
*/
public class SiteResourceKey
{
/**
* Constructs a new site resource key for the specified resource,
* loaded via the specified site.
*/
public SiteResourceKey (int siteId, String path)
{
_path = path;
_sites = new int[] { siteId };
}
/**
* Resource keys that contain only a single site id will return that
* site id as their primary. Those that contain multiple sites are
* assumed to reference default resources (which may be applicable to
* multiple sites) in which case the default site id is returned.
*/
public int getSiteId ()
{
return (_sites.length == 1 ? _sites[0] :
SiteIdentifier.DEFAULT_SITE_ID);
}
/**
* Returns the path to the resource referenced by this site resource
* key.
*/
public String getPath ()
{
return _path;
}
/**
* Returns true if this resource key contains the specified site in
* its site set.
*/
public boolean containsSite (int siteId)
{
return IntListUtil.contains(_sites, siteId);
}
/**
* Adds a site to this site resource key. The resource to which this
* key maps is assumed to be valid for the added site.
*/
public void addSite (int siteId)
{
// sanity check
if (siteId == 0) {
throw new IllegalArgumentException(
"Site IDs cannot have the value 0.");
}
// add the site to the list only if it's not already there
int[] sites = IntListUtil.testAndAdd(_sites, siteId);
if (sites != null) {
_sites = sites;
}
}
/**
* Removes a site from this site resource key. The resource to which
* this key maps is no longer assumed to be valid for the removed
* site.
*/
public boolean removeSite (int siteId)
{
// sanity check
if (siteId == 0) {
throw new IllegalArgumentException(
"Site IDs cannot have the value 0.");
}
// remove the site from the list
return (IntListUtil.remove(_sites, siteId) != 0);
}
/**
* We hash to the hash value of our path.
*/
public int hashCode ()
{
return _path.hashCode();
}
/**
* Two site resource keys are equal if the paths are the same and at
* least one site id overlaps between the two keys. Most equality
* comparisons will involve one multi-site key and one key that
* contains only a single site.
*/
public boolean equals (Object other)
{
if (other instanceof SiteResourceKey) {
SiteResourceKey okey = (SiteResourceKey)other;
// make sure the paths match
if (!_path.equals(okey._path)) {
return false;
}
// select the key with fewer sites in its sites array to be in
// the outer loop
int[] s1 = _sites;
int[] s2 = okey._sites;
if (s2.length < s1.length) {
s1 = s2;
s2 = _sites;
}
for (int i1 = 0; i1 < s1.length; i1++) {
for (int i2 = 0; i2 < s2.length; i2++) {
if (s1[i1] == s2[i2]) {
return true;
}
}
}
}
return false;
}
/**
* The string representation of a resource key is just the path so
* that it is backwards compatible with older resource loaders.
*/
public String toString ()
{
return _path;
}
/**
* Returns a string description of this object.
*/
public String description ()
{
return ("[path=" + _path +
", sites=" + StringUtil.toString(_sites) + "]");
}
/** The path to the resource. */
protected String _path;
/** The sites for which this resource is valid. */
protected int[] _sites;
}
@@ -35,13 +35,8 @@ import com.samskivert.servlet.SiteResourceLoader;
/**
* A resource manager implementation for Velocity that first loads site
* specific resources (via the {@link SiteJarResourceLoader}), but falls
* back to default resources if no site-specific resource loader is
* available.
*
* <p> If this resource manager is to be used, resources must be fetched
* using {@link SiteResourceKey} objects as keys rather than simple
* strings.
* specific resources (via the {@link SiteJarResourceLoader}), but falls back
* to default resources if no site-specific resource loader is available.
*/
public class SiteResourceManager extends ResourceManagerImpl
{
@@ -87,37 +82,35 @@ public class SiteResourceManager extends ResourceManagerImpl
}
protected Resource loadResource(
Object resourceKey, int resourceType, String encoding)
String resourceName, int resourceType, String encoding)
throws ResourceNotFoundException, ParseErrorException, Exception
{
SiteKey skey = new SiteKey(resourceName);
// create a blank new resource
Resource resource =
ResourceFactory.getResource(resourceKey, resourceType);
ResourceFactory.getResource(skey.path, resourceType);
resource.setRuntimeServices(rsvc);
resource.setKey(resourceKey);
resource.setEncoding(encoding);
// if the resource was requested using a site resource key, we can
// attempt to load a site-specific version
if (resourceKey instanceof SiteResourceKey) {
SiteResourceKey rkey = (SiteResourceKey)resourceKey;
// make sure the site we're loading for is not the default
// site, in which case we want to skip to the second resource
// loader directly
if (rkey.getSiteId() != SiteIdentifier.DEFAULT_SITE_ID) {
// try loading it via the site-specific resource loader
try {
resolveResource(resource, _siteLoader);
} catch (ResourceNotFoundException rnfe) {
// nothing to worry about here
}
// attempt to load a site-specific version; also make sure the site
// we're loading for is not the default site, in which case we want to
// skip to the second resource loader directly
if (skey.siteId != -1 && skey.siteId != SiteIdentifier.DEFAULT_SITE_ID) {
// try loading it via the site-specific resource loader
try {
resource.setName(resourceName);
resolveResource(resource, _siteLoader);
} catch (ResourceNotFoundException rnfe) {
// nothing to worry about here
}
}
// try the servlet context loader if we didn't find a
// site-specific resource
// try the servlet context loader if we didn't find a site-specific
// resource
if (resource.getData() == null) {
resource.setName(skey.path);
resolveResource(resource, _contextLoader);
}
@@ -135,8 +128,8 @@ public class SiteResourceManager extends ResourceManagerImpl
resource.touch();
}
/** A reference to the servlet context through which we'll load
* default resources. */
/** A reference to the servlet context through which we'll load default
* resources. */
protected ServletContext _sctx;
/** A reference to the site identifier in use by the application. */