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