Performance related patches

David E Jones <[email protected]> Tue, 4 Mar 2003 19:20:26 -0800
Newsgroups gmane.comp.java.jpublish.devel
Organization The Open For Business Project
Message-ID <[email protected]>
I've been doing some work on performance and resource management issues, so 
here are some patches related to that...

The most major ones are in SiteContext (just changed how RepositoryContent is 
instantiated), and there are more changes in RepositoryContent and just the 
addition of an equals method in TemplateContent.

These files are current as of the most recent CVS, ie as of a few minutes ago 
and are merged in. This set also includes some that I sent to Anthony 
earlier.

I expect there will be some other patches, but these are the big ones for 
now...

If you have any questions let me know.

Later,
-David E. Jones
SiteContext.java (text/x-java, 42.5 KB)
/*-- 

 Copyright (C) 2001-2003 Aetrion LLC.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.
 
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows 
    these conditions in the documentation and/or other materials 
    provided with the distribution.

 3. The name "JPublish" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact [email protected].
 
 4. Products derived from this software may not be called "JPublish", nor
    may "JPublish" appear in their name, without prior written permission
    from Aetrion LLC ([email protected]).
 
 In addition, the authors of this software request (but do not require) 
 that you include in the end-user documentation provided with the 
 redistribution and/or in the software itself an acknowledgement equivalent 
 to the following:
     "This product includes software developed by
      Aetrion LLC (http://www.aetrion.com/)."

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, 
 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 POSSIBILITY OF SUCH DAMAGE.

 For more information on JPublish, please see <http://www.jpublish.org/>.
 
 */

package org.jpublish;

import java.io.File;

import java.io.InputStream;
import java.io.FileInputStream;

import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.StringTokenizer;

import javax.servlet.ServletContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.anthonyeden.lib.util.IOUtilities;
import com.anthonyeden.lib.config.Configuration;
import com.anthonyeden.lib.config.XMLConfiguration;
import com.anthonyeden.lib.config.ConfigurationException;
import com.anthonyeden.lib.resource.ResourceRecipient;
import com.anthonyeden.lib.resource.FileResourceLoader;

import org.jpublish.view.ViewRenderer;
import org.jpublish.util.MimeTypeMap;
import org.jpublish.util.PathUtilities;
import org.jpublish.util.InternalURI;
import org.jpublish.util.RepositoryURI;
import org.jpublish.util.InternalURIParser;
import org.jpublish.util.CharacterEncodingManager;
import org.jpublish.action.ActionManager;
import org.jpublish.template.TemplateContent;
import org.jpublish.repository.RepositoryContent;

/** This class contains configuration information for a particular
    site.  Pages are loaded and actions are executed within the
    context of the site and have access to all of the methods within
    this class.
    
    <p>Instances of the SiteContext class will also reload themselves
    automatically when the underlying configuration file changes.</p>
    
    @author Anthony Eden
*/

public class SiteContext implements ResourceRecipient{
    
    public static final String DEFAULT_PAGE_MANAGER = 
        "org.jpublish.page.filesystem.FileSystemPageManager";
    public static final String DEFAULT_TEMPLATE_MANAGER = 
        "org.jpublish.template.filesystem.FileSystemTemplateManager";
    public static final String DEFAULT_STATIC_RESOURCE_MANAGER = 
        "org.jpublish.resource.filesystem.FileSystemStaticResourceManager";
    public static final String DEFAULT_VIEW_RENDERER = 
        "org.jpublish.view.velocity.VelocityViewRenderer";
    public static final String DEFAULT_COMPONENT_MANAGER = 
        "org.jpublish.component.InMemoryComponentManager";
    public static final String DEFAULT_PAGE_ROOT = "pages";
    public static final String DEFAULT_TEMPLATE_ROOT = "templates";
    public static final String DEFAULT_ACTION_ROOT = "actions";
    public static final String DEFAULT_STATIC_ROOT = "static";
    public static final String DEFAULT_ACTION_INDENTIFIER = "action";
    public static final String DEFAULT_PAGE = "index.html";
    public static final String DEFAULT_TEMPLATE = "basic";
    
    public static final Log syslog = LogFactory.getLog("syslog");
    
    private static Log log = LogFactory.getLog(SiteContext.class);
    
    private File configurationFile;
    private FileResourceLoader fileResourceLoader;
    
    private File root;
    private File contextRoot;
    
    private File pageRoot;
    private File templateRoot;
    private File actionRoot;
    private File staticRoot;
    private File webInfPath;
    private ServletContext servletContext;
    private String actionIdentifier;
    private String defaultPage = DEFAULT_PAGE;
    private String defaultTemplate = DEFAULT_TEMPLATE;
    private boolean protectReservedNames = false;
    private boolean parameterActionsEnabled = false;
    private boolean debug = false;
    private List modules;
    private List repositories;
    private List defaultErrorHandlers;
    private Map errorHandlerMap;
    private Map cachedErrorHandlers;
    
    private PageManager pageManager;
    private ActionManager actionManager;
    private TemplateManager templateManager;
    private StaticResourceManager staticResourceManager;
    private ViewRenderer viewRenderer;
    private ComponentManager componentManager;
    
    private MimeTypeMap mimeTypeMap;
    private CharacterEncodingManager characterEncodingManager;
    
    private Map attributes;

    /** Construct a new SiteContext using the given File to load the
        context's configuration.
        
        @param contextRoot Get the application context root
        @param configurationFile The configuration File
        @throws Exception 
    */

    public SiteContext(File contextRoot, String configPath) throws Exception{
        setContextRoot(contextRoot);
        
        File configurationFile = new File(configPath);
        if(!configurationFile.isAbsolute()){
            configurationFile = new File(contextRoot, 
                configurationFile.getPath());
        }
        
        this.configurationFile = configurationFile;
        
        modules = new ArrayList();
        repositories = new ArrayList();
        defaultErrorHandlers = new ArrayList();
        errorHandlerMap = new HashMap();
        cachedErrorHandlers = new HashMap();
        mimeTypeMap = new MimeTypeMap();
        characterEncodingManager = new CharacterEncodingManager();
        
        attributes = new HashMap();
        
        fileResourceLoader = new FileResourceLoader();
        fileResourceLoader.loadResource(configurationFile.getAbsolutePath(), 
            this);
    }
    
    /** Get the configuration File.
    
        @return The configuration File
    */
    
    public File getConfigurationFile(){
        return configurationFile;
    }
    
    public ServletContext getServletContext(){
        return servletContext;
    }
    
    public void setServletContext(ServletContext servletContext){
        this.servletContext = servletContext;
    }
    
    /** Return the root directory.  This directory may be used as
        a base path for all of the other directories required by 
        JPublish.  If the root is not set then this method will
        return the context root.
        
        @return The root directory
    */
    
    public File getRoot(){
        if(root == null){
            log.debug("Root is null - using context root instead");
            return getContextRoot();
        }
        return root;
    }
    
    /** Set the root directory.
    
        @param root The new root directory
    */
    
    public void setRoot(File root){
        this.root = root;
    }
    
    /** Set the root directory.
    
        @param root The new root directory
    */
    
    public void setRoot(String root){
        if(root != null){
            setRoot(new File(root));
        }
    }
    
    /** Get the context root.  The context root is set by the
        JPublishServlet.  The value is the root of the web
        application context.
        
        @return The webapp context root
    */
    
    public File getContextRoot(){
        return contextRoot;
    }
    
    /** Set the context root.
    
        @param contextRoot The new context root
    */
    
    public void setContextRoot(File contextRoot){
        log.debug("setContextRoot(" + contextRoot + ")");
        this.contextRoot = contextRoot;
    }
    
    /** Set the context root.
    
        @param contextRoot The new context root
    */
    
    public void setContextRoot(String contextRoot){
        if(contextRoot != null){
            setContextRoot(new File(contextRoot));
        }
    }
    
    /** Get the directory where page configuration files are stored.  Page
        configurations are stored as XML files and are cached to improve
        performance.  The page will automatically be reloaded if it is modified.
        
        @return The page root
    */

    public File getPageRoot(){
        return pageRoot;
    }
    
    /** Set the directory where page configuration files are stored.  Page
        configurations are stored as XML files and are cached to improve
        performance.  The page will automatically be reloaded if it is modified.
        
        @param pageRoot The new page root
    */
    
    public void setPageRoot(File pageRoot){
        this.pageRoot = pageRoot;
    }
    
    /** Set the directory where page configuration files are stored.  Page
        configurations are stored as XML files and are cached to improve
        performance.  The page will automatically be reloaded if it is modified.
        
        @param pageRoot The new page root
    */
    
    public void setPageRoot(String pageRoot){
        if(pageRoot != null){
            setPageRoot(new File(pageRoot));
        }
    }
    
    /** If the value of <code>getPageRoot()</code> is an absolute file
        path then that value is returned, otherwise the return value is 
        is <code>new File(getRoot(), getPageRoot())</code>.
        
        @return The real page root
    */
    
    public File getRealPageRoot(){
        File pageRoot = getPageRoot();
        if(!pageRoot.isAbsolute()){
            pageRoot = new File(getRoot(), pageRoot.getPath());
        }
        return pageRoot;
    }
    
    /** Get the directory where templates are stored.  Templates are merged
        with the runtime context to produce a final document.
        
        @return The template root
    */
    
    public File getTemplateRoot(){
        return templateRoot;
    }
    
    /** Set the directory where templates are stored.  Templates are merged
        with the runtime context to produce a final document.
        
        @param templateRoot The template root
    */
    
    public void setTemplateRoot(File templateRoot){
        this.templateRoot = templateRoot;
    }
    
    /** Set the directory where templates are stored.  Templates are merged
        with the runtime context to produce a final document.
        
        @param templateRoot The template root
    */
    
    public void setTemplateRoot(String templateRoot){
        if(templateRoot != null){
            setTemplateRoot(new File(templateRoot));
        }
    }
    
    /** If the value of <code>getTemplateRoot()</code> is an absolute file
        path then that value is returned, otherwise the return value is 
        is <code>new File(getRoot(), getTemplateRoot())</code>.
        
        @return The real template root
    */
    
    public File getRealTemplateRoot(){
        File templateRoot = getTemplateRoot();
        if(!templateRoot.isAbsolute()){
            templateRoot = new File(getRoot(), templateRoot.getPath());
        }
        return templateRoot;
    }
    
