/*
* Copyright 2003-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.velocity.tools.struts;
import java.util.Stack;
import java.util.Map;
import java.util.Iterator;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.dotcms.repackage.org.apache.struts.tiles.ComponentContext;
import com.dotcms.repackage.org.apache.struts.tiles.ComponentDefinition;
import com.dotcms.repackage.org.apache.struts.tiles.AttributeDefinition;
import com.dotcms.repackage.org.apache.struts.tiles.DirectStringAttribute;
import com.dotcms.repackage.org.apache.struts.tiles.DefinitionAttribute;
import com.dotcms.repackage.org.apache.struts.tiles.DefinitionNameAttribute;
import com.dotcms.repackage.org.apache.struts.tiles.PathAttribute;
import com.dotcms.repackage.org.apache.struts.tiles.TilesUtil;
import com.dotcms.repackage.org.apache.struts.tiles.DefinitionsFactoryException;
import com.dotcms.repackage.org.apache.struts.tiles.Controller;
import org.apache.velocity.context.Context;
import org.apache.velocity.tools.view.ImportSupport;
import org.apache.velocity.tools.view.context.ViewContext;
import org.apache.velocity.tools.view.tools.ViewTool;
/**
* View tool to use struts-tiles with Velocity.
* <p><pre>
* Template example(s):
* <!-- insert a tile -->
* $tiles.myTileDefinition
*
* <!-- get named attribute value from the current tiles-context -->
* $tiles.getAttribute("myTileAttribute")
*
* <!-- import all attributes of the current tiles-context into the velocity-context. -->
* $tiles.importAttributes()
*
* Toolbox configuration:
* <tool>
* <key>tiles</key>
* <scope>request</scope>
* <class>org.apache.velocity.tools.struts.TilesTool</class>
* </tool>
* </pre></p>
*
* <p>This tool should only be used in the request scope.</p>
*
* @author <a href="mailto:marinoj@centrum.is">Marino A. Jonsson</a>
* @since VelocityTools 1.1
* @version $Revision: 329695 $ $Date: 2005-10-30 17:34:08 -0800 (Sun, 30 Oct 2005) $
*/
public class TilesTool extends ImportSupport implements ViewTool
{
protected static final Log LOG = LogFactory.getLog(TilesTool.class);
static final String PAGE_SCOPE = "page";
static final String REQUEST_SCOPE = "request";
static final String SESSION_SCOPE = "session";
static final String APPLICATION_SCOPE = "application";
protected Context velocityContext;
/**
* 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");
}
ViewContext viewContext = (ViewContext)obj;
this.velocityContext = viewContext.getVelocityContext();
this.request = viewContext.getRequest();
this.response = viewContext.getResponse();
this.application = viewContext.getServletContext();
}
/***************************** View Helpers ******************************/
/**
* A generic tiles insert function.
*
* <p>This is functionally equivalent to
* <code><tiles:insert attribute="foo" /></code>.</p>
*
* @param obj 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 obj)
{
try
{
Object value = getCurrentContext().getAttribute(obj.toString());
if (value != null)
{
return processObjectValue(value);
}
return processAsDefinitionOrURL(obj.toString());
}
catch (Exception e)
{
LOG.error("Exeption while rendering Tile " + obj + ": ", e);
return null;
}
}
/**
* Fetches a named attribute-value from the current tiles-context.
*
* <p>This is functionally equivalent to
* <code><tiles:getAsString name="foo" /></code>.</p>
*
* @param name the name of the tiles-attribute to fetch
* @return attribute value for the named attribute
*/
public Object getAttribute(String name)
{
Object value = getCurrentContext().getAttribute(name);
if (value == null)
{
LOG.warn("Tile attribute '" + name + "' wasn't found in context.");
}
return value;
}
/**
* Imports the named attribute-value from the current tiles-context into the
* current Velocity context.
*
* <p>This is functionally equivalent to
* <code><tiles:importAttribute name="foo" /></code>
*
* @param name the name of the tiles-attribute to import
*/
public void importAttribute(String name)
{
this.importAttribute(name, PAGE_SCOPE);
}
/**
* Imports the named attribute-value from the current tiles-context into the
* named context ("page", "request", "session", or "application").
*
* <p>This is functionally equivalent to
* <code><tiles:importAttribute name="foo" scope="scopeValue" /></code>
*
* @param name the name of the tiles-attribute to import
* @param scope the named context scope to put the attribute into.
*/
public void importAttribute(String name, String scope)
{
Object value = getCurrentContext().getAttribute(name);
if (value == null)
{
LOG.warn("Tile attribute '" + name + "' wasn't found in context.");
}
if (scope.equals(PAGE_SCOPE))
{
velocityContext.put(name, value);
}
else if (scope.equals(REQUEST_SCOPE))
{
request.setAttribute(name, value);
}
else if (scope.equals(SESSION_SCOPE))
{
request.getSession().setAttribute(name, value);
}
else if (scope.equals(APPLICATION_SCOPE))
{
application.setAttribute(name, value);
}
}
/**
* Imports all attributes in the current tiles-context into the
* current velocity-context.
*
* <p>This is functionally equivalent to
* <code><tiles:importAttribute /></code>.</p>
*/
public void importAttributes()
{
this.importAttributes(PAGE_SCOPE);
}
/**
* Imports all attributes in the current tiles-context into the named
* context ("page", "request", "session", or "application").
*
* <p>This is functionally equivalent to
* <code><tiles:importAttribute scope="scopeValue" /></code>.</p>
*
* @param scope the named context scope to put the attributes into.
*/
public void importAttributes(String scope)
{
ComponentContext context = getCurrentContext();
Iterator names = context.getAttributeNames();
if (scope.equals(PAGE_SCOPE))
{
while (names.hasNext())
{
String name = (String)names.next();
velocityContext.put(name, context.getAttribute(name));
}
}
else if (scope.equals(REQUEST_SCOPE))
{
while (names.hasNext())
{
String name = (String)names.next();
request.setAttribute(name, context.getAttribute(name));
}
}
else if (scope.equals(SESSION_SCOPE))
{
HttpSession session = request.getSession();
while (names.hasNext())
{
String name = (String)names.next();
session.setAttribute(name, context.getAttribute(name));
}
}
else if (scope.equals(APPLICATION_SCOPE))
{
while (names.hasNext())
{
String name = (String)names.next();
application.setAttribute(name, context.getAttribute(name));
}
}
}
/************************** 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, this.request, this.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 && !this.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 && !this.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, this.request);
/* Call controller if any */
if (controller != null)
{
controller.execute(subCompContext,
this.request,
this.response,
this.application);
}
/* pass things off to ImportSupport */
return this.acquireString(page);
}
finally
{
popTilesContext();
}
}
/**
* Retrieve the current tiles component context.
* This is pretty much just a convenience method.
*/
protected ComponentContext getCurrentContext()
{
return ComponentContext.getContext(this.request);
}
/**
* <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 (this.contextStack == null)
{
this.contextStack = new Stack();
}
contextStack.push(getCurrentContext());
}
/**
* Pops the tiles sub-context off the context-stack after the lower level
* tiles have been rendered.
*/
protected void popTilesContext()
{
ComponentContext context = (ComponentContext)this.contextStack.pop();
ComponentContext.setContext(context, this.request);
}
}