/* * $Id: InsertTag.java 471754 2006-11-06 14:55:09Z husted $ * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.struts.tiles.taglib; import java.io.IOException; import java.io.PrintWriter; import java.util.Map; import java.util.StringTokenizer; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.jsp.JspException; import javax.servlet.jsp.PageContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.struts.Globals; import org.apache.struts.tiles.taglib.util.TagUtils; import org.apache.struts.tiles.AttributeDefinition; import org.apache.struts.tiles.ComponentContext; import org.apache.struts.tiles.ComponentDefinition; import org.apache.struts.tiles.Controller; import org.apache.struts.tiles.DefinitionAttribute; import org.apache.struts.tiles.DefinitionNameAttribute; import org.apache.struts.tiles.DefinitionsFactoryException; import org.apache.struts.tiles.DirectStringAttribute; import org.apache.struts.tiles.FactoryNotFoundException; import org.apache.struts.tiles.NoSuchDefinitionException; import org.apache.struts.tiles.TilesUtil; /** * This is the tag handler for <tiles:insert>, which includes * a template. The tag's body content consists of <tiles:put> * tags, which are accessed by <tiles:get> in the template. * * @version $Rev: 471754 $ $Date: 2006-11-06 15:55:09 +0100 (Mon, 06 Nov 2006) $ */ public class InsertTag extends DefinitionTagSupport implements PutTagParent, ComponentConstants, PutListTagParent { /** * The role delimiter. * @deprecated This will be removed in a release after Struts 1.2. */ public static final String ROLE_DELIMITER = ","; /** * Commons Logging instance. */ protected static Log log = LogFactory.getLog(InsertTag.class); /* JSP Tag attributes */ /** * Flush attribute value. */ protected boolean flush = true; /** * Name to insert. */ protected String name = null; /** * Name of attribute from which to read page name to include. */ protected String attribute = null; /** * Name of bean used as entity to include. */ protected String beanName = null; /** * Name of bean property, if any. */ protected String beanProperty = null; /** * Scope of bean, if any. */ protected String beanScope = null; /** * Are errors ignored. This is the property for attribute 'ignore'. * Default value is false, which throw an exception. * Only 'attribute not found' errors are ignored. */ protected boolean isErrorIgnored = false; /** * Name of component instance to include. */ protected String definitionName = null; /* Internal properties */ /** * Does the end tag need to be processed. * Default value is true. Boolean set in case of ignored errors. */ protected boolean processEndTag = true; /** * Current component context. */ protected ComponentContext cachedCurrentContext; /** * Final handler of tag methods. */ protected TagHandler tagHandler = null; /** * Trick to allows inner classes to access pageContext. */ protected PageContext pageContext = null; /** * Reset member values for reuse. This method calls super.release(), * which invokes TagSupport.release(), which typically does nothing. */ public void release() { super.release(); attribute = null; beanName = null; beanProperty = null; beanScope = null; definitionName = null; flush = true; name = null; page = null; role = null; isErrorIgnored = false; releaseInternal(); } /** * Reset internal member values for reuse. */ protected void releaseInternal() { cachedCurrentContext = null; processEndTag = true; // pageContext = null; // orion doesn't set it between two tags tagHandler = null; } /** * Set the current page context. * Called by the page implementation prior to doStartTag(). * <p> * Needed to allow inner classes to access pageContext. */ public void setPageContext(PageContext pc) { this.pageContext = pc; super.setPageContext(pc); } /** * Get the pageContext property. */ public PageContext getPageContext() { return pageContext; } /** * Set name. */ public void setName(String value) { this.name = value; } /** * Get name. */ public String getName() { return name; } /** * Set component. */ public void setComponent(String name) { this.page = name; } /** * Set definition. */ public void setDefinition(String name) { this.definitionName = name; } /** * Get definition name. */ public String getDefinitionName() { return definitionName; } /** * Set attribute. */ public void setAttribute(String value) { this.attribute = value; } /** * Set bean name. */ public void setBeanName(String value) { this.beanName = value; } /** * Get bean name. */ public String getBeanName() { return beanName; } /** * Set bean property. */ public void setBeanProperty(String value) { this.beanProperty = value; } /** * Get bean property. */ public String getBeanProperty() { return beanProperty; } /** * Set bean scope. */ public void setBeanScope(String value) { this.beanScope = value; } /** * Get bean scope. */ public String getBeanScope() { return beanScope; } /** * Set flush. */ public void setFlush(boolean flush) { this.flush = flush; } /** * Get flush. */ public boolean getFlush() { return flush; } /** * Set flush. * Method added for compatibility with JSP1.1 */ public void setFlush(String flush) { this.flush = (Boolean.valueOf(flush).booleanValue()); } /** * Set ignore. */ public void setIgnore(boolean ignore) { this.isErrorIgnored = ignore; } /** * Get ignore. */ public boolean getIgnore() { return isErrorIgnored; } ///////////////////////////////////////////////////////////////////////// /** * Add a body attribute. * Erase any attribute with same name previously set. */ public void putAttribute(String name, Object value) { tagHandler.putAttribute(name, value); } /** * Process nested ≶put> tag. * Method calls by nested ≶put> tags. * Nested list is added to current list. * If role is defined, it is checked immediately. */ public void processNestedTag(PutTag nestedTag) throws JspException { // Check role HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); String role = nestedTag.getRole(); if (role != null && !request.isUserInRole(role)) { // not allowed : skip attribute return; } putAttribute(nestedTag.getName(), nestedTag.getRealValue()); } /** * Process nested ≶putList> tag. * Method calls by nested ≶putList> tags. * Nested list is added to sub-component attributes * If role is defined, it is checked immediately. */ public void processNestedTag(PutListTag nestedTag) throws JspException { // Check role HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); String role = nestedTag.getRole(); if (role != null && !request.isUserInRole(role)) { // not allowed : skip attribute return; } // Check if a name is defined if (nestedTag.getName() == null) { throw new JspException("Error - PutList : attribute name is not defined. It is mandatory as the list is added as attribute of 'insert'."); } // now add attribute to enclosing parent (i.e. : this object). putAttribute(nestedTag.getName(), nestedTag.getList()); } /** * Method calls by nested ≶putList> tags. * A new list is added to current insert object. */ public void putAttribute(PutListTag nestedTag) throws JspException { // Check role HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); String role = nestedTag.getRole(); if (role != null && !request.isUserInRole(role)) { // not allowed : skip attribute return; } putAttribute(nestedTag.getName(), nestedTag.getList()); } /** * Get current component context. */ private ComponentContext getCurrentContext() { if (cachedCurrentContext == null) { cachedCurrentContext = (ComponentContext) pageContext.getAttribute( ComponentConstants.COMPONENT_CONTEXT, PageContext.REQUEST_SCOPE); } return cachedCurrentContext; } /** * Get instantiated Controller. * Return controller denoted by controllerType, or <code>null</code> if controllerType * is null. * @throws JspException If controller can't be created. */ private Controller getController() throws JspException { if (controllerType == null) { return null; } try { return ComponentDefinition.createController( controllerName, controllerType); } catch (InstantiationException ex) { throw new JspException(ex); } } /** * Process the start tag by checking tag's attributes and creating appropriate handler. * Possible handlers : * <ul> * <li> URL * <li> definition * <li> direct String * </ul> * Handlers also contain sub-component context. */ public int doStartTag() throws JspException { // Additional fix for Bug 20034 (2005-04-28) cachedCurrentContext = null; // Check role immediatly to avoid useless stuff. // In case of insertion of a "definition", definition's role still checked later. // This lead to a double check of "role" ;-( HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); if (role != null && !request.isUserInRole(role)) { processEndTag = false; return SKIP_BODY; } try { tagHandler = createTagHandler(); } catch (JspException e) { if (isErrorIgnored) { processEndTag = false; return SKIP_BODY; } else { throw e; } } return tagHandler.doStartTag(); } /** * Process the end tag by including the template. * Simply call the handler doEndTag */ public int doEndTag() throws JspException { if (!processEndTag) { releaseInternal(); return EVAL_PAGE; } int res = tagHandler.doEndTag(); // Reset properties used by object, in order to be able to reuse object. releaseInternal(); return res; } /** * Process tag attribute and create corresponding tag handler. */ public TagHandler createTagHandler() throws JspException { // Check each tag attribute. // page Url attribute must be the last checked because it can appears concurrently // with others attributes. if (definitionName != null) { return processDefinitionName(definitionName); } else if (attribute != null) { return processAttribute(attribute); } else if (beanName != null) { return processBean(beanName, beanProperty, beanScope); } else if (name != null) { return processName(name); } else if (page != null) { return processUrl(page); } else { throw new JspException("Error - Tag Insert : At least one of the following attribute must be defined : template|page|attribute|definition|name|beanName. Check tag syntax"); } } /** * Process an object retrieved as a bean or attribute. * 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 JspException - Throws by underlying nested call to * processDefinitionName() */ public TagHandler processObjectValue(Object value) throws JspException { // 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 name. * Search in following order : * <ul> * <li>Component context - if found, process it as value.</li> * <li>definitions factory</li> * <li>URL</li> * <li></li> * </ul> * * @return appropriate tag handler. * @throws JspException - Throws by underlying nested call to * processDefinitionName() */ public TagHandler processName(String name) throws JspException { Object attrValue = getCurrentContext().getAttribute(name); if (attrValue != null) { return processObjectValue(attrValue); } return processAsDefinitionOrURL(name); } /** * Process the url. * @throws JspException If failed to create controller */ public TagHandler processUrl(String url) throws JspException { return new InsertHandler(url, role, getController()); } /** * Process tag attribute "definition". * First, search definition in the factory, then create handler from this definition. * @param name Name of the definition. * @return Appropriate TagHandler. * @throws JspException- NoSuchDefinitionException No Definition found for name. * @throws JspException- FactoryNotFoundException Can't find Definitions factory. * @throws JspException- DefinedComponentFactoryException General error in factory. * @throws JspException InstantiationException Can't create requested controller */ protected TagHandler processDefinitionName(String name) throws JspException { try { ComponentDefinition definition = TilesUtil.getDefinition( name, (HttpServletRequest) pageContext.getRequest(), pageContext.getServletContext()); if (definition == null) { // is it possible ? throw new NoSuchDefinitionException(); } return processDefinition(definition); } catch (NoSuchDefinitionException ex) { throw new JspException( "Error - Tag Insert : Can't get definition '" + definitionName + "'. Check if this name exist in definitions factory.", ex); } catch (FactoryNotFoundException ex) { throw new JspException(ex.getMessage()); } catch (DefinitionsFactoryException ex) { if (log.isDebugEnabled()) { ex.printStackTrace(); } // Save exception to be able to show it later pageContext.setAttribute( Globals.EXCEPTION_KEY, ex, PageContext.REQUEST_SCOPE); throw new JspException(ex); } } /** * End of Process tag attribute "definition". * Overload definition with tag attributes "template" and "role". * Then, create appropriate tag handler. * @param definition Definition to process. * @return Appropriate TagHandler. * @throws JspException InstantiationException Can't create requested controller */ protected TagHandler processDefinition(ComponentDefinition definition) throws JspException { // Declare local variable in order to not change Tag attribute values. String role = this.role; String page = this.page; Controller controller = null; try { controller = definition.getOrCreateController(); // Overload definition with tag's template and role. if (role == null) { role = definition.getRole(); } if (page == null) { page = definition.getTemplate(); } if (controllerName != null) { controller = ComponentDefinition.createController( controllerName, controllerType); } // Can check if page is set return new InsertHandler( definition.getAttributes(), page, role, controller); } catch (InstantiationException ex) { throw new JspException(ex); } } /** * Process a bean. * Get bean value, eventually using property and scope. Found value is process by processObjectValue(). * @param beanName Name of the bean * @param beanProperty Property in the bean, or null. * @param beanScope bean scope, or null. * @return Appropriate TagHandler. * @throws JspException - NoSuchDefinitionException No value associated to bean. * @throws JspException an error occur while reading bean, or no definition found. * @throws JspException - Throws by underlying nested call to processDefinitionName() */ protected TagHandler processBean( String beanName, String beanProperty, String beanScope) throws JspException { Object beanValue = TagUtils.getRealValueFromBean( beanName, beanProperty, beanScope, pageContext); if (beanValue == null) { throw new JspException( "Error - Tag Insert : No value defined for bean '" + beanName + "' with property '" + beanProperty + "' in scope '" + beanScope + "'."); } return processObjectValue(beanValue); } /** * Process tag attribute "attribute". * Get value from component attribute. * Found value is process by processObjectValue(). * @param name Name of the attribute. * @return Appropriate TagHandler. * @throws JspException - NoSuchDefinitionException No Definition found for name. * @throws JspException - Throws by underlying nested call to processDefinitionName() */ public TagHandler processAttribute(String name) throws JspException { Object attrValue = getCurrentContext().getAttribute(name); if (attrValue == null) { throw new JspException( "Error - Tag Insert : No value found for attribute '" + name + "'."); } return processObjectValue(attrValue); } /** * Try to process name as a definition, or as an URL if not found. * @param name Name to process. * @return appropriate TagHandler * @throws JspException InstantiationException Can't create requested controller */ public TagHandler processAsDefinitionOrURL(String name) throws JspException { try { ComponentDefinition definition = TilesUtil.getDefinition( name, pageContext.getRequest(), pageContext.getServletContext()); 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); } /** * Process typed attribute according to its type. * @param value Typed attribute to process. * @return appropriate TagHandler. * @throws JspException - Throws by underlying nested call to processDefinitionName() */ public TagHandler processTypedAttribute(AttributeDefinition value) throws JspException { if (value instanceof DirectStringAttribute) { return new DirectStringHandler((String) value.getValue()); } else if (value instanceof DefinitionAttribute) { return processDefinition((ComponentDefinition) value.getValue()); } else if (value instanceof DefinitionNameAttribute) { return processDefinitionName((String) value.getValue()); } return new InsertHandler( (String) value.getValue(), role, getController()); } /** * Do an include of specified page. * This method is used internally to do all includes from this class. It delegates * the include call to the TilesUtil.doInclude(). * @param page The page that will be included * @param flush If the writer should be flushed before the include * @throws ServletException - Thrown by call to pageContext.include() * @throws IOException - Thrown by call to pageContext.include() */ protected void doInclude(String page, boolean flush) throws ServletException, IOException { TilesUtil.doInclude(page, pageContext, flush); } ///////////////////////////////////////////////////////////////////////////// /** * Inner Interface. * Sub handler for tag. */ protected interface TagHandler { /** * Create ComponentContext for type depicted by implementation class. */ public int doStartTag() throws JspException; /** * Do include for type depicted by implementation class. */ public int doEndTag() throws JspException; /** * Add a component parameter (attribute) to subContext. */ public void putAttribute(String name, Object value); } // end inner interface ///////////////////////////////////////////////////////////////////////////// /** * Real handler, after attribute resolution. * Handle include sub-component. */ protected class InsertHandler implements TagHandler { protected String page; protected ComponentContext currentContext; protected ComponentContext subCompContext; protected String role; protected Controller controller; /** * Constructor. * Create insert handler using Component definition. */ public InsertHandler( Map attributes, String page, String role, Controller controller) { this.page = page; this.role = role; this.controller = controller; subCompContext = new ComponentContext(attributes); } /** * Constructor. * Create insert handler to insert page at specified location. */ public InsertHandler(String page, String role, Controller controller) { this.page = page; this.role = role; this.controller = controller; subCompContext = new ComponentContext(); } /** * Create a new empty context. */ public int doStartTag() throws JspException { // Check role HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); if (role != null && !request.isUserInRole(role)) { return SKIP_BODY; } // save current context this.currentContext = getCurrentContext(); return EVAL_BODY_INCLUDE; } /** * Add attribute to sub context. * Do nothing. */ public void putAttribute(String name, Object value) { subCompContext.putAttribute(name, value); } /** * Include requested page. */ public int doEndTag() throws JspException { // Check role HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); if (role != null && !request.isUserInRole(role)) { return EVAL_PAGE; } try { if (log.isDebugEnabled()) { log.debug("insert page='" + page + "'."); } // set new context for included component. pageContext.setAttribute( ComponentConstants.COMPONENT_CONTEXT, subCompContext, PageContext.REQUEST_SCOPE); // Call controller if any if (controller != null) { try { controller.execute( subCompContext, (HttpServletRequest) pageContext.getRequest(), (HttpServletResponse) pageContext.getResponse(), pageContext.getServletContext()); } catch (Exception e) { throw new ServletException(e); } } // include requested component. if (flush) { pageContext.getOut().flush(); } doInclude(page, flush); } catch (IOException e) { String msg = "Can't insert page '" + page + "' : " + e.getMessage(); log.error(msg, e); throw new JspException(msg); } catch (IllegalArgumentException e) { // Can't resolve page uri, should we ignore it? if (!(page == null && isErrorIgnored)) { String msg = "Can't insert page '" + page + "'. Check if it exists.\n" + e.getMessage(); log.error(msg, e); throw new JspException(msg,e); } } catch (ServletException e) { Throwable cause = e; if (e.getRootCause() != null) { cause = e.getRootCause(); } String msg = "ServletException in '" + page + "': " + cause.getMessage(); log.error(msg, e); throw new JspException(msg,e); } finally { // restore old context only if currentContext not null // (bug with Silverstream ?; related by Arvindra Sehmi 20010712) if (currentContext != null) { pageContext.setAttribute( ComponentConstants.COMPONENT_CONTEXT, currentContext, PageContext.REQUEST_SCOPE); } } return EVAL_PAGE; } /** * Process an exception. * Depending of debug attribute, print full exception trace or only * its message in output page. * @param ex Exception * @param msg An additional message to show in console and to propagate if we can't output exception. * @deprecated This method will be removed in a release after Struts 1.2. */ protected void processException(Throwable ex, String msg) throws JspException { try { if (msg == null) { msg = ex.getMessage(); } if (log.isDebugEnabled()) { // show full trace log.debug(msg, ex); pageContext.getOut().println(msg); ex.printStackTrace( new PrintWriter(pageContext.getOut(), true)); } else { // show only message pageContext.getOut().println(msg); } } catch (IOException ioex) { // problems. Propagate original exception pageContext.setAttribute( ComponentConstants.EXCEPTION_KEY, ex, PageContext.REQUEST_SCOPE); throw new JspException(msg,ioex); } } } /** * Parse the list of roles and return <code>true</code> or <code>false</code> based on whether * the user has that role or not. * @param role Comma-delimited list of roles. * @param request The request. */ static public boolean userHasRole( HttpServletRequest request, String role) { StringTokenizer st = new StringTokenizer(role, ","); while (st.hasMoreTokens()) { if (request.isUserInRole(st.nextToken())) { return true; } } return false; } ///////////////////////////////////////////////////////////////////////////// /** * Handle insert direct string. */ protected class DirectStringHandler implements TagHandler { /** Object to print as a direct string */ private Object value; /** * Constructor. */ public DirectStringHandler(Object value) { this.value = value; } /** * Do nothing, there is no context for a direct string. */ public int doStartTag() throws JspException { return SKIP_BODY; } /** * Add attribute to sub context. * Do nothing. */ public void putAttribute(String name, Object value) { } /** * Print String in page output stream. */ public int doEndTag() throws JspException { try { if (flush) { pageContext.getOut().flush(); } pageContext.getOut().print(value); } catch (IOException ex) { if (log.isDebugEnabled()) { log.debug("Can't write string '" + value + "' : ", ex); } pageContext.setAttribute( ComponentConstants.EXCEPTION_KEY, ex, PageContext.REQUEST_SCOPE); throw new JspException( "Can't write string '" + value + "' : " + ex.getMessage(), ex); } return EVAL_PAGE; } } }