From 3be945b22a9c7d05f46191f180a03076455f73b8 Mon Sep 17 00:00:00 2001 From: mdb Date: Tue, 6 Nov 2001 04:49:32 +0000 Subject: [PATCH] Many changes to provide site-specific resource loading (we're almost there) and to bring things up to date with some Velocity changes Geir made at my prompting. git-svn-id: https://samskivert.googlecode.com/svn/trunk@428 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- .../com/samskivert/velocity/Application.java | 102 +++++++--- .../velocity/DispatcherServlet.java | 50 ++--- .../samskivert/velocity/ImportDirective.java | 170 ++++++++++++++++ .../ServletContextResourceLoader.java | 20 +- .../velocity/SiteJarResourceLoader.java | 182 ++++++++++++++++++ .../samskivert/velocity/SiteResourceKey.java | 180 +++++++++++++++++ .../velocity/SiteResourceManager.java | 137 +++++++++++++ 7 files changed, 786 insertions(+), 55 deletions(-) create mode 100644 projects/samskivert/src/java/com/samskivert/velocity/ImportDirective.java create mode 100644 projects/samskivert/src/java/com/samskivert/velocity/SiteJarResourceLoader.java create mode 100644 projects/samskivert/src/java/com/samskivert/velocity/SiteResourceKey.java create mode 100644 projects/samskivert/src/java/com/samskivert/velocity/SiteResourceManager.java diff --git a/projects/samskivert/src/java/com/samskivert/velocity/Application.java b/projects/samskivert/src/java/com/samskivert/velocity/Application.java index 75286cc2..aa21dd32 100644 --- a/projects/samskivert/src/java/com/samskivert/velocity/Application.java +++ b/projects/samskivert/src/java/com/samskivert/velocity/Application.java @@ -1,5 +1,5 @@ // -// $Id: Application.java,v 1.4 2001/11/02 00:58:22 mdb Exp $ +// $Id: Application.java,v 1.5 2001/11/06 04:49:32 mdb Exp $ // // samskivert library - useful routines for java programs // Copyright (C) 2001 Michael Bayne @@ -38,10 +38,63 @@ import com.samskivert.util.StringUtil; public class Application { /** - * This should be overridden by the application implementation to - * perform any necessary initialization. + * Performs initializations common to all applications. Applications + * should override {@link #willInit} to perform initializations that + * need to take place before the common initialization (which includes + * the creation of the site identifier and message manager) and should + * override {@link #didInit} to perform initializations that need to + * take place after the common initialization (like passing the + * application to entities that might turn around and request a + * reference to our site identifier). + * + * @param context the servlet context in which this application is + * operating. + * @param logicPkg the base package for all of the logic + * implementations for this application. */ - public void init (ServletContext context) + public void init (ServletContext context, String logicPkg) + { + // keep this around for later + _context = context; + + // let the derived application do pre-init stuff + willInit(); + + // remove any trailing dot + if (logicPkg.endsWith(".")) { + _logicPkg = logicPkg.substring(0, logicPkg.length()-1); + } else { + _logicPkg = logicPkg; + } + + // instantiate our message manager if the application wants one + String bpath = getMessageBundlePath(); + if (bpath != null) { + _msgmgr = new MessageManager(bpath); + } + + // create our site identifier + _siteIdent = createSiteIdentifier(_context); + + // let the derived application do post-init stuff + didInit(); + } + + /** + * This should be overridden by the application implementation to + * invoke any necessary pre-initialization code. They should be sure + * to call super.willInit(). + */ + protected void willInit () + { + } + + /** + * This should be overridden by the application implementation to + * invoke any necessary post-initialization code. They should be sure + * to call super.didInit(). + */ + protected void didInit () { } @@ -53,6 +106,15 @@ public class Application { } + /** + * Returns a reference to the servlet context in which this + * application is operating. + */ + public ServletContext getServletContext () + { + return _context; + } + /** * Returns the message manager in effect for this application, if one * is in effect. @@ -144,34 +206,6 @@ public class Application return _msgmgr.getMessage(ctx.getRequest(), msg, args); } - /** - * Performs initializations common to all applications. We could put - * this in {@link #init} and require application derived classes to - * call super.init() but we want it to happen after the - * application initializes itself. - * - * @param logicPkg the base package for all of the logic - * implementations for this application. - */ - protected void postInit (ServletContext ctx, String logicPkg) - { - // remove any trailing dot - if (logicPkg.endsWith(".")) { - _logicPkg = logicPkg.substring(0, logicPkg.length()-1); - } else { - _logicPkg = logicPkg; - } - - // instantiate our message manager if the application wants one - String bpath = getMessageBundlePath(); - if (bpath != null) { - _msgmgr = new MessageManager(bpath); - } - - // create our site identifier - _siteIdent = createSiteIdentifier(ctx); - } - /** * Given the servlet path (the part of the URI after the context path) * this generates the classname of the logic class that should handle @@ -190,6 +224,10 @@ public class Application return _logicPkg + path; } + /** A reference to the servlet context in which this application is + * operating. */ + protected ServletContext _context; + /** The prefix that we use to generate fully qualified logic class * names. */ protected String _logicPkg; diff --git a/projects/samskivert/src/java/com/samskivert/velocity/DispatcherServlet.java b/projects/samskivert/src/java/com/samskivert/velocity/DispatcherServlet.java index 9332ecb3..26579f2b 100644 --- a/projects/samskivert/src/java/com/samskivert/velocity/DispatcherServlet.java +++ b/projects/samskivert/src/java/com/samskivert/velocity/DispatcherServlet.java @@ -1,5 +1,5 @@ // -// $Id: DispatcherServlet.java,v 1.8 2001/11/02 18:46:48 mdb Exp $ +// $Id: DispatcherServlet.java,v 1.9 2001/11/06 04:49:32 mdb Exp $ // // samskivert library - useful routines for java programs // Copyright (C) 2001 Michael Bayne @@ -35,12 +35,14 @@ import org.apache.velocity.app.Velocity; import org.apache.velocity.context.Context; import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.RuntimeSingleton; import org.apache.velocity.servlet.VelocityServlet; import com.samskivert.Log; import com.samskivert.servlet.MessageManager; import com.samskivert.servlet.RedirectException; +import com.samskivert.servlet.SiteIdentifier; import com.samskivert.servlet.util.ExceptionMap; import com.samskivert.servlet.util.FriendlyException; @@ -162,16 +164,13 @@ public class DispatcherServlet extends VelocityServlet /** * Initialize ourselves and our application. */ - public void init (ServletConfig config) - throws ServletException + protected void initVelocity (ServletConfig config) + throws ServletException { - super.init(config); - - // Log.info("Initializing dispatcher servlet."); - // load up our application configuration try { String appcl = config.getInitParameter(APP_CLASS_PROPS_KEY); + Log.info("Creating application [class=" + appcl + "]."); if (appcl == null) { _app = new Application(); } else { @@ -184,21 +183,17 @@ public class DispatcherServlet extends VelocityServlet if (StringUtil.blank(logicPkg)) { logicPkg = ""; } - _app.init(getServletContext()); - _app.postInit(getServletContext(), logicPkg); + _app.init(getServletContext(), logicPkg); } catch (Throwable t) { Log.warning("Error instantiating application."); Log.logStackTrace(t); } - } - // documentation inherited - protected void initVelocity (ServletConfig config) - throws ServletException - { - // stick the servlet context into the Velocity application context - Velocity.setApplicationContext(getServletContext()); + // stick the application into the Velocity application context + Velocity.setApplicationContext(_app); + + // now let velocity initialize itself super.initVelocity(config); } @@ -219,13 +214,20 @@ public class DispatcherServlet extends VelocityServlet */ public Template handleRequest (HttpServletRequest req, HttpServletResponse rsp, - Context ctx) throws Exception + Context ctx) + throws Exception { InvocationContext ictx = (InvocationContext)ctx; String errmsg = null; - // first we select the template - Template tmpl = selectTemplate(ictx); + // obtain the siteid for this request and stuff that into the + // context + SiteIdentifier ident = _app.getSiteIdentifier(); + int siteId = ident.identifySite(req); + ctx.put("__siteid__", new Integer(siteId)); + + // then select the template + Template tmpl = selectTemplate(siteId, ictx); // assume an HTML response unless otherwise massaged by the logic rsp.setContentType("text/html"); @@ -313,12 +315,16 @@ public class DispatcherServlet extends VelocityServlet * * @return The template to be used in generating the response. */ - protected Template selectTemplate (InvocationContext ctx) + 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(); - // Log.info("Loading template [path=" + path + "]."); - return getTemplate(path); + SiteResourceKey key = new SiteResourceKey(siteId, path); + + // Log.info("Loading template [key=" + key + "]."); + return RuntimeSingleton.getRuntimeServices().getTemplate(key); } /** diff --git a/projects/samskivert/src/java/com/samskivert/velocity/ImportDirective.java b/projects/samskivert/src/java/com/samskivert/velocity/ImportDirective.java new file mode 100644 index 00000000..e8f82b43 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/velocity/ImportDirective.java @@ -0,0 +1,170 @@ +// +// $Id: ImportDirective.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 java.io.IOException; +import java.io.Writer; + +import org.apache.velocity.context.InternalContextAdapter; + +import org.apache.velocity.Template; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.directive.Directive; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.SimpleNode; +import org.apache.velocity.runtime.resource.Resource; + +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; + +import com.samskivert.servlet.SiteIdentifier; +import com.samskivert.util.StringUtil; + +/** + * Pluggable directive that handles the #import() statement in VTL. Import + * is like #parse() except that it creates a {@link SiteResourceKey} based + * on information from the current request when fetching the imported + * template. + */ +public class ImportDirective extends Directive +{ + /** + * Returns name of this directive. + */ + public String getName () + { + return "import"; + } + + /** + * Return type of this directive. + */ + public int getType () + { + return LINE; + } + + /** + * Loads and renders the imported template. + */ + public boolean render ( + InternalContextAdapter context, Writer writer, Node node) + throws IOException, ResourceNotFoundException, ParseErrorException, + MethodInvocationException + { + // make sure an argument was supplied to the directive + if (node.jjtGetChild(0) == null) { + rsvc.error("#import() error : null argument"); + return false; + } + + // make sure that argument has a value + Object value = node.jjtGetChild(0).value(context); + if (value == null) { + rsvc.error("#import() error : null argument"); + return false; + } + + // obtain the path to the desired template + String path = value.toString(); + + // make sure we haven't exceeded the configured parse depth + Object[] templateStack = context.getTemplateNameStack(); + int maxlen = rsvc.getInt( + RuntimeConstants.PARSE_DIRECTIVE_MAXDEPTH, 20); + if (templateStack.length >= maxlen) { + rsvc.error("Max recursion depth reached (" + maxlen + "). " + + "File stack: " + + StringUtil.toString(templateStack) + "."); + return false; + } + + // inherit the current encoding if there is a current template, + // otherwise use the configured encoding + String encoding = null; + Resource current = context.getCurrentResource(); + if (current != null) { + encoding = current.getEncoding(); + } else { + encoding = (String) + 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 + 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."); + } + Object tkey = new SiteResourceKey(siteId, path); + + // locate the requested template + Template t = null; + try { + t = rsvc.getTemplate(tkey, encoding); + + } catch (ResourceNotFoundException rnfe) { + rsvc.error("#import(): cannot find template '" + tkey + + "', called from template " + + context.getCurrentTemplateName() + + " at (" + getLine() + ", " + getColumn() + ")"); + throw rnfe; + + } catch (ParseErrorException pee) { + rsvc.error("#import(): syntax error in #import()-ed template '" + + tkey + "', called from template " + + context.getCurrentTemplateName() + + " at (" + getLine() + ", " + getColumn() + ")"); + throw pee; + + } catch (Exception e) { + rsvc.error("#import(): Error [path=" + tkey + + ", error=" + e + "]."); + return false; + } + + // finally render the template + try { + context.pushCurrentTemplateName(path); + ((SimpleNode)t.getData()).render(context, writer); + + } catch (Exception e) { + // we want to pass method invocation exceptions through + if (e instanceof MethodInvocationException) { + throw (MethodInvocationException)e; + } + rsvc.error("Exception rendering #import(" + path + "): " + e); + return false; + + } finally { + context.popCurrentTemplateName(); + } + + return true; + } +} diff --git a/projects/samskivert/src/java/com/samskivert/velocity/ServletContextResourceLoader.java b/projects/samskivert/src/java/com/samskivert/velocity/ServletContextResourceLoader.java index 200f294d..b72f1751 100644 --- a/projects/samskivert/src/java/com/samskivert/velocity/ServletContextResourceLoader.java +++ b/projects/samskivert/src/java/com/samskivert/velocity/ServletContextResourceLoader.java @@ -1,5 +1,5 @@ // -// $Id: ServletContextResourceLoader.java,v 1.2 2001/11/02 02:30:24 mdb Exp $ +// $Id: ServletContextResourceLoader.java,v 1.3 2001/11/06 04:49:32 mdb Exp $ // // samskivert library - useful routines for java programs // Copyright (C) 2001 Michael Bayne @@ -38,6 +38,24 @@ import org.apache.velocity.runtime.resource.loader.ResourceLoader; */ public class ServletContextResourceLoader extends ResourceLoader { + /** + * When used with the default Velocity resource manager, we are + * constructed with our zero-argument constructor and later + * initialized via {@link #init}. + */ + public ServletContextResourceLoader () + { + } + + /** + * When used with the {@link SiteResourceManager} we are constructed + * with our servlet context reference and not later initialized. + */ + public ServletContextResourceLoader (ServletContext sctx) + { + _sctx = sctx; + } + /** * Called by Velocity to initialize this resource loader. */ diff --git a/projects/samskivert/src/java/com/samskivert/velocity/SiteJarResourceLoader.java b/projects/samskivert/src/java/com/samskivert/velocity/SiteJarResourceLoader.java new file mode 100644 index 00000000..d3465165 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/velocity/SiteJarResourceLoader.java @@ -0,0 +1,182 @@ +// +// $Id: SiteJarResourceLoader.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 java.io.InputStream; +import java.io.IOException; +import javax.servlet.ServletContext; + +import org.apache.commons.collections.ExtendedProperties; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.resource.Resource; +import org.apache.velocity.runtime.resource.loader.ResourceLoader; + +import com.samskivert.Log; +import com.samskivert.servlet.SiteIdentifier; + +/** + * A Velocity resource loader that loads resources from site-specific jar + * files. This cannot be used with the default Velocity resource manager, + * but is used by the {@link SiteResourceManager} which automatically + * handles the creation and use of this resource loader. + * + * @see com.samskivert.servlet.SiteResourceLoader + */ +public class SiteResourceLoader extends ResourceLoader +{ + /** + * Constructs a site resource loader that will use the specified site + * identifier to map site ids to site strings. + */ + public SiteResourceLoader (SiteIdentifier ident, ServletContext context) + { + // naming conflict... alas. + _loader = new com.samskivert.servlet.SiteResourceLoader( + ident, context); + } + + /** + * This is not called. + */ + public void init (ExtendedProperties config) + { + } + + /** + * Returns the input stream that can be used to load the named + * resource. + * + * @param resourceKey the locator key 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) + throws ResourceNotFoundException + { + SiteResourceKey skey = castKey(resourceKey); + if (skey == null) { + String errmsg = "Cannot use SiteResourceLoader without " + + "loading resources with SiteResourceKey instances."; + throw new ResourceNotFoundException(errmsg); + } + + // load it on up + try { + InputStream stream = _loader.getResourceAsStream( + skey.getSiteId(), skey.getPath(), false); + if (stream == null) { + String errmsg = "Unable to load resource via " + + "site-specific jar file [key=" + skey + "]."; + throw new ResourceNotFoundException(errmsg); + } + return stream; + + } catch (IOException ioe) { + throw new ResourceNotFoundException(ioe.getMessage()); + } + } + + /** + * 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. + */ + 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; + } + + // 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) { + 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 + try { + return (resource.getLastModified() < + _loader.getLastModified(siteId)); + } catch (IOException ioe) { + Log.warning("Failure obtaining last modified time of " + + "site-specific jar file [siteId=" + siteId + + ", error=" + ioe + "]."); + return false; + } + } + } + + /** + * 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. + */ + 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; + } + + // 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) { + return 0; + + } else { + // otherwise return the last modified time of the associated + // site-specific jar file + try { + return _loader.getLastModified(siteId); + } catch (IOException ioe) { + Log.warning("Failure obtaining last modified time of " + + "site-specific jar file [siteId=" + 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. */ + protected com.samskivert.servlet.SiteResourceLoader _loader; +} diff --git a/projects/samskivert/src/java/com/samskivert/velocity/SiteResourceKey.java b/projects/samskivert/src/java/com/samskivert/velocity/SiteResourceKey.java new file mode 100644 index 00000000..5d4d85a2 --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/velocity/SiteResourceKey.java @@ -0,0 +1,180 @@ +// +// $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; +} diff --git a/projects/samskivert/src/java/com/samskivert/velocity/SiteResourceManager.java b/projects/samskivert/src/java/com/samskivert/velocity/SiteResourceManager.java new file mode 100644 index 00000000..3e40dc8d --- /dev/null +++ b/projects/samskivert/src/java/com/samskivert/velocity/SiteResourceManager.java @@ -0,0 +1,137 @@ +// +// $Id: SiteResourceManager.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 javax.servlet.ServletContext; + +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.resource.Resource; +import org.apache.velocity.runtime.resource.ResourceFactory; +import org.apache.velocity.runtime.resource.ResourceManagerImpl; +import org.apache.velocity.runtime.resource.loader.ResourceLoader; + +import com.samskivert.Log; +import com.samskivert.servlet.SiteIdentifier; + +/** + * A resource manager implementation for Velocity that first loads site + * specific resources (via the {@link SiteResourceLoader}), but falls back + * to default resources if no site-specific resource loader is available. + * + *

If this resource manager is to be used, resources must be fetched + * using {@likn SiteResourceKey} objects as keys rather than simple + * strings. + */ +public class SiteResourceManager extends ResourceManagerImpl +{ + public void initialize (RuntimeServices rsvc) + throws Exception + { + super.initialize(rsvc); + + // the web framework was kind enough to slip this into the runtime + // instance when it started up + Application app = (Application)rsvc.getApplicationContext(); + if (app == null) { + rsvc.warn("SiteResourceManager: Application reference " + + "was not supplied as application context. A " + + "user of the site resource manager must supply " + + "a reference to the Application instance via " + + "Velocity.setApplicationContext()."); + } + + // get handles on the good stuff + _sctx = app.getServletContext(); + _ident = app.getSiteIdentifier(); + + // create our resource loaders + _siteLoader = new SiteResourceLoader(_ident, _sctx); + _contextLoader = new ServletContextResourceLoader(_sctx); + } + + protected Resource loadResource( + Object resourceKey, int resourceType, String encoding) + throws ResourceNotFoundException, ParseErrorException, Exception + { + // create a blank new resource + Resource resource = + ResourceFactory.getResource(resourceKey, 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; + Log.info("Looking for site-specific version of " + + rkey.description() + "."); + + // 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); + Log.info("Loaded site-specific [key=" + resourceKey + "]."); + } catch (ResourceNotFoundException rnfe) { + // nothing to worry about here + } + } + } + + // try the servlet context loader if we didn't find a + // site-specific resource + if (resource.getData() == null) { + resolveResource(resource, _contextLoader); + Log.info("Loaded default [key=" + resourceKey + "]."); + } + + return resource; + } + + protected void resolveResource (Resource resource, ResourceLoader loader) + throws ResourceNotFoundException, ParseErrorException, Exception + { + resource.setResourceLoader(loader); + resource.process(); + resource.setLastModified(loader.getLastModified(resource)); + resource.setModificationCheckInterval( + loader.getModificationCheckInterval()); + resource.touch(); + } + + /** 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. */ + protected SiteIdentifier _ident; + + /** We use this to load site-specific resources. */ + protected SiteResourceLoader _siteLoader; + + /** We use this to load default resources. */ + protected ServletContextResourceLoader _contextLoader; +}