Hi,

I came across a reference to the FreeMarker templating engine a couple of weeks ago 
whilst
reading up on the Spring framework. It looked pretty interesting so I thought I'd have 
a
go at writing an opt-freemarker package.

Unlike Velocity though FreeMarker doesn't have a dispatch servlet but I still wanted to
utilise the functionality of DocumentView (the setting of the model bean attribute in 
the
scope specified on the document view). DocumentView currently assumes the use of a
dispatcher though so I've made a few updates (attached) that allow the "document" view
type to optionally use the existing DocumentTransform class to perform the task of
dispatching the supplied document "path" rather than use an aggregated DispatchedView
instance. The updated DocumentView class no longer needs DispatchedView since the 
dispatch
include/forward is handled by the DocumentTransform that's created when the view
configuration is loaded.

Could you committers have a look at the updates please and let me know what you think?
opt-freemarker is currently dependent on them.

many thanks,
Ed Ward

PS. If you'd like to look at the opt-freemarker package, I've placed a copy at
http://www.commlock.freeserve.co.uk/opt-freemarker-20040722.zip (632 KB)


/*
 * $Id: ViewWithTransforms.java,v 1.1 2002/06/06 12:23:54 lhoriman Exp $
 * $Source: /cvsroot/mav/maverick/src/java/org/infohazard/maverick/flow/ViewWithTransforms.java,v $
 */

package org.infohazard.maverick.flow;

import java.io.IOException;
import javax.servlet.ServletException;

/**
 * ViewWithTransforms is a decorator that sets transforms when
 * rendering a view.
 */
class ViewWithTransforms implements View
{
    /** The decorated view */
    protected View decorated;

    /** The transforms associated with the decorated view */
    protected Transform[] transforms;

    /**
     * @param decorate the view to be decorated
     * @param trans the transforms used to decorate the view
     */
    public ViewWithTransforms(View decorate, Transform[] trans)
    {
        if (trans == null || trans.length == 0)
            throw new IllegalArgumentException("Don't use this decorator without transforms");

        if (decorate instanceof ViewWithTransforms)
        {
            final ViewWithTransforms other = (ViewWithTransforms)decorate;
            final Transform[] transforms = new Transform[trans.length + other.transforms.length];
            System.arraycopy(other.transforms, 0, transforms, 0, other.transforms.length);
            System.arraycopy(trans, 0, transforms, other.transforms.length, trans.length);
            this.transforms = transforms;
            this.decorated = other.decorated;
        }
        else
        {
            this.decorated = decorate;
            this.transforms = trans;
        }
    }

    /**
     * @param vctx the view context
     * @throws IOException
     * @throws ServletException
     */
    public void go(ViewContext vctx) throws IOException, ServletException
    {
        ((MaverickContext)vctx).setTransforms(this.transforms);

        this.decorated.go(vctx);
    }
}
/*
 * $Id: MasterFactory.java,v 1.3 2004/06/27 17:41:31 eelco12 Exp $
 * $Source: /cvsroot/mav/maverick/src/java/org/infohazard/maverick/flow/MasterFactory.java,v $
 */

package org.infohazard.maverick.flow;

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

import javax.servlet.ServletConfig;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.infohazard.maverick.util.XML;
import org.jdom.Element;

/**
 * Factory for creating View and Transform objects.  This calls out to specific instances
 * of ViewFactory and TransformFactory to actually create the objects.
 */
class MasterFactory
{
    /** Logger. */
    private static Log log = LogFactory.getLog(MasterFactory.class);

    /** xml attribute for type, value = 'type'. */
    protected static final String ATTR_FACTORY_TYPE_NAME = "type";
    /** xml attribute for the factory provider, value = 'provider'. */
    protected static final String ATTR_FACTORY_PROVIDER = "provider";
    /** xml attribute for the type name, value = 'type' */
    protected static final String ATTR_TYPE_NAME = "type";
    /** xml attribute for the transform type name (for a view node), value = 'transform-type' */
    protected static final String ATTR_TRANSFORM_TYPE_NAME = "transform-type";


    /** xml tag name for a transform, value = 'transform'. */
    public static final String TAG_TRANSFORM = "transform";

    /**
     * Holds mapping of typeName to ViewFactory.
     */
    protected Map viewFactories = new HashMap();

    /**
     * Holds mapping of typeName to TransformFactory.
     */
    protected Map transformFactories = new HashMap();

    /**
     * The default type of factory to use if no explicit type is set.
     */
    protected String defaultViewType;

