org.apache.shale.tiger.faces.VariableResolverImpl Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.shale.tiger.faces;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import javax.faces.el.PropertyNotFoundException;
import javax.faces.el.ValueBinding;
import javax.faces.el.VariableResolver;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shale.tiger.config.FacesConfigConfig;
import org.apache.shale.tiger.managed.Bean;
import org.apache.shale.tiger.managed.Value;
import org.apache.shale.tiger.managed.config.ListEntriesConfig;
import org.apache.shale.tiger.managed.config.ListEntryConfig;
import org.apache.shale.tiger.managed.config.ManagedBeanConfig;
import org.apache.shale.tiger.managed.config.ManagedPropertyConfig;
import org.apache.shale.tiger.managed.config.MapEntriesConfig;
import org.apache.shale.tiger.managed.config.MapEntryConfig;
import org.apache.shale.tiger.view.faces.LifecycleListener2;
import org.apache.shale.util.ConverterHelper;
import org.apache.shale.util.Messages;
import org.apache.shale.util.PropertyHelper;
/**
* Implementation of VariableResolver
that delegates
* to the original handler under these circumstances:
*
* - There is bean already in existence with the specified name.
* - There is no managed beans definition for the specified name.
*
*
* If control is not delegated, implement the standard functionality
* for creating managed beans (this is necessary because JSF does not
* expose any direct API to access these capabilities), with the following
* additions:
*
* - If the specified managed bean class includes the
* {@link Bean} annotation, the type and scope properties
* will have been preconfigured from the corresponding
* attribute values. (These default settings can be
* overridden by specifying an actual managed bean element
* in a
faces-config.xml
resource.)
* - If a field of the specified bean class includes the
* {@link Value} annotation, the property initialization
* will be preconfigured to the literal or expression
* specified by the
value
attribute of the
* annotation. (These default settings can be overridden
* by specifiying an actual managed bean element, and
* including a property definition that sets the value.
*
*
* FIXME - Incomplete implemetnation of standard managed beans
* functionality in the following areas:
*
* - Partial support for list entries on managed beans and managed properties.
* It currently works for lists, but not for arrays.
*
*
* IMPLEMENTATION NOTE - There is no faces-config.xml
* resource that registers this variable resolver, since we could end up with
* ordering issues with the standard Shale variable resolver. Therefore, the
* standard implementation will dynamically insert an instance of this
* resolver (below the standard instance, but above the implementation
* provided instance) in the standard decorator chain that is implemented
* as the JavaServer Faces runtime parses configuration resources.
*/
public class VariableResolverImpl extends VariableResolver {
/**
* Create a new VariableResolverImpl
wrapping the
* specified VariableResolver
.
*
* @param original Original VariableResolver
to wrap
*/
public VariableResolverImpl(VariableResolver original) {
this.original = original;
if (log().isInfoEnabled()) {
log().info(messages().getMessage("variable.resolver",
new Object[] { original }));
}
}
// ----------------------------------------------------- Instance Variables
/**
* Helper bean for performing conversions.
*/
private ConverterHelper convHelper = new ConverterHelper();
/**
* Log instance for this class.
*/
private transient Log log = null;
/**
* Messages
instance for this class.
*/
private transient Messages messages = null;
/**
* The original VariableResolver
to which we should
* delegate when necessary.
*/
private VariableResolver original = null;
/**
* Helper bean for accessing properties.
*/
private PropertyHelper propHelper = new PropertyHelper();
// ----------------------------------------------- VariableResolver Methods
/**
* Resolve the specified variable name, creating and initializing
* a new managed bean if necessary.
*
* @param context FacesContext
used to resolve variables
* @param name Name of the variable to be resolved
*
* @exception EvaluationException if an evaluation error occurs
*/
public Object resolveVariable(FacesContext context,
String name) throws EvaluationException {
if (log().isDebugEnabled()) {
log().debug("resolveVariable(" + name + ")");
}
// Is there an existing bean by this name? If so, delegate
Object value = context.getExternalContext().getRequestMap().get(name);
if (value == null) {
value = context.getExternalContext().getSessionMap().get(name);
}
if (value == null) {
value = context.getExternalContext().getApplicationMap().get(name);
}
if (value != null) {
if (log().isTraceEnabled()) {
log().trace("resolveVariable(" + name + ") --> existing bean, so delegate");
}
return original.resolveVariable(context, name);
}
// Do we have a managed bean definition for this name? If not, delegate
FacesConfigConfig config = config(context);
if (config == null) {
if (log().isTraceEnabled()) {
log().trace("resolveVariable(" + name + ") --> no FacesConfigConfig, so delegate");
}
return original.resolveVariable(context, name);
}
ManagedBeanConfig mb = config.getManagedBean(name);
if (mb == null) {
if (log().isTraceEnabled()) {
log().trace("resolveVariable(" + name + ") --> no ManagedBeanConfig, so delegate");
}
return original.resolveVariable(context, name);
}
// Configure and return a new managed bean
Object created = create(context, mb);
return created;
}
// -------------------------------------------------------- Private Methods
/**
* The {@link FacesConfigConfig} entry containing our managed bean
* definitions, lazily instantiated.
*/
private FacesConfigConfig config = null;
/**
* Return the {@link FacesConfigConfig} bean describing the
* configuration information for this applicaiton, if any.
*
* @param context FacesContext for the current request
*/
private FacesConfigConfig config(FacesContext context) {
if (config == null) {
config = (FacesConfigConfig)
context.getExternalContext().getApplicationMap().
get(LifecycleListener2.FACES_CONFIG_CONFIG);
}
return config;
}
/**
* Create, configure, and return a new instance based on the
* specified managed bean, after storing it in the configured
* scope (if any).
*
* @param context FacesContext
for the current request
* @param mb ManagedBeanConfig describing the bean to be created
*
* @exception EvaluationException if an evaluation error occurs
*/
private Object create(FacesContext context, ManagedBeanConfig mb)
throws EvaluationException {
if (log().isDebugEnabled()) {
log().debug("create(" + mb.getName() + ")");
}
// Instantiate a new instance of the appropriate bean class
Object instance = instance(context, mb);
// Configure properties as necessary
for (ManagedPropertyConfig mp : mb.getProperties().values()) {
property(context, mb, mp, instance);
}
// Configure list entries as necessary
ListEntriesConfig listEntries = mb.getListEntries();
if (listEntries != null) {
// FIXME - arrays are not yet supported
if (!List.class.isAssignableFrom(instance.getClass())) {
throw new EvaluationException(messages().
getMessage("list.list",
context.getViewRoot().getLocale(),
new Object[] { instance.getClass().getName() }));
}
list(context, listEntries, (List) instance);
}
// Configure map entries as necessary
MapEntriesConfig mapEntries = mb.getMapEntries();
if (mapEntries != null) {
if (!Map.class.isAssignableFrom(instance.getClass())) {
throw new EvaluationException(messages().
getMessage("map.map",
context.getViewRoot().getLocale(),
new Object[] { instance.getClass().getName() }));
}
map(context, mapEntries, (Map) instance);
}
// Place the bean into a scope, if necessary
scope(context, mb, instance);
return instance;
}
/**
* Create and return an instance of the class named by the
* specified managed bean configuration bean.
*
* @param context FacesContext
for the current request
* @param mb {@link ManagedBeanConfig} defining the bean to create
*
* @exception EvaluationException if an evaluation error occurs
*/
private Object instance(FacesContext context, ManagedBeanConfig mb)
throws EvaluationException {
// Is there actually a class name available for us to use?
if (mb.getType() == null) {
throw new EvaluationException(messages().
getMessage("variable.type",
context.getViewRoot().getLocale(),
new Object[] { mb.getName() }));
}
// Instantiate an instance of the appropriate class
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class clazz = null;
Object instance = null;
try {
clazz = cl.loadClass(mb.getType());
instance = clazz.newInstance();
} catch (ClassNotFoundException e) {
throw new EvaluationException(messages().
getMessage("variable.class",
context.getViewRoot().getLocale(),
new Object[] { mb.getName(), mb.getType() }), e);
} catch (IllegalAccessException e) {
throw new EvaluationException(messages().
getMessage("variable.access",
context.getViewRoot().getLocale(),
new Object[] { mb.getName(), mb.getType() }), e);
} catch (InstantiationException e) {
throw new EvaluationException(messages().
getMessage("variable.instantiate",
context.getViewRoot().getLocale(),
new Object[] { mb.getName(), mb.getType() }), e);
}
return instance;
}
/**
*
Populate the contents of the specified List
from the
* specified list entries configuration information.
*
* @param context FacesContext
for the current request
* @param config {@link ListEntriesConfig} describing this list
* @param list List
instance to have entries appended
*
* @exception EvaluationException if an evaluation error occurs
*/
private void list(FacesContext context, ListEntriesConfig config, List list) {
// Determine the type to which list entries should be conveted, if any
String valueType = config.getValueType();
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class type = null;
try {
if (valueType != null) {
type = cl.loadClass(valueType);
}
} catch (ClassNotFoundException e) {
throw new EvaluationException(messages().
getMessage("list.class",
context.getViewRoot().getLocale(),
new Object[] { valueType }), e);
}
// Add a list entry for each configuration element that is present
for (ListEntryConfig entry : config.getEntries()) {
if (entry.isNullValue()) {
list.add(null);
} else if (entry.isExpression()) {
// Evaluate the specified value binding expression
ValueBinding vb =
context.getApplication().createValueBinding(entry.getValue());
list.add(vb.getValue(context));
} else {
if (type != null) {
list.add(convHelper.asObject(context, type, entry.getValue()));
} else {
list.add(entry.getValue());
}
}
}
}
/**
* Return the Log
instance to be used for this class,
* instantiating a new one if necessary.
*/
private Log log() {
if (log == null) {
log = LogFactory.getLog(VariableResolverImpl.class);
}
return log;
}
/**
* Populate the contents of the specified Map
from the
* specified map entries configuration information.
*
* @param context FacesContext
for the current request
* @param config {@link MapEntriesConfig} describing this list
* @param map Map
instance to have entries appended
*
* @exception EvaluationException if an evaluation error occurs
*/
private void map(FacesContext context, MapEntriesConfig config, Map map) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// Determine the type to which map keys should be conveted, if any
String keyType = config.getKeyType();
Class keyClass = null;
try {
if (keyType != null) {
keyClass = cl.loadClass(keyType);
}
} catch (ClassNotFoundException e) {
throw new EvaluationException(messages().
getMessage("map.keyClass",
context.getViewRoot().getLocale(),
new Object[] { keyType }), e);
}
// Determine the type to which map values should be conveted, if any
String valueType = config.getValueType();
Class valueClass = null;
try {
if (valueType != null) {
valueClass = cl.loadClass(valueType);
}
} catch (ClassNotFoundException e) {
throw new EvaluationException(messages().
getMessage("map.valueClass",
context.getViewRoot().getLocale(),
new Object[] { valueType }), e);
}
// Add a map key/value pair for each configuration element that is present
for (MapEntryConfig entry : config.getEntries()) {
Object key = null;
if (keyClass != null) {
key = convHelper.asObject(context, keyClass, entry.getKey());
} else {
key = entry.getKey();
}
if (entry.isNullValue()) {
map.put(key, null);
} else if (entry.isExpression()) {
// Evaluate the specified value binding expression
ValueBinding vb =
context.getApplication().createValueBinding(entry.getValue());
map.put(key, vb.getValue(context));
} else {
if (valueClass != null) {
map.put(key, convHelper.asObject(context, valueClass, entry.getValue()));
} else {
map.put(key, entry.getValue());
}
}
}
}
/**
* Return the Messages
instance to be used for this class,
* instantiating a new one if necessary.
*/
private Messages messages() {
if (messages == null) {
messages = new Messages("org.apache.shale.tiger.faces.Bundle",
Thread.currentThread().getContextClassLoader());
}
return messages;
}
/**
* Configure the specified property of the specified bean instance,
* based on information from the specified managed property entry.
*
* @param context FacesContext
for the current request
* @param mb {@link ManagedBeanConfig} for the bean being scoped
* @param mp {@link ManagedPropertyConfig} for the property being configured
* @param instance Bean instance to be potentially placed in scope
*
* @exception EvaluationException if an evaluation error occurs
*/
private void property(FacesContext context, ManagedBeanConfig mb,
ManagedPropertyConfig mp, Object instance)
throws EvaluationException {
// Configure list entries as necessary
ListEntriesConfig listEntries = mp.getListEntries();
if (listEntries != null) {
// Acquire the value of the specified property from the
// specified instance, if it exists
Object property = null;
try {
property = propHelper.getValue(instance, mp.getName());
// property = PropertyUtils.getProperty(instance, mp.getName());
} catch (PropertyNotFoundException e) {
; // Fall through to creating our own list
} catch (Exception e) {
throw new EvaluationException(messages().
getMessage("list.get",
context.getViewRoot().getLocale(),
new Object[] { mp.getName(), mb.getName()}), e);
}
if (property == null) {
property = new ArrayList();
}
// FIXME - arrays are not yet supported
if (!List.class.isAssignableFrom(property.getClass())) {
throw new EvaluationException(messages().
getMessage("list.listProperty",
context.getViewRoot().getLocale(),
new Object[] { mp.getName(), mb.getName(), property.getClass().getName() }));
}
// Accumulate the new values for the list
list(context, listEntries, (List) property);
// Store the value of the property
try {
propHelper.setValue(instance, mp.getName(), property);
// BeanUtils.setProperty(instance, mp.getName(), property);
} catch (Exception e) {
throw new EvaluationException(messages().
getMessage("list.set",
context.getViewRoot().getLocale(),
new Object[] { mp.getName(), mb.getName()}), e);
}
// We are through with this property, so return
return;
}
// Configure map entries as necessary
MapEntriesConfig mapEntries = mp.getMapEntries();
if (mapEntries != null) {
// Acquire the value of the specified property from the
// specified instance, if it exists
Object property = null;
try {
property = propHelper.getValue(instance, mp.getName());
// property = PropertyUtils.getProperty(instance, mp.getName());
} catch (PropertyNotFoundException e) {
; // Fall through to creating our own map
} catch (Exception e) {
throw new EvaluationException(messages().
getMessage("map.get",
context.getViewRoot().getLocale(),
new Object[] { mp.getName(), mb.getName()}), e);
}
if (property == null) {
property = new HashMap();
}
if (!Map.class.isAssignableFrom(property.getClass())) {
throw new EvaluationException(messages().
getMessage("map.mapProperty",
context.getViewRoot().getLocale(),
new Object[] { mp.getName(), mb.getName(), property.getClass().getName() }));
}
// Accumulate the new values for the list
map(context, mapEntries, (Map) property);
// Store the value of the property
try {
propHelper.setValue(instance, mp.getName(), property);
// BeanUtils.setProperty(instance, mp.getName(), property);
} catch (Exception e) {
throw new EvaluationException(messages().
getMessage("map.set",
context.getViewRoot().getLocale(),
new Object[] { mp.getName(), mb.getName()}), e);
}
// We are through with this property, so return
return;
}
// Does this managed property specify initialization?
if ((mp.getValue() == null) && !mp.isNullValue()) {
return;
}
// Acquire the value to be set (possibly requiring conversion)
Object value = null;
if (!mp.isNullValue()) {
if (mp.isExpression()) {
// Evaluate the specified value binding expression
ValueBinding vb =
context.getApplication().createValueBinding(mp.getValue());
value = vb.getValue(context);
} else {
// Grab the string literal version of the value to set
value = mp.getValue();
}
}
// Assign the acquired value to the specified bean property
try {
Class type = propHelper.getType(instance, mp.getName());
if ((value != null) && (value instanceof String)) {
value = convHelper.asObject(context, type, (String) value);
}
propHelper.setValue(instance, mp.getName(), value);
} catch (Exception e) {
throw new EvaluationException(messages().
getMessage("variable.evaluate",
context.getViewRoot().getLocale(),
new Object[] { mb.getName(), mp.getName(), mp.getValue() }), e);
}
}
/**
* Install the specified bean instance in the specified scope (if any).
*
*
* @param context FacesContext
for the current request
* @param mb {@link ManagedBeanConfig} for the bean being scoped
* @param instance Bean instance to be potentially placed in scope
*
* @exception EvaluationException if an evaluation error occurs
*/
private void scope(FacesContext context, ManagedBeanConfig mb, Object instance)
throws EvaluationException {
if (log().isTraceEnabled()) {
log().trace("Store bean " + mb.getName() + " in scope " + mb.getScope());
}
ExternalContext econtext = context.getExternalContext();
String scope = mb.getScope();
if ("request".equalsIgnoreCase(scope)) {
econtext.getRequestMap().put(mb.getName(), instance);
} else if ("session".equalsIgnoreCase(scope)) {
econtext.getSessionMap().put(mb.getName(), instance);
} else if ("application".equalsIgnoreCase(scope)) {
econtext.getApplicationMap().put(mb.getName(), instance);
}
}
}