    /** Get the directory where action scripts are stored.  Actions stored
        here are scripts written in a language supported by the BSF library.
        The language's Java implementation must be included in the classpath.
    
        @return The action root
    */
    
    public File getActionRoot(){
        return actionRoot;
    }
    
    /** Set the directory where action scripts are stored.  Actions stored
        here are scripts written in a language supported by the BSF library.
        The language's Java implementation must be included in the classpath.
    
        @param actionRoot The new action root
    */
    
    public void setActionRoot(File actionRoot){
        this.actionRoot = actionRoot;
    }
    
    /** Set the directory where action scripts are stored.  Actions stored
        here are scripts written in a language supported by the BSF library.
        The language's Java implementation must be included in the classpath.
    
        @param actionRoot The new action root
    */
    
    public void setActionRoot(String actionRoot){
        if(actionRoot != null){
            setActionRoot(new File(actionRoot));
        }
    }
    
    /** If the value of <code>getActionRoot()</code> is an absolute file
        path then that value is returned, otherwise the return value is 
        is <code>new File(getRoot(), getActionRoot())</code>.
        
        @return The real action root
    */
    
    public File getRealActionRoot(){
        File actionRoot = getActionRoot();
        if(!actionRoot.isAbsolute()){
            actionRoot = new File(getRoot(), actionRoot.getPath());
        }
        return actionRoot;
    }
    
    /** Get the directory where static files are stored.  Static files are
        files which are not processed by JPublish but are returned byte for
        byte.
        
        @return The root directory where static files are stored
    */
    
    public File getStaticRoot(){
        return staticRoot;
    }
    
    /** Set the directory where static files are stored.  Static files are
        files which are not processed by JPublish but are returned byte for
        byte.
        
        @param staticRoot The new root directory where static files are stored
    */
    
    public void setStaticRoot(File staticRoot){
        this.staticRoot = staticRoot;
    }
    
    /** Set the directory where static files are stored.  Static files are
        files which are not processed by JPublish but are returned byte for
        byte.
        
        @param staticRoot The path of the new root directory where static 
            files are stored
    */
    
    public void setStaticRoot(String staticRoot){
        if(staticRoot != null){
            setStaticRoot(new File(staticRoot));
        }
    }
    
    /** If the value of <code>getStaticRoot()</code> is an absolute file
        path then that value is returned, otherwise the return value is 
        is <code>new File(getRoot(), getStaticRoot())</code>.
        
        @return The real static root
    */
    
    public File getRealStaticRoot(){
        File staticRoot = getStaticRoot();
        if(!staticRoot.isAbsolute()){
            staticRoot = new File(getRoot(), staticRoot.getPath());
        }
        return staticRoot;
    }
    
    /** Get the File for the WEB-INF directory.  This is used to locate the
        classes and JARs so they are accessible to scripting languages.
        
        @return The WEB-INF file
    */
    
    public File getWebInfPath(){
        return webInfPath;
    }
    
    /** Set the File for the WEB-INF directory.
        
        @param webInfPath The WEB-INF file
    */
    
    public void setWebInfPath(File webInfPath){
        this.webInfPath = webInfPath;
    }
    
    /** Get the action identifier which is used to trigger parameter actions.
        The default value is 'action'.
    
        @return The action identifier
    */
    
    public String getActionIdentifier(){
        return actionIdentifier;
    }
    
    /** Set the action identifier.  If this method is invoked with a null
        argument then the action identifier will be reset to the default
        value.
        
        @param actionIdentifier The new action identifer or null to reset
    */
    
    public synchronized void setActionIdentifier(String actionIdentifier){
        if(actionIdentifier == null){
            this.actionIdentifier = DEFAULT_ACTION_INDENTIFIER;
        } else {
            this.actionIdentifier = actionIdentifier;
        }
    }
    
    /** Return true if parameter actions are enabled.
    
        @return True if parameter actions are enabled
        @since 1.4.1
    */
    
    public boolean isParameterActionsEnabled(){
        return parameterActionsEnabled;
    }
    
    /** Set to true to enable parameter actions.  Parameter actions are disabled
        by default because of the inherent security risks involved in using
        them.
        
        @param parameterActionsEnabled True to enabled parameter actions
        @since 1.4.1
    */
    
    public void setParameterActionsEnabled(boolean parameterActionsEnabled){
        this.parameterActionsEnabled = parameterActionsEnabled;
    }
    
    /** Set to true to enable parameter actions.  Parameter actions are disabled
        by default because of the inherent security risks involved in using
        them.
        
        @param parameterActionsEnabled True to enabled parameter actions
        @since 1.4.1
    */
    
    public void setParameterActionsEnabled(String parameterActionsEnabled){
        setParameterActionsEnabled("true".equals(parameterActionsEnabled));
    }
    
    /** Get the default page.  This value will be used when directories
        are requested.  By default this returns 'index.html'.
        
        @return The default page
    */
    
    public String getDefaultPage(){
        return defaultPage;
    }
    
    /** Set the default page.  This value will be used when directories
        are requested.
    
        @param defaultPage The new default page
    */
    
    public void setDefaultPage(String defaultPage){
        this.defaultPage = defaultPage;
    }
    
    /** Get the default template.  This method returns the name of the
        template which will be used when no template is specified in 
        a page's configuration.
        
        @return The default template
    */
    
    public String getDefaultTemplate(){
        return defaultTemplate;
    }
    
    /** Set the default template. The default template will be used when 
        no template is specified in a page's configuration.
        
        @param defaultTemplate The new default template
    */
    
    public void setDefaultTemplate(String defaultTemplate){
        this.defaultTemplate = defaultTemplate;
    }
    
    /** Get the default mime type.  This method delegates to the current
        MimeTypeMap.
        
        @return The default mime type
    */
    
    public String getDefaultMimeType(){
        return getMimeTypeMap().getDefaultMimeType();
    }
    
    /** Set the default mime type.  This method delegates to the current
        MimeTypeMap.
        
        @param defaultMimeType The new default mime type
    */
    
    public void setDefaultMimeType(String defaultMimeType){
        getMimeTypeMap().setDefaultMimeType(defaultMimeType);
    }
    
    /** Returns true if reserved names should be protected in the
        JPublishContext.  This method returns false by default.
        
        @return True if reserved names should be protected
    */
    
    public boolean isProtectReservedNames(){
        return protectReservedNames;
    }
    
    /** Set to true to protect reserved names in the JPublishContext.
    
        @param protectReservedNames True to protect reserved names
    */
    
    public void setProtectReservedNames(boolean protectReservedNames){
        if(protectReservedNames)
            log.info("Protect reserved names enabled");
        this.protectReservedNames = protectReservedNames;
    }
    
    /** Set to "true" to protect reserved names in the JPublishContext.
    
        @param protectReservedNames "true" to protect reserved names
    */
    
    public void setProtectReservedNames(String protectReservedNames){
        setProtectReservedNames("true".equals(protectReservedNames));
    }
    
    /** Return true if debugging is enabled.
    
        @return True if debugging is enabled
    */
    
    public boolean isDebug(){
        return debug;
    }
    
    /** Set to true to enable debugging.
    
        @param debug True to enable debugging
    */
    
    public void setDebug(boolean debug){
        if(debug)
            log.info("JPublish debugging enabled.");
        this.debug = debug;
    }
    
    /** Set to "true" to enable debugging.
    
        @param debug "true" to enable debugging
    */
    
    public void setDebug(String debug){
        setDebug("true".equals(debug));
    }
    
    /** Get a List of all loaded modules.
    
        @return List of loaded modules
    */
    
    public List getModules(){
        return modules;
    }
    
    /** Get a list of all registered repositories.
    
        @return A list of all registered Repository objects
    */
    
    public List getRepositories(){
        return repositories;
    }
    
    /** Get a List of all error handlers for the given path.  The path can
        include the '*' wildcard.  If there are no error handlers for the
        given path then this method will return the default handlers.  If
        you do not want any handlers then define a error handler mapping
        with no defined error handlers.
        
        @param path The path
        @return A List of error handlers
    */
    
    public List getErrorHandlers(String path){
        List errorHandlers = (List)cachedErrorHandlers.get(path);
        if(errorHandlers == null){
            Iterator keys = errorHandlerMap.keySet().iterator();
            while(keys.hasNext()){
                String key = (String)keys.next();
                if(PathUtilities.match(path, key)){
                    errorHandlers = (List)errorHandlerMap.get(key);
                    cachedErrorHandlers.put(path, errorHandlers);
                    return errorHandlers;
                }
            }
            return getDefaultErrorHandlers();
        } else {
            return errorHandlers;
        }
    }
    
    /** Get the List of default error handlers.  These error handlers should
        be used whenever no error handlers are defined.
        
        @return The default error handlers
    */
    
    public List getDefaultErrorHandlers(){
        return defaultErrorHandlers;
    }
    
    /** Get a Repository by name.  If the repository was not registered
        then this method will return null.
        
        @param name The name of the Repository
        @return The repository or null
    */
    
    public Repository getRepository(String name){
        Iterator iter = getRepositories().iterator();
        while(iter.hasNext()){
            Repository repository = (Repository)iter.next();
            if(repository.getName().equals(name)){
                return repository;
            }
        }
        return null;
    }
    
    /** Get the site's ActionManager.
    
        @return The ActionManager
        @see org.jpublish.action.ActionManager
    */
    
    public ActionManager getActionManager(){
        return actionManager;
    }
    
    /** Get the site's PageManager.
    
        @return The PageManager
        @see org.jpublish.PageManager
    */
    
    public PageManager getPageManager(){
        return pageManager;
    }
    
    /** Get the site's TemplateManager.
    
        @return The TemplateManager
        @see org.jpublish.TemplateManager
    */
    
    public TemplateManager getTemplateManager(){
        return templateManager;
    }
    
    /** Get the site's StaticResourceManager.
    
        @return The StaticResourceManager
        @see org.jpublish.StaticResourceManager
    */
    
    public StaticResourceManager getStaticResourceManager(){
        return staticResourceManager;
    }
    
    /** Get the site's ViewRenderer which is used to render content.
    
        @return The ViewRenderer
    */
    
    public ViewRenderer getViewRenderer(){
        return viewRenderer;
    }
    
    /** Get the site's ComponentManager.
    
        @return The ComponentManager
        @since 2.0
    */
    
    public ComponentManager getComponentManager(){
        return componentManager;
    }
    
