/* Copyright 2005-2006 Tim Fennell * * 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 net.sourceforge.stripes.tag.layout; import java.io.IOException; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.BodyContent; import javax.servlet.jsp.tagext.BodyTag; import javax.servlet.jsp.tagext.DynamicAttributes; import javax.servlet.jsp.tagext.Tag; import net.sourceforge.stripes.exception.StripesJspException; import net.sourceforge.stripes.exception.StripesRuntimeException; import net.sourceforge.stripes.util.Log; /** * Renders a named layout, optionally overriding one or more components in the layout. Any * attributes provided to the class other than 'name' will be placed into page context during * the evaluation of the layout, making them available to other tags, and in EL. * * @author Tim Fennell, Ben Gunter * @since Stripes 1.1 */ public class LayoutRenderTag extends LayoutTag implements BodyTag, DynamicAttributes { private static final Log log = Log.getInstance(LayoutRenderTag.class); private String name; private LayoutContext context; private boolean contextIsNew, silent; private LayoutRenderTagPath path; private BodyContent bodyContent; /** Gets the name of the layout to be used. */ public String getName() { return name; } /** Sets the name of the layout to be used and then calls {@link #initialize()}. */ public void setName(String name) { this.name = name; initialize(); } /** Get the {@link LayoutRenderTagPath} that identifies this tag within the current page. */ public LayoutRenderTagPath getPath( ) { return path; } /** * Initialize fields before execution begins. Typically, this would be done by overriding * {@link #setPageContext(javax.servlet.jsp.PageContext)}, but that isn't possible in this case * because some of the logic depends on {@link #setName(String)} having been called, which does * not happen until after {@link #setPageContext(javax.servlet.jsp.PageContext)} has been * called. */ protected void initialize() { LayoutContext context = LayoutContext.lookup(pageContext); boolean create = context == null || !context.isComponentRenderPhase() || isChildOfCurrentComponent(); LayoutRenderTagPath path; if (create) { context = LayoutContext.push(this); path = context.getComponentPath(); } else { path = new LayoutRenderTagPath(this); } this.context = context; this.contextIsNew = create; this.path = path; this.silent = context.getOut().isSilent(); } /** Returns true if this tag is a child of the current component tag. */ public boolean isChildOfCurrentComponent() { try { LayoutTag parent = getLayoutParent(); return parent instanceof LayoutComponentTag && ((LayoutComponentTag) parent).isCurrentComponent(); } catch (StripesJspException e) { // This exception would have been thrown before this tag ever executed throw new StripesRuntimeException("Something has happened that should never happen", e); } } /** Used by the JSP container to provide the tag with dynamic attributes. */ public void setDynamicAttribute(String uri, String localName, Object value) throws JspException { context.getParameters().put(localName, value); } /** * On the first pass (see {@link LayoutContext#isComponentRenderPhase()}): * <ul> * <li>Push the values of any dynamic attributes into page context attributes for the duration * of the tag.</li> * <li>Create a new context and places it in request scope.</li> * <li>Include the layout definition page named by the {@code name} attribute.</li> * </ul> * * @return EVAL_BODY_INCLUDE in all cases */ @Override public int doStartTag() throws JspException { try { if (contextIsNew) { log.debug("Start layout init in ", context.getRenderPage()); pushPageContextAttributes(context.getParameters()); } if (context.isComponentRenderPhase()) { log.debug("Start component render phase for ", context.getComponent(), " in ", context.getRenderPage()); exportComponentRenderers(); } // Render tags never output their contents directly context.getOut().setSilent(true, pageContext); return contextIsNew ? EVAL_BODY_BUFFERED : EVAL_BODY_INCLUDE; } catch (IOException e) { throw new JspException(e); } } /** * Set the tag's body content. Called by the JSP engine during component registration phase, * when {@link #doStartTag()} returns {@link BodyTag#EVAL_BODY_BUFFERED} */ public void setBodyContent(BodyContent bodyContent) { this.bodyContent = bodyContent; } /** Does nothing. */ public void doInitBody() throws JspException { } /** Returns {@link Tag#SKIP_BODY}. */ public int doAfterBody() throws JspException { return SKIP_BODY; } /** * After the first pass (see {@link LayoutContext#isComponentRenderPhase()}): * <ul> * <li>Ensure the layout rendered successfully by checking {@link LayoutContext#isRendered()}.</li> * <li>Remove the current layout context from request scope.</li> * <li>Restore previous page context attribute values.</li> * </ul> * * @return EVAL_PAGE in all cases. */ @Override public int doEndTag() throws JspException { try { if (contextIsNew) { log.debug("End layout init in ", context.getRenderPage()); try { log.debug("Start layout exec in ", context.getDefinitionPage()); context.getOut().setSilent(true, pageContext); context.doInclude(pageContext, getName()); log.debug("End layout exec in ", context.getDefinitionPage()); } catch (Exception e) { throw new StripesJspException( "An exception was raised while invoking a layout. The layout used was " + "'" + getName() + "'. The following information was supplied to the render " + "tag: " + context.toString(), e); } // Check that the layout actually got rendered as some containers will // just quietly ignore includes of non-existent pages! if (!context.isRendered()) { throw new StripesJspException( "Attempt made to render a layout that does not exist. The layout name " + "provided was '" + getName() + "'. Please check that a JSP/view exists at " + "that location within your web application." ); } context.getOut().setSilent(silent, pageContext); LayoutContext.pop(pageContext); popPageContextAttributes(); // remove any dynattrs from page scope } else { context.getOut().setSilent(silent, pageContext); } if (context.isComponentRenderPhase()) { log.debug("End component render phase for ", context.getComponent(), " in ", context.getRenderPage()); cleanUpComponentRenderers(); } return EVAL_PAGE; } catch (IOException e) { throw new JspException(e); } finally { this.context = null; this.contextIsNew = false; this.path = null; this.silent = false; if (this.bodyContent != null) { this.bodyContent.clearBody(); this.bodyContent = null; } } } }