DUE TO SPAM, SIGN-UP IS DISABLED. Goto Selfserve wiki signup and request an account.
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();
}
}
} } }