    /** Get the site's MimeType map.  Mime types can be mapped to file
        suffixes.
        
        @return The MimeTypeMap
    */
    
    public MimeTypeMap getMimeTypeMap(){
        return mimeTypeMap;
    }
    
    /** Return the CharacterEncodingManager.
    
        @return The CharacterEncodingManager
    */
    
    public CharacterEncodingManager getCharacterEncodingManager(){
        return characterEncodingManager;
    }
    
    /** Retrieve a Content object for the specified named content.  The content
        name must include the origin prefix.  In the case of content pulled from
        a repository this would be:
        
        <blockquote>
        <code>repository:repository_name://path/to/content</code>
        </blockquote>
        
        <p>In the case of templates, this would be:</p>
        
        <blockquote>
        <code>template:/path/to/template</code>
        </blockquote>
        
        <p>Ultimately there should be a common repository system for all content
        whether it be text content, templates, binary content, etc.</p>
        
        <p>This method must return null if the named content can not be 
        found.</p>
    
        @param name The content name
        @return The Content object
    */
    
    public Content getContent(String name){
        try{
            if (log.isDebugEnabled()) log.debug("getContent(" + name + ")");
            InternalURI uri = InternalURIParser.getInstance().parse(name);
            String protocol = uri.getProtocol();
            if(protocol.equalsIgnoreCase("template")){
                String path = uri.getPath();
                if (log.isDebugEnabled()) log.debug("Looking for template: " + path);
                return new TemplateContent(templateManager.getTemplate(path));
            } else if(protocol.equalsIgnoreCase("repository")){
                String repositoryName = 
                    ((RepositoryURI)uri).getRepositoryName();
                Repository r = getRepository(repositoryName);
                String path = uri.getPath();
                if (log.isDebugEnabled()) log.debug("Looking for content: " + path);
                return new RepositoryContent(r, path);
            } else {
                log.warn("Protocol " + protocol + " not supported");
                return null;
            }
        } catch(Throwable t){
            // this is necessary to support FreeMarker for the moment.
            // FreeMarker adds localized parts to the file path and thus
            // the file will not be found on the first try, but FreeMarker
            // requires that this method return null, so voila!  When they 
            // fix the setLocalizedLookup() method so false works I will
            // probably remove this.
            
            log.error("Error getting content: " + t.getMessage());
            return null;
        }
    }
    
    // Start Attribute support
    
    /** Get the named site attribute.  Returns null if there is no
        site attribute for the specified name.
    
        @param name The attribute name
        @return The site attribute or null
    */
    
    public Object getAttribute(String name){
        return attributes.get(name);
    }
    
    /** Set the named site attribute.
    
        @param name The site attribute name
        @param value The site attribute value
    */
    
    public void setAttribute(String name, Object value){
        attributes.put(name, value);
    }
    
    /** Remove the named site attribute.
    
        @param name The site attribute name
    */
    
    public void removeAttribute(String name){
        attributes.remove(name);
    }
    
    /** Get an Iterator of all names for site attributes.
    
        @return Iterator of attribute names
    */
    
    public Iterator getAttributeNames(){
        return attributes.keySet().iterator();
    }
    
    // End Attribute support
    
    /** Reload the site configuration. */
    
    public void reload(){
        try{
            log.info("Loading site configuration.");
            loadConfiguration();
            log.info("Configuration loaded.");
        } catch(Exception e){
            e.printStackTrace();
        }
    }
    
    /** Load the site configuration from the given InputStream.  The
        InputStream must be attached to an XML document.
        
        @param in The InputStream
        @throws Exception
    */

    public void load(InputStream in) throws Exception{
        log.debug("Loading configuration");
        
        // construct the Configuration object from the stream
        Configuration configuration = new XMLConfiguration(in);

        // get the classloader
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        
        // construct the ActionManager
        actionManager = new ActionManager(this);
        
        // setup the ActionManager
        Configuration actionManagerConfiguration = configuration.getChild(
            "action-manager");
        if(actionManagerConfiguration != null){
            List classPathElements = actionManager.getClassPathElements();
            Configuration classpathConfiguration = 
                actionManagerConfiguration.getChild("classpath");
            Iterator pathElements = 
                classpathConfiguration.getChildren("pathelement").iterator();
            while(pathElements.hasNext()){
                Configuration pathElement = (Configuration)pathElements.next();
                classPathElements.add(pathElement.getValue());
            }
        }
        
        // load the PageManager
        log.debug("Creating PageManager");
        Configuration pageManagerConfiguration = configuration.getChild(
            "page-manager");
        if(pageManagerConfiguration != null){
            String pageManagerClass = pageManagerConfiguration.getAttribute(
                "classname");
            if(pageManagerClass == null){
                // handle using page-manager value as class name
                pageManagerClass = pageManagerConfiguration.getValue();
                log.warn("The page-manager class should now be specified " + 
                    "using the classname attribute");
            }
            
            pageManager = 
                (PageManager)cl.loadClass(pageManagerClass).newInstance();
            pageManager.setSiteContext(this);
            pageManager.loadConfiguration(pageManagerConfiguration);
        } else {
            pageManager = 
                (PageManager)cl.loadClass(DEFAULT_PAGE_MANAGER).newInstance();
            pageManager.setSiteContext(this);
        }
        
        // load the TemplateManager
        log.debug("Creating TemplateManager");
        Configuration templateManagerConfiguration = 
            configuration.getChild("template-manager");
        if(templateManagerConfiguration != null){
            String templateManagerClass = 
                templateManagerConfiguration.getAttribute("classname");
            if(templateManagerClass == null){
                // handle using element value as class name
                templateManagerClass = templateManagerConfiguration.getValue();
                log.warn("The template-manager class should now be specified " + 
                    "using the classname attribute");
            }
            templateManager = (TemplateManager)cl.loadClass(
                templateManagerClass).newInstance();
            templateManager.setSiteContext(this);
            templateManager.loadConfiguration(templateManagerConfiguration);
        } else {
            templateManager = (TemplateManager)cl.loadClass(
                DEFAULT_TEMPLATE_MANAGER).newInstance();
            templateManager.setSiteContext(this);
        }
        
        // load the StaticResourceManager
        log.debug("Creating StaticResourceManager");
        Configuration staticResourceManagerConfiguration = 
            configuration.getChild("static-resource-manager");
        if(staticResourceManagerConfiguration != null){
            String staticResourceManagerClass = 
                staticResourceManagerConfiguration.getAttribute("classname");
            if(staticResourceManagerClass == null){
                // handle using element value as class name
                staticResourceManagerClass = 
                    staticResourceManagerConfiguration.getValue();
                log.warn("The static-resource-manager class should be " + 
                    "specified using the classname attribute");
            }
            staticResourceManager = (StaticResourceManager)cl.loadClass(
                staticResourceManagerClass).newInstance();
            staticResourceManager.setSiteContext(this);
            staticResourceManager.loadConfiguration(
                staticResourceManagerConfiguration);
        } else {
            staticResourceManager = (StaticResourceManager)cl.loadClass(
                DEFAULT_STATIC_RESOURCE_MANAGER).newInstance();
            staticResourceManager.setSiteContext(this);
        }
        
        // load the ViewRenderer
        log.debug("Creating ViewRenderer");
        Configuration viewRendererConfiguration = 
            configuration.getChild("view-renderer");
        if(viewRendererConfiguration != null){
            String viewRendererClass = 
                viewRendererConfiguration.getAttribute("classname");
            if(viewRendererClass == null){
                // handle using element value as class name
                viewRendererClass = 
                    viewRendererConfiguration.getValue();
                log.warn("The view-renderer class should be " + 
                    "specified using the classname attribute");
            }
            viewRenderer = (ViewRenderer)cl.loadClass(
                viewRendererClass).newInstance();
            viewRenderer.setSiteContext(this);
            viewRenderer.loadConfiguration(viewRendererConfiguration);
        } else {
            viewRenderer = (ViewRenderer)cl.loadClass(
                DEFAULT_VIEW_RENDERER).newInstance();
            viewRenderer.setSiteContext(this);
        }
        log.debug("View renderer: " + viewRenderer.getClass());
        viewRenderer.init();
        
        // load modules
        Iterator moduleElements = 
            configuration.getChildren("module").iterator();
        while(moduleElements.hasNext()){
            Configuration moduleElement = (Configuration)moduleElements.next();
            String className = moduleElement.getAttribute("classname");
            try{
                JPublishModule module = 
                    (JPublishModule)cl.loadClass(className).newInstance();
                module.init(this, moduleElement);
                modules.add(module);
            } catch(Exception e){
                log.error("Unable to load module " + className);
                e.printStackTrace();
            }
        }
        
        // load repository references
        Iterator repositoryElements = 
            configuration.getChildren("repository").iterator();
        while(repositoryElements.hasNext()){
            Configuration repositoryElement = 
                (Configuration)repositoryElements.next();
            String className = repositoryElement.getAttribute("classname");
            Repository repository = 
                (Repository)cl.loadClass(className).newInstance();
            repository.setSiteContext(this);
            repository.loadConfiguration(repositoryElement);
            getRepositories().add(repository);
        }
        
        // load the ComponentManager
        log.debug("Creating ComponentManager");
        Configuration componentManagerConfiguration = 
            configuration.getChild("component-manager");
        String componentManagerClass = null;
        if(componentManagerConfiguration != null){
            componentManagerClass = 
                componentManagerConfiguration.getAttribute("classname");
        }
        if(componentManagerClass == null){
            componentManagerClass = DEFAULT_COMPONENT_MANAGER;
        }
        log.info("Component manager class: " + componentManagerClass);
        componentManager = (ComponentManager)cl.loadClass(
            componentManagerClass).newInstance();
        componentManager.setSiteContext(this);
        if(componentManagerConfiguration != null){
            componentManager.loadConfiguration(componentManagerConfiguration);
        }
        
        // Configure root paths
        setRoot(configuration.getChildValue("root"));
        
        // eventually these should be moved into the configuration
        // methods for the actual managers since having roots only
        // makes sense there
        setPageRoot(configuration.getChildValue("page-root", 
            DEFAULT_PAGE_ROOT));
        setTemplateRoot(configuration.getChildValue("template-root", 
            DEFAULT_TEMPLATE_ROOT));
        setActionRoot(configuration.getChildValue("action-root", 
            DEFAULT_ACTION_ROOT));
        setStaticRoot(configuration.getChildValue("static-root", 
            DEFAULT_STATIC_ROOT));
        
        setActionIdentifier(configuration.getChildValue("action-identifier"));
        
        // Set defaults
        setDefaultPage(configuration.getChildValue("default-page", 
            DEFAULT_PAGE));
        setDefaultTemplate(configuration.getChildValue("default-template", 
            DEFAULT_TEMPLATE));
        setDefaultMimeType(configuration.getChildValue("default-mime-type"));
        
        // configuration JNDI settings
        log.debug("Configuring JNDI");
        configureJNDI(configuration.getChild("jndi"));
        
        // protect reserved names in the context
        setProtectReservedNames(configuration.getChildValue(
            "protect-reserved-names", "false"));
        
        // enable or disable parameter actions
        setParameterActionsEnabled(configuration.getChildValue(
            "parameter-actions-enabled", "false"));
            
        // enable or disable debugging
        setDebug(configuration.getChildValue("debug", "false"));
        
        // load character encoding maps
        log.debug("Loading CharacterEncodingMaps");
        characterEncodingManager.loadConfiguration(configuration);
        
        // load all actions
        actionManager.loadConfiguration(configuration);
        
        // load the mime type map
        Iterator mimeTypeMapElements = 
            configuration.getChildren("mime-mapping").iterator();
        while(mimeTypeMapElements.hasNext()){
            Configuration mimeTypeMapElement = 
                (Configuration)mimeTypeMapElements.next();
            String ext = mimeTypeMapElement.getAttribute("ext");
            String mimeType = mimeTypeMapElement.getAttribute("mimetype");
            mimeTypeMap.put(ext, mimeType);
        }
        
        // load default error handlers
        Configuration defaultErrorHandlersElement = 
            configuration.getChild("default-error-handlers");
        if(defaultErrorHandlersElement != null){
            
            Iterator defaultErrorHandlerElements = 
                defaultErrorHandlersElement.getChildren(
                "error-handler").iterator();
                
            while(defaultErrorHandlerElements.hasNext()){
                Configuration defaultErrorHandlerElement = 
                    (Configuration)defaultErrorHandlerElements.next();
                String className = 
                    defaultErrorHandlerElement.getAttribute("class");
                defaultErrorHandlers.add(cl.loadClass(className).newInstance());
            }
        }
        
        // load error handlers
        Iterator errorHandlerMapElements = 
            configuration.getChildren("error-handler-map").iterator();
        while(errorHandlerMapElements.hasNext()){
            Configuration errorHandlerMapElement = 
                (Configuration)errorHandlerMapElements.next();
            String path = errorHandlerMapElement.getAttribute("path");
            if(path == null){
                throw new ConfigurationException(
                    "Error handler path must be defined");
            }
            
            Iterator errorHandlerElements = 
                errorHandlerMapElement.getChildren("error-handler").iterator();
            while(errorHandlerElements.hasNext()){
                Configuration errorHandlerElement = 
                    (Configuration)errorHandlerElements.next();
                String errorHandlerClass = 
                    errorHandlerElement.getAttribute("class");
                List errorHandlers = (List)errorHandlerMap.get(path);
                if(errorHandlers == null){
                    errorHandlers = new ArrayList();
                    errorHandlerMap.put(path, errorHandlers);
                }
                errorHandlers.add(
                    cl.loadClass(errorHandlerClass).newInstance());
            }
        }
    }
    
