July 23, 2003
Here is the much anticipated TilesTool, to use with Velocity Tools and Struts 1.1
I personally use the nested tiles and tile-controllers features, but I haven't tried other features you might be interested in.
Any comments are welcome on the Velocity Developer's List <velocity-dev@jakarta.apache.org>.
Marinó A. Jónsson
{ { { package org.apache.velocity.tools.struts; import java.util.Stack; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.servlet.ServletContext; import java.io.StringWriter; import org.apache.velocity.app.Velocity; import org.apache.velocity.Template; import org.apache.struts.tiles.ComponentContext; import org.apache.struts.tiles.ComponentDefinition; import org.apache.struts.tiles.AttributeDefinition; import org.apache.struts.tiles.DirectStringAttribute; import org.apache.struts.tiles.DefinitionAttribute; import org.apache.struts.tiles.DefinitionNameAttribute; import org.apache.struts.tiles.PathAttribute; import org.apache.struts.tiles.TilesUtil; import org.apache.struts.tiles.DefinitionsFactoryException; import org.apache.struts.tiles.Controller; import org.apache.velocity.tools.view.context.ViewContext; import org.apache.velocity.tools.view.tools.ViewTool; /** {{{ * <p>Title: TilesTool</p> * <p>Description: A tool to use struts-tiles with Velocity</p> * <p>Usage: * * Just call $tiles.name_of_tile_definition from the template to insert * the tile. * * $tiles.getString("name_of_tile_attribute") fetches a named attribute-value * from the current tiles-context. * * @author <a href="mailto:marinoj@centrum.is">Marino A. Jonsson</a> * @version $Revision: 1.1 $ $Date: 2003/07/24 04:59:21 $ */
public class TilesTool implements ViewTool
{
protected ViewContext context; protected ServletContext application; protected HttpServletRequest request; protected HttpServletResponse response; /** * A stack to hold ComponentContexts while nested tile-definitions * are rendered. */ protected Stack contextStack; /******************************* Constructors ****************************/ /** * Default constructor. Tool must be initialized before use. */ public TilesTool() {} /** * Initializes this tool. * * @param obj the current ViewContext * @throws IllegalArgumentException if the param is not a ViewContext */ public void init(Object obj) { if (!(obj instanceof ViewContext)) { throw new IllegalArgumentException("Tool can only be initialized with a ViewContext"); } this.context = (ViewContext)obj; this.request = context.getRequest(); this.response = context.getResponse(); this.application = context.getServletContext(); } /***************************** View Helpers ******************************/ /** * Fetches a named attribute value from the current tiles-context. * * <p>This is functionally equivalent to * <code><tiles:getAsString name="title" /></code>.</p> * * @param name the name of the tiles-attribute to fetch * @return the attribute value as String */ public String getString(String name) { ComponentContext context = ComponentContext.getContext(request); Object attrValue = context.getAttribute(name); if (attrValue == null) { return null; } return attrValue.toString(); } /** * <p>A generic tiles insert function</p> * * <p>This is functionally equivalent to * <code><tiles:insert attribute="menu" /></code>.</p> * * @param attr - can be any of the following: * AttributeDefinition, * tile-definition name, * tile-attribute name, * regular uri. * (checked in that order) * @return the rendered template or value as a String * @throws Exception on failure */ public String get(Object attr) throws Exception { ComponentContext currentContext = ComponentContext.getContext(request); Object attrValue = currentContext.getAttribute(attr.toString()); if (attrValue != null) { return processObjectValue(attrValue); } return processAsDefinitionOrURL(attr.toString()); } /************************** Protected Methods ****************************/ /** * Process an object retrieved as a bean or attribute. * * @param value - Object can be a typed attribute, a String, or anything * else. If typed attribute, use associated type. Otherwise, apply * toString() on object, and use returned string as a name. * @throws Exception - Throws by underlying nested call to * processDefinitionName() * @return the fully processed value as String */ protected String processObjectValue(Object value) throws Exception { /* First, check if value is one of the Typed Attribute */ if (value instanceof AttributeDefinition) { /* We have a type => return appropriate IncludeType */ return processTypedAttribute((AttributeDefinition)value); } else if (value instanceof ComponentDefinition) { return processDefinition((ComponentDefinition)value); } /* Value must denote a valid String */ return processAsDefinitionOrURL(value.toString()); } /** * Process typed attribute according to its type. * * @param value Typed attribute to process. * @return the fully processed attribute value as String. * @throws Exception - Throws by underlying nested call to processDefinitionName() */ protected String processTypedAttribute(AttributeDefinition value) throws Exception { if (value instanceof DirectStringAttribute) { return (String)value.getValue(); } else if (value instanceof DefinitionAttribute) { return processDefinition((ComponentDefinition)value.getValue()); } else if (value instanceof DefinitionNameAttribute) { return processAsDefinitionOrURL((String)value.getValue()); } /* else if( value instanceof PathAttribute ) */ return doInsert((String)value.getValue(), null, null); } /** * Try to process name as a definition, or as an URL if not found. * * @param name Name to process. * @return the fully processed definition or URL * @throws Exception */ protected String processAsDefinitionOrURL(String name) throws Exception { try { ComponentDefinition definition = TilesUtil.getDefinition(name, request, application); if (definition != null) { return processDefinition(definition); } } catch (DefinitionsFactoryException ex) { /* silently failed, because we can choose to not define a factory. */ } /* no definition found, try as url */ return processUrl(name); } /** * End of Process for definition. * * @param definition Definition to process. * @return the fully processed definition. * @throws Exception from InstantiationException Can't create requested controller */ protected String processDefinition(ComponentDefinition definition) throws Exception { Controller controller = null; try { controller = definition.getOrCreateController(); String role = definition.getRole(); String page = definition.getTemplate(); return doInsert(definition.getAttributes(), page, role, controller); } catch (InstantiationException ex) { throw new Exception(ex.getMessage()); } } /** * Processes an url * * @param url the URI to process. * @return the rendered template as String. * @throws Exception */ protected String processUrl(String url) throws Exception { return doInsert(url, null, null); } /** * Use this if there is no nested tile. * * @param page the page to process. * @param role possible user-role * @param controller possible tiles-controller * @return the rendered template as String. * @throws Exception */ protected String doInsert(String page, String role, Controller controller) throws Exception { if (role != null && !request.isUserInRole(role)) { return null; } ComponentContext subCompContext = new ComponentContext(); return doInsert(subCompContext, page, role, controller); } /** * Use this if there is a nested tile. * * @param attributes attributes for the sub-context * @param page the page to process. * @param role possible user-role * @param controller possible tiles-controller * @return the rendered template as String. * @throws Exception */ protected String doInsert(Map attributes, String page, String role, Controller controller) throws Exception { if (role != null && !request.isUserInRole(role)) { return null; } ComponentContext subCompContext = new ComponentContext(attributes); return doInsert(subCompContext, page, role, controller); } /** * An extension of the other two doInsert functions * * @param subCompContext the sub-context to set in scope when the * template is rendered. * @param page the page to process. * @param role possible user-role * @param controller possible tiles-controller * @return the rendered template as String. * @throws Exception */ protected String doInsert(ComponentContext subCompContext, String page, String role, Controller controller) throws Exception { pushTilesContext(); try { ComponentContext.setContext(subCompContext, request); /* Call controller if any */ if (controller != null) { controller.perform(subCompContext, request, response, application); } return parse(page); } finally { popTilesContext(); } } /** * <p>pushes the current tiles context onto the context-stack. * preserving the context is necessary so that a sub-context can be * put into request scope and lower level tiles can be rendered</p> */ protected void pushTilesContext() { if (contextStack == null) { contextStack = new Stack(); } contextStack.push(ComponentContext.getContext(request)); } /** * <p>pops the tiles sub-context off the context-stack after the lower level * tiles have been rendered</p> */ protected void popTilesContext() { ComponentContext.setContext((ComponentContext)contextStack.pop(), request); } /** * <p>Renders a template</p> * * @param templateName - name of template to be rendered * @throws Exception if it fails * @return the rendered template as a String */ protected String parse(String templateName) throws Exception { StringWriter sw = new StringWriter(); Template template = Velocity.getTemplate(templateName); template.merge(context.getVelocityContext(), sw); return sw.toString(); }
}
} } }