com.sun.jsftemplating.component.factory.ComponentFactoryBase 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.component.factory;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.el.ValueExpression;
import javax.faces.component.ActionSource;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import com.sun.jsftemplating.component.ComponentUtil;
import com.sun.jsftemplating.el.VariableResolver;
import com.sun.jsftemplating.layout.descriptors.LayoutComponent;
import com.sun.jsftemplating.layout.descriptors.handler.Handler;
import com.sun.jsftemplating.layout.event.CommandActionListener;
import com.sun.jsftemplating.layout.event.ValueChangeListener;
import com.sun.jsftemplating.util.LogUtil;
/**
* This abstract class provides common functionality for UIComponent
* factories.
*
* @author Ken Paulsen ([email protected])
*/
public abstract class ComponentFactoryBase implements ComponentFactory {
/**
* This is the factory method responsible for creating the
* UIComponent
.
*
* @param context The FacesContext
* @param descriptor The {@link LayoutComponent} descriptor associated
* with the requested UIComponent
.
* @param parent The parent UIComponent
*
* @return The newly created UIComponent
.
*/
public abstract UIComponent create(FacesContext context, LayoutComponent descriptor, UIComponent parent);
/**
* This method iterates through the Map of options. It looks at each
* one, if it contians an EL expression, it sets a value binding.
* Otherwise, it calls setAttribute() on the component (which in turn
* will invoke the bean setter if there is one).
*
* This method also interates through the child
* LayoutElement
s of the given {@link LayoutComponent}
* descriptor and adds Facets or children as appropriate.
*
* @param context The FacesContext
* @param desc The {@link LayoutComponent} descriptor associated with
* the requested UIComponent
.
* @param comp The UIComponent
*/
protected void setOptions(FacesContext context, LayoutComponent desc, UIComponent comp) {
if (desc == null) {
// Nothing to do
return;
}
// First set the id if supplied, treated special b/c the component
// used for ${} expressions is the parent and this must be set first
// so other ${} expressions can use $this{id} and $this{clientId}.
String compId = (String) desc.getId(context, comp.getParent());
if ((compId != null) && (!compId.equals(""))) {
comp.setId(compId);
}
// Loop through all the options and set the values
// FIXME: Figure a way to skip options that should not be set on the Component
Iterator it = desc.getOptions().keySet().iterator();
String key = null;
while (it.hasNext()) {
// Get next property
key = it.next();
setOption(context, comp, desc, key, desc.getOption(key));
}
// Check for "command" handlers...
List handlers = desc.getHandlers(LayoutComponent.COMMAND);
if ((handlers != null) && (comp instanceof ActionSource)) {
((ActionSource) comp).addActionListener(
CommandActionListener.getInstance());
}
// Check for "valueChange" handlers...
handlers = desc.getHandlers(ValueChangeListener.VALUE_CHANGE);
if ((handlers != null) && (comp instanceof EditableValueHolder)) {
((EditableValueHolder) comp).addValueChangeListener(
ValueChangeListener.getInstance());
}
// Set the events on the new component
storeInstanceHandlers(desc, comp);
}
/**
* This method sets an individual option on the
* UIComponent
. It will check to see if it is a
* ValueExpression
, if it is it will store it as
* such.
*/
protected void setOption(FacesContext context, UIComponent comp, LayoutComponent desc, String key, Object value) {
ComponentUtil.getInstance(context).setOption(context, key, value, desc, comp);
}
/**
* This method is responsible for interating over the "instance"
* handlers and applying them to the UIComponent. An "instance"
* handler is one that is defined outside a renderer, or a
* nested component within a renderer. In other words, a handler
* that would not get fired by the TemplateRenderer. By passing this
* in via the UIComponent, code that is aware of events (see
* {@link com.sun.jsftemplating.layout.descriptors.LayoutElementBase})
* may find these events and fire them. These may vary per "instance"
* of a particular component (i.e. TreeNode
) unlike the
* handlers defined in a TemplateRender's XML (which are shared and
* therefor should not change dynamically).
*
* This method is invoked from setOptions(), however, if setOptions
* is not used in by a factory, this method may be invoked directly.
* Calling this method multiple times will not cause any harm,
* besides making an extra unnecessary call.
*
* @param desc The descriptor potentially containing handlers to copy.
* @param comp The UIComponent instance to store the handlers.
*/
protected void storeInstanceHandlers(LayoutComponent desc, UIComponent comp) {
if (!desc.isNested()) {
UIComponent parent = comp.getParent();
if ((parent == null) || (parent instanceof UIViewRoot)) {
// This is not a nested LayoutComponent, it should not store
// instance handlers
// NOTE: could skip TemplateComponent children also. Although
// this is harder to detect as dynamic children aren't
// defined in the template and therefor must be stored in
// the UIComponent tree.
return;
}
}
// Iterate over the instance handlers
Iterator it = desc.getHandlersByTypeMap().keySet().iterator();
if (it.hasNext()) {
String eventType = null;
Map compAttrs = comp.getAttributes();
while (it.hasNext()) {
// Assign instance handlers to attribute for retrieval later
// (NOTE: retrieval must be explicit, see LayoutElementBase)
eventType = it.next();
if (eventType.equals(LayoutComponent.BEFORE_CREATE)) {
// This is handled directly, no need for instance handler
continue;
} else if (eventType.equals(LayoutComponent.AFTER_CREATE)) {
// This is handled directly, no need for instance handler
continue;
}
compAttrs.put(eventType, desc.getHandlers(eventType));
}
}
}
/**
* This method associates the given child with the given parent. By
* using this method we centralize the code so that if we decide
* later to add it as a real child it can be done in one place.
*
* @param context The FacesContext
* @param descriptor The {@link LayoutComponent} descriptor associated
* with the requested UIComponent
.
* @param parent The parent UIComponent
* @param child The child UIComponent
*/
protected void addChild(FacesContext context, LayoutComponent descriptor, UIComponent parent, UIComponent child) {
// Check to see if we should add this as a facet. NOTE: We add
// UIViewRoot children as facets b/c we render them via the
// LayoutElement tree.
String facetName = descriptor.getFacetName(parent);
if (facetName != null) {
// Add child as a facet...
if (LogUtil.configEnabled() && facetName.equals("_noname")) {
// Warn the developer that they may have a problem
LogUtil.config("Warning: no id was supplied for "
+ "component '" + child + "'!");
}
// Resolve the id if its dynamic
facetName = (String) ComponentUtil.getInstance(context).resolveValue(
context, descriptor, child, facetName);
parent.getFacets().put(facetName, child);
} else {
// Add this as an actual child
parent.getChildren().add(child);
}
}
/**
* This method instantiates the UIComponent
given its
* ComponentType
. It will respect the
* binding
property so that a UIComponent
* can be created via the binding
property. While a
* custom {@link ComponentFactory} can do a better job, at times it
* may be desirable to use binding
instead.
*/
protected UIComponent createComponent(FacesContext ctx, String componentType, LayoutComponent desc, UIComponent parent) {
UIComponent comp = null;
// Check for the "binding" property
String binding = null;
if (desc != null) {
binding =
(String) desc.getEvaluatedOption(ctx, "binding", parent);
}
if ((binding != null) && ComponentUtil.getInstance(ctx).isValueReference(binding)) {
// Create a ValueExpression
ValueExpression ve =
ctx.getApplication().getExpressionFactory().
createValueExpression(
ctx.getELContext(), binding, UIComponent.class);
// Create / get the UIComponent
comp = ctx.getApplication().createComponent(
ve, ctx, componentType);
} else {
// No binding, do the normal way...
comp = ctx.getApplication().createComponent(componentType);
}
// Parent the new component
if (parent != null) {
addChild(ctx, desc, parent, comp);
}
// Return it...
return comp;
}
/**
* This method returns the extraInfo that was set for this
* ComponentFactory
from the
* {@link com.sun.jsftemplating.layout.descriptors.ComponentType}.
*/
public Serializable getExtraInfo() {
return _extraInfo;
}
/**
* This method is invoked from the
* {@link com.sun.jsftemplating.layout.descriptors.ComponentType} to
* provide more information to the factory. For example, if the JSF
* component type was passed in, a single factory class could
* instatiate multiple components the extra info that is passed in.
*
* Some factory implementations may want to override this method to
* execute intialization code for the factory based in the value
* passed in.
*/
public void setExtraInfo(Serializable extraInfo) {
_extraInfo = extraInfo;
}
/**
* Extra information associated with this ComponentFactory.
*/
private Serializable _extraInfo = null;
}