com.sun.jsftemplating.layout.descriptors.LayoutElementBase Maven / Gradle / Ivy
/*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the license at
* https://jsftemplating.dev.java.net/cddl1.html or
* jsftemplating/cddl1.txt.
* See the License for the specific language governing
* permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at jsftemplating/cddl1.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* you own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
*/
package com.sun.jsftemplating.layout.descriptors;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import com.sun.jsftemplating.component.ComponentUtil;
import com.sun.jsftemplating.layout.LayoutDefinitionManager;
import com.sun.jsftemplating.layout.descriptors.handler.Handler;
import com.sun.jsftemplating.layout.descriptors.handler.HandlerContext;
import com.sun.jsftemplating.layout.descriptors.handler.HandlerContextImpl;
import com.sun.jsftemplating.layout.event.AfterEncodeEvent;
import com.sun.jsftemplating.layout.event.BeforeEncodeEvent;
import com.sun.jsftemplating.layout.event.EncodeEvent;
import com.sun.jsftemplating.util.LayoutElementUtil;
/**
* This class provides some common functionality between the various types
* of {@link LayoutElement}s. It is the base class of most
* implementations (perhaps all).
*
* @author Ken Paulsen ([email protected])
*/
public abstract class LayoutElementBase implements LayoutElement {
/**
* Constructor.
*
* @param parent The parent LayoutElement
* @param id Identifier for this LayoutElement
*/
protected LayoutElementBase(LayoutElement parent, String id) {
setParent(parent);
_id = id;
}
/**
* This method is used to add a {@link LayoutElement}.
* {@link LayoutElement}s should be added sequentially in the order
* in which they are to be rendered.
*
* @param element The {@link LayoutElement} to add as a child.
*/
public void addChildLayoutElement(LayoutElement element) {
_layoutElements.add(element);
}
/**
* This method returns the {@link LayoutElement}s as a
* List
of {@link LayoutElement}.
*
* @return List of {@link LayoutElement}s.
*/
public List getChildLayoutElements() {
return _layoutElements;
}
/**
* This method returns the requested child {@link LayoutElement} by
* id
.
*
* @param id The id
of the child to find and return.
*
* @return The requested {@link LayoutElement}; null
if not
* found.
*/
public LayoutElement getChildLayoutElement(String id) {
Iterator it = getChildLayoutElements().iterator();
LayoutElement elt = null;
while (it.hasNext()) {
elt = it.next();
if (id.equals(elt.getUnevaluatedId())) {
return elt;
}
}
return null;
}
/**
* This method searches the LayoutElement
tree
* breadth-first for a LayoutElement
with the given
* id.
*/
public LayoutElement findLayoutElement(String id) {
if (id == null) {
return null;
}
// FIXME: Generalize this code so we can use it when creating the tree as well as searching for stuff.
// First look at all the immediate children, save compositions if
// we encounter them.
List children = getChildLayoutElements();
for (LayoutElement elt : children) {
if (id.equals(elt.getUnevaluatedId())) {
// Found it!
return elt;
}
}
// First make sure we aren't a LayoutComposition ourselves
LayoutElement result = null;
FacesContext context = FacesContext.getCurrentInstance();
if (this instanceof LayoutComposition) {
// Add LayoutComposition to the stack
LayoutComposition.push(context, this);
// Find the new LD tree...
LayoutDefinition def = LayoutDefinitionManager.getLayoutDefinition(
context, ((LayoutComposition) this).getTemplate());
// Recurse...
result = def.findLayoutElement(id);
LayoutComposition.pop(context);
}
// Next we need to walk deeper...
for (LayoutElement elt : children) {
if ((elt instanceof LayoutComposition)
&& (((LayoutComposition) elt).getTemplate() != null)) {
// Add LayoutComposition to the stack
LayoutComposition.push(context, elt);
// Find the new LD tree...
LayoutDefinition def = LayoutDefinitionManager.getLayoutDefinition(
context, ((LayoutComposition) elt).getTemplate());
// Recurse...
result = def.findLayoutElement(id);
LayoutComposition.pop(context);
} else if (elt instanceof LayoutInsert) {
// FIXME: Look through Stack/List of compositions we've already walked for inserted value.
} else {
// Just walk its children...
result = elt.findLayoutElement(id);
}
if (result != null) {
// FIXME: Manage stack!!!
break;
}
}
// Return result if found
return result;
}
/**
* This method walks to the top-most {@link LayoutElement}, which
* should be a {@link LayoutDefinition}. If not, it will throw an
* exception.
*
* @return The {@link LayoutDefinition}.
*/
public LayoutDefinition getLayoutDefinition() {
// Find the top-most LayoutElement
LayoutElement cur = this;
while (cur.getParent() != null) {
cur = cur.getParent();
}
// Incomplete LayoutElement trees may not have a LD at the root, make
// sure we have a LD.
if (!(cur instanceof LayoutDefinition)) {
// Not a LD, there is no LD... set return value to null
cur = null;
}
// This should be the LayoutDefinition, return it
return (LayoutDefinition) cur;
}
/**
* This method returns the parent {@link LayoutElement}.
*
* @return parent LayoutElement
*/
public LayoutElement getParent() {
return _parent;
}
/**
* This method sets the parent {@link LayoutElement}.
*
* @param parent Parent {@link LayoutElement}.
*/
protected void setParent(LayoutElement parent) {
_parent = parent;
}
/**
* Accessor method for id. This returns a non-null value, it may
* return "" if id is not set or does not apply.
*
* This method will also NOT resolve EL strings.
*
* @return a non-null id
*/
private String getId() {
if ((_id == null) || (_id == "")) {
// Ensure we ALWAYS have an id, however, generating ids is
// potentially dangerous because the results may not be the same
// always. Effort has been taken to avoid most problems, though.
_id = LayoutElementUtil.getGeneratedId((String) null);
}
return _id;
}
/**
* This method generally should not be used. It does not resolve
* expressions. Instead use
* {@link #getId(FacesContext, UIComponent)}.
*
* @return The unevaluated id.
*/
public String getUnevaluatedId() {
return getId();
}
/**
* Accessor method for id. This returns a non-null value, it may
* return "" if id is not set or does not apply.
*
* This method will also attempt to resolve EL strings.
*
* @param context The FacesContext
* @param parent The parent UIComponent
. This is used
* because the current UIComponent is typically
* unknown (or not even created yet).
*
* @return A non-null id.
*/
public String getId(FacesContext context, UIComponent parent) {
// Evaluate the id...
Object value = resolveValue(context, parent, getId());
// Return the result
return (value == null) ? "" : value.toString();
}
/**
* This method will attempt to resolve EL strings in the given
* value.
*
* @param context The FacesContext
* @param parent The parent UIComponent
. This is used
* because the current UIComponent is typically
* unknown (or not even created yet).
* @param value The String to resolve
*
* @return The evaluated value (may be null).
*/
public Object resolveValue(FacesContext context, UIComponent parent, Object value) {
return ComponentUtil.getInstance(context).resolveValue(context, this, parent, value);
}
/**
* This method allows each LayoutElement to provide it's own encode
* functionality. If the {@link LayoutElement} should render its
* children, this method should return true. Otherwise, this method
* should return false.
*
* @param context The FacesContext
* @param component The UIComponent
*
* @return true if children are to be rendered, false otherwise.
*/
protected abstract boolean encodeThis(FacesContext context, UIComponent component) throws IOException;
/**
* This is the base implementation for encode. Typically each type of
* LayoutElement wants to do something specific then conditionally have
* its children rendered. This method invokes the abstract method
* "encodeThis" to do specific functionality, it the walks the children
* and renders them, if encodeThis returns true. It skips the children
* if encodeThis returns false.
*
* NOTE: Some subclasses override this method, be careful when
* changing/adding to this code.
*
* @param context The FacesContext
* @param component The UIComponent
*/
public void encode(FacesContext context, UIComponent component) throws IOException {
// Invoke "before" handlers
Object result = dispatchHandlers(context, BEFORE_ENCODE,
new BeforeEncodeEvent(component));
if ((result != null) && (result.toString().equals("false"))) {
// Skip...
return;
}
// Do LayoutElement specific stuff...
boolean renderChildren = encodeThis(context, component);
// FIXME: Consider buffering HTML and passing to "endDisplay" handlers...
// FIXME: Storing in the EventObject may be useful if we go this route.
// Perhaps we want our own Response writer to buffer children?
//ResponseWriter out = context.getResponseWriter();
// Conditionally render children...
if (renderChildren) {
result = dispatchHandlers(context, ENCODE,
new EncodeEvent(component));
// Iterate over children
LayoutElement childElt = null;
Iterator it = getChildLayoutElements().iterator();
while (it.hasNext()) {
childElt = it.next();
childElt.encode(context, component);
}
}
// Invoke "after" handlers
result = dispatchHandlers(context, AFTER_ENCODE,
new AfterEncodeEvent(component));
}
/**
* This method iterates over the {@link Handler}s and executes each
* one. A {@link HandlerContext} will be created to pass to each
* {@link Handler}. The {@link HandlerContext} object is reused
* across all {@link Handler}s that are invoked; the
* {@link HandlerContext#setHandler(Handler)} method is invoked with
* the correct {@link Handler} descriptor before the handler is
* executed.
*
* @param context The FacesContext
* @param eventType The event type which is being fired
* @param event An optional EventObject
*
* @return By default, (null) is returned. However, if any of the
* {@link Handler}s produce a non-null return value, the value
* from the last {@link Handler} to produces a non-null return
* value is returned.
*/
public Object dispatchHandlers(FacesContext context, String eventType, EventObject event) {
// Get the handlers for this eventType
Object eventObj = event.getSource();
if (!(eventObj instanceof UIComponent)) {
eventObj = null;
}
List handlers = getHandlers(eventType, (UIComponent) eventObj);
// Make sure we have something to do...
if (handlers == null) {
return null;
}
// Create a HandlerContext
HandlerContext handlerContext =
createHandlerContext(context, event, eventType);
// This method is broken down so that recursion is easier
return dispatchHandlers(handlerContext, handlers);
}
/**
* As currently implemented, this method is essentially a utility
* method. It dispatches the given List of {@link Handler}s.
* This may be available as a static method in the future.
*/
public Object dispatchHandlers(HandlerContext handlerCtx, List handlers) {
FacesContext ctx = handlerCtx.getFacesContext();
Object retVal = null;
Object result = null;
// Only check for renderResponse if we're not already doing it
boolean checkRenderResp = !ctx.getRenderResponse();
// Iterate through the handlers...
for (Handler handler : handlers) {
if (ctx.getResponseComplete() ||
(checkRenderResp && ctx.getRenderResponse())) {
// If we shouldn't continue, just return the result.
// Should we throw an AbortProcessingException
return result;
}
handlerCtx.setHandler(handler);
try {
// Delegate to the Handler to perform invocation
retVal = handler.invoke(handlerCtx);
} catch (Exception ex) {
throw new RuntimeException(
ex.getClass().getName() + " while attempting to "
+ "process a '" + handlerCtx.getEventType()
+ "' event for '" + getId() + "'.", ex);
}
// Check for return value
if (retVal != null) {
result = retVal;
}
}
// Return the return value (null by default)
return result;
}
/**
* This method is responsible for creating a new HandlerContext. It
* does not set the Handler descriptor. This is done right before a
* Handler is invoked. This allows the HandlerContext object to be
* reused.
*
* @param context The FacesContext
*/
protected HandlerContext createHandlerContext(FacesContext context, EventObject event, String eventType) {
return new HandlerContextImpl(context, this, event, eventType);
}
/**
* This method retrieves the {@link Handler}s for the requested type.
*
* @param type The type of {@link Handler}s to retrieve.
*
* @return A List of {@link Handler}s.
*/
public List getHandlers(String type) {
return _handlersByType.get(type);
}
/**
* This method provides access to the "handlersByType"
* Map
.
*/
public Map> getHandlersByTypeMap() {
return _handlersByType;
}
/**
* This method provides a means to set the "handlersByType" Map.
* Normally this is done for each type individually via
* {@link #setHandlers(String, List)}. This Map may not be null (null
* will be ignored) and should contain entries that map to
* List
s of {@link Handler}s.
*/
public void setHandlersByTypeMap(Map> map) {
if (map != null) {
_handlersByType = map;
}
}
/**
* This method retrieves the {@link Handler}s for the requested
* type.
*
* @param type The type of Handler
s to retrieve.
* @param comp The associated UIComponent
(or null).
*
* @return A List
of {@link Handler}s.
*/
public List getHandlers(String type, UIComponent comp) {
// 1st get list of handlers for definition of this LayoutElement
List handlers = getHandlers(type);
// NOTE: At this point, very few types should support "instance"
// NOTE: handlers (LayoutComponent, LayoutDefinition, more??). To
// NOTE: support them, the future, the specific LayoutElement subclass
// NOTE: will have to deal with this. For example, LayoutComponent
// NOTE: "instance" handlers are dealt with in LayoutComponent (it
// NOTE: overrides this method).
return handlers;
}
/**
* This method associates 'type' with the given list of
* {@link Handler}s.
*
* @param type The String type for the List of {@link Handler}s
* @param handlers The List of {@link Handler}s
*/
public void setHandlers(String type, List handlers) {
_handlersByType.put(type, handlers);
}
/**
* This method is a convenience method for encoding the given
* UIComponent
. It calls the appropriate encoding
* methods on the component and calls itself recursively for all
* UIComponent
children that do not render their own
* children.
*
* @param context FacesContext
* @param component UIComponent
to encode
*/
public static void encodeChild(FacesContext context, UIComponent component) throws IOException {
if (!component.isRendered()) {
return;
}
/******* REMOVE THIS IF TABLE IS EVER FIXED TO WORK RIGHT *******/
/*
* This code is removed b/c of the way the Table code is designed. It
* needs to recalculate the clientId all the time. Rather than deal with
* this in the table code, the design requires that every component
* regenerate its clientId every time it is rendered. Hopefully the Table
* code will be rewritten to not require this, or to do this task itself.
*
* For now, I will avoid doing the "right" thing and reset the id blindly.
* This causes the clientId to be erased and regenerated.
*/
String id = component.getId();
if (id != null) {
component.setId(id);
}
/******* REMOVE THIS IF TABLE IS EVER FIXED TO WORK RIGHT *******/
// FIXME: May have to change to encodeAll()!!!
component.encodeBegin(context);
if (component.getRendersChildren()) {
component.encodeChildren(context);
} else {
Iterator it = component.getChildren().iterator();
while (it.hasNext()) {
encodeChild(context, it.next());
}
}
component.encodeEnd(context);
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer();
LayoutElementUtil.dumpTree(this, buf, "");
return buf.toString();
}
/**
* List of child LayoutElements (if, facet, UIComponents, etc.)
*/
private List _layoutElements = new ArrayList();
/**
* The parent LayoutElement. This will be null for the LayoutDefinition.
*/
private LayoutElement _parent = null;
/**
* Map
containing List
s of
* {@link Handler}s.
*/
private Map> _handlersByType = new HashMap>();
/**
* This stores the id for the LayoutElement
*/
private String _id = null;
/**
* This is the "type" for handlers to be invoked after the encoding
* of this element.
*/
public static final String AFTER_ENCODE = "afterEncode";
/**
* This is the "type" for handlers to be invoked before the encoding
* of this element.
*/
public static final String BEFORE_ENCODE = "beforeEncode";
/**
* This is the "type" for handlers to be invoked during the encoding
* of this element. This occurs before any child LayoutElements are
* invoked and only if child Elements are to be invoked.
*/
public static final String ENCODE = "encode";
}