    // private methods
    
    /** Configure the initial JNDI context properties.  If the element is null
        then the property values will not be modified.
        
        @param element The JDOM configuration element
        @throws Exception
    */
    
    private void configureJNDI(Configuration configuration) throws Exception{
        if(configuration != null){
            System.setProperty("java.naming.factory.initial", 
                configuration.getChildValue("initial-factory"));
            System.setProperty("java.naming.provider.url", 
                configuration.getChildValue("provider"));
        }
    }
    
    /** Load the configuration.  This method opens the stream to the
        configuration and then calls the <code>load()</code> method.
        
        @throws Exception
    */
    
    private void loadConfiguration() throws Exception{
        InputStream in = null;
        
        try{
            log.debug("Loading configuration from: " + configurationFile);
            in = new FileInputStream(configurationFile);
            load(in);
        } catch(Exception e){
            throw e;
        } finally {
            IOUtilities.close(in);
        }
    }

}
ActionManager.java (text/x-java, 25 KB)
/*-- 

 Copyright (C) 2001-2003 Aetrion LLC.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.
 
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows 
    these conditions in the documentation and/or other materials 
    provided with the distribution.

 3. The name "JPublish" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact [email protected].
 
 4. Products derived from this software may not be called "JPublish", nor
    may "JPublish" appear in their name, without prior written permission
    from Aetrion LLC ([email protected]).
 
 In addition, the authors of this software request (but do not require) 
 that you include in the end-user documentation provided with the 
 redistribution and/or in the software itself an acknowledgement equivalent 
 to the following:
     "This product includes software developed by
      Aetrion LLC (http://www.aetrion.com/)."

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, 
 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 POSSIBILITY OF SUCH DAMAGE.

 For more information on JPublish, please see <http://www.jpublish.org/>.
 
 */

package org.jpublish.action;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.anthonyeden.lib.util.IOUtilities;
import com.anthonyeden.lib.util.LogUtilities;
import com.anthonyeden.lib.config.Configuration;
import com.anthonyeden.lib.config.ConfigurationException;

import org.jpublish.SiteContext;
import org.jpublish.JPublishModule;
import org.jpublish.JPublishContext;
import org.jpublish.util.PathUtilities;
import org.jpublish.util.FileToPathIterator;
import org.jpublish.util.BreadthFirstFileTreeIterator;
import org.jpublish.util.vfs.VFSFile;
import org.jpublish.util.vfs.VFSProvider;
import org.jpublish.util.vfs.provider.filesystem.FileSystemProvider;

/** Class which manages all actions in the JPublish framework.

    @author Anthony Eden
*/

public class ActionManager{
    
    private static Log log = LogFactory.getLog(ActionManager.class);
    
    private Map definedActions;
    private List classPathElements;
    private List startupActions;
    private List shutdownActions;
    private List globalActions;
    private List pathActions;
    private List preEvaluationActions;
    private List postEvaluationActions;
    private SiteContext siteContext;
    private VFSProvider provider;

    private Map cachedScriptActions = new HashMap();
    
    /** Construct a new ActionManager with the given SiteContext.
    
        @param siteContext The SiteContext
    */
    
    public ActionManager(SiteContext siteContext){
        this.siteContext = siteContext;
        this.definedActions = new HashMap();
        this.startupActions = new ArrayList();
        this.shutdownActions = new ArrayList();
        this.globalActions = new ArrayList();
        this.pathActions = new ArrayList();
        this.preEvaluationActions = new ArrayList();
        this.postEvaluationActions = new ArrayList();
        this.classPathElements = new ArrayList();
    }
    
    /** Get a Map of all defined actions.
    
        @return A Map of defined actions
    */
    
    public Map getDefinedActions(){
        return definedActions;
    }
    
    /** Add an action.
    
        @param name The action name
        @param action The Action implementation
    */

    public void addAction(String name, Action action){
        definedActions.put(name, action);
    }
    
    /** Remove an action.
    
        @param name The action name
    */
    
    public void removeAction(String name){
        definedActions.remove(name);
    }
    
    /** Get the class path used by script actions for loading classes.
    
        @return The class path
    */
    
    public synchronized String getClassPath(){
        StringBuffer buffer = new StringBuffer();
        Iterator classPathElements = getClassPathElements().iterator();
        while(classPathElements.hasNext()){
            buffer.append(classPathElements.next().toString());
            if(classPathElements.hasNext()){
                buffer.append(System.getProperty("path.separator"));
            }
        }
        return buffer.toString();
    }
    
    /** Get a list of all elements in the ActionManager class path.
    
        @return The class path elements list
    */
    
    public List getClassPathElements(){
        return classPathElements;
    }
    
    /** Get a List of startup actions.
    
        @return List of startup actions
    */
    
    public List getStartupActions(){
        return startupActions;
    }
    
    /** Get a List of shutdown actions.
    
        @return List of shutdown actions
    */
    
    public List getShutdownActions(){
        return shutdownActions;
    }
    
    /** Get a List of global actions.
    
        @return List of global actions
    */
    
    public List getGlobalActions(){
        return globalActions;
    }
    
    /** Get the List of path actions.
    
        @return The path actions
    */
    
    public List getPathActions(){
        return pathActions;
    }
    
    /** Get the List of actions which are executed immediately
        upon receipt of any request.  These actions are 
        executed before a page search occurs.
        
        @return The pre-evaluation actions
        @since 1.3
    */
    
    public List getPreEvaluationActions(){
        return preEvaluationActions;
    }
    
    /** Get the List of actions which are executed after the
        HTTP request has been completed, but before the response
        is sent back to the client.
        
        @return The post-evaluation actions
        @since 1.3
    */
    
    public List getPostEvaluationActions(){
        return postEvaluationActions;
    }
    
    /** Execute all startup actions.
    
        @throws Exception Any Exception
    */
    
    public void executeStartupActions() throws Exception{
        Iterator actions = getStartupActions().iterator();
        while(actions.hasNext()){
            ActionWrapper action = (ActionWrapper)actions.next();
            action.execute(null);
        }
    }
    
    /** Execute all shutdown actions.
    
        @throws Exception Any Exception
    */
    
    public void executeShutdownActions() throws Exception{
        Iterator actions = getShutdownActions().iterator();
        while(actions.hasNext()){
            ActionWrapper action = (ActionWrapper)actions.next();
            action.execute(null);
        }
    }
    
    /** Execute all global actions using the given context.
    
        @param context The current context
        @throws Exception
        @return Redirection URL or null
    */
    
    public String executeGlobalActions(JPublishContext context) throws Exception{
        if(context == null){
            log.debug("Context is null");
        }
        
        List globalActions = getGlobalActions();
        if(globalActions == null){
            log.error("Global actions list is null");
            throw new NullPointerException("Global actions is null");
        }
        
        Iterator actions = globalActions.iterator();
        while(actions.hasNext()){
            ActionWrapper action = (ActionWrapper)actions.next();
            if(action == null){
                log.error("Action retrieved from iterator is null");
            }
            
            action.execute(context);
            
            String redirect = (String)context.get("redirect");
            if(redirect != null){
                return redirect;
            }
        }
        return null;
    }
    