    /**
     * The default type of factory to use if no explicit type is set.
     */
    protected String defaultTransformType;

    /**
     * Reference to servlet config of Dispatcher servlet;
     * needed to initialize View and Transform factories.
     */
    protected ServletConfig servletCfg;

    /**
     * Create the master factory with the dispatcher servlet config.
     * @param servletCfg dispatcher servlet config
     */
    public MasterFactory(ServletConfig servletCfg)
    {
        this.servletCfg = servletCfg;
    }

    // --------- VIEWS ------------------------------------------------------

    /**
     * Sets the default type to use if one is not explicitly defined in the view node.
     * @param type the default view type
     */
    public void setDefaultViewType(String type)
    {
        this.defaultViewType = type;
    }

    /**
     * @param viewNode xml element for the view
     * @throws ConfigException
     * @return a View from the specified view node.
     */
    protected View createView(Element viewNode) throws ConfigException
    {
        View v = this.createPlainView(viewNode);

        final List transformsList = viewNode.getChildren(TAG_TRANSFORM);

        // Look for a "view transform", specified by the presence of a "path" attribute
        final String transformPath = viewNode.getAttributeValue("path");
        if (transformPath != null)
        {
            String typeName = viewNode.getAttributeValue(ATTR_TRANSFORM_TYPE_NAME);
            if (typeName == null)
            {
                // No transform type was set explicitly so we look for a transform type
                // with the same name as that used for the view
                typeName = viewNode.getAttributeValue(ATTR_TYPE_NAME);
                if (typeName == null)
                    typeName = this.defaultViewType;
            }

            final TransformFactory tf = (TransformFactory)transformFactories.get(typeName);
            if (tf != null)
                v = new ViewWithTransforms(v, new Transform[] {tf.createTransform(viewNode)});
        }

        if (!transformsList.isEmpty())
        {
            final Transform[] t = this.createTransforms(transformsList);
            v = new ViewWithTransforms(v, t);
        }

        final Map params = XML.getParams(viewNode);
        if (params != null)
            v = new ViewWithParams(v, params);

        return v;
    }

    /**
     * Create a plain (undecorated) view object.
     * @param viewNode xml node of the view
     * @return View the plain (undecorated) view object
     * @throws ConfigException
     */
    protected View createPlainView(Element viewNode) throws ConfigException
    {
        String typeName = viewNode.getAttributeValue(ATTR_TYPE_NAME);
        if (typeName == null)
            typeName = this.defaultViewType;

        log.debug("Creating view of type " + typeName);

        ViewFactory fact = (ViewFactory)this.viewFactories.get(typeName);

        if (fact == null)
            throw new ConfigException("No view factory can be found for " + XML.toString(viewNode));

        return fact.createView(viewNode);
    }

    /**
     * Defines a view factory for the specified type; Can be used to
     * override a previously set factory.
     * @param typeName name of the view type
     * @param fact view factory
     */
    public void defineViewFactory(String typeName, ViewFactory fact)
    {
        log.info("View factory for \"" + typeName + "\" is " + fact.getClass().getName());

        this.viewFactories.put(typeName, fact);

        if (this.defaultViewType == null)
            this.defaultViewType = typeName;
    }

    /**
     * Define the view factories from the factory nodes.
     * @param viewFactoryNodes the factory nodes
     * @throws ConfigException
     */
    public void defineViewFactories(List viewFactoryNodes) throws ConfigException
    {
        // Define view factories specified in the config file.
        Iterator it = viewFactoryNodes.iterator();
        while (it.hasNext())
        {
            Element viewFactoryNode = (Element)it.next();

            String typeName = viewFactoryNode.getAttributeValue(ATTR_FACTORY_TYPE_NAME);
            String providerName = viewFactoryNode.getAttributeValue(ATTR_FACTORY_PROVIDER);

            if (typeName == null || providerName == null)
                throw new ConfigException("Not a valid view factory node:  " + XML.toString(viewFactoryNode));

            Class providerClass;
            ViewFactory instance;
            try
            {
                providerClass = loadClass(providerName);
                instance = (ViewFactory)providerClass.newInstance();
            }
            catch (Exception ex)
            {
                throw new ConfigException("Unable to define view factory for " + typeName, ex);
            }

            // Give the factory an opportunity to initialize itself from any subnodes
            instance.init(viewFactoryNode, this.servletCfg);

            this.defineViewFactory(typeName, instance);
        }
    }

    // --------- TRANSFORM DEFINES ------------------------------------------------------

