Revision: 1228
          http://stripes.svn.sourceforge.net/stripes/?rev=1228&view=rev
Author:   bengunter
Date:     2010-05-13 16:17:41 +0000 (Thu, 13 May 2010)

Log Message:
-----------
First cut at a fix for STS-391. The tags have been changed so they stream their 
contents to the client instead of buffering them and then dumping the contents 
all at once.

Modified Paths:
--------------
    
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutComponentTag.java
    
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutContext.java
    
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutDefinitionTag.java
    
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutRenderTag.java

Modified: 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutComponentTag.java
===================================================================
--- 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutComponentTag.java
       2010-05-12 12:24:25 UTC (rev 1227)
+++ 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutComponentTag.java
       2010-05-13 16:17:41 UTC (rev 1228)
@@ -14,13 +14,16 @@
  */
 package net.sourceforge.stripes.tag.layout;
 
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+import javax.servlet.ServletException;
+import javax.servlet.jsp.JspException;
+
+import net.sourceforge.stripes.exception.StripesJspException;
 import net.sourceforge.stripes.tag.StripesTagSupport;
 import net.sourceforge.stripes.util.Log;
 
-import javax.servlet.jsp.JspException;
-import javax.servlet.jsp.tagext.BodyTag;
-import javax.servlet.jsp.tagext.BodyContent;
-
 /**
  * Defines a component in a layout. Used both to define the components in a 
layout definition
  * and to provide overridden component definitions during a layout rendering 
request.
@@ -28,91 +31,152 @@
  * @author Tim Fennell
  * @since Stripes 1.1
  */