    /** Execute the path actions with the given context.  If any of the 
        actions sets the value redirect in the context then that signals that 
        the servlet should redirect the request to the specified URL.
    
        @param path The request path
        @param context The current context
        @return The redirect value or null
        @throws Exception
    */
    
    public String executePathActions(String path, JPublishContext context) 
    throws Exception{
        Iterator actions = getPathActions().iterator();
        while(actions.hasNext()){
            ActionWrapper actionWrapper = (ActionWrapper)actions.next();
            PathAction action = (PathAction)actionWrapper.getAction();
            if(PathUtilities.match(path, action.getPath())){
                actionWrapper.execute(context);
                
                String redirect = (String)context.get("redirect");
                if(redirect != null){
                    return redirect;
                }
            }
        }
        return null;
    }
    
    /** Execute pre-evaluation actions.  Pre-evaluation actions are only 
        executed if their path argument matches the current path.
        
        <p><b>Note:</b> Since these actions are executed prior to 
        locating the page the page variable is not in the context.
        
        <p>To stop processing and return immediately, set the value
        <code>stop-processing</code> in the context to a non-null value.

        @param path The request path
        @param context The current request context
        @return True if the processing should stop
        @throws Exception
        @since 1.3
    */
        
    public boolean executePreEvaluationActions(String path,
    JPublishContext context) throws Exception{
        Iterator actions = getPreEvaluationActions().iterator();
        while(actions.hasNext()){
            ActionWrapper actionWrapper = (ActionWrapper)actions.next();
            PathAction action = (PathAction)actionWrapper.getAction();
            if(PathUtilities.match(path, action.getPath())){
                actionWrapper.execute(context);
                
                String stopProcessingFlag = 
                    (String)context.get("stop-processing");
                if(stopProcessingFlag != null){
                    return true;
                }
            }
        }
        return false;
    }
    
    /** Execute post-evaluation actions.
    
        @param path The request path
        @param context The request context
        @throws Exception
        @since 1.3
    */
    
    public void executePostEvaluationActions(String path, 
    JPublishContext context) throws Exception{
        Iterator actions = getPostEvaluationActions().iterator();
        while(actions.hasNext()){
            ActionWrapper actionWrapper = (ActionWrapper)actions.next();
            PathAction action = (PathAction)actionWrapper.getAction();
            if(PathUtilities.match(path, action.getPath())){
                actionWrapper.execute(context);
                
                //String redirect = (String)context.get("redirect");
                //if(redirect != null){
                //  return redirect;
                //}
            }
        }
    }
    
    /** Execute the named action with the given context.  If the action sets
        the value redirect in the context then that signals that the servlet
        should redirect the request to the specified URL.
    
        @param name The action name
        @param context The current context
        @return The redirect value or null
        @throws Exception
    */
    
    public String execute(String name, JPublishContext context) throws 
    Exception{   
        log.debug("Executing action: " + name);
        
        Action action = findAction(name);
        if(action != null){
            action.execute(context, null);
            
            String redirect = (String)context.get("redirect");
            log.debug("Action redirect: " + redirect);
            if(redirect != null){
                return redirect;
            }
        }
        return null;
    }
    
    /** Return the text for the specified script action.
    
        @param name The script name
        @return The script action text
        @throws IOException
    */
    
    public String getScriptActionText(String name) throws IOException{
        File actionRoot = siteContext.getRealActionRoot();
        File actionFile = new File(actionRoot, name);
        StringWriter writer = null;
        FileReader reader = null;
        try{
            writer = new StringWriter();
            reader = new FileReader(actionFile);
            int c = -1;
            while((c = reader.read()) != -1){
                writer.write((char)c);
            }
            return writer.toString();
        } finally {
            IOUtilities.close(writer);
            IOUtilities.close(reader);
        }
    }
    
    /** Set the text for the specified script action.
    
        @param name The script action name
        @param scriptActionText The script action text
        @throws IOException
    */
    
    public void setScriptActionText(String name, String scriptActionText)
    throws IOException{
        File actionRoot = siteContext.getRealActionRoot();
        File actionFile = new File(actionRoot, name);
        PrintWriter writer = null;
        try{
            writer = new PrintWriter(new FileWriter(actionFile));
            writer.print(scriptActionText);
        } finally {
            IOUtilities.close(writer);
        }
    }
    
    /** Remove the named script action.
    
        @param name The name
    */
    
    public void removeScriptAction(String name){
        File actionRoot = siteContext.getRealActionRoot();
        File actionFile = new File(actionRoot, name);
        actionFile.delete();
    }
    
    /** Make the directory for the specified path.  Parent directories
        will also be created if they do not exist.
        
        @param path The directory path
    */
    
    public void makeDirectory(String path){
        File file = new File(siteContext.getRealActionRoot(), path);
        file.mkdirs();
    }
    
    /** Remove the directory for the specified path.  The directory
        must be empty.
    
        @param path The path
        @throws Exception
    */
    
    public void removeDirectory(String path) throws Exception{
        log.info("Remove directory: " + path);
        File file = new File(siteContext.getRealActionRoot(), path);
        log.debug("Deleting file: " + file.getAbsolutePath());
        if(file.isDirectory()){
            file.delete();
        } else {
            throw new Exception("Path is not a directory: " + path);
        }
    }
    
    /** Get the Virtual File System root file.  The Virtual File System
        provides a datasource-independent way of navigating through all
        items known to the StaticResourceManager.
        
        @return The root VFSFile
        @throws Exception
    */
    
    public VFSFile getVFSRoot() throws Exception{
        if(provider == null){
            provider = new FileSystemProvider(siteContext.getRealActionRoot());
        }
        return provider.getRoot();
    }
    
    /** Find an action with the given name.  The name may be the name of an 
        action registered with the ActionManager at startup, an action from a
        module, a partial file path rooted in the action root directory or a 
        fully qualified Java class.
        
        @param name The name of the action
        @return The action
        @throws ActionNotFoundException If the action is not found
    */

    public Action findAction(String name){
        // look in registered classes first
        log.debug("Looking for registered action.");
        Action action = (Action)definedActions.get(name);
        if(action != null){
            log.debug("Registered action found.");
            return action;
        }
        
        // look in modules
        log.debug("Looking for action in modules.");
        Iterator modules = siteContext.getModules().iterator();
        while(modules.hasNext()){
            JPublishModule module = (JPublishModule)modules.next();
            action = (Action)(module.getDefinedActions().get(name));
            if(action != null){
                log.debug("Action found in module.");
                return action;
            }
        }
        
        // look in the action directory for scripts
        action = (Action) cachedScriptActions.get(name);
        if (action != null) {
            log.debug("Action found in script actions cache.");
            return action;
        }
        
        log.debug("Looking for action in action root.");
        File actionRoot = siteContext.getRealActionRoot();
        
        log.debug("Action root: " + actionRoot);
        File actionFile = new File(actionRoot, name);
        if(actionFile.exists()){
            log.debug("Action found [" + actionFile + "]");
            action = new ScriptAction(siteContext, actionFile);
            cachedScriptActions.put(name, action);
            return action;
        }
        
        // look in classpath
        try{
            log.debug("Looking for action in the classpath.");
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            action = (Action)cl.loadClass(name).newInstance();
            return action;
        } catch(Exception e){
            throw new ActionNotFoundException(e, name);
        }
        
    }
    
    /** Load the ActionManager's configuration from the given configuration
        object.
        
        @param configuration The configuration object
        @throws ConfigurationException
    */
    
    public void loadConfiguration(Configuration configuration) throws 
    ConfigurationException{
        try{
            // load action definitions
            Iterator defineActionElements = configuration.getChildren(
                "define-action").iterator();
            while(defineActionElements.hasNext()){
                Configuration defineActionElement = 
                    (Configuration)defineActionElements.next();
                String name = defineActionElement.getAttribute("name");
                String className = 
                    defineActionElement.getAttribute("classname");
                ClassLoader cl = Thread.currentThread().getContextClassLoader();
                Action action = (Action)cl.loadClass(className).newInstance();
                log.debug("Defined action: " + name + " [" + className + "]");
                definedActions.put(name, action);
            }
            
            // load startup actions
            Iterator startupActionElements = 
                configuration.getChildren("startup-action").iterator();
            while(startupActionElements.hasNext()){
                Configuration startupActionElement = 
                    (Configuration)startupActionElements.next();
                String name = startupActionElement.getAttribute("name");
                startupActions.add(new ActionWrapper(findAction(name), 
                    startupActionElement));
            }
            
            // load shutdown actions
            Iterator shutdownActionElements = 
                configuration.getChildren("shutdown-action").iterator();
            while(shutdownActionElements.hasNext()){
                Configuration shutdownActionElement = 
                    (Configuration)shutdownActionElements.next();
                String name = shutdownActionElement.getAttribute("name");
                shutdownActions.add(new ActionWrapper(findAction(name), 
                    shutdownActionElement));
            }
            
            // load global actions
            log.debug("Configuring global actions");
            Iterator globalActionElements = 
                configuration.getChildren("global-action").iterator();
            while(globalActionElements.hasNext()){
                Configuration globalActionElement = 
                    (Configuration)globalActionElements.next();
                String name = globalActionElement.getAttribute("name");
                log.debug("Finding global action '" + name + "'");
                Action action = findAction(name);
                if(action == null){
                    log.error("No action '" + name + "' found");
                } else {
                    log.debug("Action '" + name + "' found");
                    globalActions.add(new ActionWrapper(action, 
                        globalActionElement));
                }
            }
            
            // load path actions
            Iterator pathActionElements = 
                configuration.getChildren("path-action").iterator();
            while(pathActionElements.hasNext()){
                Configuration pathActionElement = 
                    (Configuration)pathActionElements.next();
                String name = pathActionElement.getAttribute("name");
                String path = pathActionElement.getAttribute("path");
                
                // debugging
                /*
                HashMap data = new HashMap();
                data.put("path", path);
                data.put("action", name);
                LogUtilities.debug(log, "Registered path action.", data);
                */
                
                pathActions.add(new ActionWrapper(
                    new PathAction(path, findAction(name)), pathActionElement));
            }
            
            // load pre-evaluation actions
            Iterator preEvaluationActionElements = 
                configuration.getChildren("pre-evaluation-action").iterator();
            while(preEvaluationActionElements.hasNext()){
                Configuration preEvaluationActionElement = 
                    (Configuration)preEvaluationActionElements.next();
                String name = preEvaluationActionElement.getAttribute("name");
                String path = preEvaluationActionElement.getAttribute("path");
                
                // debugging
                /*
                HashMap data = new HashMap();
                data.put("path", path);
                data.put("action", name);
                LogUtilities.debug(log, "Registered pre-evaluation action.", data);
                */
                
                preEvaluationActions.add(new ActionWrapper(
                    new PathAction(path, findAction(name)), 
                    preEvaluationActionElement));
            }
            
            // load post-evaluation actions
            Iterator postEvaluationActionElements = 
                configuration.getChildren("post-evaluation-action").iterator();
            while(postEvaluationActionElements.hasNext()){
                Configuration postEvaluationActionElement = 
                    (Configuration)postEvaluationActionElements.next();
                String name = postEvaluationActionElement.getAttribute("name");
                String path = postEvaluationActionElement.getAttribute("path");
                
                // debugging
                /*
                HashMap data = new HashMap();
                data.put("path", path);
                data.put("action", name);
                LogUtilities.debug(log, "Registered post-evaluation action.", data);
                */
                
                postEvaluationActions.add(new ActionWrapper(
                    new PathAction(path, findAction(name)), 
                    postEvaluationActionElement));
            }
        } catch(Exception e){
            throw new ConfigurationException(e);
        }
    }
    
