Revision: 1238
          http://stripes.svn.sourceforge.net/stripes/?rev=1238&view=rev
Author:   bengunter
Date:     2010-05-21 05:18:07 +0000 (Fri, 21 May 2010)

Log Message:
-----------
Improved fix for STS-391 (layout tags) that handles nested renders and is just 
a lot more robust. However, the decorator pattern is not working at the moment. 
More to come.

Modified Paths:
--------------
    
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutComponentRenderer.java
    
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

Added Paths:
-----------
    branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutTag.java
    
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutWriter.java

Modified: 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutComponentRenderer.java
===================================================================
--- 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutComponentRenderer.java
  2010-05-19 18:53:03 UTC (rev 1237)
+++ 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutComponentRenderer.java
  2010-05-21 05:18:07 UTC (rev 1238)
@@ -1,17 +1,28 @@
-/**
- * Created by Ben Gunter on May 12, 2010 at 2:01:33 PM.
+/* Copyright 2010 Ben Gunter
  *
- * Copyright 2010 Comsquared Systems. All rights reserved.
+ * 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 java.util.Iterator;
 import java.util.LinkedList;
 
 import javax.servlet.ServletException;
 import javax.servlet.jsp.PageContext;
 import javax.servlet.jsp.tagext.BodyContent;
 
+import net.sourceforge.stripes.exception.StripesJspException;
 import net.sourceforge.stripes.exception.StripesRuntimeException;
 
 /**
@@ -26,7 +37,7 @@
  */
 public class LayoutComponentRenderer {
     private LinkedList<PageContext> pageContext;
-    private String componentName, layoutName;
+    private String componentName;
 
     /**
      * Create a new instance to render the specified component tag to a 
string. The tag itself is
@@ -34,9 +45,9 @@
      * as layout name and component name.
      * 
      * @param tag The layout component to render.
+     * @throws StripesJspException If the tag cannot find a layout context.
      */
-    public LayoutComponentRenderer(LayoutComponentTag tag) {
-        this.layoutName = tag.getLayoutName();
+    public LayoutComponentRenderer(LayoutComponentTag tag) throws 
StripesJspException {
         this.componentName = tag.getName();
     }
 
@@ -70,27 +81,45 @@
 
     @Override
     public String toString() {
+        // Save the current component name so it can be restored when we're 
done
         PageContext pageContext = getPageContext();
-        LayoutContext context = LayoutContext.find(pageContext, layoutName);
+        if (pageContext == null)
+            return componentName + " (page context is missing)";
 
-        // Save the current component name so it can be restored when we're 
done
-        String restore = context.getCurrentComponentName();
-        context.setCurrentComponentName(componentName);
+        // FIXME decorator pattern is broken
+        if (1 == Integer.valueOf("1"))
+            return componentName + " (fix me!)";
 
-        try {
-            BodyContent body = pageContext.pushBody();
-            pageContext.include(context.getRenderPage());
-            pageContext.popBody();
-            return body.getString();
+        Iterator<LayoutContext> iterator = LayoutContext.getStack(pageContext, 
true)
+                .descendingIterator();
+        while (iterator.hasNext()) {
+            LayoutContext context = iterator.next();
+            boolean flag = context.isComponentRenderPhase();
+            String name = context.getComponent();
+            context.setComponentRenderPhase(true);
+            context.setComponent(componentName);
+
+            try {
+                if (context.getComponents().containsKey(componentName)) {
+                    BodyContent body = pageContext.pushBody();
+                    pageContext.include(context.getRenderPage(), false);
+                    pageContext.popBody();
+                    if (context.getComponent() == null)
+                        return body.getString();
+                }
+            }
+            catch (ServletException e) {
+                throw new StripesRuntimeException(e);
+            }
+            catch (IOException e) {
+                throw new StripesRuntimeException(e);
+            }
+            finally {
+                context.setComponentRenderPhase(flag);
+                context.setComponent(name);
+            }
         }
-        catch (ServletException e) {
-            throw new StripesRuntimeException(e);
-        }
-        catch (IOException e) {
-            throw new StripesRuntimeException(e);
-        }
-        finally {
-            context.setCurrentComponentName(restore);
-        }
+
+        return componentName + " (render failed)";
     }
 }

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-19 18:53:03 UTC (rev 1237)
+++ 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutComponentTag.java
       2010-05-21 05:18:07 UTC (rev 1238)
@@ -15,23 +15,23 @@
 package net.sourceforge.stripes.tag.layout;
 
 import java.io.IOException;
+import java.util.Iterator;
 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;
 
 /**
  * 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.
  *
- * @author Tim Fennell
+ * @author Tim Fennell, Ben Gunter
  * @since Stripes 1.1
  */
-public class LayoutComponentTag extends StripesTagSupport {
+public class LayoutComponentTag extends LayoutTag {
     private static final Log log = Log.getInstance(LayoutComponentTag.class);
 
     /** Regular expression that matches valid Java identifiers. */
@@ -40,6 +40,7 @@
 
     private String name;
     private LayoutContext context;
+    private Boolean silent;
 
     /** Gets the name of the component. */
     public String getName() { return name; }
@@ -47,10 +48,22 @@
     /** Sets the name of the component. */
     public void setName(String name) { this.name = name; }
 
-    /** Get the current layout context. */
-    public LayoutContext getContext() {
+    /**
+     * Get the current layout context.
+     * 
+     * @throws StripesJspException If a {...@link LayoutContext} is not found.
+     */
+    public LayoutContext getContext() throws StripesJspException {
         if (context == null) {
-            context = LayoutContext.find(getPageContext(), getLayoutName());
+            context = LayoutContext.lookup(pageContext);
+
+            if (context == null) {
+                throw new StripesJspException("A component tag named \"" + 
getName() + "\" in "
+                        + getCurrentPagePath() + " was unable to find a layout 
context.");
+            }
+
+            log.trace("Component ", getName() + " has context ", 
context.getRenderPage(), " -> ",
+                    context.getDefinitionPage());
         }
 
         return context;
@@ -59,36 +72,15 @@
     /**
      * True if this tag is the component to be rendered on this pass from
      * {...@link LayoutDefinitionTag}.
+     * 
+     * @throws StripesJspException If a {...@link LayoutContext} is not found.
      */
-    public boolean isCurrentComponent() {
-        LayoutContext context = getContext();
-        if (context == null) {
-            return false;
-        }
-        else {
-            String name = context.getCurrentComponentName();
-            return name != null && name.equals(getName());
-        }
+    public boolean isCurrentComponent() throws StripesJspException {
+        String name = getContext().getComponent();
+        return name != null && name.equals(getName());
     }
 
     /**
-     * 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
@@ -108,50 +100,81 @@
      */
     @Override
     public int doStartTag() throws JspException {
-        LayoutRenderTag renderTag;
+        try {
+            LayoutContext context = getContext();
 
-        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.)");
+            if (isChildOfRender()) {
+                if (context.isComponentRenderPhase()) {
+                    if (isCurrentComponent()) {
+                        log.debug("Render ", getName(), " in ", 
context.getRenderPage());
+                        silent = context.getOut().isSilent();
+                        context.getOut().setSilent(false, pageContext);
+                        return EVAL_BODY_INCLUDE;
+                    }
+                    else {
+                        log.debug("No-op for ", getName(), " in ", 
context.getRenderPage());
+                    }
                 }
+                else {
+                    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));
+                    log.debug("Register component ", getName(), " with ", 
context.getRenderPage());
+                    context.getComponents().put(getName(), new 
LayoutComponentRenderer(this));
+                }
             }
+            else if (isChildOfDefinition()) {
+                if (!context.isComponentRenderPhase()) {
+                    // Set render phase flag and the name of the component to 
render. Iterate down
+                    // the stack of layout contexts, executing each render 
page that contains a
+                    // component with the same name as this one until the 
component has rendered or
+                    // we run out of contexts.
+                    context.setComponentRenderPhase(true);
+                    context.setComponent(getName());
+                    Iterator<LayoutContext> iterator = 
LayoutContext.getStack(pageContext, true)
+                            .descendingIterator();
+                    while (iterator.hasNext() && context.getComponent() != 
null) {
+                        LayoutContext renderer = iterator.next();
+                        if (renderer.getComponents().containsKey(getName())) {
+                            String renderPage = renderer.getRenderPage();
+                            log.debug("Execute component ", getName(), " in ", 
context
+                                    .getDefinitionPage(), " with include of ", 
renderPage);
+                            pageContext.include(renderPage, false);
+                        }
+                    }
+                    context.setComponentRenderPhase(false);
 
-            return isCurrentComponent() ? EVAL_BODY_INCLUDE : SKIP_BODY;
-        }
-        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());
+                    // The current component name should be cleared after the 
component has
+                    // rendered. If it is not cleared then the component did 
not render so we need
+                    // to output the default content from the layout 
definition.
+                    if (context.getComponent() != null) {
+                        log.debug("Component was not present in ", 
context.getRenderPage(),
+                                " so using default content from ", 
context.getDefinitionPage());
+
+                        silent = context.getOut().isSilent();
+                        context.getOut().setSilent(false, pageContext);
+                        context.setComponent(null);
+                        return EVAL_BODY_INCLUDE;
+                    }
+                }
+                else {
+                    log.debug("No-op for ", getName(), " in ", 
context.getDefinitionPage());
+                }
             }
-            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;
         }
+        catch (ServletException e) {
+            throw new StripesJspException(e);
+        }
+        catch (IOException e) {
+            throw new StripesJspException(e);
+        }
     }
 
     /**
@@ -167,16 +190,18 @@
             // 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;
-            }
+            LayoutContext context = getContext();
+            if (isCurrentComponent())
+                context.setComponent(null);
+
+            if (silent != null)
+                context.getOut().setSilent(silent, pageContext);
+
+            return EVAL_PAGE;
         }
         finally {
             this.context = null;
+            this.silent = 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-19 18:53:03 UTC (rev 1237)
+++ 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutContext.java
    2010-05-21 05:18:07 UTC (rev 1238)
@@ -14,82 +14,125 @@
  */
 package net.sourceforge.stripes.tag.layout;
 
+import java.io.IOException;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.Map;
-import java.util.HashMap;
 
 import javax.servlet.ServletRequest;
 import javax.servlet.jsp.PageContext;
 
+import net.sourceforge.stripes.util.Log;
+
 /**
  * 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
  * to the render tag.
  *
- * @author Tim Fennell
+ * @author Tim Fennell, Ben Gunter
  * @since Stripes 1.1
  */
 public class LayoutContext {
-    /**
-     * 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.";
+    private static final Log log = Log.getInstance(LayoutContext.class);
 
+    /** The attribute name by which the stack of layout contexts can be found 
in the request. */
+    public static final String REQ_ATTR_NAME = "stripes.layout.ContextStack";
+
     /**
-     * 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.
+     * Look up the stack of layout contexts 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;
+    public static LinkedList<LayoutContext> getStack(PageContext pageContext, 
boolean create) {
         ServletRequest request = pageContext.getRequest();
-        LinkedList<LayoutContext> stack = (LinkedList<LayoutContext>) 
request.getAttribute(key);
+        LinkedList<LayoutContext> stack = (LinkedList<LayoutContext>) request
+                .getAttribute(REQ_ATTR_NAME);
         if (create && stack == null) {
             stack = new LinkedList<LayoutContext>();
-            request.setAttribute(key, stack);
+            request.setAttribute(REQ_ATTR_NAME, stack);
         }
         return stack;
     }
 
     /**
-     * Look up the current layout context associated with the named layout in 
a JSP page context.
+     * Create a new layout context for the given render tag and push it onto 
the stack of layout
+     * contexts in a JSP page context.
+     */
+    public static LayoutContext push(LayoutRenderTag renderTag) {
+        LayoutContext context = new LayoutContext(renderTag);
+        log.debug("Push context ", context.getRenderPage(), " -> ", 
context.getDefinitionPage());
+        PageContext pageContext = renderTag.getPageContext();
+        LinkedList<LayoutContext> stack = getStack(pageContext, true);
+        if (stack.isEmpty()) {
+            // Clear the output buffer before beginning a layout render
+            try {
+                pageContext.getOut().clearBuffer();
+            }
+            catch (IOException e) {
+                log.warn("Failed to clear output buffer before rendering a 
layout");
+            }
+
+            // Create a new layout writer and push a new body
+            context.out = new LayoutWriter(pageContext.getOut());
+            pageContext.pushBody(context.out);
+        }
+        else {
+            context.out = stack.getLast().out;
+        }
+        stack.add(context);
+        return context;
+    }
+
+    /**
+     * Look up the current layout context 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;
+    public static LayoutContext lookup(PageContext pageContext) {
+        LinkedList<LayoutContext> stack = getStack(pageContext, false);
+        return stack == null || stack.isEmpty() ? null : stack.getLast();
     }
 
     /**
-     * Remove the current layout context from the stack of layout contexts 
associated with the named
-     * layout.
+     * Remove the current layout context from the stack of layout contexts.
      * 
      * @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;
+    public static LayoutContext pop(PageContext pageContext) {
+        LinkedList<LayoutContext> stack = getStack(pageContext, false);
+        LayoutContext context = stack == null || stack.isEmpty() ? null : 
stack.removeLast();
+        log.debug("Pop context ", context.getRenderPage(), " -> ", 
context.getDefinitionPage());
+        return context;
     }
 
+    private LayoutRenderTag renderTag;
+    private LayoutWriter out;
     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;
+    private String renderPage, component;
+    private boolean componentRenderPhase, rendered;
 
     /**
-     * Gets the Map of overridden components.  Will return an empty Map if no 
components were
+     * A new context may be created only by a {...@link LayoutRenderTag}. The 
tag provides all the
+     * information necessary to initialize the context.
+     * 
+     * @param tag The tag that is beginning a new layout render process.
+     */
+    public LayoutContext(LayoutRenderTag renderTag) {
+        this.renderTag = renderTag;
+        this.renderPage = renderTag.getCurrentPagePath();
+    }
+
+    /** Get the render tag that created this context. */
+    public LayoutRenderTag getRenderTag() { return renderTag; }
+
+    /**
+     * Gets the Map of overridden components. Will return an empty Map if no 
components were
      * overridden.
      */
     public Map<String,LayoutComponentRenderer> getComponents() { return 
components; }
@@ -106,15 +149,24 @@
     /** 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 path to the page that contains the {...@link 
LayoutDefinitionTag} referenced by the render tag. */
+    public String getDefinitionPage() { return getRenderTag().getName(); }
 
-    /** Get the name of the component to be rendered on this pass from 
{...@link LayoutDefinitionTag}. */
-    public String getCurrentComponentName() { return currentComponentName; }
+    /** True if the intention of the current page execution is solely to 
render a component. */
+    public boolean isComponentRenderPhase() { return componentRenderPhase; }
 
-    /** Set the name of the component to be rendered on this pass from 
{...@link LayoutDefinitionTag}. */
-    public void setCurrentComponentName(String currentComponentName) { 
this.currentComponentName = currentComponentName; }
+    /** Set the flag that indicates that the coming execution phase is solely 
to render a component. */ 
+    public void setComponentRenderPhase(boolean b) { this.componentRenderPhase 
= b; } 
 
+    /** Get the name of the component to be rendered during the current phase 
of execution. */
+    public String getComponent() { return component; }
+
+    /** Set the name of the component to be rendered during the current phase 
of execution. */
+    public void setComponent(String component) { this.component = component; }
+
+    /** Get the layout writer to which the layout is rendered. */
+    public LayoutWriter getOut() { return out; }
+
     /** 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-19 18:53:03 UTC (rev 1237)
+++ 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutDefinitionTag.java
      2010-05-21 05:18:07 UTC (rev 1238)
@@ -14,56 +14,38 @@
  */
 package net.sourceforge.stripes.tag.layout;
 
-import net.sourceforge.stripes.controller.StripesConstants;
-import net.sourceforge.stripes.exception.StripesJspException;
-import net.sourceforge.stripes.tag.StripesTagSupport;
-import net.sourceforge.stripes.util.Log;
+import java.util.Map;
+import java.util.Map.Entry;
 
 import javax.servlet.jsp.JspException;
 import javax.servlet.jsp.PageContext;
 
-import java.io.IOException;
-import java.util.Map;
-import java.util.Map.Entry;
+import net.sourceforge.stripes.exception.StripesJspException;
 
 /**
  * On the surface, allows a developer to define a layout using a custom tag - 
but is actually
  * the tag responsible for generating the output of the layout.  A layout can 
have zero or more
  * nested components, as well as regular text and other custom tags nested 
within it.
  *
- * @author Tim Fennell
+ * @author Tim Fennell, Ben Gunter
  * @since Stripes 1.1
  */
-public class LayoutDefinitionTag extends StripesTagSupport {
-    private static final Log log = Log.getInstance(LayoutDefinitionTag.class);
-
-    private String layoutName;
+public class LayoutDefinitionTag extends LayoutTag {
     private LayoutContext context;
+    private boolean renderPhase, silent;
 
     /**
-     * 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 String getLayoutName() {
-        if (layoutName == null) {
-            layoutName = (String) getPageContext().getRequest().getAttribute(
-                    StripesConstants.REQ_ATTR_INCLUDE_PATH);
-        }
-
-        return layoutName;
-    }
-
-    /**
      * Get the current layout context.
      * 
      * @throws StripesJspException If there is no {...@link LayoutContext} for 
this layout in the
      *             current {...@link PageContext}.
      */
-    public LayoutContext getLayoutContext() throws StripesJspException {
+    public LayoutContext getContext() throws StripesJspException {
         if (context == null) {
-            context = LayoutContext.find(getPageContext(), getLayoutName());
+            context = LayoutContext.lookup(pageContext);
+
             if (context == null) {
-                throw new StripesJspException("The JSP page " + getLayoutName()
+                throw new StripesJspException("The JSP page " + 
getCurrentPagePath()
                         + " contains a layout-definition tag and was invoked 
directly. "
                         + "A layout-definition can only be invoked by a page 
that contains "
                         + "a layout-render tag.");
@@ -82,26 +64,27 @@
      */
     @Override
     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 {
-            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);
-        }
+        LayoutContext context = getContext(); // Initialize context
+        renderPhase = context.isComponentRenderPhase(); // Initialize phase 
flag
 
-        // Put any additional parameters into page context for the definition 
to use
-        LayoutContext context = getLayoutContext();
-        for (Map.Entry<String, Object> entry : 
context.getParameters().entrySet()) {
-            pageContext.setAttribute(entry.getKey(), entry.getValue());
+        if (!renderPhase) {
+            // Put any additional parameters into page context for the 
definition to use
+            for (Map.Entry<String, Object> entry : 
context.getParameters().entrySet()) {
+                pageContext.getRequest().setAttribute(entry.getKey(), 
entry.getValue());
+            }
+            for (Entry<String, LayoutComponentRenderer> entry : 
context.getComponents().entrySet()) {
+                entry.getValue().pushPageContext(pageContext);
+                pageContext.getRequest().setAttribute(entry.getKey(), 
entry.getValue());
+            }
+
+            // Flag this definition has rendered, even though it's not really 
done yet.
+            context.setRendered(true);
         }
-        for (Entry<String, LayoutComponentRenderer> entry : 
context.getComponents().entrySet()) {
-            entry.getValue().pushPageContext(pageContext);
-            pageContext.setAttribute(entry.getKey(), entry.getValue());
-        }
 
+        // Save output's current silent flag to be restored later
+        silent = context.getOut().isSilent();
+        context.getOut().setSilent(renderPhase, pageContext);
+
         return EVAL_BODY_INCLUDE;
     }
 
@@ -112,18 +95,23 @@
     @Override
     public int doEndTag() throws JspException {
         try {
-            getLayoutContext().setRendered(true);
+            LayoutContext context = getContext();
+            if (!renderPhase) {
+                // Pop our page context off the renderer's page context stack
+                for (LayoutComponentRenderer renderer : 
context.getComponents().values()) {
+                    renderer.popPageContext();
+                }
+            }
+
+            // Restore output's silent flag
+            context.getOut().setSilent(silent, pageContext);
+
             return SKIP_PAGE;
         }
         finally {
-            // Pop our page context off the renderer's page context stack
-            for (LayoutComponentRenderer renderer : 
context.getComponents().values()) {
-                renderer.popPageContext();
-            }
-
-            // Set fields back to null
-            this.layoutName = null;
             this.context = null;
+            this.renderPhase = false;
+            this.silent = false;
         }
     }
 }

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-19 18:53:03 UTC (rev 1237)
+++ 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutRenderTag.java
  2010-05-21 05:18:07 UTC (rev 1238)
@@ -14,77 +14,78 @@
  */
 package net.sourceforge.stripes.tag.layout;
 
-import javax.servlet.http.HttpServletRequest;
+import java.util.LinkedList;
+
 import javax.servlet.jsp.JspException;
-import javax.servlet.jsp.PageContext;
 import javax.servlet.jsp.tagext.DynamicAttributes;
 
 import net.sourceforge.stripes.exception.StripesJspException;
-import net.sourceforge.stripes.tag.StripesTagSupport;
-import net.sourceforge.stripes.util.HttpUtil;
+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
+ * @author Tim Fennell, Ben Gunter
  * @since Stripes 1.1
  */
-public class LayoutRenderTag extends StripesTagSupport implements 
DynamicAttributes {
+public class LayoutRenderTag extends LayoutTag implements DynamicAttributes {
+    private static final Log log = Log.getInstance(LayoutRenderTag.class);
+
     private String name;
-    private Boolean recursing;
     private LayoutContext context;
+    private Boolean newContext;
+    private boolean silent;
 
+    /**
+     * True if this is the "outer" tag. That is, the render tag that kicks off 
the whole layout
+     * rendering process.
+     */
+    public boolean isOuterTag() {
+        LinkedList<LayoutContext> stack = LayoutContext.getStack(pageContext, 
false);
+        return stack != null && stack.size() < 2;
+    }
+
     /** Gets the name of the layout to be used. */
     public String getName() { return name; }
 
     /** 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();
+            LayoutContext context = LayoutContext.lookup(pageContext);
+            boolean contextNew = false;
+
+            if (context == null || !context.isComponentRenderPhase()) {
+                context = LayoutContext.push(this);
+                contextNew = true;
+            }
+
+            this.context = context;
+            this.newContext = contextNew;
         }
 
         return context;
     }
 
+    /** True if the context returned by {...@link #getContext()} was newly 
created by this tag. */
+    public boolean isNewContext() {
+        // Force initialization of the context if necessary
+        if (newContext == null)
+            getContext();
+
+        return newContext;
+    }
+
     /** Used by the JSP container to provide the tag with dynamic attributes. 
*/
     public void setDynamicAttribute(String uri, String localName, Object 
value) throws JspException {
         getContext().getParameters().put(localName, value);
     }
 
     /**
-     * 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 renderer the object that will render the component to a string
-     */
-    public void addComponent(String name, LayoutComponentRenderer renderer) {
-        getContext().getComponents().put(name, renderer);
-    }
-
-    /**
      * On the first pass (see {...@link #isRecursing()}):
      * <ul>
      * <li>Push the values of any dynamic attributes into page context 
attributes for the duration
@@ -97,7 +98,11 @@
      */
     @Override
     public int doStartTag() throws JspException {
-        if (!isRecursing()) {
+        LayoutContext context = getContext();
+
+        if (isNewContext()) {
+            log.debug("Start layout init in ", context.getRenderPage());
+
             // Ensure absolute path for layout name
             if (!getName().startsWith("/")) {
                 throw new StripesJspException("The name= attribute of the 
layout-render tag must be " +
@@ -105,9 +110,16 @@
                     "layout-render tag with the name '" + getName() + "' 
accordingly.");
             }
 
-            pushPageContextAttributes(getContext().getParameters());
+            pushPageContextAttributes(context.getParameters());
         }
 
+        // Save output's current silent flag to be restored later
+        silent = context.getOut().isSilent();
+        context.getOut().setSilent(false, pageContext);
+
+        log.debug("Start component render phase for ", context.getComponent(), 
" in ", context
+                .getRenderPage());
+
         return EVAL_BODY_INCLUDE;
     }
 
@@ -124,17 +136,20 @@
     @Override
        public int doEndTag() throws JspException {
         try {
-            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());
+            LayoutContext context = getContext();
+            if (isNewContext()) {
+                log.debug("End layout init in ", context.getRenderPage());
 
-                // Now include the target JSP
-                HttpServletRequest request = (HttpServletRequest) 
pageContext.getRequest();
-                
getContext().setRenderPage(HttpUtil.getRequestedServletPath(request));
                 try {
+                    log.debug("Start layout exec in ", 
context.getDefinitionPage());
+                    boolean outer = isOuterTag();
+                    boolean silent = context.getOut().isSilent();
+                    if (!outer)
+                        context.getOut().setSilent(true, pageContext);
                     pageContext.include(this.name, false);
+                    if (!outer)
+                        context.getOut().setSilent(silent, pageContext);
+                    log.debug("End layout exec in ", 
context.getDefinitionPage());
                 }
                 catch (Exception e) {
                     throw new StripesJspException(
@@ -145,7 +160,7 @@
 
                 // Check that the layout actually got rendered as some 
containers will
                 // just quietly ignore includes of non-existent pages!
-                if (!getContext().isRendered()) {
+                if (!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 " +
@@ -153,13 +168,20 @@
                         );
                 }
 
-                LayoutContext.pop(pageContext, getName());
+                LayoutContext.pop(pageContext);
                 popPageContextAttributes(); // remove any dynattrs from page 
scope
             }
+
+            // Restore output's silent flag
+            context.getOut().setSilent(silent, pageContext);
+
+            log.debug("End component render phase for ", 
context.getComponent(), " in ", context
+                    .getRenderPage());
         }
         finally {
-            this.recursing = null;
             this.context = null;
+            this.newContext = null;
+            this.silent = false;
         }
 
         return EVAL_PAGE;

Added: 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutTag.java
===================================================================
--- 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutTag.java    
                            (rev 0)
+++ 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutTag.java    
    2010-05-21 05:18:07 UTC (rev 1238)
@@ -0,0 +1,63 @@
+/* Copyright 2010 Ben Gunter
+ *
+ * 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 javax.servlet.http.HttpServletRequest;
+import javax.servlet.jsp.tagext.Tag;
+
+import net.sourceforge.stripes.controller.StripesConstants;
+import net.sourceforge.stripes.tag.StripesTagSupport;
+import net.sourceforge.stripes.util.HttpUtil;
+
+/**
+ * Abstract base class for the tags that handle rendering of layouts.
+ * 
+ * @author Ben Gunter
+ * @since Stripes 1.5.4
+ */
+public abstract class LayoutTag extends StripesTagSupport {
+    /** Get the context-relative path of the page that invoked this tag. */
+    public String getCurrentPagePath() {
+        HttpServletRequest request = (HttpServletRequest) 
pageContext.getRequest();
+        String path = (String) 
request.getAttribute(StripesConstants.REQ_ATTR_INCLUDE_PATH);
+        if (path == null)
+            path = HttpUtil.getRequestedPath(request);
+        return path;
+    }
+
+    /** True if this tag is executing as a child of the {...@link 
LayoutDefinitionTag}. */
+    public boolean isChildOfDefinition() {
+        for (Tag tag = getParent(); tag != null; tag = tag.getParent()) {
+            if (tag instanceof LayoutDefinitionTag)
+                return true;
+            else if (tag instanceof LayoutRenderTag)
+                return false;
+        }
+
+        return false;
+    }
+
+    /** True if this tag is executing as a child of the {...@link 
LayoutRenderTag}. */
+    public boolean isChildOfRender() {
+        for (Tag tag = getParent(); tag != null; tag = tag.getParent()) {
+            if (tag instanceof LayoutDefinitionTag)
+                return false;
+            else if (tag instanceof LayoutRenderTag)
+                return true;
+        }
+
+        return false;
+    }
+}

Added: 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutWriter.java
===================================================================
--- 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutWriter.java 
                            (rev 0)
+++ 
branches/1.5.x/stripes/src/net/sourceforge/stripes/tag/layout/LayoutWriter.java 
    2010-05-21 05:18:07 UTC (rev 1238)
@@ -0,0 +1,90 @@
+/* Copyright 2010 Ben Gunter
+ *
+ * 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 java.io.Writer;
+
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.PageContext;
+
+import net.sourceforge.stripes.util.Log;
+
+/**
+ * A writer that wraps around the normal JSP writer with the ability to 
silence the output
+ * temporarily. This is required to prevent the non-layout contents of a 
{...@link LayoutDefinitionTag}
+ * from rendering more than once when {...@link LayoutRenderTag}s and 
{...@link LayoutComponentTag}s are
+ * nested within it. The definition tag silences output during a component 
render phase, and the
+ * component that wishes to render turns output back on during its body 
evaluation.
+ * 
+ * @author Ben Gunter
+ * @since Stripes 1.5.4
+ */
+public class LayoutWriter extends Writer {
+    private static final Log log = Log.getInstance(LayoutWriter.class);
+
+    private JspWriter out;
+    private boolean silent;
+
+    /**
+     * Create a new layout writer that wraps the given JSP writer.
+     * 
+     * @param out The JSP writer to which output will be written.
+     */
+    public LayoutWriter(JspWriter out) {
+        log.debug("Create layout writer wrapped around ", out);
+        this.out = out;
+    }
+
+    /** If true, then discard all output. If false, then resume sending output 
to the JSP writer. */
+    public boolean isSilent() {
+        return silent;
+    }
+
+    /**
+     * Enable or disable silent mode. The output buffer for the given page 
context will be flushed
+     * before silent mode is enabled to ensure all buffered data are written.
+     */
+    public void setSilent(boolean silent, PageContext context) {
+        try {
+            if (context != null)
+                context.getOut().flush();
+        }
+        catch (IOException e) {
+            // This seems to happen once at the beginning and once at the end. 
Don't know why.
+            log.debug("Failed to flush buffer: ", e.getMessage());
+        }
+        finally {
+            this.silent = silent;
+            log.trace("Output is " + (silent ? "DISABLED" : "ENABLED"));
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        out.close();
+    }
+
+    @Override
+    public void flush() throws IOException {
+        out.flush();
+    }
+
+    @Override
+    public void write(char[] cbuf, int off, int len) throws IOException {
+        if (!isSilent())
+            out.write(cbuf, off, len);
+    }
+}


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