    /**
     * Sets the default type to use if one is not explicitly defined in the transform node.
     * @param type the default transform type
     */
    public void setDefaultTransformType(String type)
    {
        this.defaultTransformType = type;
    }

    /**
     * Create transforms.
     * @param transformNodes transform nodes
     * @return array of transforms, possibly with length zero
     * @throws ConfigException
     */
    protected Transform[] createTransforms(List transformNodes) throws ConfigException
    {
        Transform[] retVal = new Transform[transformNodes.size()];

        int index = 0;
        Iterator it = transformNodes.iterator();
        while (it.hasNext())
        {
            Element transNode = (Element)it.next();

            retVal[index] = this.createTransform(transNode);

            index++;
        }

        return retVal;
    }

    /**
     * Creates a possibly decorated transform.
     * @param transformNode transform node
     * @return a transform object
     * @throws ConfigException
     */
    protected Transform createTransform(Element transformNode) throws ConfigException
    {
        Transform t = this.createPlainTransform(transformNode);

        Map params = XML.getParams(transformNode);
        if (params != null)
            t = new TransformWithParams(t, params);

        return t;
    }

    /**
     * Create a plain (undecorated) transform object.
     * @param transformNode transform node
     * @return transform object
     * @throws ConfigException
     */
    protected Transform createPlainTransform(Element transformNode) throws ConfigException
    {
        String typeName = transformNode.getAttributeValue(ATTR_TYPE_NAME);
        if (typeName == null)
            typeName = this.defaultTransformType;

        log.debug("Creating transform of type " + typeName);

        TransformFactory fact = (TransformFactory)this.transformFactories.get(typeName);

        if (fact == null)
            throw new ConfigException("No transform factory can be found for " + XML.toString(transformNode));

        return fact.createTransform(transformNode);
    }

    /**
     * Defines a transform factory for the specified type. Can be used to
     * override a previously set factory.
     * @param typeName name of type
     * @param fact the transform factory
     */
    public void defineTransformFactory(String typeName, TransformFactory fact)
    {
        log.info("Transform factory for \"" + typeName + "\" is " + fact.getClass().getName());

        this.transformFactories.put(typeName, fact);

        if (this.defaultTransformType == null)
            this.defaultTransformType = typeName;
    }

    /**
     * Processes a collection of transform factory nodes.
     * @param transFactoryNodes a list factory nodes
     * @throws ConfigException
     */
    public void defineTransformFactories(List transFactoryNodes) throws ConfigException
    {
        // Define transform factories specified in the config file.
        Iterator it = transFactoryNodes.iterator();
        while (it.hasNext())
        {
            Element transFactoryNode = (Element)it.next();

            String typeName = transFactoryNode.getAttributeValue(ATTR_FACTORY_TYPE_NAME);
            String providerName = transFactoryNode.getAttributeValue(ATTR_FACTORY_PROVIDER);

            if (typeName == null || providerName == null)
                throw new ConfigException("Not a valid transform factory node:  " + XML.toString(transFactoryNode));

            Class providerClass;
            TransformFactory instance;
            try
            {
                providerClass = loadClass(providerName);
                instance = (TransformFactory)providerClass.newInstance();
            }
            catch (Exception ex)
            {
                throw new ConfigException("Unable to define transform factory for " + typeName, ex);
            }

            // Give the factory an opportunity to initialize itself from any subnodes
            instance.init(transFactoryNode, this.servletCfg);

            this.defineTransformFactory(typeName, instance);
        }
    }

    // --------- UTILITY METHODS ------------------------------------------------------

    /**
     * Load class.
     * @param className full class name
     * @return Class the class
     * @throws ClassNotFoundException
     */
    protected Class loadClass(String className) throws ClassNotFoundException
    {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if (classLoader == null)
        {
            classLoader = DefaultControllerFactory.class.getClassLoader();
        }
        Class cls = classLoader.loadClass(className);
        return cls;
    }
}
/*
 * $Id: DocumentView.java,v 1.4 2003/10/22 11:13:49 thusted Exp $
 * $Source: /cvsroot/mav/maverick/src/java/org/infohazard/maverick/view/DocumentView.java,v $
 */

package org.infohazard.maverick.view;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.ServletException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.infohazard.maverick.flow.TransformStep;
import org.infohazard.maverick.flow.View;
import org.infohazard.maverick.flow.ViewContext;

