javax.faces.webapp.UIComponentClassicTagBase Maven / Gradle / Ivy
/*
*/
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package javax.faces.webapp;
import javax.faces.FacesException;
import javax.faces.application.Application;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIOutput;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.jstl.core.LoopTag;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTag;
import javax.servlet.jsp.tagext.JspIdConsumer;
import javax.servlet.jsp.tagext.Tag;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
/**
* UIComponentTagBase
is the base class
* for all JSP tags that use the "classic" JSP tag interface that
* correspond to a {@link javax.faces.component.UIComponent} instance in
* the view. In Faces 1.2, all component tags are BodyTag
* instances to allow for the execution of the page to build the
* component tree, but not render it. Rendering happens only after the
* component tree is completely built.
*
* {@link UIComponentTag} extends
* UIComponentClassicTagBase
to add support for properties
* that conform to the Faces 1.1 EL.
*
* {@link UIComponentELTag} extends
* UIComponentClassicTagBase
class to add support for
* properties that conform to the EL API.
*
* The default implementation allows the proper interweaving of
* template text, non-Faces JSP tag output, and Faces component tag
* output in the same page, as expected by the page author.
*
* The CASE markers in the following example will be cited in the
* method descriptions of this class.
*
*
*
* CASE 1 describes template text and/or non-component custom tag
* output occurring as the child of a component tag, but before the
* first component tag child of that component tag.
*
* CASE 2 describes template text and/or non-component custom tag
* output occurring between two sibling component tags.
*
* CASE 3 describes template text and/or non-component custom tag
* output occurring as the child content of an <f:verbatim>
* tag at any point in the page.
*
* CASE 4 describes template text and/or non-component custom tag
* output occurring between the last child component tag and its
* enclosing parent component tag's end tag.
*
*
*
*
<h:panelGrid style="color:red" border="4" columns="2">
CASE 1
<h:outputText value="component 1"/>
CASE 2
<h:outputText value="component 2"/>
<f:verbatim>CASE 3</f:verbatim>
<c:out value="${pageScope.CASE4}" />
</h:panelGrid>
*
*
* The preceding arrangement of faces component tags, must yield
* markup that will render identically to the following (assuming that
* ${pageScope.CASE4}
evaluates to "CASE 4
"
* without the quotes).
*
*
<table border="4" style="color:red">
<tbody>
<tr><td>CASE 1</td></tr> <tr><td>component 1</td></tr>
<tr><td>CASE 2</td> <tr><td>component 2</td></tr>
<tr><td>CASE 3</td> <td>CASE 4</td></tr>
</tbody>
</table>
*
*
*/
public abstract class UIComponentClassicTagBase extends UIComponentTagBase implements JspIdConsumer, BodyTag {
// ------------------------------------------------------ Manifest Constants
/**
* The request scope attribute under which a component tag stack
* for the current request will be maintained.
*/
private static final String COMPONENT_TAG_STACK_ATTR =
"javax.faces.webapp.COMPONENT_TAG_STACK";
/**
* The {@link UIComponent} attribute under which we will store a
* List
of the component identifiers of child components
* created on the previous generation of this page (if any).
*/
private static final String JSP_CREATED_COMPONENT_IDS =
"javax.faces.webapp.COMPONENT_IDS";
/**
* The {@link UIComponent} attribute under which we will store a
* List
of the facet names of facets created on the previous
* generation of this page (if any).
*/
private static final String JSP_CREATED_FACET_NAMES =
"javax.faces.webapp.FACET_NAMES";
/**
*
The attribute name under which we will store all {@link UIComponent}
* IDs of the current translation unit.
*/
private static final String GLOBAL_ID_VIEW =
"javax.faces.webapp.GLOBAL_ID_VIEW";
/**
* The attribute name under which we will store the {@link FacesContext}
* for this request.
*/
private static final String CURRENT_FACES_CONTEXT =
"javax.faces.webapp.CURRENT_FACES_CONTEXT";
/**
* The attribute name under which we will store the {@link UIViewRoot}
* for this request.
*/
private static final String CURRENT_VIEW_ROOT =
"javax.faces.webapp.CURRENT_VIEW_ROOT";
/**
* Used as the prefix for ids. This is necessary to avoid
* uniqueness conflicts with the transient verbatim components.
*/
protected static final String UNIQUE_ID_PREFIX =
UIViewRoot.UNIQUE_ID_PREFIX + '_';
/**
* Used to store the previousJspId Map in requestScope
*/
private static final String PREVIOUS_JSP_ID_SET =
"javax.faces.webapp.PREVIOUS_JSP_ID_SET";
/**
* This is a Page
scoped marker to help us
* keep track of the different execution context we could
* be operating within, e.g. an include, or a tag file.
* The value of the attribute is an Integer that is unqiue
* to this page context.
*/
private static final String JAVAX_FACES_PAGECONTEXT_MARKER =
"javax.faces.webapp.PAGECONTEXT_MARKER";
/**
* This is a request
scoped attribute which contains
* an AtomicInteger which we use to increment the PageContext
* count.
*/
private static final String JAVAX_FACES_PAGECONTEXT_COUNTER =
"javax.faces.webapp.PAGECONTEXT_COUNTER";
// ------------------------------------------------------ Instance Variables
/**
* The bodyContent
for this tag handler.
*/
protected BodyContent bodyContent = null;
/**
* The {@link UIComponent} that is being encoded by this tag,
* if any.
*/
private UIComponent component = null;
/**
* The {@link FacesContext} for the request being processed, if any.
*
*/
private FacesContext context = null;
/**
* Was a new component instance dynamically created when our
* findComponent()
method was called.
*/
private boolean created = false;
/**
* The Lst
of {@link UIComponent} ids created or located
* by nested {@link UIComponentTag}s while processing the current
* request.
*/
private List createdComponents = null;
/**
* The List
of facet names created or located by nested
* {@link UIComponentTag}s while processing the current request.
*/
private List createdFacets = null;
/**
* The JSP PageContext
for the page we are embedded in.
*/
protected PageContext pageContext = null;
/**
* The JSP Tag
that is the parent of this tag.
*/
private Tag parent = null;
/**
* {@link #setJspId}
*/
private String jspId = null;
/**
* Only consulted in setJspId to detect the iterator case.
* Set in {@link #release}. Never cleared.
*/
//private String oldJspId = null;
/**
* This is simply the jspId prefixed by {@link #UNIQUE_ID_PREFIX}.
*/
private String facesJspId = null;
/**
* The component identifier for the associated component.
*/
private String id = null;
/**
* Caches the nearest enclosing {@link UIComponentClassicTagBase} of this
* tag. This is used for duplicate id detection.
*/
private UIComponentClassicTagBase parentTag = null;
/**
* Set to true if this component is nested inside of an iterating
* tag
*/
private boolean isNestedInIterator = false;
// --------------------------------------------- Support Methods for Tag
//
// Simple methods to be overridden by subclasses if necessary
//
/**
* Return the flag value that should be returned from the
* doStart()
method when it is called. Subclasses
* may override this method to return the appropriate value.
*
* @throws JspException to cause doStart()
to
* throw an exception
*/
protected int getDoStartValue() throws JspException {
int result = EVAL_BODY_BUFFERED;
return result;
}
/**
* Return the flag value that should be returned from the
* doEnd()
method when it is called. Subclasses
* may override this method to return the appropriate value.
*
* @throws JspException to cause doEnd()
to
* throw an exception
*/
protected int getDoEndValue() throws JspException {
return (EVAL_PAGE);
}
/**
* Delegate to the encodeBegin()
method of our
* corresponding {@link UIComponent}. This method is called from
* doStartTag()
. Normally, delegation occurs unconditionally;
* however, this method is abstracted out so that advanced tags can
* conditionally perform this call.
*
* @throws IOException if an input/output error occurs
*
* @deprecated No encoding is done during JSP page execution.
* Encoding is deferred until the page has completed executing to
* allow the entire tree to be built before any encoding occurs.
*/
protected void encodeBegin() throws IOException {
component.encodeBegin(context);
}
/**
*
Delegate to the encodeChildren()
method of our
* corresponding {@link UIComponent}. This method is called from
* doStartTag()
. Normally, delegation occurs unconditionally;
* however, this method is abstracted out so that advanced tags can
* conditionally perform this call.
*
* @throws IOException if an input/output error occurs
*
* @deprecated No encoding is done during JSP page execution.
* Encoding is deferred until the page has completed executing to
* allow the entire tree to be built before any encoding occurs.
*/
protected void encodeChildren() throws IOException {
component.encodeChildren(context);
}
/**
*
Delegate to the encodeEnd()
method of our
* corresponding {@link UIComponent}. This method is called from
* doStartTag()
. Normally, delegation occurs unconditionally;
* however, this method is abstracted out so that advanced tags can
* conditionally perform this call.
*
* @throws IOException if an input/output error occurs
*
* @deprecated No encoding is done during JSP page execution.
* Encoding is deferred until the page has completed executing to
* allow the entire tree to be built before any encoding occurs.
*/
protected void encodeEnd() throws IOException {
component.encodeEnd(context);
}
// --------------------------------------------------------- Tag Properties
/**
*
Set the PageContext
of the page containing this
* tag instance.
*
* @param pageContext The enclosing PageContext
*/
public void setPageContext(PageContext pageContext) {
this.pageContext = pageContext;
}
/**
* Return the Tag
that is the parent of this instance.
*/
public Tag getParent() {
return (this.parent);
}
/**
* Set the Tag
that is the parent of this instance.
*
* @param parent The new parent Tag
*/
public void setParent(Tag parent) {
this.parent = parent;
}
//
// Complex methods to support Tag
//
/**
* Set up the {@link javax.faces.context.ResponseWriter} for the
* current response, if this has not been done already.
*
* @deprecated. {@link
* javax.faces.application.ViewHandler#renderView} is now
* responsible for setting up the response writer. This method is
* now a no-op.
*/
protected void setupResponseWriter() {
}
/**
* Create a new child component using createComponent
,
* initialize its properties, and add it to its parent as a child.
*
* @param context {@link FacesContext} for the current request
* @param parent Parent {@link UIComponent} for the new child
* @param componentId Component identifier for the new child,
* or null
for no explicit identifier
*/
private UIComponent createChild(FacesContext context, UIComponent parent,
String componentId) throws JspException {
UIComponent component = createComponent(context, componentId);
UIComponentTagBase parentTag = getParentUIComponentClassicTagBase(pageContext);
int indexOfNextChildTag = parentTag.getIndexOfNextChildTag();
if (indexOfNextChildTag > parent.getChildCount()) {
indexOfNextChildTag = parent.getChildCount();
}
parent.getChildren().add(indexOfNextChildTag, component);
created = true;
return (component);
}
/**
* Create a new child component using createComponent
,
* initialize its properties, and add it to its parent as a facet.
*
* @param context {@link FacesContext} for the current request
* @param parent Parent {@link UIComponent} of the new facet
* @param name Name of the new facet
* @param newId id of the new facet
*/
private UIComponent createFacet(FacesContext context, UIComponent parent,
String name, String newId) throws JspException {
UIComponent component = createComponent(context, newId);
parent.getFacets().put(name, component);
created = true;
return (component);
}
/**
* Return a child with the specified component id from the specified
* component, if any; otherwise, return null
.
*
* @param component {@link UIComponent} to be searched
* @param componentId Component id to search for
*/
private static UIComponent getChild(UIComponent component, String componentId) {
Iterator kids = component.getChildren().iterator();
while (kids.hasNext()) {
UIComponent kid = kids.next();
if (componentId.equals(kid.getId())) {
return (kid);
}
}
return (null);
}
/**
* Find and return the {@link UIComponent}, from the component
* tree, that corresponds to this tag handler instance. If there
* is no such {@link UIComponent}, create one
* and add it as a child or facet of the {@link UIComponent} associated
* with our nearest enclosing {@link UIComponentTag}. The process for
* locating or creating the component is:
*
* - If we have previously located this component, return it.
* - Locate the parent component by looking for a parent
* {@link UIComponentTag} instance, and ask it for its component.
* If there is no parent {@link UIComponentTag} instance, this tag
* represents the root component, so get it from the current
*
Tree
and return it.
* - If this {@link UIComponentTag} instance has the
*
facetName
attribute set, ask the parent
* {@link UIComponent} for a facet with this name. If not found,
* create one, call setProperties()
with the new
* component as a parameter, and register it under this name.
* Return the found or created facet {@link UIComponent}.
* - Determine the component id to be assigned to the new
* component, as follows: if this {@link UIComponentTag} has
* an
id
attribute set, use that value; otherwise,
* generate an identifier that is guaranteed to be the same for
* this {@link UIComponent} every time this page is processed
* (i.e. one based on the location of all {@link UIComponentTag}
* instances without an id
attribute set).
* - Ask the parent {@link UIComponent} for a child with this identifier.
* If not found, create one, call
setProperties()
* with the new component as a parameter, and register it as a child
* with this identifier. Return the found or created
* child {@link UIComponent}.
*
* When creating a component, the process is:
*
* - Retrieve the component type by calling
* {@link UIComponentTag#getComponentType}
* - If the component has a
binding
attribute,
* create an expression from it, and call
* {@link Application#createComponent} with that expression,
* the {@link FacesContext}, and the component type. Store the
* expression using the key "binding"
.
* - Otherwise, call {@link Application#createComponent} with
* only the component type.
*
- Call
setProperties()
.
* - Add the new component as a child or facet of its parent
*
*/
protected UIComponent findComponent(FacesContext context)
throws JspException {
// Step 1 -- Have we already found the relevant component?
if (component != null) {
return (component);
}
// Step 2 -- Identify the component that is, or will be, our parent
UIComponentClassicTagBase parentTag =
getParentUIComponentClassicTagBase(pageContext);
UIComponent parentComponent;
if (parentTag != null) {
parentComponent = parentTag.getComponentInstance();
} else {
// Special case. The component to be found is the
// UIViewRoot.
// see if this is the first time this tag instance is trying
// to be bound to the UIViewRoot
parentComponent = context.getViewRoot();
// Has this UIViewRoot instance had a tag bound to it
// before?
if (null ==
parentComponent.getAttributes().get(CURRENT_VIEW_ROOT)) {
// No it hasn't.
// make sure setProperties() and setId() are called
// once per UIViewRoot instance.
try {
setProperties(parentComponent);
}
catch (FacesException e) {
if (e.getCause() instanceof JspException) {
throw ((JspException)e.getCause());
}
throw e;
}
if (null != this.id) {
parentComponent.setId(this.id);
}
else {
assert(null != getFacesJspId());
parentComponent.setId(getFacesJspId());
}
parentComponent.getAttributes().put(CURRENT_VIEW_ROOT,
CURRENT_VIEW_ROOT);
created = true;
}
else if (hasBinding()) {
try {
setProperties(parentComponent);
}
catch (FacesException e) {
if (e.getCause() instanceof JspException) {
throw ((JspException)e.getCause());
}
throw e;
}
}
// this is not the first time this tag instance is trying to
// be bound to this UIViewRoot, take no extra action.
component = parentComponent;
return (component);
}
// Step 3 -- Calculate the component identifier for this component
String newId = createId(context);
// Step 4 -- Create or return a facet with the specified name (if any)
String facetName = getFacetName();
if (facetName != null) {
component = parentComponent.getFacets().get(facetName);
if (component == null) {
component = createFacet(context, parentComponent, facetName,
newId);
}
return (component);
}
// Step 5 -- Create or return a child with the specified id
component = getChild(parentComponent, newId);
if (component == null) {
component = createChild(context, parentComponent, newId);
}
return (component);
}
//
// Tag tree navigation
//
/**
* Locate and return the nearest enclosing {@link UIComponentClassicTagBase}
* if any; otherwise, return null
.
*
* @param context PageContext
for the current page
*/
public static UIComponentClassicTagBase getParentUIComponentClassicTagBase(PageContext context) {
FacesContext facesContext = getFacesContext(context);
List list = (List) facesContext.getExternalContext().getRequestMap()
.get(COMPONENT_TAG_STACK_ATTR);
if (list != null) {
return ((UIComponentClassicTagBase) list.get(list.size() - 1));
} else {
return (null);
}
}
//
// Methods related to the createdComponents and createdFacets lists.
//
protected int getIndexOfNextChildTag() {
if (createdComponents != null) {
return (createdComponents.size());
} else {
return (0);
}
}
protected void addChild(UIComponent child) {
if (createdComponents == null) {
createdComponents = new ArrayList(6);
}
createdComponents.add(child.getId());
}
/*
* Adds argument child to component tree as a child of this component.
*/
void addChildToComponentAndTag(UIComponent child) {
UIComponent myComponent = this.getComponentInstance();
int indexOfNextChildTag = this.getIndexOfNextChildTag();
if (indexOfNextChildTag > myComponent.getChildCount()) {
indexOfNextChildTag = myComponent.getChildCount();
}
myComponent.getChildren().add(indexOfNextChildTag, child);
this.addChild(child);
}
protected void addFacet(String name) {
if (createdFacets == null) {
//noinspection CollectionWithoutInitialCapacity
createdFacets = new ArrayList(3);
}
createdFacets.add(name);
}
/**
* Pop the top {@link UIComponentTag} instance off of our component tag
* stack, deleting the stack if this was the last entry.
*/
private void popUIComponentClassicTagBase() {
Map requestMap =
context.getExternalContext().getRequestMap();
List list = (List) requestMap.get(COMPONENT_TAG_STACK_ATTR);
// if an exception occurred in a nested tag,
//there could be a few tags left in the stack.
UIComponentClassicTagBase uic = null;
while (list != null && uic != this) {
int idx = list.size() - 1;
uic = (UIComponentClassicTagBase) list.get(idx);
list.remove(idx);
if (idx < 1) {
requestMap.remove(COMPONENT_TAG_STACK_ATTR);
list = null;
}
}
}
/**
* Push the specified {@link UIComponentTag} instance onto our component
* tag stack, creating a stack if necessary.
*/
private void pushUIComponentClassicTagBase() {
Map requestMap =
context.getExternalContext().getRequestMap();
List list = TypedCollections.dynamicallyCastList((List)
requestMap.get(COMPONENT_TAG_STACK_ATTR), UIComponentClassicTagBase.class);
if (list == null) {
//noinspection CollectionWithoutInitialCapacity
list = new ArrayList();
requestMap.put(COMPONENT_TAG_STACK_ATTR, list);
}
list.add(this);
}
/**
* Retrieve from the {@link UIComponent} corresponding to this tag
* handler the list of all child component ids created by
* {@link UIComponentTag} instances the previous time this tree was
* rendered. Compare it to the list of children created during this
* page processing pass, and remove all children present on the old list
* but not in the new list. Save the list as a {@link UIComponent}
* attribute so that it gets saved as part of the component's state.
*/
private void removeOldChildren() {
// Remove old children that are no longer present
List oldList = TypedCollections.dynamicallyCastList(
(List) component.getAttributes().get(JSP_CREATED_COMPONENT_IDS), String.class);
if (oldList != null && !oldList.isEmpty()) {
if (createdComponents != null) {
// Components not in the new list need to be removed
Iterator olds = oldList.iterator();
while (olds.hasNext()) {
String old = olds.next();
if (!createdComponents.contains(old)) {
UIComponent child = component.findComponent(old);
// if a component is marked transient, it would have
// been already removed from the child list, but the
// oldList would still have it. In addition, the component
// might have manually been removed. So, if findComponent
// isn't successful, don't call remove child (it will NPE)
if ( child != null) {
component.getChildren().remove(child);
}
}
}
} else {
// All old components need to be removed
Iterator olds = oldList.iterator();
while (olds.hasNext()) {
String old = olds.next();
UIComponent child = component.findComponent(old);
if (child != null) {
component.getChildren().remove(child);
}
}
}
}
// Save the current list as a component attribute
if (createdComponents != null) {
component.getAttributes().put(JSP_CREATED_COMPONENT_IDS,
createdComponents);
} else {
component.getAttributes().remove(JSP_CREATED_COMPONENT_IDS);
}
createdComponents = null;
}
/**
* Retrieve from the {@link UIComponent} corresponding to this tag
* handler the list of all facet names created by {@link UIComponentTag}
* instances the previous time this tree was rendered. Compare it to the
* list of facets created during this page processing pass, and remove
* all facets present on the old list but not in the new list. Save the
* list as a {@link UIComponent} attribute so that it gets saved as part
* of the component's state.
*/
private void removeOldFacets() {
// Remove old facets that are no longer present
List oldList = TypedCollections.dynamicallyCastList(
(List) component.getAttributes().get(JSP_CREATED_FACET_NAMES), String.class);
if (oldList != null) {
if (createdFacets != null) {
// Facets not in the new list need to be removed
Iterator olds = oldList.iterator();
while (olds.hasNext()) {
String old = olds.next();
if (!createdFacets.contains(old)) {
component.getFacets().remove(old);
}
}
} else {
// All old facets need to be removed
Iterator olds = oldList.iterator();
while (olds.hasNext()) {
String old = olds.next();
component.getFacets().remove(old);
}
}
}
// Save the current list as a component attribute
if (createdFacets != null) {
component.getAttributes().put(JSP_CREATED_FACET_NAMES,
createdFacets);
} else {
component.getAttributes().remove(JSP_CREATED_FACET_NAMES);
}
createdFacets = null;
}
//
// Methods to support content interweaving
//
/**
*
* Create a transient UIOutput component from the body content,
* of this tag instance or return null if there is no body content,
* the body content is whitespace, or the body content is a
* comment.
*/
protected UIComponent createVerbatimComponentFromBodyContent() {
UIOutput verbatim = null;
String bodyContentString;
String trimString;
if (null != bodyContent &&
null != (bodyContentString = bodyContent.getString()) &&
0 < (trimString = bodyContent.getString().trim()).length()) {
if (!(trimString.startsWith(""))) {
verbatim = createVerbatimComponent();
verbatim.setValue(bodyContentString);
bodyContent.clearBody();
} else {
StringBuilder content = new StringBuilder(trimString.length());
int sIdx = trimString.indexOf("", sIdx);
while (sIdx >= 0 && eIdx >= 0) {
if (sIdx == 0) {
trimString = trimString.substring(eIdx + 3);
} else {
content.append(trimString.substring(0, sIdx));
trimString = trimString.substring(eIdx + 3);
}
sIdx = trimString.indexOf("", sIdx);
}
content.append(trimString);
String result = content.toString();
if (result.trim().length() > 0) {
verbatim = createVerbatimComponent();
verbatim.setValue(content.toString());
}
bodyContent.clearBody();
}
}
return verbatim;
}
/**
* Use the {@link Application} instance to create a new component
* with the following characteristics.
*
* componentType
is
* javax.faces.HtmlOutputText
.
*
* transient
is true
.
*
* escape
is false
.
*
* id
is
* FacesContext.getViewRoot().createUniqueId()
*
*/
protected UIOutput createVerbatimComponent() {
assert(null != getFacesContext());
UIOutput verbatim;
Application application = getFacesContext().getApplication();
verbatim = (UIOutput)
application.createComponent("javax.faces.HtmlOutputText");
verbatim.setTransient(true);
verbatim.getAttributes().put("escape", Boolean.FALSE);
verbatim.setId(getFacesContext().getViewRoot().createUniqueId());
return verbatim;
}
/**
* Add verbatim as a sibling of component in
* component in the parent's child list. verbatim is
* added to the list at the position immediatly preceding
* component.
*/
protected void addVerbatimBeforeComponent(
UIComponentClassicTagBase parentTag,
UIComponent verbatim,
UIComponent component) {
UIComponent parent = component.getParent();
if (null == parent) {
return;
}
List children = parent.getChildren();
// EDGE CASE:
// Consider CASE 1 or 2 where the component is provided via a
// component binding in session or application scope.
// The automatically created UIOuput instances for the template text
// will already be present. Check the JSP_CREATED_COMPONENT_IDS attribute,
// if present and the number of created components is the same
// as the number of children replace at a -1 offset from the current
// value of indexOfComponentInParent, otherwise, call add()
List createdIds = (List)
parent.getAttributes().get(JSP_CREATED_COMPONENT_IDS);
int indexOfComponentInParent = children.indexOf(component);
boolean replace =
(indexOfComponentInParent > 0 && createdIds != null &&
createdIds.size() == children.size());
if (replace) {
UIComponent oldVerbatim = children.get(indexOfComponentInParent - 1);
if (oldVerbatim instanceof UIOutput && oldVerbatim.isTransient()) {
children.set((indexOfComponentInParent - 1), verbatim);
} else {
children.add(indexOfComponentInParent, verbatim);
}
} else {
children.add(indexOfComponentInParent, verbatim);
}
parentTag.addChild(verbatim);
}
/**
* Add verbatim as a sibling of component in
* component in the parent's child list. verbatim is
* added to the list at the position immediatly following
* component.
*/
protected void addVerbatimAfterComponent(UIComponentClassicTagBase parentTag,
UIComponent verbatim,
UIComponent component) {
int indexOfComponentInParent;
UIComponent parent = component.getParent();
// invert the order of this if and the assignment below. Since this line is
// here, it appears an early return is acceptable/desired if parent is null,
// and, if it is null, we should probably check for that before we try to
// access it. 2006-03-15 jdl
if (null == parent) {
return;
}
List children = parent.getChildren();
indexOfComponentInParent = children.indexOf(component);
if (children.size() - 1 == indexOfComponentInParent) {
children.add(verbatim);
}
else {
children.add(indexOfComponentInParent + 1, verbatim);
}
parentTag.addChild(verbatim);
}
// ------------------------------------------------------------ Tag Methods
/**
*
* Perform any processing necessary to find (or create) the
* {@link UIComponent} instance in the view corresponding to this
* tag instance in the page and, if and only if a component was
* created, insert it into the tree at the proper location as
* expected by the page author. Secondarily, cause a transient
* {@link UIOutput} component to be created and placed in the tree
* before the UIComponent
instance for
* this tag. The value of this UIOutput
* component must include anything covered by CASE 1
or
* CASE 2
in the class description.
*
* The default implementation, which is intended to be sufficient
* for most components, implements this secondary requirement by
* calling {@link #getParentUIComponentClassicTagBase}, and calling
* {@link #createVerbatimComponentFromBodyContent} on the result.
* It then adds the returned component to the tree before the
* actual component for this tag instance instance by calling
* {@link #addVerbatimBeforeComponent}.
*
* Before returning, the component is pushed onto the component
* stack for this response to enable the {@link
* #getParentUIComponentClassicTagBase} method to work properly.
*
* The flag value to be returned is acquired by calling the
* getDoStartValue()
method, which tag subclasses may
* override if they do not want the default value.
*
* @throws JspException if an error occurs
*/
public int doStartTag() throws JspException {
// make sure that these ivars are reset at the beginning of the
// lifecycle for this tag.
createdComponents = null;
createdFacets = null;
UIComponent verbatim = null;
context = getFacesContext();
if (null == context) {
// PENDING(edburns): I18N
throw new JspException("Can't find FacesContext");
}
parentTag = getParentUIComponentClassicTagBase(pageContext);
Map requestMap = context.getExternalContext().getRequestMap();
Map componentIds;
if (parentTag == null) {
// create the map if we're the top level UIComponentTag
//noinspection CollectionWithoutInitialCapacity
componentIds = new HashMap();
requestMap.put(GLOBAL_ID_VIEW, componentIds);
} else {
componentIds = TypedCollections.dynamicallyCastMap((Map)
requestMap.get(GLOBAL_ID_VIEW), String.class, UIComponentTagBase.class);
}
// If we're not inside of a facet, and if we are inside of a
// rendersChildren==true component, stuff any template text or
// custom tag output into a transient component.
if (null == getFacetName() &&
null != parentTag) {
Tag p = this.getParent();
// If we're not inside a JSP tag or we're not inside
// a UIComponentTag flush the buffer
if (null == p || !(p instanceof UIComponentTagBase)) {
JspWriter out = pageContext.getOut();
if (!(out instanceof BodyContent)) {
try {
out.flush();
}
catch (IOException ioe) {
throw new JspException(ioe);
}
}
}
verbatim = parentTag.createVerbatimComponentFromBodyContent();
}
// Locate the UIComponent associated with this UIComponentTag,
// creating one if necessary
component = findComponent(context);
// if we have a verbatim component, add it after this component.
if (null != verbatim) {
addVerbatimBeforeComponent(parentTag,
verbatim, component);
}
Object tagInstance = null;
String clientId = null;
if (this.id != null) {
clientId = component.getClientId(context);
UIComponentClassicTagBase temp = (UIComponentClassicTagBase)
componentIds.get(clientId);
// According to the JavaDocs for JspIdConsumer tag handlers
// that implement this interface are not to be pooled, however
// due to a bug in Jasper this is not the case.
// Because of this, two tags with the same ID within the same
// naming container will not be detected as duplicates. So
// in order to ensure we detect it, if the instance is the same,
// verify the JSP IDs are different. If they are, then continue,
// if they aren't, then we're dealing with EVAL_BODY_AGAIN (see
// below)
//noinspection ObjectEquality
if (temp == this
&& !this.getJspId().equals(temp.getJspId())) {
tagInstance = this;
} else if (temp != null
&& temp != this
&& this.getJspId().equals(temp.getJspId())) {
// new instance, same JSP ID - this is the EVAL_BODY_AGAIN case.
tagInstance = temp;
}
}
// If we have a tag instance, then, most likely, a tag handler
// returned EVAL_BODY_AGAIN somewhere. Make sure the instance
// returned is the same as the current instance and if this is the case,
// do not perform ID validation as it has already been done.
if (tagInstance == null) {
// only check for id uniqueness if the author has manually given
// us an id.
if (null != this.id) {
// assert component ID uniqueness
if (clientId != null) {
if (componentIds.containsKey(clientId)) {
// PENDING i18n
StringWriter writer = new StringWriter(128);
printTree(context.getViewRoot(), clientId, writer, 0);
String msg = "Duplicate component id: '"
+ clientId
+ "', first used in tag: '"
+ componentIds.get(clientId).getClass().getName()
+ "'\n"
+ writer.toString();
throw new JspException(new IllegalStateException(msg));
} else {
componentIds.put(clientId, this);
}
}
}
// Add to parent's list of created components or facets if needed
if (parentTag != null) {
if (getFacetName() == null) {
parentTag.addChild(component);
} else {
parentTag.addFacet(getFacetName());
}
}
}
// Rendering is deferred until after the tree is completely
// created
// Return the appropriate control value
pushUIComponentClassicTagBase();
return (getDoStartValue());
}
/**
*
* Perform any processing necessary to handle the content
* implications of CASE 3 in the class description.
*
* The default implementation, which is intended to be sufficient
* for most components, calls {@link
* #createVerbatimComponentFromBodyContent} on this instance
* and adds it as a child of the component for this tag's component
* at the end of the child list. In addition, the following
* housekeeping steps are taken.
*
*
*
* - Retrieve from the {@link UIComponent} the set of component
* ids of child components created by {@link UIComponentTag}
* instances the last time this page was processed (if any).
* Compare it to the list of children created during this page
* processing pass, and remove all children present in the old list
* but not the new. Save the new list as a component attribute so
* that it gets saved as part of the component's state.
*
* - Retrieve from the {@link UIComponent} the set of facet names
* of facets created by {@link UIComponentTag} instances the last
* time this page was processed (if any). Compare it to the list of
* facets created during this page processing pass, and remove all
* facets present in the old list but not the new. Save the new
* list as a component attribute so that it gets saved as part of
* the component's state.
*
* - Release all references to the component, and pop it from the
* component stack for this response, removing the stack if this was
* the outermost component.
*
* The flag value to be returned is acquired by calling the
* getDoEndValue()
method, which tag subclasses may
* override if they do not want the default value.
*
* @throws JspException if an error occurs
*/
public int doEndTag() throws JspException {
// Remove old children and facets as needed
popUIComponentClassicTagBase();
removeOldChildren();
removeOldFacets();
// Render the children (if needed) and end of the component
// associated with this tag
try {
UIComponent verbatim;
UIComponentClassicTagBase parentTag =
getParentUIComponentClassicTagBase(pageContext);
if (null != (verbatim = this.createVerbatimComponentFromBodyContent())) {
component.getChildren().add(verbatim);
if (null != parentTag) {
parentTag.addChild(verbatim);
}
}
// else, we don't render rendersChildren==true
// components here
} catch (Throwable e) {
throw new JspException(e);
} finally {
component = null;
context = null;
}
// Return the appropriate control value
created = false;
this.release();
return (getDoEndValue());
}
/**
* Release any resources allocated during the execution of this
* tag handler.
*/
public void release() {
this.parent = null;
this.id = null;
this.facesJspId = null;
this.created = false;
this.bodyContent = null;
this.isNestedInIterator = false;
}
// -------------------------------------------- Support methods for BodyTag
/**
* Return the flag value that should be returned from the
* doAfterBody()
method when it is called. Subclasses
* may override this method to return the appropriate value.
*/
protected int getDoAfterBodyValue() throws JspException {
return (SKIP_BODY);
}
// -------------------------------------------------------- BodyTag Methods
/**
* Set the bodyContent
for this tag handler. This method
* is invoked by the JSP page implementation object at most once per
* action invocation, before doInitiBody()
. This method
* will not be invoked for empty tags or for non-empty tags whose
* doStartTag()
method returns SKIP_BODY
or
* EVAL_BODY_INCLUDE
.
*
* @param bodyContent The new BodyContent
for this tag
*/
public void setBodyContent(BodyContent bodyContent) {
this.bodyContent = bodyContent;
}
/**
* Get the JspWriter
from our BodyContent
.
*
*/
public JspWriter getPreviousOut() {
return (this.bodyContent.getEnclosingWriter());
}
public BodyContent getBodyContent() {
return (this.bodyContent);
}
/**
* Prepare for evaluation of the body. This method is invoked by the
* JSP page implementation object after setBodyContent()
* and before the first time the body is to be evaluated. This method
* will not be invoked for empty tags or for non-empty tags whose
* doStartTag()
method returns SKIP_BODY
* or EVAL_BODY_INCLUDE
.
*
* @throws JspException if an error is encountered
*/
public void doInitBody() throws JspException {
; // Default implementation does nothing
}
/**
*
* Perform any processing necessary to handle the content
* implications of CASE 4 in the class description.
*
* Return result from {@link #getDoAfterBodyValue}
* @throws JspException if an error is encountered
*/
public int doAfterBody() throws JspException {
UIComponent verbatim;
UIComponentClassicTagBase parentTag =
getParentUIComponentClassicTagBase(pageContext);
// if we are the root tag, or if we are inside of a
// rendersChildren==true component
//noinspection ObjectEquality
if (this == parentTag ||
(null != parentTag &&
parentTag.getComponentInstance().getRendersChildren())) {
// stuff the template text or custom tag output into a
// transient component
if (null != (verbatim = this.createVerbatimComponentFromBodyContent())) {
// EDGE CASE:
// Consider CASE 4 where the component is provided via a
// component binding in session or application scope.
// The verbatim instance will already be present. If we
// add again, the user will get duplicate component ID
// errors. Check the JSP_CREATED_COMPONENT_IDS attribute. If it is not present, we
// need to add the new verbatim child. If it is present, assume it is a
// List and check its size. If the size of the list is equal to the
// number of children currently in the component, replace the replace
// the child of this component at the index derived as follows. If
// indexOfChildInParent is 0, replace the child at the 0th index with
// the new verbatim child. Otherwise, replace the child at the
// (indexOfChildInParent - 1)th index with the new verbatim child.
List createdIds = (List)
component.getAttributes().get(JSP_CREATED_COMPONENT_IDS);
if (createdIds != null) {
int listIdx = component.getChildCount();
if (createdIds.size() == listIdx) {
component.getChildren().set((listIdx - 1), verbatim);
} else {
component.getChildren().add(verbatim);
}
} else {
component.getChildren().add(verbatim);
}
parentTag.addChild(verbatim);
}
}
return (getDoAfterBodyValue());
}
// ----------------------------------------------- Methods relating to Id
/**
* Set the component identifier for our component. If the
* argument begins with {@link
* UIViewRoot#UNIQUE_ID_PREFIX} throw an
* IllegalArgumentException
*
* @param id The new component identifier. This may not start with
* {@link UIViewRoot#UNIQUE_ID_PREFIX}.
*
* @throws IllegalArgumentException if the argument is
* non-null
and starts with {@link
* UIViewRoot#UNIQUE_ID_PREFIX}.
*/
public void setId(String id) {
if (null != id && id.startsWith(UIViewRoot.UNIQUE_ID_PREFIX)) {
throw new IllegalArgumentException();
}
this.id = id;
}
/**
* Return the id
value assigned by the page author.
*/
protected String getId() {
return (id);
}
/**
* If this method has been called before on this tag's useful
* lifetime (before {@link #release} was called), return the
* previously returned value. Otherwise, if {@link #getJspId}
* returns non-null
, prepend {@link #UNIQUE_ID_PREFIX}
* to the jspId
and return the result.
*/
protected String getFacesJspId() {
if (null == facesJspId) {
if (null != jspId) {
facesJspId = UNIQUE_ID_PREFIX + jspId;
// if this tag happens to be nested within ,
// jspId will be the same for each iteration. So it is
// transformed into a unique "id" by appending a counter which
// gets stored in request scope with jspId as the key for use
// during the next iteration.
if (isDuplicateId(facesJspId)) {
facesJspId = generateIncrementedId(facesJspId);
}
} else {
// jspId will be null if we're running in a container
// that doesn't support JspIdConsumer
facesJspId = getFacesContext().getViewRoot().createUniqueId();
}
}
return facesJspId;
}
/**
* Returns true if a component already exists with the same
* id
. This will be the case if this tag is
* nested within
tag or any other JSTL loop tag
* or if the page has components with the same Id
.
*
* @param componentId id
to be looked up for possible
* duplication.
* @return true if this nested with facesJspId
is duplicate.
*/
private boolean isDuplicateId(String componentId) {
boolean result = false;
if (parentTag != null) {
if (parentTag.isNestedInIterator) {
return true;
}
List childComponents = parentTag.createdComponents;
// PENDING: Need to analyze the impact of this look up on pages
// with several levels of nesting.
if (childComponents != null) {
result = childComponents.contains(componentId);
if (result && (!isNestedInIterator)) {
return true;
}
}
}
return result;
}
/*
* Appends a counter to the passed in id
and stores the
* id
and counter information in request scope.
*
* @return String id
with a counter appended to it.
*/
private String generateIncrementedId (String componentId) {
Map requestMap = getFacesContext().getExternalContext().getRequestMap();
Integer serialNum = (Integer) requestMap.get(componentId);
if (null == serialNum) {
serialNum = 1;
} else {
serialNum = serialNum.intValue() + 1;
}
requestMap.put(componentId, serialNum);
componentId = componentId + UNIQUE_ID_PREFIX + serialNum.intValue();
return componentId;
}
/**
* Returns the List
of {@link UIComponent} ids created or
* located by nested {@link UIComponentTag}s while processing the current
* request.
*/
protected List getCreatedComponents() {
return createdComponents;
}
/**
* Create the component identifier to be used for this component.
*/
private String createId(FacesContext context)
throws JspException {
if (this.id == null) {
return getFacesJspId();
} else {
// if this tag happens to be nested within , jspId
// will be the same for each iteration. So it is
// transformed into a unique "id" by appending a counter which gets
// stored in request scope with jspId as the key for use during next
// iteration.
if (isDuplicateId(this.id)) {
if (!isSpecifiedIdUnique(this.id)) {
if (isNestedInIterator) {
this.id = generateIncrementedId(this.id);
} else {
StringWriter writer = new StringWriter(128);
printTree(context.getViewRoot(), this.id, writer, 0);
String msg = "Component ID '"
+ this.id
+ "' has already been used"
+ " in the view.\n"
+ "See below for the view up to the point of"
+ " the detected error.\n"
+ writer.toString();
throw new JspException(msg);
}
}
}
return (this.id);
}
}
/**
* @param id the component ID
* @return true
if this ID is unique within the closest naming
* container, otherwise false
*/
private boolean isSpecifiedIdUnique(String id) {
UIComponentClassicTagBase containerTag = getParentNamingContainerTag();
UIComponent c = containerTag.component.findComponent(id);
if (c == null) {
return true;
} else {
UIComponent parent = c.getParent();
if (parent.equals(this.parentTag.component)) {
// the component we found has the same parent, If we find
// a sibling with the same ID, return true so that the
// id is incremented, otherwise, return false.
List created = this.parentTag.createdComponents;
return !(created != null && created.contains(id));
} else {
return false;
}
}
}
/**
* @return the parent tag that represents the closest NamingContainer
* component.
*/
private UIComponentClassicTagBase getParentNamingContainerTag() {
if (this.parentTag == null) {
return this;
}
UIComponentClassicTagBase parent = this.parentTag;
while (parent != null) {
if (parent.component instanceof NamingContainer
|| parent.parentTag == null && parent.component instanceof UIViewRoot) {
return parent;
}
parent = parent.parentTag;
}
return null;
}
// ------------------------------------------------ JspIdConsumer Methods
/**
* Defined on {@link JspIdConsumer}. This method is called by
* the container before {@link #doStartTag}. The argument is
* guaranteed to be unique within the page.
*
* IMPLEMENTATION NOTE: This method will detect where we
* are in an include and assign a unique ID for each include
* in a particular 'logical page'. This allows us to avoid
* possible duplicate ID situations for included pages that
* have components without explicit IDs.
*
* @param id the container generated id for this tag, guaranteed to
* be unique within the page.
*/
public void setJspId(String id) {
// reset JSP ID here instead of release as we may need
// to check the ID after the tag has been used
this.jspId = null;
Map reqMap =
getFacesContext().getExternalContext().getRequestMap();
AtomicInteger aInt = (AtomicInteger) reqMap.get(JAVAX_FACES_PAGECONTEXT_COUNTER);
if (aInt == null) {
aInt = new AtomicInteger();
reqMap.put(JAVAX_FACES_PAGECONTEXT_COUNTER, aInt);
}
Integer pcId = (Integer)
pageContext.getAttribute(JAVAX_FACES_PAGECONTEXT_MARKER,
PageContext.PAGE_SCOPE);
if (pcId == null) {
pcId = aInt.incrementAndGet();
pageContext.setAttribute(JAVAX_FACES_PAGECONTEXT_MARKER, pcId);
}
if (pcId.intValue() > 1) {
StringBuilder builder = new StringBuilder(id.length() + 3);
builder.append(id).append("pc").append(pcId);
jspId = builder.toString();
} else {
jspId = id;
}
facesJspId = null;
updatePreviousJspIdAndIteratorStatus(jspId);
}
/**
* Called from {@link #setJspId} to update the value saved for
* the previous call to {@link #setJspId} for this component on
* this request. If this method is presented with the same
* argument id
for the same tag instance more than once
* on the same request, then we know that the tag instance lies
* inside an iterator tag, such as c:forEach
. If so,
* we set the isNestedInIterator
ivar to
* true
otherwise, we set it to false
.
*
* The implementation for this method stores a Map from tag
* instance to id String as a request scoped attribute. This map
* contains the value used as the previousJspId and compared with
* the argument id
.
*
* @param id the id to be compared with the previous id, if any, for
* this tag instance on this request.
*/
private void updatePreviousJspIdAndIteratorStatus(String id) {
Set previousJspIdSet;
if (null == (previousJspIdSet = TypedCollections.dynamicallyCastSet((Set)
pageContext.getAttribute(PREVIOUS_JSP_ID_SET, PageContext.PAGE_SCOPE), String.class))) {
//noinspection CollectionWithoutInitialCapacity
pageContext.setAttribute(PREVIOUS_JSP_ID_SET,
previousJspIdSet = new HashSet(),
PageContext.PAGE_SCOPE);
}
// detect the iterator case
if (previousJspIdSet.contains(id)) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Id " + id +
" is nested within an iterating tag.");
}
isNestedInIterator = true;
} else {
isNestedInIterator = false;
previousJspIdSet.add(id);
}
}
public String getJspId() {
return jspId;
}
// ------------------------------------------------------- Abstract methods
/**
* Override properties and attributes of the specified component,
* if the corresponding properties of this tag handler instance were
* explicitly set. This method must be called ONLY
* if the specified {@link UIComponent} was in fact created during
* the execution of this tag handler instance, and this call will occur
* BEFORE the {@link UIComponent} is added to
* the view.
*
* Tag subclasses that want to support additional set properties
* must ensure that the base class setProperties()
* method is still called. A typical implementation that supports
* extra properties foo
and bar
would look
* something like this:
*
* protected void setProperties(UIComponent component) {
* super.setProperties(component);
* if (foo != null) {
* component.setAttribute("foo", foo);
* }
* if (bar != null) {
* component.setAttribute("bar", bar);
* }
* }
*
*
* The default implementation overrides the following properties:
*
* rendered
- Set if a value for the
* rendered
property is specified for
* this tag handler instance.
* rendererType
- Set if the getRendererType()
* method returns a non-null value.
*
*
* @param component {@link UIComponent} whose properties are to be
* overridden
*/
protected abstract void setProperties(UIComponent component);
/**
* Create and return a new child component of the type returned
* by calling getComponentType()
. If this {@link
* UIComponentTag} has a non-null binding
attribute,
* this is done by call {@link Application#createComponent} with the
* expression created for the binding
attribute, and
* the expression will be stored on the component. Otherwise,
* {@link Application#createComponent} is called with only the
* component type. Finally, initialize the components id and other
* properties.
* @param context {@link FacesContext} for the current request
* @param newId id of the component
*/
protected abstract UIComponent createComponent(FacesContext context,
String newId) throws JspException;
/**
* Return true
if this component has a
* non-null
binding attribute. This method is
* necessary to allow subclasses that expose the
* binding
property as an Faces 1.1 style EL property
* as well as subclasses that expose it as an EL API property.
*/
protected abstract boolean hasBinding();
// --------------------------------------------------------- Properties
/**
* Return the {@link UIComponent} instance that is associated with
* this tag instance. This method is designed to be used by tags nested
* within this tag, and only returns useful results between the
* execution of doStartTag()
and doEndTag()
* on this tag instance.
*/
public UIComponent getComponentInstance() {
return (this.component);
}
/**
* Return true
if we dynamically created a new component
* instance during execution of this tag. This method is designed to be
* used by tags nested within this tag, and only returns useful results
* between the execution of doStartTag()
and
* doEndTag()
on this tag instance.
*/
public boolean getCreated() {
return (this.created);
}
protected FacesContext getFacesContext() {
if (context == null) {
if (null == (context = (FacesContext)
pageContext.getAttribute(CURRENT_FACES_CONTEXT))) {
context = FacesContext.getCurrentInstance();
if (context == null) { // PENDING - i18n
throw new RuntimeException("Cannot find FacesContext");
}
// store the current FacesContext for use by other
// UIComponentTags in the same page
pageContext.setAttribute(CURRENT_FACES_CONTEXT, context);
}
}
return (context);
}
/**
* Return the facet name that we should be stored under, if any;
* otherwise, return null (indicating that we will be a child component).
*
*/
protected String getFacetName() {
Tag parent = getParent();
if (parent instanceof FacetTag) {
return (((FacetTag) parent).getName());
} else {
return (null);
}
}
private static FacesContext getFacesContext(PageContext pageContext) {
FacesContext context = (FacesContext)
pageContext.getAttribute(CURRENT_FACES_CONTEXT);
if (context == null) {
context = FacesContext.getCurrentInstance();
if (context == null) {
throw new RuntimeException("Cannot find FacesContext");
} else {
pageContext.setAttribute(CURRENT_FACES_CONTEXT, context);
}
}
return (context);
}
private static void printTree(UIComponent root,
String duplicateId,
Writer out,
int curDepth) {
if (null == root) {
return;
}
if (duplicateId.equals(root.getId())) {
indentPrintln(out, "+id: " + root.getId() + " <===============",
curDepth);
} else {
indentPrintln(out, "+id: " + root.getId(), curDepth);
}
//noinspection ObjectToString
indentPrintln(out, " type: " + root.toString(), curDepth);
curDepth++;
// print all the facets of this component
for (UIComponent uiComponent : root.getFacets().values()) {
printTree(uiComponent, duplicateId, out, curDepth);
}
// print all the children of this component
for (UIComponent uiComponent : root.getChildren()) {
printTree(uiComponent, duplicateId, out, curDepth);
}
}
private static void indentPrintln(Writer out, String str, int curDepth) {
// handle indentation
try {
for (int i = 0; i < curDepth; i++) {
out.write(" ");
}
out.write(str + '\n');
} catch (IOException ex) {
// ignore
}
}
}