    /** Get an Iterator of paths of action scripts which are known to the
        ActionManager.
        
        @return An iterator of paths
        @throws Exception
    */
    
    public Iterator getPaths() throws Exception{
        return getPaths("");
    }
    
    /** Get an Iterator of paths of action scripts which are known to the 
        ActionManager, starting from the specified base path.
        
        @param base The base path
        @return An iterator of paths
        @throws Exception
    */
    
    public Iterator getPaths(String base) throws Exception{
        File actionRoot = siteContext.getRealActionRoot();
        File baseFile = new File(actionRoot, base);
        return new FileToPathIterator(baseFile.toString(), 
            new BreadthFirstFileTreeIterator(baseFile));
    }

}
ScriptAction.java (text/x-java, 8.5 KB)
/*-- 

 Copyright (C) 2001-2003 Aetrion LLC.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.
 
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows 
    these conditions in the documentation and/or other materials 
    provided with the distribution.

 3. The name "JPublish" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact [email protected].
 
 4. Products derived from this software may not be called "JPublish", nor
    may "JPublish" appear in their name, without prior written permission
    from Aetrion LLC ([email protected]).
 
 In addition, the authors of this software request (but do not require) 
 that you include in the end-user documentation provided with the 
 redistribution and/or in the software itself an acknowledgement equivalent 
 to the following:
     "This product includes software developed by
      Aetrion LLC (http://www.aetrion.com/)."

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, 
 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 POSSIBILITY OF SUCH DAMAGE.

 For more information on JPublish, please see <http://www.jpublish.org/>.
 
 */

package org.jpublish.action;

import java.io.File;
import java.io.FileReader;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.ibm.bsf.BSFManager;
import com.ibm.bsf.BSFException;
import com.ibm.bsf.util.IOUtils;
import com.ibm.bsf.util.CodeBuffer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.anthonyeden.lib.config.Configuration;

import org.jpublish.Page;
import org.jpublish.SiteContext;
import org.jpublish.JPublishContext;
import org.jpublish.util.CustomClassLoader;

/** An action which is implemented in a BSF supported scripting language.  
    Script actions have access to several varibles:
    
    <p>These are always available:</p>
    
    <p>
    <b>site</b> - The SiteContext<br>
    <b>syslog</b> - Standard logging stream (Log4J Category)<br>
    </p>
    
    <p>If there is a context defined when the action is executed (all
    actions excluding startup actions):</p>
    
    <p>
    <b>context</b> - The current context<br>
    <b>application</b> - The ServletContext<br>
    <b>request</b> - The HTTP request<br>
    <b>response</b> - The HTTP response<br>
    <b>session</b> - The HTTP session<br>
    <b>page</b> - The Page object<br>
    </p>

    @author Anthony Eden
*/

public class ScriptAction implements Action{
    
    private static Log log = LogFactory.getLog(ScriptAction.class);
    
    private SiteContext siteContext;
    private File script;
    private String scriptLang = null;
    private long timeLastLoaded = 0;
    private String scriptString = null;

    /** Construct a new ScriptAction for the given script.  The path to the
        script should be an absolute path.
    
        @param siteContext The SiteContext
        @param script The path to the script
    */

    public ScriptAction(SiteContext siteContext, String script){
        this(siteContext, new File(script));
    }
    
    /** Construct a new ScriptAction for the given script.
    
        @param siteContext The SiteContext
        @param script The file representing the script
    */

    public ScriptAction(SiteContext siteContext, File script){
        this.siteContext = siteContext;
        this.script = script;
        log.debug("Creating new ScriptAction for " + script.getName());
    }
    
    /** Execute the action script represented by this ScriptAction.
    
        @param context The current context
        @param configuration The configuration object
        @throws Exception
    */

    public void execute(JPublishContext context, Configuration configuration) 
    throws Exception{
        log.debug("Executing script: " + script);
        
        //ClassLoader cl = Thread.currentThread().getContextClassLoader();
        
        BSFManager bsfManager = new BSFManager();
        //bsfManager.setClassPath(siteContext.getActionManager().getClassPath());
        //bsfManager.setClassLoader(new CustomClassLoader(cl));
        
        // expose standard items in the context
        if(context != null){
            ServletContext application = 
                (ServletContext)context.get("application");
            HttpServletRequest request = 
                (HttpServletRequest)context.get("request");
            HttpServletResponse response = 
                (HttpServletResponse)context.get("response");
            HttpSession session = (HttpSession)context.get("session");
            Page page = (Page)context.get("page");
            
            // expose the context
            bsfManager.declareBean("context", context, JPublishContext.class);
            
            // expose the context.  The variable name vc should be considered
            // deprecated
            bsfManager.declareBean("vc", context, JPublishContext.class);
            
            // expose the page object.
            if(page == null){
                log.debug("Page request is null");
            } else {
                bsfManager.declareBean("page", page, Page.class);
            }
            
            // expose standard HttpServlet objects
            if(request == null){
                log.debug("HTTP request is null");
            } else {
                bsfManager.declareBean("request", request, 
                    HttpServletRequest.class);
            }
            
            if(response == null){
                log.debug("HTTP response is null");
            } else {
                bsfManager.declareBean("response", response, 
                    HttpServletResponse.class);
            }
            
            if(session == null){
                log.debug("HTTP session is null");
            } else {
                bsfManager.declareBean("session", session, HttpSession.class);
            }
            
            if(application == null){
                log.debug("ServletContext is null");
            } else {
                bsfManager.declareBean("application", application, 
                    ServletContext.class);
            }
        }
        
        // these objects are exposed regardless if there is a context
        // object or not.  In other words they are accesible to startup
        // actions
        bsfManager.declareBean("syslog", SiteContext.syslog, Log.class);
        
        if(siteContext == null){
            log.debug("SiteContext is null");
        } else {
            bsfManager.declareBean("site", siteContext, SiteContext.class);
        }
        
        if(configuration == null){
            log.debug("Configuration is null");
        } else {
            bsfManager.declareBean("configuration", configuration, 
                Configuration.class);
        }
        
        if (scriptLang == null) scriptLang = BSFManager.getLangFromFilename(script.getName());
        
        boolean reloadScript = false;
        long scriptLastModified = script.lastModified();
        if (scriptLastModified > timeLastLoaded) {
            log.debug("Loading updated or new script: " + script.getName());
            reloadScript = true;
        }
        
        if (reloadScript || scriptString == null) {
            synchronized (this) {
                if (reloadScript || scriptString == null) {
                    timeLastLoaded = System.currentTimeMillis();
                    scriptString = IOUtils.getStringFromReader(new FileReader(script));
                }
            }
        }
        
        bsfManager.exec(scriptLang, script.getName(), 0, 0, scriptString);
    }
}
PageInstance.java (text/x-java, 12.7 KB)
/*-- 

 Copyright (C) 2001-2003 Aetrion LLC.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.
 
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows 
    these conditions in the documentation and/or other materials 
    provided with the distribution.

 3. The name "JPublish" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact [email protected].
 
 4. Products derived from this software may not be called "JPublish", nor
    may "JPublish" appear in their name, without prior written permission
    from Aetrion LLC ([email protected]).
 
 In addition, the authors of this software request (but do not require) 
 that you include in the end-user documentation provided with the 
 redistribution and/or in the software itself an acknowledgement equivalent 
 to the following:
     "This product includes software developed by
      Aetrion LLC (http://www.aetrion.com/)."

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, 
 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 POSSIBILITY OF SUCH DAMAGE.

 For more information on JPublish, please see <http://www.jpublish.org/>.
 
 */

package org.jpublish.page;

import java.io.File;
import java.io.Writer;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import java.util.List;
import java.util.Locale;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ArrayList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.anthonyeden.lib.config.Configuration;
import com.anthonyeden.lib.config.XMLConfiguration;
import com.anthonyeden.lib.config.ConfigurationException;

import org.jpublish.Page;
import org.jpublish.SiteContext;
import org.jpublish.JPublishContext;
import org.jpublish.action.Action;
import org.jpublish.action.ScriptAction;
import org.jpublish.action.ActionWrapper;

/** A representation of a single web page.  A page is defined by an XML
    document in the pages directory.  Each page has a template associated
    with the page and can have 0 or more actions attached to the page. 
    
    <p>Actions attached to a page will be triggered each time the page
    is requested.  Actions will be triggered in the order that they are
    listed within the Page's configuration.</p>
    
    <p>There should only be a single PageInstance in memory for each path.
    Each PageInstance is actually wrapped in a Page class which provides
    request-specific features.

    @author Anthony Eden
*/

public class PageInstance{
    
    private static Log log = LogFactory.getLog(PageInstance.class);
    
    private SiteContext siteContext;
    private List pageActions;
    private String path;
    private String pageName;
    private String pageType;
    private String templateName;
    private Map properties;