/**
 * <p>
 * DocumentView is the base class for the default Maverick "document" view type.
 * </p>
 * <p>
 * A DocumentView is a [EMAIL PROTECTED] View} that sets up a "model" object in request
 * or session scope and forwards to another resource to render the final
 * response.
 * The "model" object exposes dynamic data so that it can be rendered as part
 * of the response.
 * </p>
 * <p>
 * This class is used by the Maverick [EMAIL PROTECTED]  DocumentViewFactory}.
 * The DocumentViewFactory defines the abstract SetAttribute method
 * for each instance according to the parameters passed through the View XML
 * element.
 * </p>
 * <p>
 * By default, a DocumentView will be associated with a
 * [EMAIL PROTECTED] DispatchedViewFactory DispatchedView} that uses the
 * [EMAIL PROTECTED] javax.servlet.RequestDispatcher RequestDispatcher} to forward control
 * to another resource (e.g. service or servlet that renders a server page).
 * </p>
 */
public abstract class DocumentView implements View
{
    private static Log log = LogFactory.getLog(DocumentView.class);

    /**
     * <p>
     * The name of the bean in the appropriate scope attributes.
     * </p>
     */
    protected String beanName;

    /**
     * <p>
     * Convenience constructor to pass the "model" object name and the
     * aggregated [EMAIL PROTECTED] View}.
     * </p>
     * @param beanName The name of the bean in the appropriate scope attributes.
     */
    public DocumentView(String beanName)
    {
        this.beanName = beanName;
    }

    /**
     * <p>
     * Entry method that initiates the View rendering process.
     * Here, the DocumentView sets the the "model" object to the appropriate
     * scope and invokes the [EMAIL PROTECTED] #forward} View to complete the response.
     * </p>
     * @param vctx is placed in an attribute collection.
     * @throws IOException
     * @throws ServletException
     * @see View#go
     */
    public void go(ViewContext vctx)  throws IOException, ServletException
    {
        // Should we put the null in the collection?
        if (vctx.getModel() != null)
            this.setAttribute(vctx);

        // Put any params in the request attributes
        if (vctx.getViewParams() != null)
        {
            if (log.isDebugEnabled())
                log.debug("Setting " + vctx.getViewParams().size() + " params");

            Iterator entryIt = vctx.getViewParams().entrySet().iterator();
            while (entryIt.hasNext())
            {
                Map.Entry entry = (Map.Entry)entryIt.next();
                vctx.getRequest().setAttribute((String)entry.getKey(), entry.getValue());
            }
        }
        vctx.getNextStep().go("");
    }

    /**
     * <p>
     * Template method that can be used to place the "model" object in
     * whatever scope is appropriate for this View element instance.
     * </p>
     * <p>
     * The [EMAIL PROTECTED] DocumentViewFactory} provides an implementation of this
     * method appropriate to the View parameters passed from the Maverick
     * configuration document.
     * </p>
     * @param vctx provides access to request and session scope collections,
     *   if needed.
     */
    protected abstract void setAttribute(ViewContext vctx);
}
/*
 * $Id: DocumentViewFactory.java,v 1.7 2003/10/22 11:13:49 thusted Exp $
 * $Source: /cvsroot/mav/maverick/src/java/org/infohazard/maverick/view/DocumentViewFactory.java,v $
 */

package org.infohazard.maverick.view;

import javax.servlet.ServletConfig;

import org.infohazard.maverick.flow.ConfigException;
import org.infohazard.maverick.flow.View;
import org.infohazard.maverick.flow.ViewContext;
import org.infohazard.maverick.flow.ViewFactory;
import org.infohazard.maverick.util.XML;
import org.jdom.Element;

/**
 * <p>
 * Factory for building JSP, Velocity, and other resource-based views which
 * place the model bean in one of the servlet attribute collections and
 * use the RequestDispatcher to forward to the actual content.
 * This is also used for static documents.
 * </p>
 * <p>
 * This factory has certain options which can be defined when
 * declaring the factory.  The defaults are shown here:
 * </p>
 * <pre>
 * &lt;view-factory type="document" provider="org.infohazard.maverick.view.DocumentViewFactory"&gt;
 *   &lt;default-bean-name value="model"/&gt;
 * &lt;/view-factory&gt;
 * </pre>
 * <ul>
 *   <li>
 *     <b>default-bean-name</b> - If no "bean" attribute is specified
 *     for individual views, this is the key which will be
 *     used for placing the model in an attribute collection.
 *   </li>
 * </ul>
 * <p>
 * The options for an individual view are like this:
 * </p>
 * <pre>
 * &lt;view type="document" path="blah.jsp" scope="request" bean="model"/&gt;
 * </pre>
 * <p>
 * Scope can be one of <code>request</code>, <code>session</code>,
 * <code>application</code>.  The default is <code>request</code>.
 * </p>
 * <p>
 * The default model name is specified on the factory, or "model"
 * if nothing is defined.
 * </p>
 * <p>
 * Document views can have transforms by specifying a
 * "<code>transform</code>" child element.
 * </p>
 */
