org.activiti.engine.impl.javax.el.CompositeELResolver Maven / Gradle / Ivy
The newest version!
/*
* Based on JUEL 2.2.1 code, 2006-2009 Odysseus Software GmbH
*
* Licensed 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.activiti.engine.impl.javax.el;
import java.beans.FeatureDescriptor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* Maintains an ordered composite list of child ELResolvers. Though only a single ELResolver is
* associated with an ELContext, there are usually multiple resolvers considered for any given
* variable or property resolution. ELResolvers are combined together using a CompositeELResolver,
* to define rich semantics for evaluating an expression. For the
* {@link #getValue(ELContext, Object, Object)}, {@link #getType(ELContext, Object, Object)},
* {@link #setValue(ELContext, Object, Object, Object)} and
* {@link #isReadOnly(ELContext, Object, Object)} methods, an ELResolver is not responsible for
* resolving all possible (base, property) pairs. In fact, most resolvers will only handle a base of
* a single type. To indicate that a resolver has successfully resolved a particular (base,
* property) pair, it must set the propertyResolved property of the ELContext to true. If it could
* not handle the given pair, it must leave this property alone. The caller must ignore the return
* value of the method if propertyResolved is false. The CompositeELResolver initializes the
* ELContext.propertyResolved flag to false, and uses it as a stop condition for iterating through
* its component resolvers. The ELContext.propertyResolved flag is not used for the design-time
* methods {@link #getFeatureDescriptors(ELContext, Object)} and
* {@link #getCommonPropertyType(ELContext, Object)}. Instead, results are collected and combined
* from all child ELResolvers for these methods.
*/
public class CompositeELResolver extends ELResolver {
private final List resolvers = new ArrayList();
/**
* Adds the given resolver to the list of component resolvers. Resolvers are consulted in the
* order in which they are added.
*
* @param elResolver
* The component resolver to add.
* @throws NullPointerException
* If the provided resolver is null.
*/
public void add(ELResolver elResolver) {
if (elResolver == null) {
throw new NullPointerException("resolver must not be null");
}
resolvers.add(elResolver);
}
/**
* Returns the most general type that this resolver accepts for the property argument, given a
* base object. One use for this method is to assist tools in auto-completion. The result is
* obtained by querying all component resolvers. The Class returned is the most specific class
* that is a common superclass of all the classes returned by each component resolver's
* getCommonPropertyType method. If null is returned by a resolver, it is skipped.
*
* @param context
* The context of this evaluation.
* @param base
* The base object to return the most general property type for, or null to enumerate
* the set of top-level variables that this resolver can evaluate.
* @return null if this ELResolver does not know how to handle the given base object; otherwise
* Object.class if any type of property is accepted; otherwise the most general property
* type accepted for the given base.
*/
@Override
public Class> getCommonPropertyType(ELContext context, Object base) {
Class> result = null;
for (ELResolver resolver : resolvers) {
Class> type = resolver.getCommonPropertyType(context, base);
if (type != null) {
if (result == null || type.isAssignableFrom(result)) {
result = type;
} else if (!result.isAssignableFrom(type)) {
result = Object.class;
}
}
}
return result;
}
/**
* Returns information about the set of variables or properties that can be resolved for the
* given base object. One use for this method is to assist tools in auto-completion. The results
* are collected from all component resolvers. The propertyResolved property of the ELContext is
* not relevant to this method. The results of all ELResolvers are concatenated. The Iterator
* returned is an iterator over the collection of FeatureDescriptor objects returned by the
* iterators returned by each component resolver's getFeatureDescriptors method. If null is
* returned by a resolver, it is skipped.
*
* @param context
* The context of this evaluation.
* @param base
* The base object to return the most general property type for, or null to enumerate
* the set of top-level variables that this resolver can evaluate.
* @return An Iterator containing zero or more (possibly infinitely more) FeatureDescriptor
* objects, or null if this resolver does not handle the given base object or that the
* results are too complex to represent with this method
*/
@Override
public Iterator getFeatureDescriptors(final ELContext context, final Object base) {
return new Iterator() {
Iterator empty = Collections. emptyList().iterator();
Iterator resolvers = CompositeELResolver.this.resolvers.iterator();
Iterator features = empty;
Iterator features() {
while (!features.hasNext() && resolvers.hasNext()) {
features = resolvers.next().getFeatureDescriptors(context, base);
if (features == null) {
features = empty;
}
}
return features;
}
public boolean hasNext() {
return features().hasNext();
}
public FeatureDescriptor next() {
return features().next();
}
public void remove() {
features().remove();
}
};
}
/**
* For a given base and property, attempts to identify the most general type that is acceptable
* for an object to be passed as the value parameter in a future call to the
* {@link #setValue(ELContext, Object, Object, Object)} method. The result is obtained by
* querying all component resolvers. If this resolver handles the given (base, property) pair,
* the propertyResolved property of the ELContext object must be set to true by the resolver,
* before returning. If this property is not true after this method is called, the caller should
* ignore the return value. First, propertyResolved is set to false on the provided ELContext.
* Next, for each component resolver in this composite:
*
* - The getType() method is called, passing in the provided context, base and property.
* - If the ELContext's propertyResolved flag is false then iteration continues.
* - Otherwise, iteration stops and no more component resolvers are considered. The value
* returned by getType() is returned by this method.
*
* If none of the component resolvers were able to perform this operation, the value null is
* returned and the propertyResolved flag remains set to false. Any exception thrown by
* component resolvers during the iteration is propagated to the caller of this method.
*
* @param context
* The context of this evaluation.
* @param base
* The base object to return the most general property type for, or null to enumerate
* the set of top-level variables that this resolver can evaluate.
* @param property
* The property or variable to return the acceptable type for.
* @return If the propertyResolved property of ELContext was set to true, then the most general
* acceptable type; otherwise undefined.
* @throws NullPointerException
* if context is null
* @throws PropertyNotFoundException
* if base is not null and the specified property does not exist or is not readable.
* @throws ELException
* if an exception was thrown while performing the property or variable resolution.
* The thrown exception must be included as the cause property of this exception, if
* available.
*/
@Override
public Class> getType(ELContext context, Object base, Object property) {
context.setPropertyResolved(false);
for (ELResolver resolver : resolvers) {
Class> type = resolver.getType(context, base, property);
if (context.isPropertyResolved()) {
return type;
}
}
return null;
}
/**
* Attempts to resolve the given property object on the given base object by querying all
* component resolvers. If this resolver handles the given (base, property) pair, the
* propertyResolved property of the ELContext object must be set to true by the resolver, before
* returning. If this property is not true after this method is called, the caller should ignore
* the return value. First, propertyResolved is set to false on the provided ELContext. Next,
* for each component resolver in this composite:
*
* - The getValue() method is called, passing in the provided context, base and property.
* - If the ELContext's propertyResolved flag is false then iteration continues.
* - Otherwise, iteration stops and no more component resolvers are considered. The value
* returned by getValue() is returned by this method.
*
* If none of the component resolvers were able to perform this operation, the value null is
* returned and the propertyResolved flag remains set to false. Any exception thrown by
* component resolvers during the iteration is propagated to the caller of this method.
*
* @param context
* The context of this evaluation.
* @param base
* The base object to return the most general property type for, or null to enumerate
* the set of top-level variables that this resolver can evaluate.
* @param property
* The property or variable to return the acceptable type for.
* @return If the propertyResolved property of ELContext was set to true, then the result of the
* variable or property resolution; otherwise undefined.
* @throws NullPointerException
* if context is null
* @throws PropertyNotFoundException
* if base is not null and the specified property does not exist or is not readable.
* @throws ELException
* if an exception was thrown while performing the property or variable resolution.
* The thrown exception must be included as the cause property of this exception, if
* available.
*/
@Override
public Object getValue(ELContext context, Object base, Object property) {
context.setPropertyResolved(false);
for (ELResolver resolver : resolvers) {
Object value = resolver.getValue(context, base, property);
if (context.isPropertyResolved()) {
return value;
}
}
return null;
}
/**
* For a given base and property, attempts to determine whether a call to
* {@link #setValue(ELContext, Object, Object, Object)} will always fail. The result is obtained
* by querying all component resolvers. If this resolver handles the given (base, property)
* pair, the propertyResolved property of the ELContext object must be set to true by the
* resolver, before returning. If this property is not true after this method is called, the
* caller should ignore the return value. First, propertyResolved is set to false on the
* provided ELContext. Next, for each component resolver in this composite:
*
* - The isReadOnly() method is called, passing in the provided context, base and property.
* - If the ELContext's propertyResolved flag is false then iteration continues.
* - Otherwise, iteration stops and no more component resolvers are considered. The value
* returned by isReadOnly() is returned by this method.
*
* If none of the component resolvers were able to perform this operation, the value false is
* returned and the propertyResolved flag remains set to false. Any exception thrown by
* component resolvers during the iteration is propagated to the caller of this method.
*
* @param context
* The context of this evaluation.
* @param base
* The base object to return the most general property type for, or null to enumerate
* the set of top-level variables that this resolver can evaluate.
* @param property
* The property or variable to return the acceptable type for.
* @return If the propertyResolved property of ELContext was set to true, then true if the
* property is read-only or false if not; otherwise undefined.
* @throws NullPointerException
* if context is null
* @throws PropertyNotFoundException
* if base is not null and the specified property does not exist or is not readable.
* @throws ELException
* if an exception was thrown while performing the property or variable resolution.
* The thrown exception must be included as the cause property of this exception, if
* available.
*/
@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
context.setPropertyResolved(false);
for (ELResolver resolver : resolvers) {
boolean readOnly = resolver.isReadOnly(context, base, property);
if (context.isPropertyResolved()) {
return readOnly;
}
}
return false;
}
/**
* Attempts to set the value of the given property object on the given base object. All
* component resolvers are asked to attempt to set the value. If this resolver handles the given
* (base, property) pair, the propertyResolved property of the ELContext object must be set to
* true by the resolver, before returning. If this property is not true after this method is
* called, the caller can safely assume no value has been set. First, propertyResolved is set to
* false on the provided ELContext. Next, for each component resolver in this composite:
*
* - The setValue() method is called, passing in the provided context, base, property and
* value.
* - If the ELContext's propertyResolved flag is false then iteration continues.
* - Otherwise, iteration stops and no more component resolvers are considered.
*
* If none of the component resolvers were able to perform this operation, the propertyResolved
* flag remains set to false. Any exception thrown by component resolvers during the iteration
* is propagated to the caller of this method.
*
* @param context
* The context of this evaluation.
* @param base
* The base object to return the most general property type for, or null to enumerate
* the set of top-level variables that this resolver can evaluate.
* @param property
* The property or variable to return the acceptable type for.
* @param value
* The value to set the property or variable to.
* @throws NullPointerException
* if context is null
* @throws PropertyNotFoundException
* if base is not null and the specified property does not exist or is not readable.
* @throws PropertyNotWritableException
* if the given (base, property) pair is handled by this ELResolver but the
* specified variable or property is not writable.
* @throws ELException
* if an exception was thrown while attempting to set the property or variable. The
* thrown exception must be included as the cause property of this exception, if
* available.
*/
@Override
public void setValue(ELContext context, Object base, Object property, Object value) {
context.setPropertyResolved(false);
for (ELResolver resolver : resolvers) {
resolver.setValue(context, base, property, value);
if (context.isPropertyResolved()) {
return;
}
}
}
/**
* Attempts to resolve and invoke the given method
on the given base
* object by querying all component resolvers.
*
*
* If this resolver handles the given (base, method) pair, the propertyResolved
* property of the ELContext
object must be set to true
by the
* resolver, before returning. If this property is not true
after this method is
* called, the caller should ignore the return value.
*
*
*
* First, propertyResolved
is set to false
on the provided
* ELContext
.
*
*
*
* Next, for each component resolver in this composite:
*
* - The
invoke()
method is called, passing in the provided context
,
* base
, method
, paramTypes
, and params
.
* - If the
ELContext
's propertyResolved
flag is false
* then iteration continues.
* - Otherwise, iteration stops and no more component resolvers are considered. The value
* returned by
getValue()
is returned by this method.
*
*
*
*
* If none of the component resolvers were able to perform this operation, the value
* null
is returned and the propertyResolved
flag remains set to
* false
*
*
*
* Any exception thrown by component resolvers during the iteration is propagated to the caller
* of this method.
*
*
* @param context
* The context of this evaluation.
* @param base
* The bean on which to invoke the method
* @param method
* The simple name of the method to invoke. Will be coerced to a String
.
* If method is "<init>"or "<clinit>" a NoSuchMethodException is raised.
* @param paramTypes
* An array of Class objects identifying the method's formal parameter types, in
* declared order. Use an empty array if the method has no parameters. Can be
* null
, in which case the method's formal parameter types are assumed
* to be unknown.
* @param params
* The parameters to pass to the method, or null
if no parameters.
* @return The result of the method invocation (null
if the method has a
* void
return type).
* @since 2.2
*/
@Override
public Object invoke(ELContext context, Object base, Object method, Class>[] paramTypes, Object[] params) {
context.setPropertyResolved(false);
for (ELResolver resolver : resolvers) {
Object result = resolver.invoke(context, base, method, paramTypes, params);
if (context.isPropertyResolved()) {
return result;
}
}
return null;
}
}