    /** Construct a new Page for the given path.  The name of the page is 
        the last part of the path (i.e. the file component) without the 
        dot ending.  The page type is the dot-ending.
    
        @param siteContext The SiteContext
        @param path The request path
        @param pageName The name of the page
        @param pageType The page type
    */
        
    public PageInstance(SiteContext siteContext, String path, String pageName, 
    String pageType){
        this.siteContext = siteContext;
        this.path = path;
        this.pageName = pageName;
        this.pageType = pageType;
        this.pageActions = new ArrayList();
        this.templateName = siteContext.getDefaultTemplate();
        this.properties = new HashMap();
    }
    
    /** Get the request path.
    
        @return The request path
    */
    
    public String getPath(){
        return path;
    }
    
    /** Get the page name.
    
        @return The page name
    */
    
    public String getPageName(){
        return pageName;
    }
    
    /** Get the page type.
    
        @return The page type
    */
    
    public String getPageType(){
        return pageType;
    }
    
    /** Return the page title.  Initially the page title is extracted from 
        the page's definition document, however it can be set programtically
        at runtime.
        
        <p>This method is deprecated.  Use getProperty("title") instead and
        include a property named title in the page configuration file.  The
        old &lt;title&gt; element and this method will be removed for the 
        1.0 release.
        
        @deprecated Use getProperty("title") instead
        @return The page title
    */

    public String getTitle(){
        String title = getProperty("Title");
        if(title == null){
            title = getProperty("title");
        }
        return title;
    }
    
    /** Set the title.  This will temporarily alter the page's title.
        
        <p>This method is deprecated.  The old &lt;title&gt; element 
        and this method will be removed for the 1.0 release.
    
        @deprecated
        @param title The page title
    */
    
    public void setTitle(String title){
        if(title != null){
            log.debug("setTitle(" + title + ")");
            setProperty("title", title, null);
        }
    }
    
    /** Get the full template file name, with the .suffix attached.
    
        @return The full template name
    */
    
    public String getFullTemplateName(){
        return templateName + "." + pageType;
    }
    
    /** Get the template name.  If the template name is not specified in the
        page configuration then the default template as specified in the 
        SiteContext will be used.
    
        @return The template name
    */
    
    public String getTemplateName(){
        return templateName;
    }
    
    /** Set the template name.  Invoking this method with a null value will 
        reset the template to the default template as specified in the 
        SiteContext.
    
        @param templateName The new template name or null to reset
    */
    
    public synchronized void setTemplateName(String templateName){
        log.debug("setTemplateName(" + templateName + ")");
        if(templateName == null){
            templateName = siteContext.getDefaultTemplate();
            log.debug("Using default template: " + templateName);
        }
        this.templateName = templateName;
    }
    
    /** Get a List of page actions.  To add an action to the page just add
        the action to this List.  Page actions are triggered each time the
        page is requested.
        
        @return A List of page actions
    */
    
    public List getPageActions(){
        return pageActions;
    }
    
    /** Get the named page property using the default Locale.  If the
        property is not found then return null.
    
        @param name The property name
        @return The value or null
    */
    
    public String getProperty(String name){
        return getProperty(name, Locale.getDefault());
    }
    
    /** Get the Locale-specific value for the given named property.  If
        the property is not found then return null.  This method will try to 
        find the most suitable locale by searching the property values in the
        following manner:
        
        <p>
        language + "_" + country + "_" + variant<br>
        language + "_" + country<br>
        langauge<br>
        ""
        </p>
        
        @param name The property name
        @param locale The locale
        @return The value
    */
    
    public String getProperty(String name, Locale locale){
        log.debug("Get property [name=" + name + ",locale=" + locale + "]");
        PageProperty property = (PageProperty)properties.get(name);
        if(property != null){
            return property.getValue(locale);
        } else {
            return null;
        }
    }
    
    /** Get the named property.  This method is equivilent to the
        <code>getProperty(name)</code> method.  This method is provided
        as a convenience to view code.
        
        @param name The property name
        @return The value
    */
    
    public String get(String name){
        log.debug("get(" + name + ") called to retrieve property");
        return getProperty(name);
    }
    
    /** Execute the page actions using the given context.
    
        @param context The current context
        @return A redirection value or null if there is no redirection
        @throws Exception Any Exception which occurs while executing the action
    */
    
    public String executeActions(JPublishContext context) throws Exception{
        Iterator pageActions = getPageActions().iterator();
        while(pageActions.hasNext()){
            ((ActionWrapper)pageActions.next()).execute(context);
            
            String redirect = (String)context.get("redirect");
            if(redirect != null){
                return redirect;
            }
        }
        return null;
    }
    
    /** Load the page configuration from the page's XML stream.
    
        @param in The InputStream
        @throws Exception Any exception
    */
    
    public synchronized void load(InputStream in) throws Exception{
        log.debug("Loading page.");
        Configuration configuration = new XMLConfiguration(in);
        loadConfiguration(configuration);
    }
    
    /** Load the page configuration from the given Configuration object.
    
        @param configuration The Configuration object
        @throws ConfigurationException
    */
    
    public synchronized void loadConfiguration(Configuration configuration) 
    throws ConfigurationException{
        setTitle(configuration.getChildValue("title"));
        setTemplateName(configuration.getChildValue("template"));
        
        // load page actions
        log.debug("Looping through page-action elements.");
        Iterator pageActionElements = 
            configuration.getChildren("page-action").iterator();
        while(pageActionElements.hasNext()){
            Configuration pageActionElement = 
                (Configuration)pageActionElements.next();
            String name = pageActionElement.getAttribute("name");
            
            // Removed for version 2.x
            if(name == null){
                name = pageActionElement.getValue();
            }
            
            if(name == null){
                throw new ConfigurationException(
                    "Error configuring page-action.");
            }
            
            Action action = siteContext.getActionManager().findAction(name);
            if(action == null){
                throw new ConfigurationException(
                    "Action " + name + " not defined");
            }
            
            pageActions.add(new ActionWrapper(action, pageActionElement));
        }
        
        // load page properties
        log.debug("Loading page properties");
        Iterator propertyElements = 
            configuration.getChildren("property").iterator();
        while(propertyElements.hasNext()){
            Configuration propertyElement = 
                (Configuration)propertyElements.next();
            setProperty(propertyElement.getAttribute("name"), 
                propertyElement.getValue(), 
                propertyElement.getAttribute("locale"));
        }
    }
    
    /** Set the property value.
    
        @param name The property name
        @param value The value
        @param locale The locale String or null
    */
    
    private void setProperty(String name, String value, String locale){
        log.debug("setProperty() [name=" + name + ",value=" + value + 
            ",locale=" + locale);
        PageProperty property = (PageProperty)properties.get(name);
        if(property == null){
            // named property not in property map
            property = new PageProperty(name);
            properties.put(name, property);
        }
        property.setValue(value, locale);
    }

}
RepositoryContent.java (text/x-java, 6.4 KB)
/*-- 

 Copyright (C) 2001-2003 Aetrion LLC.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.
 
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows 
    these conditions in the documentation and/or other materials 
    provided with the distribution.

 3. The name "JPublish" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact [email protected].
 
 4. Products derived from this software may not be called "JPublish", nor
    may "JPublish" appear in their name, without prior written permission
    from Aetrion LLC ([email protected]).
 
 In addition, the authors of this software request (but do not require) 
 that you include in the end-user documentation provided with the 
 redistribution and/or in the software itself an acknowledgement equivalent 
 to the following:
     "This product includes software developed by
      Aetrion LLC (http://www.aetrion.com/)."

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, 
 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 POSSIBILITY OF SUCH DAMAGE.

 For more information on JPublish, please see <http://www.jpublish.org/>.
 
 */

package org.jpublish.repository;

import java.io.Reader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.jpublish.Content;
import org.jpublish.Repository;

/** Interface which represents a single item of content.  This interface
    is read-only.
    
    @author Anthony Eden
    @since 2.0
*/

public class RepositoryContent implements Content{
    
    private static final Log log = LogFactory.getLog(RepositoryContent.class);
    
    private Repository repository = null;
    private String path = null;
    private String data = null;
    private long lastModified = -1;

    /** Construct a new RepositoryContent object.
    
        @param repository The Repository used to load the content
        @param path The path to the content in the repository
    */
    
    public RepositoryContent(Repository repository, String path) {
        this.repository = repository;
        this.path = path;
        this.data = null;
        this.lastModified = -1;
    }
    
    /** Construct a new RepositoryContent object.
    
        @param data The data
        @param lastModified The last modified time
    */
    
    public RepositoryContent(String data, long lastModified) {
        this.data = data;
        this.lastModified = lastModified;
    }
    
    /** Get the last-modified time of the content or -1 if it is not known.
    
        @return The last modified time
    */
    
    public long getLastModified(){
        if (this.repository != null) {
            try {
                return this.repository.getLastModified(this.path);
            } catch (Exception e) {
                log.warn("Could not getLastModified time from repository (returning -1): " + e.toString());
                return -1;
            }
        }
        return lastModified;
    }
    
    /** Get an InputStream for reading the content data.
    
        @return The content InputStream
    */
    
    public InputStream getInputStream(){
        if (this.repository != null) {
            try {
                return this.repository.getInputStream(this.path);
            } catch (Exception e) {
                log.warn("Could not getInputStream from repository (returning null): " + e.toString());
                return null;
            }
        }
        return new ByteArrayInputStream(data.getBytes());
    }

    /** Get a Reader for reading the content data.
    
        @return The content Reader
    */
    
    public Reader getReader(){
        return getReader(null);
    }
    
    /** Get a Reader for reading the content data with the specified content
        encoding.
        
        @param encoding The content encoding
        @return The Reader
    */
    
    public Reader getReader(String encoding){
        InputStream repInputStrem = null;
        if (repository != null) {
            try {
                repInputStrem = repository.getInputStream(path);
            } catch (Exception e) {
                log.warn("Could not getInputStream from repository (returning null): " + e.toString());
                return null;
            }
        }
        
        if(encoding == null){
            if (repository != null) return new InputStreamReader(repInputStrem);
            return new StringReader(data);
        } else {
            try{
                if (repository != null) return new InputStreamReader(repInputStrem, encoding);
                return new StringReader(new String(data.getBytes(), encoding));
            } catch(UnsupportedEncodingException e){
                log.warn("Unsupported encoding " + encoding + "; using default encoding");
                if (repository != null) return new InputStreamReader(repInputStrem);
                return new StringReader(data);
            }
        }
    }

