com.sun.jsftemplating.component.ComponentUtil 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;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.el.ELContext;
import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import com.sun.jsftemplating.el.VariableResolver;
import com.sun.jsftemplating.layout.descriptors.ComponentType;
import com.sun.jsftemplating.layout.descriptors.LayoutComponent;
import com.sun.jsftemplating.layout.descriptors.LayoutElement;
import com.sun.jsftemplating.util.LogUtil;
import com.sun.jsftemplating.util.TypeConverter;
/**
* Utility class that contains helper methods for components.
*
* @author Ken Paulsen ([email protected])
*/
public class ComponentUtil {
/**
* Default Constructor.
*/
private ComponentUtil() {
}
/**
* Method to get access to the ComponentUtil instance for the current
* application.
*/
public static ComponentUtil getInstance(FacesContext ctx) {
ComponentUtil cu = null;
if (ctx != null) {
Map appMap =
ctx.getExternalContext().getApplicationMap();
cu = (ComponentUtil) appMap.get(COMPONENT_UTIL_KEY);
if (cu == null) {
cu = new ComponentUtil();
// Perhaps a SoftReference would be a good idea here?
appMap.put(COMPONENT_UTIL_KEY, cu);
}
} else {
// Not JSF env, create every time...
cu = new ComponentUtil();
}
return cu;
}
/**
* Return a child with the specified component id from the specified
* component. If not found, return null
.
*
* This method will NOT create a new UIComponent
.
*
* @param parent UIComponent
to be searched
* @param id Component id (or facet name) to search for
*
* @return The child UIComponent
if it exists, null otherwise.
*/
public UIComponent getChild(UIComponent parent, String id) {
return findChild(parent, id, id);
}
/**
* Return a child with the specified component id (or facetName) from
* the specified component. If not found, return null
.
* facetName
or id
may be null to avoid
* searching the facet Map or the parent
's children.
*
* This method will NOT create a new UIComponent
.
*
* @param parent UIComponent
to be searched
* @param id id to search for
* @param facetName Facet name to search for
*
* @return The child UIComponent
if it exists, null otherwise.
*/
public UIComponent findChild(UIComponent parent, String id, String facetName) {
// Sanity Check
if (parent == null) {
return null;
}
// First search for facet
UIComponent child = null;
if (facetName != null) {
child = parent.getFacets().get(facetName);
if (child != null) {
return child;
}
}
// Search for component by id
if (id != null) {
Iterator it = parent.getChildren().iterator();
while (it.hasNext()) {
child = it.next();
if (id.equals(child.getId())) {
return (child);
}
}
}
// Not found, return null
return null;
}
/**
* This method finds or creates a child UIComponent
* identified by the given id. If the child is not found, it will
* attempt to create it using the provided
* {@link com.sun.jsftemplating.component.factory.ComponentFactory}
* (factoryClass
).
*
* If there are Properties
to be set on the UIComponent,
* this method should generally be avoided. It is preferable to use
* the {@link #getChild(UIComponent, String, String, Properties)}
* form of getChild
.
*
*
* // Example (no properties):
* UIComponent child = Util.getChild(component, "jklLabel", "com.sun.jsftemplating.component.factory.basic.LabelFactory");
* ((Label)child).setText("JKL Label:");
* ((Label)child).setFor("jkl");
*
* {@link LayoutComponent#encodeChild(FacesContext, UIComponent) LayoutComponent.encodeChild}(context, child);
*
*
* @param parent Parent UIComponent
* @param id Identifier for child UIComponent
* @param factoryClass Full {@link com.sun.jsftemplating.component.factory.ComponentFactory} class name
*
* @return The child UIComponent that was found or created.
*
* @see #getChild(UIComponent, String, String, Properties)
*/
public UIComponent getChild(UIComponent parent, String id, String factoryClass) {
return getChild(parent, id, factoryClass, id);
}
/**
* Same as {@link #getChild(UIComponent, String, String)} except that
* it allows you to specify a facetName different than the id. If
* null is supplied, it won't save the component as a facet.
*
* @param parent Parent UIComponent
* @param id Identifier for the child UIComponent
* @param factoryClass Full {@link com.sun.jsftemplating.component.factory.ComponentFactory} class name
* @param facetName The facet name (null means don't store it)
*
* @return The child UIComponent that was found or created.
*
* @see #getChild(UIComponent, String, String)
*/
public UIComponent getChild(UIComponent parent, String id, String factoryClass, String facetName) {
return getChild(parent, id, getComponentType(factoryClass),
null, facetName);
}
/**
* This method finds or creates a child UIComponent
* identified by the given id. If the child is not found, it will
* attempt to create it using the provided
* {@link com.sun.jsftemplating.component.factory.ComponentFactory}
* (factoryClass
). It will also initialize the
* UIComponent
using the provided set of
* Properties
.
*
*
* // Example (with properties):
* Properties props = new Properties();
* props.setProperty("text", "ABC Label:");
* props.setProperty("for", "abc");
* UIComponent child = Util.getChild(component, "abcLabel", "com.sun.jsftemplating.component.factory.basic.LabelFactory", props);
*
* {@link LayoutComponent#encodeChild(FacesContext, UIComponent) LayoutComponent.encodeChild}(context, child);
*
*
* @param parent Parent UIComponent
* @param id Identifier for the child UIComponent
* @param factoryClass Full {@link com.sun.jsftemplating.component.factory.ComponentFactory} class name
* @param properties java.util.Properties
needed to
* create and/or initialize the
* UIComponent
*
* @return The child UIComponent that was found or created.
*/
public UIComponent getChild(UIComponent parent, String id, String factoryClass, Properties properties) {
return getChild(parent, id, factoryClass, properties, id);
}
/**
* Same as {@link #getChild(UIComponent, String, String, Properties)}
* except that it allows you to specify a facetName different than the
* id. If null is supplied, it won't save the component as a
* facet.
*
* @param parent Parent UIComponent
* @param id Identifier for the child UIComponent
* @param factoryClass Full {@link com.sun.jsftemplating.component.factory.ComponentFactory} class name
* @param properties java.util.Properties
needed to
* create and/or initialize the
* UIComponent
* @param facetName The facet name (null means don't store it)
*
* @return The child UIComponent that was found or created.
*/
public UIComponent getChild(UIComponent parent, String id, String factoryClass, Properties properties, String facetName) {
return getChild(parent, id, getComponentType(factoryClass),
properties, facetName);
}
/**
* This method finds or creates a child UIComponent
* identified by the given id. If the child is not found, it will
* attempt to create it using the provided {@link ComponentType}
* (type
). It will also initialize the
* UIComponent
using the provided set of
* properties
.
*
* @param parent Parent UIComponent
* @param id Identifier for the child
* UIComponent
* @param type The ComponentType
class name
* @param properties Properties needed to create and/or initialize
* the UIComponent
* @param facetName The facet name (null means don't store it)
*
* @return The child UIComponent
that was found or created.
*/
private UIComponent getChild(UIComponent parent, String id, ComponentType type, Properties properties, String facetName) {
LayoutComponent desc = new LayoutComponent(null, id, type);
if (properties != null) {
//Remove Generics for now. Check with Ken to change thisinto HashMap.
desc.setOptions((Map) properties);
}
if (facetName != null) {
// Add the facetName to use
// FIXME: Decide if this should have its own method
desc.addOption(LayoutComponent.FACET_NAME, facetName);
}
return getChild(parent, desc);
}
/**
* This method creates a {@link ComponentType} instance from the given
* factoryClass
. It will first check its cache to see if
* one has already been created. If not, it will create one and add
* to the cache for future use.
*
* This method sets factoryClass
for the
* {@link ComponentType} id.
*
* @param facatoryClass The full classname of the
* {@link com.sun.jsftemplating.component.factory.ComponentFactory}.
*
* @return A ComponentType instance for factoryClass
.
*/
private ComponentType getComponentType(String factoryClass) {
// Check the cache
ComponentType type = (ComponentType) _types.get(factoryClass);
if (type == null) {
// Not in the cache... add it...
type = new ComponentType(factoryClass, factoryClass);
Map newMap =
new HashMap(_types);
newMap.put(factoryClass, type);
_types = newMap;
}
// Return the ComponentType
return type;
}
/**
* This method finds or creates a child UIComponent
* identified by the given id. If the child is not found, it will
* attempt to create it using the provided {@link LayoutComponent}
* (descriptor
). It will also initialize the
* UIComponent
using the options set on the
* {@link LayoutComponent}.
*
* If parent
implements {@link ChildManager}, then the
* responsibility of finding and creating the child will be delegated
* to the {@link ChildManager} UIComponent
.
*
* If you are constructing and populating a LayoutComponent before
* calling this method, there are a few features that should be noted.
* Besides id
and type
which can be set in
* the LayoutComponent constructor, you can also set
* options
, and
* {@link com.sun.jsftemplating.layout.descriptors.handler.Handler}'s.
*
* Options
may be set via
* {@link LayoutComponent#setOptions(Map)}. These options will be
* applied to the UIComponent
and may also be used by the
* {@link com.sun.jsftemplating.component.factory.ComponentFactory}
* while instantiating the UIComponent
.
*
* {@link com.sun.jsftemplating.layout.descriptors.handler.Handler}'s
* can be supplied by calling
* {@link LayoutComponent#setHandlers(String, List)}. The
* type
must match the event name which invokes the
* List
of handlers you provide. The
* Renderer
for this UIComponent
is
* responsible for declaring and dispatching events.
* {@link com.sun.jsftemplating.renderer.TemplateRenderer}
* will invoke beforeCreate
and afterCreate
* events for each child it creates (such as the one being requested
* here).
*
*
* // Example (with LayoutComponent):
* {@link ComponentType} type = new {@link ComponentType#ComponentType(String, String) ComponentType}("LabelFactory", "com.sun.jsftemplating.component.factory.basic.LabelFactory");
* {@link LayoutComponent} descriptor = new {@link LayoutComponent#LayoutComponent(LayoutElement, String, ComponentType) LayoutComponent}(null, "abcLabel", type);
* {@link LayoutComponent#addOption(String, Object) descriptor.addOption}("text", "ABC Label:");
* {@link LayoutComponent#addOption(String, Object) descriptor.addOption}("for", "abc");
* UIComponent child = Util.getChild(component, descriptor);
*
* {@link LayoutComponent#encodeChild(FacesContext, UIComponent) LayoutComponent.encodeChild}(context, child);
*
*
* @param parent Parent UIComponent
* @param descriptor The {@link LayoutComponent} describing the
* UIComponent
*
* @return The child UIComponent
that was found or created.
*/
public UIComponent getChild(UIComponent parent, LayoutComponent descriptor) {
FacesContext context = FacesContext.getCurrentInstance();
// First check to see if the UIComponent can create its own children
if (parent instanceof ChildManager) {
return ((ChildManager) parent).getChild(
context, descriptor);
}
// Make sure it doesn't already exist
String childId = descriptor.getId(context, parent);
UIComponent childComponent = findChild(parent, childId,
(String) descriptor.getEvaluatedOption(
context, LayoutComponent.FACET_NAME, null));
if (childComponent != null) {
return childComponent;
}
// Not found, create a new UIComponent
return createChildComponent(context, descriptor, parent);
}
/**
* This method creates a child UIComponent
by using the
* provided {@link LayoutComponent} (descriptor
). It
* will associate the parent and the newly created
* UIComponent
.
*
* It is recommended that this method NOT be called from a Renderer.
* It should not be called if you have not yet checked to see if a
* child UIComponent with the requested ID already exists.
*
* @param context The FacesContext
object.
* @param descriptor The {@link LayoutComponent} describing the
* UIComponent
to be created.
* @param parent Parent UIComponent
.
*
* @return A new UIComponent
based on the provided
* {@link LayoutComponent}.
*
* @throws IllegalArgumentException Thrown if descriptor equals null.
*
* @see #getChild(UIComponent, LayoutComponent)
* @see #getChild(UIComponent, String, String, Properties)
* @see LayoutComponent#getType()
* @see ComponentType#getFactory()
* @see com.sun.jsftemplating.component.factory.ComponentFactory#create(FacesContext, LayoutComponent, UIComponent)
*/
public UIComponent createChildComponent(FacesContext context, LayoutComponent descriptor, UIComponent parent) {
// Make sure a LayoutComponent was provided.
if (descriptor == null) {
throw new IllegalArgumentException("'descriptor' cannot be null!");
}
// Create & return the child UIComponent
return descriptor.getType().getFactory().create(
context, descriptor, parent);
}
/**
* This util method will set the given key/value on the
* UIComponent
. It will resolve all $...{...}
* expressions, and convert the String into a
* ValueExpression
if a valid expression is detected.
* The return value will be a ValueExpression
or the
* value.
*
* @param context FacesContext
* @param key The Property name to set
* @param value The Property value to set
* @param desc The {@link LayoutElement} associated with the
* UIComponent
* @param comp The UIComponent
*
* @return A ValueExpression
, or the "$...{...}" evaulated
* value (if no ValueExpression
is present).
*/
public Object setOption(FacesContext context, String key, Object value, LayoutElement desc, UIComponent comp) {
// Invoke our own EL. This is needed b/c JSF's EL is designed for
// context-insensitive EL. Resolve our variables now because we
// cannot depend on the individual components to do this later. We
// need to find a way to make this work as a regular ValueExpression...
// for now, we'll continue to use it...
value = VariableResolver.resolveVariables(context, desc, comp, value);
// Next check to see if the value is/contains a JSF ValueExpression
if (value instanceof ValueExpression) {
if (comp != null) {
comp.setValueExpression(key, (ValueExpression) value);
}
} else if ((value instanceof String) && isValueReference((String) value)) {
ValueExpression ve =
context.getApplication().getExpressionFactory().
createValueExpression(
context.getELContext(), (String) value, Object.class);
if (comp != null) {
comp.setValueExpression(key, ve);
}
value = ve;
} else if (comp != null) {
// In JSF, you must directly modify the attribute Map
Map attributes = comp.getAttributes();
if (value == null) {
// Setting null, assume they want to remove the value
try {
attributes.remove(key);
} catch (Exception ex) { // Switched from IAE to E b/c of MyFaces incompatibility
// JSF is mesed up... it throws an exception if it has a
// property descriptor and you call remove(...). It also
// throws an exception if you attempt to call put w/ null
// and there is no property descriptor. Either way you
// MUST catch something and then handle the other case.
try {
attributes.put(key, (Object) null);
} catch (Exception iae) { // Switched from IAE to E b/c of MyFaces incompatibility
// We'll make this non-fatal, but log a message
if (LogUtil.infoEnabled()) {
LogUtil.info("JSFT0006", new Object[] {
key, comp.getId(), comp.getClass().getName()});
if (LogUtil.fineEnabled()) {
LogUtil.fine("Unable to set (" + key + ").", iae);
}
}
}
}
} else {
try {
// Attempt to set the value as given...
attributes.put(key, value);
} catch (Exception ex) { // Switched from IAE to E b/c of MyFaces incompatibility
// Ok, try a little harder...
Class type = findPropertyType(comp, key);
if (type != null) {
try {
attributes.put(key, TypeConverter.asType(type, value));
} catch (Exception ex2) { // Switched from IAE to E b/c of MyFaces incompatibility
throw new IllegalArgumentException(
"Failed to set property (" + key + ") with "
+ "value (" + value + "), which is of type ("
+ value.getClass().getName() + "). Expected "
+ "type (" + type.getName() + "). This "
+ "occured on the component named ("
+ comp.getId() + ") of type ("
+ comp.getClass().getName() + ").", ex2);
}
} else {
throw new IllegalArgumentException(
"Failed to set property (" + key + ") with value ("
+ value + "), which is of type ("
+ value.getClass().getName() + "). This occured "
+ "on the component named (" + comp.getId()
+ ") of type (" + comp.getClass().getName()
+ ").", ex);
}
}
}
}
// Return the value (which may be a ValueExpression)
return value;
}
/**
* This method attempts to resolve the expected type for the given
* property key
and UIComponent
.
*/
private Class findPropertyType(UIComponent comp, String key) {
// First check to see if we've done this before...
Class compClass = comp.getClass();
String cacheKey = compClass.getName() + ';' + key;
if (_typeCache.containsKey(cacheKey)) {
// May return null if method previously executed unsuccessfully
return _typeCache.get(cacheKey);
}
// Search a little...
Class val = null;
Method meth = null;
String methodName = getGetterName(key);
try {
meth = compClass.getMethod(methodName);
} catch (NoSuchMethodException ex) {
// May fail if we have a boolean property that has an "is" getter.
try {
// Try again, replace "get" with "is"
meth = compClass.getMethod(
"is" + methodName.substring(3));
} catch (NoSuchMethodException ex2) {
// Still not found, must not have getter / setter
ex2.printStackTrace();
}
}
if (meth != null) {
val = meth.getReturnType();
} else {
Object obj = comp.getAttributes().get("key");
if (val != null) {
val = obj.getClass();
}
}
// Save the value for future calls for the same information
// NOTE: We do it this way to avoid modifying a shared Map
Map newTypeCache =
new HashMap(_typeCache);
newTypeCache.put(cacheKey, val);
_typeCache = newTypeCache;
// Return the result
return val;
}
/**
* This method converts the given name
to a bean getter
* method name. In other words, it capitalizes the first letter and
* prepends "get".
*/
public String getGetterName(String name) {
return "get" + ((char) (name.charAt(0) & 0xFFDF)) + name.substring(1);
}
/**
* This method will attempt to resolve the given EL string.
*
* @param context The FacesContext
.
* @param elt The LayoutElement associated w/ the expression.
* @param parent The parent UIComponent
. This is used
* because the current UIComponent is typically
* unknown (or not even created yet).
* @param value The String (or List / Array) to resolve.
*
* @return The evaluated value (may be null).
*/
public Object resolveValue(FacesContext context, LayoutElement elt, UIComponent parent, Object value) {
// Invoke our own EL. This is needed b/c JSF's EL is designed for
// Bean getters only. It does not get CONSTANTS or pull data from
// other sources without adding a custom VariableResolver and/or
// PropertyResolver. Eventually we may want to find a good way to
// make this work as a regular ValueExpression expression... but for
// now, we'll just resolve it this way.
Object result = VariableResolver.resolveVariables(
context, elt, parent, value);
// Next check to see if the result contains a JSF ValueExpression
if ((result != null) && (result instanceof String) && isValueReference((String) result)) {
ELContext elctx = context.getELContext();
ValueExpression ve =
context.getApplication().getExpressionFactory().
createValueExpression(elctx, (String) result, Object.class);
result = ve.getValue(elctx);
/*
1.1+
// JSF 1.1 VB:
try {
ValueBinding vb =
context.getApplication().createValueBinding((String) result);
result = vb.getValue(context);
} catch (EvaluationException ex) {
if (LogUtil.infoEnabled()) {
LogUtil.info("JSFT0007", new Object[] {value});
}
throw ex;
}
*/
}
// Return the result
return result;
}
/**
* Returns true if this expression looks like an EL expression.
*/
public boolean isValueReference(String value) {
if (value == null) {
return false;
}
// FIXME: Consider adding logic to look for "matching" {}'s
int start = value.indexOf("#{");
if ((start != -1) && (start < value.indexOf('}', start))) {
return true;
}
return false;
}
// While this is static, the information seems reasonable to share across
// applications as it is very unlikely to be different... leaving for now
private static Map _typeCache =
new HashMap();
/**
* This Map caches ComponentTypes by their factoryClass name.
*/
private Map _types =
new HashMap();
/**
* Application scope key for an instance of
* ComponentUtil
.
*/
public static final String COMPONENT_UTIL_KEY = "_jsft_COMP_UTIL";
}