-public class LayoutComponentTag extends StripesTagSupport implements BodyTag {
+public class LayoutComponentTag extends StripesTagSupport {
     private static final Log log = Log.getInstance(LayoutComponentTag.class);
+
+    /** Regular expression that matches valid Java identifiers. */
+    private static final Pattern javaIdentifierPattern = Pattern
+            
.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*");
+
     private String name;
-    private BodyContent bodyContent;
-    private LayoutDefinitionTag definitionTag;
-    private LayoutRenderTag renderTag;
+    private LayoutContext context;
 
     /** Gets the name of the component. */
     public String getName() { return name; }
 
     /** Sets the name of the component. */
-    public void setName(String name) {
-        if (name != null && name.length() > 0) {
-            int length = name.length();
-            boolean validJava = 
Character.isJavaIdentifierStart(name.charAt(0));
-            int index = 1;
-            while (validJava && index < length) {
-                validJava = Character.isJavaIdentifierPart(name.charAt(index));
-                index++;
-            }
-            if (!validJava) {
-                log.warn("The layout-component name '", name, "' is not a 
valid Java identifier. ",
-                    "While this may work, it can cause bugs that are difficult 
to track down. Please ",
-                    "consider using valid Java identifiers for component names 
(no hyphens, no spaces, etc.)");
-            }
+    public void setName(String name) { this.name = name; }
+
+    /** Get the current layout context. */
+    public LayoutContext getContext() {
+        if (context == null) {
+            context = LayoutContext.find(getPageContext(), getLayoutName());
         }
-        this.name = name;
+
+        return context;
     }
 
-    /** Save the body content output by the tag. */
-    public void setBodyContent(BodyContent bodyContent) {
-        this.bodyContent = bodyContent;
+    /**
+     * True if this tag is the component to be rendered on this pass from
+     * {...@link LayoutDefinitionTag}.
+     */
+    public boolean isCurrentComponent() {
+        LayoutContext context = getContext();
+        if (context == null) {
+            return false;
+        }
+        else {
+            String name = context.getCurrentComponentName();
+            return name != null && name.equals(getName());
+        }
     }
 
     /**
-     * Behaviour varies depending on whether the tag is nested inside a 
LayoutRenderTag or a
-     * LayoutDefinitionTag.  In the first case it will always render it's 
output to a buffer so that
-     * it can be provided to the render tag.  In the second case, checks to 
see if the component
-     * has been overridden.  If so, does nothing, else writes its content to 
the output stream.
-     *
-     * @return EVAL_BODY_BUFFERED, EVAL_BODY_INCLUDE or SKIP_BODY as described 
above.
+     * Get the name of the layout that is being rendered; that is, the path to 
the JSP containing
+     * the {...@link LayoutDefinitionTag}. This value is also used to find the 
current layout context
+     * in the page context.
      */
+    public String getLayoutName() {
+        LayoutRenderTag render;
+        LayoutDefinitionTag definition;
+
+        if ((render = getParentTag(LayoutRenderTag.class)) != null)
+            return render.getName();
+        else if ((definition = getParentTag(LayoutDefinitionTag.class)) != 
null)
+            return definition.getLayoutName();
+        else
+            return null;
+    }
+
+    /**
+     * <p>
+     * If this tag is nested within a {...@link LayoutDefinitionTag}, then 
evaluate the corresponding
+     * {...@link LayoutComponentTag} nested within the {...@link 
LayoutRenderTag} that invoked the parent
+     * {...@link LayoutDefinitionTag}. If, after evaluating the corresponding 
tag, the component has
+     * not been rendered then evaluate this tag's body by returning {...@code 
EVAL_BODY_INCLUDE}.
+     * </p>
+     * <p>
+     * If this tag is nested within a {...@link LayoutRenderTag} and this tag 
is the current component,
+     * as indicated by {...@link LayoutContext#getCurrentComponentName()}, 
then evaluate this tag's
+     * body by returning {...@code EVAL_BODY_INCLUDE}.
+     * </p>
+     * <p>
+     * In all other cases, skip this tag's body by returning SKIP_BODY.
+     * </p>
+     * 
+     * @return {...@code EVAL_BODY_INCLUDE} or {...@code SKIP_BODY}, as 
described above.
+     */
     @Override
     public int doStartTag() throws JspException {
-        this.definitionTag = getParentTag(LayoutDefinitionTag.class);
-        this.renderTag = getParentTag(LayoutRenderTag.class);
+        LayoutRenderTag renderTag;
 
-        if (this.renderTag != null) {
-            return EVAL_BODY_BUFFERED;
+        if ((renderTag = getParentTag(LayoutRenderTag.class)) != null) {
+            if (!renderTag.isRecursing()) {
+                if (!javaIdentifierPattern.matcher(getName()).matches()) {
+                    log.warn("The layout-component name '", getName(), "' is 
not a valid Java identifier. ",
+                            "While this may work, it can cause bugs that are 
difficult to track down. Please ",
+                            "consider using valid Java identifiers for 
component names (no hyphens, no spaces, etc.)");
+                }
+
+                renderTag.addComponent(getName(), new 
LayoutComponentRenderer(this));
+            }
+
+            return isCurrentComponent() ? EVAL_BODY_INCLUDE : SKIP_BODY;
         }
-        else if (this.definitionTag.permissionToRender(this.name)) {
-            return EVAL_BODY_INCLUDE;
+        else if (getParentTag(LayoutDefinitionTag.class) != null) {
+            // Include the page that had the render tag on it to execute the 
component tags again.
+            // Only the component(s) with a name matching the current 
component name in the context
+            // will actually produce output.
+            try {
+                getContext().setCurrentComponentName(getName());
+                getPageContext().include(getContext().getRenderPage());
+            }
+            catch (ServletException e) {
+                throw new StripesJspException(e);
+            }
+            catch (IOException e) {
+                throw new StripesJspException(e);
+            }
+
+            // The current component name should be cleared after the 
component tag in the render
+            // tag has rendered. If it is not cleared then the component did 
not render so we need
+            // to output the default contents from the layout definition.
+            if (getContext().getCurrentComponentName() != null) {
+                getContext().setCurrentComponentName(null);
+                return EVAL_BODY_INCLUDE;
+            }
+            else {
+                return SKIP_BODY;
+            }
         }
         else {
             return SKIP_BODY;
         }
     }
 
-    /** Does nothing. */
-    public void doInitBody() throws JspException { /* Do Nothing */ }
-
     /**
-     * Does nothing.
-     * @return SKIP_BODY in all cases.
+     * If this tag is the component that needs to be rendered, as indicated by
+     * {...@link LayoutContext#getCurrentComponentName()}, then set the 
current component name back to
+     * null to indicate that the component has rendered.
+     * 
+     * @return SKIP_PAGE if this component is the current component, otherwise 
EVAL_PAGE.
      */
-    public int doAfterBody() throws JspException { return SKIP_BODY; }
-
-    /**
-     * If the tag is nested in a LayoutRenderTag, provides the tag with the 
generated contents.
-     * Otherwise, does nothing.
-     *
-     * @return EVAL_PAGE in all cases.
-     */
     @Override
     public int doEndTag() throws JspException {
-        if (this.renderTag != null && this.bodyContent != null) {
-            this.renderTag.addComponent(this.name, 
this.bodyContent.getString());
+        try {
+            // Set current component name back to null as a signal to the 
component tag within the
+            // definition tag that the component did, indeed, render and it 
should not output the
+            // default contents.
+            if (isCurrentComponent()) {
+                getContext().setCurrentComponentName(null);
+                return SKIP_PAGE;
+            }
+            else {
+                return EVAL_PAGE;
+            }
         }
-
-        // Clean up in case the tag gets pooled
-        this.bodyContent = null;
-        this.definitionTag = null;
-        this.renderTag = null;
-
-
-        return EVAL_PAGE;
+        finally {
+            this.context = null;
+        }
     }
 }

Modified: 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutContext.java
===================================================================
--- 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutContext.java
    2010-05-12 12:24:25 UTC (rev 1227)
+++ 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutContext.java
    2010-05-13 16:17:41 UTC (rev 1228)
@@ -14,9 +14,13 @@
  */
 package net.sourceforge.stripes.tag.layout;
 
+import java.util.LinkedList;
 import java.util.Map;
 import java.util.HashMap;
 
+import javax.servlet.ServletRequest;
+import javax.servlet.jsp.PageContext;
+
 /**
  * Used to move contextual information about a layout rendering between a 
LayoutRenderTag and
  * a LayoutDefinitionTag. Holds the set of overridden components and any 
parameters provided
@@ -26,22 +30,72 @@
  * @since Stripes 1.1
  */
 public class LayoutContext {
-    private Map<String,String> components = new HashMap<String,String>();
+    /**
+     * Prefix used to construct the request attribute name used to pass 
context from the
+     * LayoutRenderTag to the LayoutDefinitionTag.
+     */
+    public static final String PREFIX = "stripes.layout.";
+
+    /**
+     * Look up the stack of layout contexts associated with the named layout 
in a JSP page context.
+     * If {...@code create} is true and no stack is found then one will be 
created and placed in the
+     * page context.
+     * 
+     * @param pageContext The JSP page context to search for the layout 
context stack.
+     * @param layoutName The name of the layout with which the contexts are 
associated.
+     * @param create If true and no stack is found, then create and save a new 
stack.
+     */
+    @SuppressWarnings("unchecked")
+    public static LinkedList<LayoutContext> findStack(PageContext pageContext, 
String layoutName,
+            boolean create) {
+        String key = PREFIX + layoutName;
+        ServletRequest request = pageContext.getRequest();
+        LinkedList<LayoutContext> stack = (LinkedList<LayoutContext>) 
request.getAttribute(key);
+        if (create && stack == null) {
+            stack = new LinkedList<LayoutContext>();
+            request.setAttribute(key, stack);
+        }
+        return stack;
+    }
+
+    /**
+     * Look up the current layout context associated with the named layout in 
a JSP page context.
+     * 
+     * @param pageContext The JSP page context to search for the layout 
context stack.
+     * @param layoutName The name of the layout with which the contexts are 
associated.
+     */
+    public static LayoutContext find(PageContext pageContext, String 
layoutName) {
+        LinkedList<LayoutContext> stack = findStack(pageContext, layoutName, 
true);
+        return !stack.isEmpty() ? stack.getLast() : null;
+    }
+
+    /**
+     * Remove the current layout context from the stack of layout contexts 
associated with the named
+     * layout.
+     * 
+     * @param pageContext The JSP page context to search for the layout 
context stack.
+     * @param layoutName The name of the layout with which the contexts are 
associated.
+     * @return The layout context that was popped off the stack, or null if 
the stack was not found
+     *         or was empty.
+     */
+    public static LayoutContext pop(PageContext pageContext, String 
layoutName) {
+        LinkedList<LayoutContext> stack = findStack(pageContext, layoutName, 
false);
+        return stack != null && !stack.isEmpty() ? stack.removeLast() : null;
+    }
+
+    private Map<String,LayoutComponentRenderer> components = new 
HashMap<String,LayoutComponentRenderer>();
     private Map<String,Object> parameters = new HashMap<String,Object>();
+    private String renderPage, currentComponentName;
     private boolean rendered = false;
 
     /**
      * Gets the Map of overridden components.  Will return an empty Map if no 
components were
      * overridden.
      */
-    public Map<String, String> getComponents() {
-        return components;
-    }
+    public Map<String,LayoutComponentRenderer> getComponents() { return 
components; }
 
     /** Gets the Map of parameters.  Will return an empty Map if none were 
provided. */
-    public Map<String, Object> getParameters() {
-        return parameters;
-    }
+    public Map<String,Object> getParameters() { return parameters; }
 
     /** Returns true if the layout has been rendered, false otherwise. */
     public boolean isRendered() { return rendered; }
@@ -49,6 +103,18 @@
     /** False initially, should be set to true when the layout is actually 
rendered. */
     public void setRendered(final boolean rendered) { this.rendered = 
rendered; }
 
+    /** Get the path to the page that contains the {...@link LayoutRenderTag} 
that created this context. */
+    public String getRenderPage() { return renderPage; }
+
+    /** Set the path to the page that contains the {...@link LayoutRenderTag} 
that created this context. */
+    public void setRenderPage(String layoutRenderer) { this.renderPage = 
layoutRenderer; }
+
+    /** Get the name of the component to be rendered on this pass from 
{...@link LayoutDefinitionTag}. */
+    public String getCurrentComponentName() { return currentComponentName; }
+
+    /** Set the name of the component to be rendered on this pass from 
{...@link LayoutDefinitionTag}. */
+    public void setCurrentComponentName(String currentComponentName) { 
this.currentComponentName = currentComponentName; }
+
     /** To String implementation the parameters, and the component names. */
     @Override
     public String toString() {

Modified: 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutDefinitionTag.java
===================================================================
--- 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutDefinitionTag.java
      2010-05-12 12:24:25 UTC (rev 1227)
+++ 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutDefinitionTag.java
      2010-05-13 16:17:41 UTC (rev 1228)
@@ -14,13 +14,16 @@
  */
 package net.sourceforge.stripes.tag.layout;
 
+import net.sourceforge.stripes.controller.StripesConstants;
 import net.sourceforge.stripes.tag.StripesTagSupport;
 import net.sourceforge.stripes.util.Log;
 
 import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.PageContext;
+
 import java.io.IOException;
-import java.util.Stack;
 import java.util.Map;
+import java.util.Map.Entry;
 
 /**
  * On the surface, allows a developer to define a layout using a custom tag - 
but is actually
@@ -33,57 +36,60 @@
 public class LayoutDefinitionTag extends StripesTagSupport {
     private static final Log log = Log.getInstance(LayoutDefinitionTag.class);
 
+    private String layoutName;
+    private LayoutContext context;
+
     /**
-     * Prefix used to construct the request attribute name used to pass 
context from the
-     * LayoutRenderTag to the LayoutDefinitionTag.
+     * Assuming that the layout definition page is always included, the 
following line gets the name
+     * of the page the tag is sitting on, as per Servlet 2.4 spec, page 65.
      */
-    public static final String PREFIX = "stripes.layout.";
+    public String getLayoutName() {
+        if (layoutName == null) {
+            layoutName = (String) getPageContext().getRequest().getAttribute(
+                    StripesConstants.REQ_ATTR_INCLUDE_PATH);
+        }
 
-    private LayoutContext context;
+        return layoutName;
+    }
 
+    /** Get the current layout context. */
+    public LayoutContext getLayoutContext() {
+        if (context == null) {
+            context = LayoutContext.find(getPageContext(), getLayoutName());
+        }
+
+        return context;
+    }
+
     /**
-     * Looks up the layout context that has been setup by a LayoutRenderTag. 
Uses the context
-     * to push any dynamic attributes supplied to the render tag in to the 
page context
-     * available during the body of the LayoutDefinitionTag.
-     *
-     * @return EVAL_BODY_INCLUDE in all cases.
+     * Looks up the layout context that has been setup by a {...@link 
LayoutRenderTag}. Uses the
+     * context to push any dynamic attributes supplied to the render tag in to 
the page context
+     * available during the body of the {...@link LayoutDefinitionTag}.
+     * 
+     * @return {...@code EVAL_BODY_INCLUDE} in all cases.
      */
     @Override
-    @SuppressWarnings("unchecked")
-       public int doStartTag() throws JspException {
-        // Since the layout-render tag pushes a new writer onto the stack, we 
can clear the
-        // buffer here to make sure we don't output anything outside the 
layout-def tag.
+    public int doStartTag() throws JspException {
+        // Try to clear the buffer to make sure we don't output anything 
outside the layout-def tag.
+        PageContext pageContext = getPageContext();
         try {
-            getPageContext().getOut().clearBuffer();
+            pageContext.getOut().clearBuffer();
         }
         catch (IOException ioe) {
             // Not a whole lot we can do if we cannot clear the buffer :/
             log.warn("Could not clear buffer before rendering a layout.", ioe);
         }
 
-        // Assuming that the layout definition page is always included, the 
following line gets
-        // the name of the page the tag is sitting on, as per Servlet 2.4 
spec, page 65.
-        String name = (String) getPageContext().getRequest()
-                .getAttribute("javax.servlet.include.servlet_path");
-
-        // Fetch the layout context containing parameters and component 
overrides
-        Stack<LayoutContext> stack = (Stack<LayoutContext>)
-                getPageContext().getRequest().getAttribute(PREFIX + name);
-        this.context = stack.peek();
-
         // Put any additional parameters into page context for the definition 
to use
-        for (Map.Entry<String,Object> entry : 
this.context.getParameters().entrySet()) {
-            getPageContext().setAttribute(entry.getKey(), entry.getValue());
+        LayoutContext context = getLayoutContext();
+        for (Map.Entry<String, Object> entry : 
context.getParameters().entrySet()) {
+            pageContext.setAttribute(entry.getKey(), entry.getValue());
         }
-
-        for (Map.Entry<String,String> entry : 
this.context.getComponents().entrySet()) {
-            getPageContext().setAttribute(entry.getKey(), entry.getValue());
+        for (Entry<String, LayoutComponentRenderer> entry : 
context.getComponents().entrySet()) {
+            entry.getValue().pushPageContext(pageContext);
+            pageContext.setAttribute(entry.getKey(), entry.getValue());
         }
 
-        // Technically we're not quite done yet, but this flag is only used to
-        // indicate that an attempt was made to render
-        this.context.setRendered(true);
-
         return EVAL_BODY_INCLUDE;
     }
 
@@ -93,31 +99,19 @@
      */
     @Override
     public int doEndTag() throws JspException {
-        return SKIP_PAGE;
-    }
-
-    /**
-     * Called by nested tags to find out if they have permission to render 
their content, or
-     * if they have been overridden in the layout rendering tag.  Returns true 
if a component
-     * has not been overridden and should render as normal.  Returns false, 
and writes out the
-     * overridden component when the component has been overridden.
-     *
-     * @param componentName the name of the component about to render
-     * @return true if the component should render itself, false otherwise
-     * @throws JspException if the JspWriter could not be written to
-     */
-    public boolean permissionToRender(String componentName) throws 
JspException {
-        if (this.context.getComponents().containsKey(componentName)) {
-            try {
-                
getPageContext().getOut().write(this.context.getComponents().get(componentName));
+        try {
+            getLayoutContext().setRendered(true);
+            return SKIP_PAGE;
+        }
+        finally {
+            // Pop our page context off the renderer's page context stack
+            for (LayoutComponentRenderer renderer : 
context.getComponents().values()) {
+                renderer.popPageContext();
             }
-            catch (IOException ioe) {
-                throw new JspException("Could not output overridden layout 
component.", ioe);
-            }
-            return false;
+
+            // Set fields back to null
+            this.layoutName = null;
+            this.context = null;
         }
-        else {
-            return true;
-        }
     }
 }

Modified: 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutRenderTag.java
===================================================================
--- 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutRenderTag.java
  2010-05-12 12:24:25 UTC (rev 1227)
+++ 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutRenderTag.java
  2010-05-13 16:17:41 UTC (rev 1228)
@@ -14,16 +14,15 @@
  */
 package net.sourceforge.stripes.tag.layout;
 
-import net.sourceforge.stripes.exception.StripesJspException;
-import net.sourceforge.stripes.tag.StripesTagSupport;
-
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.jsp.JspException;
-import javax.servlet.jsp.tagext.BodyContent;
-import javax.servlet.jsp.tagext.BodyTag;
+import javax.servlet.jsp.PageContext;
 import javax.servlet.jsp.tagext.DynamicAttributes;
-import java.util.Stack;
 
+import net.sourceforge.stripes.exception.StripesJspException;
+import net.sourceforge.stripes.tag.StripesTagSupport;
+import net.sourceforge.stripes.util.HttpUtil;
+
 /**
  * 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
@@ -32,9 +31,10 @@
  * @author Tim Fennell
  * @since Stripes 1.1
  */
-public class LayoutRenderTag extends StripesTagSupport implements BodyTag, 
DynamicAttributes {
+public class LayoutRenderTag extends StripesTagSupport implements 
DynamicAttributes {
     private String name;
-    private LayoutContext context = new LayoutContext();
+    private Boolean recursing;
+    private LayoutContext context;
 
     /** Gets the name of the layout to be used. */
     public String getName() { return name; }
@@ -42,107 +42,124 @@
     /** Sets the name of the layout to be used. */
     public void setName(String name) { this.name = name; }
 
+    /**
+     * The layout tags work in a quirky way: layout-render includes the page 
referenced in its
+     * {...@code name} attribute, which again includes the page containing the 
layout-render tag once
+     * for each layout-component tag it encounters. This flag is false if this 
is the initial
+     * invocation of the render tag and true if this invocation is coming from 
the layout-definition
+     * tag as a request to render a component.
+     */
+    public boolean isRecursing() {
+        if (recursing == null) {
+            recursing = getContext().getCurrentComponentName() != null;
+        }
+
+        return recursing;
+    }
+
+    /** Look up an existing layout context or create a new one if none is 
found. */
+    public LayoutContext getContext() {
+        if (context == null) {
+            if (getName() != null)
+                context = LayoutContext.find(getPageContext(), getName());
+            if (context == null)
+                context = new LayoutContext();
+        }
+
+        return context;
+    }
+
     /** Used by the JSP container to provide the tag with dynamic attributes. 
*/
     public void setDynamicAttribute(String uri, String localName, Object 
value) throws JspException {
-        this.context.getParameters().put(localName, value);
+        getContext().getParameters().put(localName, value);
     }
 
     /**
-     * Allows nested tags to register their contents for rendering in the 
layout.
-     *
+     * Allows nested tags to register themselves for rendering in the layout.
+     * 
      * @param name the name of the component to be overridden in the layout
-     * @param contents the output that will be used
+     * @param renderer the object that will render the component to a string
      */
-    public void addComponent(String name, String contents) {
-        this.context.getComponents().put(name, contents);
+    public void addComponent(String name, LayoutComponentRenderer renderer) {
+        getContext().getComponents().put(name, renderer);
     }
 
     /**
-     * Pushes the values of any dynamic attributes into page context 
attributes for
-     * the duration of the tag.
-     *
-     * @return EVAL_BODY_BUFFERED in all cases
+     * On the first pass (see {...@link #isRecursing()}):
+     * <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 {
-        pushPageContextAttributes(this.context.getParameters());
-        return EVAL_BODY_BUFFERED;
-    }
+        if (!isRecursing()) {
+            // Ensure absolute path for layout name
+            if (!getName().startsWith("/")) {
+                throw new StripesJspException("The name= attribute of the 
layout-render tag must be " +
+                    "an absolute path, starting with a forward slash (/). 
Please modify the " +
+                    "layout-render tag with the name '" + getName() + "' 
accordingly.");
+            }
 
-    /**
-     * Discards the body content since it is not used. Input from nested 
LayoutComponent tags is
-     * captured through a different mechanism.
-     */
-    public void setBodyContent(BodyContent bodyContent) { /* Don't use it */ }
+            pushPageContextAttributes(getContext().getParameters());
+        }
 
-    /** Does nothing. */
-    public void doInitBody() throws JspException { /* Do nothing. */ }
+        return EVAL_BODY_INCLUDE;
+    }
 
     /**
-     * Does nothing.
-     * @return SKIP_BODY in all cases.
-     */
-    public int doAfterBody() throws JspException { return SKIP_BODY; }
-
-    /**
-     * Invokes the named layout, providing it with the overridden components 
and provided
-     * parameters.
+     * After the first pass (see {...@link #isRecursing()}):
+     * <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.
-     * @throws JspException if any exceptions are encountered processing the 
request
      */
     @Override
-    @SuppressWarnings("unchecked")
        public int doEndTag() throws JspException {
         try {
-            if (!name.startsWith("/")) {
-                throw new StripesJspException("The name= attribute of the 
layout-render tag must be " +
-                    "an absolute path, starting with a forward slash (/). 
Please modify the " +
-                    "layout-render tag with the name '" + name + "' 
accordingly.");
-            }
+            if (!isRecursing()) {
+                // Put the components into the request, for the definition tag 
to use.. using a stack
+                // to allow for the same layout to be nested inside itself :o
+                PageContext pageContext = getPageContext();
+                LayoutContext.findStack(pageContext, getName(), 
true).add(getContext());
 
-            HttpServletRequest request = (HttpServletRequest) 
getPageContext().getRequest();
+                // Now include the target JSP
+                HttpServletRequest request = (HttpServletRequest) 
pageContext.getRequest();
+                
getContext().setRenderPage(HttpUtil.getRequestedServletPath(request));
+                try {
+                    pageContext.include(this.name, false);
+                }
+                catch (Exception e) {
+                    throw new StripesJspException(
+                        "An exception was raised while invoking a layout. The 
layout used was " +
+                        "'" + this.name + "'. The following information was 
supplied to the render " +
+                        "tag: " + this.context.toString(), e);
+                }
 
-            // Put the components into the request, for the definition tag to 
use.. using a stack
-            // to allow for the same layout to be nested inside itself :o
-            String attributeName = LayoutDefinitionTag.PREFIX + this.name;
-            Stack<LayoutContext> stack =
-                    (Stack<LayoutContext>) request.getAttribute(attributeName);
-            if (stack == null) {
-                stack = new Stack<LayoutContext>();
-                request.setAttribute(attributeName, stack);
-            }
+                // Check that the layout actually got rendered as some 
containers will
+                // just quietly ignore includes of non-existent pages!
+                if (!getContext().isRendered()) {
+                    throw new StripesJspException(
+                            "Attempt made to render a layout that does not 
exist. The layout name " +
+                            "provided was '" + this.name + "'. Please check 
that a JSP/view exists at " +
+                            "that location within your web application."
+                        );
+                }
 
-            stack.push(this.context);
-
-            // Now wrap the JSPWriter, and include the target JSP
-            BodyContent content = getPageContext().pushBody();
-            getPageContext().include(this.name, false);
-            getPageContext().popBody();
-            getPageContext().getOut().write(content.getString());
-
-            // Check that the layout actually got rendered as some containers 
will
-            // just quietly ignore includes of non-existent pages!
-            if (!this.context.isRendered()) {
-                throw new StripesJspException(
-                    "Attempt made to render a layout that does not exist. The 
layout name " +
-                    "provided was '" + this.name + "'. Please check that a 
JSP/view exists at " +
-                    "that location within your web application."
-                );
+                LayoutContext.pop(pageContext, getName());
+                popPageContextAttributes(); // remove any dynattrs from page 
scope
             }
-
-
-            stack.pop();
-            popPageContextAttributes(); // remove any dynattrs from page scope
-
-            // Clean up in case the tag gets pooled
-            this.context = new LayoutContext();
         }
-        catch (StripesJspException sje) { throw sje; }
-        catch (Exception e) {
-            throw new StripesJspException(
-                "An exception was raised while invoking a layout. The layout 
used was " +
-                "'" + this.name + "'. The following information was supplied 
to the render " +
-                "tag: " + this.context.toString(), e);
+        finally {
+            this.recursing = null;
+            this.context = null;
         }
 
         return EVAL_PAGE;


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

------------------------------------------------------------------------------

_______________________________________________
Stripes-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/stripes-development

Reply via email to