public class DocumentViewFactory implements ViewFactory
{
    /**
     * The default attribute name for the "model" object ["model"].
     */
    protected static final String DEFAULT_DEFAULT_BEAN_NAME = "model";

    /**
     * The XML attribute name to set a default "model" object name
     * ["default-bean-name"].
     *
     */
    protected static final String ATTR_DEFAULT_BEAN_NAME = "default-bean-name";

    /**
     * <p>
     * The XML attribute name to specify the name for the "model" object
     * ["bean"].
     * <p>
     * <p>
     * The default attribute setting would be <code>bean="model"</code>.
     * </p>
     */
    protected static final String ATTR_BEAN_NAME = "bean";

    /**
     * <p>
     * The XML attribute name to specify the scope in which to store the
     * "model" object ["scope"].
     * </p>
     * <p>
     * The default attribute setting would be <code>scope="request"</code>.
     * </p>
     */
    protected static final String ATTR_SCOPE = "scope";

    /**
     * <p>
     * The XML attribute value to specify "global" or "application" scope.
     * ["application"].
     * An "model" object set to this scope is available to the entire
     * application.
     * </p>
     */
    protected static final String SCOPE_APP = "application";

    /**
     * <p>
     * The XML attribute value to specify "client" or "session" scope.
     * ["session"].
     * An "model" object set to this scope is available to every request made
     * by the same client during this application session.
     * </p>
     */
    protected static final String SCOPE_SESSION = "session";

    /**
     * <p>
     * The XML attribute value to specify "thread" or "request" scope.
     * ["request"].
     * An "model" object set to this scope is available to only within the
     * thread of the current request.
     * </p>
     */
    protected static final String SCOPE_REQUEST = "request";

    /**
     * <p>
     * Property to hold the default name of the "model" object for this
     * factory instance ["model"].
     * </p>
     */
    protected String defaultBeanName = DEFAULT_DEFAULT_BEAN_NAME;

    /**
     * <p>
     * Initialize this factory instance by initializing the
     * [EMAIL PROTECTED] #dispatchedViewFact} property and the [EMAIL PROTECTED] #defaultBeanName}
     * property.
     * </p>
     * @param factoryNode Element
     * @param servletCfg ServletConfig
     * @throws ConfigException
     */
    public void init(Element factoryNode, ServletConfig servletCfg) throws
      ConfigException
    {
        if (factoryNode != null)
        {
            String value = XML.getValue(factoryNode, ATTR_DEFAULT_BEAN_NAME);
            if (value != null && value.length() > 0)
                this.defaultBeanName = value;
        }
    }

    /**
     * @param viewNode Element
     * @return View
     * @throws ConfigException
     * @see ViewFactory#createView(Element)
     */
    public View createView(Element viewNode) throws ConfigException
    {
        String beanName = XML.getValue(viewNode, ATTR_BEAN_NAME);
        if (beanName == null)
            beanName = this.defaultBeanName;

        String scope = XML.getValue(viewNode, ATTR_SCOPE);

        // Provide different implementations of setAttribute depending on scope
        if (SCOPE_APP.equals(scope))
        {
            return new DocumentView(beanName) {
                protected void setAttribute(ViewContext vctx) {
                    vctx.getServletContext().setAttribute(this.beanName, vctx.getModel());
                }
            };
        }
        else if (SCOPE_SESSION.equals(scope))
        {
            return new DocumentView(beanName) {
                protected void setAttribute(ViewContext vctx) {
                    vctx.getRequest().getSession().setAttribute(this.beanName, vctx.getModel());
                }
            };
        }
        else if (SCOPE_REQUEST.equals(scope) || scope == null)
        {
            return new DocumentView(beanName) {
                protected void setAttribute(ViewContext vctx) {
                    vctx.getRequest().setAttribute(this.beanName, vctx.getModel());
                }
            };
        }
        else
        {
            throw new ConfigException("Illegal scope specified:  " + XML.toString(viewNode));
        }
    }
}

Reply via email to