/*
 * $Id: InsertTag.java 165160 2005-04-28 16:29:58Z niallp $ 
 *
 * Copyright 1999-2005 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.struts.taglib.tiles;

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.taglib.tiles.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 &lt;tiles:insert&gt;, which includes
 * a template. The tag's body content consists of &lt;tiles:put&gt;
 * tags, which are accessed by &lt;tiles:get&gt; in the template.
 *
 * @version $Rev: 165160 $ $Date: 2005-04-28 17:29:58 +0100 (Thu, 28 Apr 2005) $
 */
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 &lg;put&gt; tag.
	 * Method calls by nested &lg;put&gt; 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 &lg;putList&gt; tag.
	 * Method calls by nested &lg;putList&gt; 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 &lg;putList&gt; 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.getMessage());
		}
	}

	/**
	 * 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.");

		} 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.getMessage());
		}
	}

	/**
	 * 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.getMessage());
		}
	}

	/**
	 * 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
	 * @throws ServletException - Thrown by call to pageContext.include()
	 * @throws IOException - Thrown by call to pageContext.include()
	 */
	protected void doInclude(String page)
		throws ServletException, IOException {
		TilesUtil.doInclude(page, pageContext);
	}

	/////////////////////////////////////////////////////////////////////////////

	/**
	 * 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);

			} 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);
				}

			} 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);

			} 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);
			}
		}
	}

	/**
	 * 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());
			}

			return EVAL_PAGE;
		}
	}
}