    public boolean equals(Object obj) {
        Content passed = (Content) obj;
        long thisLastModified = this.getLastModified();
        long passedLastModified = passed.getLastModified();
        if (thisLastModified == passedLastModified) {
            return true;
        }
        log.debug("Passed content not equal: thisLastModified=" + thisLastModified + ", passedLastModified=" + passedLastModified);
        return false;
    }
}
TemplateContent.java (text/x-java, 4.6 KB)
/*-- 

 Copyright (C) 2001-2003 Aetrion LLC.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.
 
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows 
    these conditions in the documentation and/or other materials 
    provided with the distribution.

 3. The name "JPublish" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact [email protected].
 
 4. Products derived from this software may not be called "JPublish", nor
    may "JPublish" appear in their name, without prior written permission
    from Aetrion LLC ([email protected]).
 
 In addition, the authors of this software request (but do not require) 
 that you include in the end-user documentation provided with the 
 redistribution and/or in the software itself an acknowledgement equivalent 
 to the following:
     "This product includes software developed by
      Aetrion LLC (http://www.aetrion.com/)."

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, 
 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 POSSIBILITY OF SUCH DAMAGE.

 For more information on JPublish, please see <http://www.jpublish.org/>.
 
 */

package org.jpublish.template;

import java.io.Reader;
import java.io.InputStream;
import java.io.StringReader;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.jpublish.Template;
import org.jpublish.Content;

/** An implementation of the Content interface which provides the data of
    a Template object.
    
    @author Anthony Eden
    @since 2.0
*/

public class TemplateContent implements Content{
    
    private static final Log log = LogFactory.getLog(TemplateContent.class);
    
    private Template template;
    
    /** Construct a new TemplateContent object.
    
        @param template The template
    */
    
    public TemplateContent(Template template){
        this.template = template;
    }
    
    /** Get an InputStream for reading the content data.
    
        @return The content InputStream
    */
    
    public InputStream getInputStream(){
        return new ByteArrayInputStream(template.getText().getBytes());
    }
    
    /** Get the template reader.
    
        @return The template reader
    */
    
    public Reader getReader(){
        return getReader(null);
    }
    
    /** Get the template reader using the specified encoding.
    
        @param The content encoding
        @return The Reader
    */
    
    public Reader getReader(String encoding){
        if(encoding == null){
            return new StringReader(template.getText());
        } else {
            try {
                return new StringReader(
                    new String(template.getText().getBytes(), encoding));
            } catch (UnsupportedEncodingException e){
                log.warn("Unsupported encoding " + encoding + 
                    "; using default encoding");
                return new StringReader(template.getText());
            }
        }
    }
    
    /** Get the last modified time of the template.
    
        @return The last modified time
    */
    
    public long getLastModified(){
        return template.getLastModified();
    }
    
    public boolean equals(Object obj) {
        Content passed = (Content) obj;
        long thisLastModified = this.getLastModified();
        long passedLastModified = passed.getLastModified();
        if (thisLastModified == passedLastModified) {
            return true;
        }
        log.debug("Passed content not equal: thisLastModified=" + thisLastModified + ", passedLastModified=" + passedLastModified);
        return false;
    }    
}
FreeMarkerViewRenderer.java (text/x-java, 6.3 KB)
/*-- 

 Copyright (C) 2001-2003 Aetrion LLC.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.
 
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows 
    these conditions in the documentation and/or other materials 
    provided with the distribution.

 3. The name "JPublish" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact [email protected].
 
 4. Products derived from this software may not be called "JPublish", nor
    may "JPublish" appear in their name, without prior written permission
    from Aetrion LLC ([email protected]).
 
 In addition, the authors of this software request (but do not require) 
 that you include in the end-user documentation provided with the 
 redistribution and/or in the software itself an acknowledgement equivalent 
 to the following:
     "This product includes software developed by
      Aetrion LLC (http://www.aetrion.com/)."

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, 
 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 POSSIBILITY OF SUCH DAMAGE.

 For more information on JPublish, please see <http://www.jpublish.org/>.
 
 */

package org.jpublish.view.freemarker;

import java.io.Reader;
import java.io.Writer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

import freemarker.template.Template;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.anthonyeden.lib.config.Configuration;
import com.anthonyeden.lib.config.ConfigurationException;

import org.jpublish.Page;
import org.jpublish.SiteContext;
import org.jpublish.JPublishContext;
import org.jpublish.view.ViewRenderer;
import org.jpublish.view.ViewRenderException;

/** ViewRenderer which uses the FreeMarker template engine.
    
    @author Anthony Eden
    @since 2.0
*/

public class FreeMarkerViewRenderer implements ViewRenderer{
    
    private static final Log log = LogFactory.getLog(
        FreeMarkerViewRenderer.class);
    
    protected SiteContext siteContext;
    protected JPublishTemplateLoader templateLoader;
    protected freemarker.template.Configuration fmConfig;
    
    /** Construct a new FreeMarkerViewRenderer. */
    
    public FreeMarkerViewRenderer(){
        
    }
    
    /** Set the SiteContext.
    
        @param siteContext The SiteContext
    */
    
    public void setSiteContext(SiteContext siteContext){
        this.siteContext = siteContext;
    }
    
    /** Initialize the ViewRenderer.
    
        @throws Exception Any Exception
    */
    
    public void init() throws Exception{
        fmConfig = new freemarker.template.Configuration();
        
        templateLoader = new JPublishTemplateLoader();
        templateLoader.setSiteContext(siteContext);
        fmConfig.setTemplateLoader(templateLoader);
        fmConfig.setLocalizedLookup(false);
    }
    
    /** Render the view.  This method will reparse the template text each time
        it is called.
    
        @param context The JPublishContext
        @param path The path to the template
        @param in The Reader to read view template from
        @param out The Writer to write the rendered view
        @throws IOException 
        @throws ViewRenderException
    */
    
    public void render(JPublishContext context, String path, Reader in,
    Writer out) throws IOException, ViewRenderException{
        log.debug("render(" + path + ")");
        try{
            Page page = (Page)context.get(JPublishContext.JPUBLISH_PAGE);
            Object viewContext = createViewContext(context, path);
            Template template = fmConfig.getTemplate(path, page.getLocale());
            template.process(viewContext, out);
        } catch(IOException e){
            throw e;
        } catch(Exception e){
            throw new ViewRenderException(e);
        }
    }

    /** Render the view.
    
        @param context The JPublishContext
        @param path The path to the template
        @param in The InputStream to read view template from
        @param out The OutputStream to write the rendered view
        @throws IOException 
        @throws ViewRenderException
    */
    
    public void render(JPublishContext context, String path, InputStream in, 
    OutputStream out) throws IOException, ViewRenderException{
        render(context, path, new InputStreamReader(in), 
            new OutputStreamWriter(out));
    }
    
    /** Load the configuration for the view.
    
        @param configuration The configuration object
    */
    
    public void loadConfiguration(Configuration configuration)
    throws ConfigurationException{
        
    }
    
    /** Create the 'root' context for the template engine.  This method can be
        overridden in subclasses in case the viewContext needs to be populated
        with additional values.  The default implementation wraps the existing
        JPublishContext in a class which is useable by FreeMarker.

        @param context The JPublishContext
        @param path The path to the template
        @return Object The 'root' template context
        @throws ViewRenderException
    */
    
    protected Object createViewContext(JPublishContext context, 
    String path) throws ViewRenderException{
        FreeMarkerViewContext viewContext =
            new FreeMarkerViewContext(context);
        return viewContext;
    }
    
}
JPublishTemplateLoader.java (text/x-java, 4.4 KB)
/*-- 

 Copyright (C) 2001-2003 Aetrion LLC.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.
 
 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows 
    these conditions in the documentation and/or other materials 
    provided with the distribution.

 3. The name "JPublish" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact [email protected].
 
 4. Products derived from this software may not be called "JPublish", nor
    may "JPublish" appear in their name, without prior written permission
    from Aetrion LLC ([email protected]).
 
 In addition, the authors of this software request (but do not require) 
 that you include in the end-user documentation provided with the 
 redistribution and/or in the software itself an acknowledgement equivalent 
 to the following:
     "This product includes software developed by
      Aetrion LLC (http://www.aetrion.com/)."

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, 
 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 POSSIBILITY OF SUCH DAMAGE.

 For more information on JPublish, please see <http://www.jpublish.org/>.
 
 */

package org.jpublish.view.freemarker;

import java.io.Reader;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import freemarker.cache.TemplateLoader;

import org.jpublish.Content;
import org.jpublish.SiteContext;
import org.jpublish.ContentNotFoundException;

/** Implementation of the FreeMarker TemplateLoader interface.

    @author Anthony Eden
    @since 2.0
*/

public class JPublishTemplateLoader implements TemplateLoader{
    
    private static final Log log = 
        LogFactory.getLog(JPublishTemplateLoader.class);
        
    private SiteContext siteContext;
    
    /** Construct a new JPublishTemplateLoader. */
    
    public JPublishTemplateLoader(){
        
    }
    
    /** Set the SiteContext.
    
        @param siteContext The SiteContext
    */
    
    public void setSiteContext(SiteContext siteContext){
        this.siteContext = siteContext;
    }
    
    /** Find the template source.
    
        @param name The template name
        @return The source object
    */
    
    public Object findTemplateSource(String name){
        if (log.isDebugEnabled()) log.debug("findTemplateSource(" + name + ")");
        
        Object content = siteContext.getContent(name);
        if (log.isDebugEnabled()) log.debug("findTemplateSource() content: " + content);
        return content;
    }
    
    /** Get the last modified time of the template source.
    
        @param templateSource The source
        @return The last modified time
    */
    
    public long getLastModified(Object templateSource){
        log.debug("getLastModified() invoked");
        long lastModified = ((Content)templateSource).getLastModified();
        log.debug("Last modified time: " + lastModified);
        return lastModified;
    }
    
    /** Get the template source reader.
    
        @param templateSource The source
        @param encoding The character encoding
        @return The Reader
    */
    
    public Reader getReader(Object templateSource, String encoding){
        log.debug("getReader() invoked");
        return ((Content)templateSource).getReader(encoding);
    }
    
    /** Close the specified template source.
    
        @param templateSource The template source
    */
    
    public void closeTemplateSource(Object templateSource){
        // currently no-op.  Cleanup needed?
    }
    
}