org.activiti.engine.impl.juel.ExpressionFactoryImpl 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.juel;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.EnumSet;
import java.util.Properties;
import org.activiti.engine.impl.javax.el.ELContext;
import org.activiti.engine.impl.javax.el.ELException;
import org.activiti.engine.impl.javax.el.ExpressionFactory;
import org.activiti.engine.impl.juel.Builder.Feature;
/**
* Expression factory implementation.
*
* This class is also used as an EL "service provider". The JUEL jar file specifies this
* class as el expression factory implementation in
* META-INF/services/javax.el.ExpressionFactory
. Calling
* {@link ExpressionFactory#newInstance()} will then return an instance of this class, configured as
* described below.
*
* If no properties are specified at construction time, properties are read from
*
* -
* If the file
JAVA_HOME/lib/el.properties
exists and if it contains property
* javax.el.ExpressionFactory
whose value is the name of this class, these properties
* are taken as default properties.
* - Otherwise, if system property
javax.el.ExpressionFactory
is set to the name of
* this class, the system properties {@link System#getProperties()} are taken as default properties.
*
* -
*
el.properties
on your classpath. These properties override the properties from
* JAVA_HOME/lib/el.properties
or {@link System#getProperties()}.
*
* There are also constructors to explicitly pass in an instance of {@link Properties}.
*
* Having this, the following properties are read:
*
* -
*
javax.el.cacheSize
- cache size (int, default is 1000)
* -
*
javax.el.methodInvocations
- allow method invocations as in
* ${foo.bar(baz)}
(boolean, default is false
).
* -
*
javax.el.nullProperties
- resolve null
properties as in
* ${foo[null]}
(boolean, default is false
).
* -
*
javax.el.varArgs
- support function/method calls using varargs (boolean, default is
* false
).
*
*
* @author Christoph Beck
*/
public class ExpressionFactoryImpl extends ExpressionFactory {
/**
* A profile provides a default set of language features that will define the builder's
* behavior. A profile can be adjusted using the javax.el.methodInvocations
,
* javax.el.varArgs
and javax.el.nullProperties
properties.
*
* @since 2.2
*/
public static enum Profile {
/**
* JEE5: none
*/
JEE5(EnumSet.noneOf(Feature.class)),
/**
* JEE6: javax.el.methodInvocations
, javax.el.varArgs
. This is the
* default profile.
*/
JEE6(EnumSet.of(Feature.METHOD_INVOCATIONS, Feature.VARARGS));
private final EnumSet features;
private Profile(EnumSet features) {
this.features = features;
}
Feature[] features() {
return features.toArray(new Feature[features.size()]);
}
boolean contains(Feature feature) {
return features.contains(feature);
}
}
/**
* javax.el.methodInvocations
*/
public static final String PROP_METHOD_INVOCATIONS = "javax.el.methodInvocations";
/**
* javax.el.varArgs
*/
public static final String PROP_VAR_ARGS = "javax.el.varArgs";
/**
* javax.el.nullProperties
*/
public static final String PROP_NULL_PROPERTIES = "javax.el.nullProperties";
/**
* javax.el.cacheSize
*/
public static final String PROP_CACHE_SIZE = "javax.el.cacheSize";
private final TreeStore store;
private final TypeConverter converter;
/**
* Create a new expression factory using the default builder and cache implementations. The
* builder and cache are configured from el.properties
(see above). The maximum
* cache size will be 1000 unless overridden in el.properties
. The builder profile
* is {@link Profile#JEE6} (features may be overridden in el.properties
).
*/
public ExpressionFactoryImpl() {
this(Profile.JEE6);
}
/**
* Create a new expression factory using the default builder and cache implementations. The
* builder and cache are configured from the specified profile and el.properties
* (see above). The maximum cache size will be 1000 unless overridden in
* el.properties
.
*
* @param profile
* builder profile (features may be overridden in el.properties
)
*
* @since 2.2
*/
public ExpressionFactoryImpl(Profile profile) {
Properties properties = loadProperties("el.properties");
this.store = createTreeStore(1000, profile, properties);
this.converter = createTypeConverter(properties);
}
/**
* Create a new expression factory using the default builder and cache implementations. The
* builder and cache are configured using the specified properties. The maximum cache size will
* be 1000 unless overridden by property javax.el.cacheSize
. The builder profile is
* {@link Profile#JEE6} (features may be overridden in properties
).
*
* @param properties
* used to initialize this factory (may be null
)
*/
public ExpressionFactoryImpl(Properties properties) {
this(Profile.JEE6, properties);
}
/**
* Create a new expression factory using the default builder and cache implementations. The
* builder and cache are configured using the specified profile and properties. The maximum
* cache size will be 1000 unless overridden by property javax.el.cacheSize
.
*
* @param profile
* builder profile (individual features may be overridden in properties)
* @param properties
* used to initialize this factory (may be null
)
*
* @since 2.2
*/
public ExpressionFactoryImpl(Profile profile, Properties properties) {
this.store = createTreeStore(1000, profile, properties);
this.converter = createTypeConverter(properties);
}
/**
* Create a new expression factory using the default builder and cache implementations. The
* builder and cache are configured using the specified properties. The maximum cache size will
* be 1000 unless overridden by property javax.el.cacheSize
. The builder profile is
* {@link Profile#JEE6} (individual features may be overridden in properties
).
*
* @param properties
* used to initialize this factory (may be null
)
* @param converter
* custom type converter
*/
public ExpressionFactoryImpl(Properties properties, TypeConverter converter) {
this(Profile.JEE6, properties, converter);
}
/**
* Create a new expression factory using the default builder and cache implementations. The
* builder and cache are configured using the specified profile and properties. The maximum
* cache size will be 1000 unless overridden by property javax.el.cacheSize
.
*
* @param profile
* builder profile (individual features may be overridden in properties)
* @param properties
* used to initialize this factory (may be null
)
* @param converter
* custom type converter
*
* @since 2.2
*/
public ExpressionFactoryImpl(Profile profile, Properties properties, TypeConverter converter) {
this.store = createTreeStore(1000, profile, properties);
this.converter = converter;
}
/**
* Create a new expression factory.
*
* @param store
* the tree store used to parse and cache parse trees.
*/
public ExpressionFactoryImpl(TreeStore store) {
this(store, TypeConverter.DEFAULT);
}
/**
* Create a new expression factory.
*
* @param store
* the tree store used to parse and cache parse trees.
* @param converter
* custom type converter
*/
public ExpressionFactoryImpl(TreeStore store, TypeConverter converter) {
this.store = store;
this.converter = converter;
}
private Properties loadDefaultProperties() {
String home = System.getProperty("java.home");
String path = home + File.separator + "lib" + File.separator + "el.properties";
File file = new File(path);
if (file.exists()) {
Properties properties = new Properties();
InputStream input = null;
try {
properties.load(input = new FileInputStream(file));
} catch (IOException e) {
throw new ELException("Cannot read default EL properties", e);
} finally {
try {
input.close();
} catch (IOException e) {
// ignore...
}
}
if (getClass().getName().equals(properties.getProperty("javax.el.ExpressionFactory"))) {
return properties;
}
}
if (getClass().getName().equals(System.getProperty("javax.el.ExpressionFactory"))) {
return System.getProperties();
}
return null;
}
private Properties loadProperties(String path) {
Properties properties = new Properties(loadDefaultProperties());
// try to find and load properties
InputStream input = null;
try {
input = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
} catch (SecurityException e) {
input = ClassLoader.getSystemResourceAsStream(path);
}
if (input != null) {
try {
properties.load(input);
} catch (IOException e) {
throw new ELException("Cannot read EL properties", e);
} finally {
try {
input.close();
} catch (IOException e) {
// ignore...
}
}
}
return properties;
}
private boolean getFeatureProperty(Profile profile, Properties properties, Feature feature, String property) {
return Boolean.valueOf(properties.getProperty(property, String.valueOf(profile.contains(feature))));
}
/**
* Create the factory's tree store. This implementation creates a new tree store using the
* default builder and cache implementations. The builder and cache are configured using the
* specified properties. The maximum cache size will be as specified unless overridden by
* property javax.el.cacheSize
.
*/
protected TreeStore createTreeStore(int defaultCacheSize, Profile profile, Properties properties) {
// create builder
TreeBuilder builder = null;
if (properties == null) {
builder = createTreeBuilder(null, profile.features());
} else {
EnumSet features = EnumSet.noneOf(Builder.Feature.class);
if (getFeatureProperty(profile, properties, Feature.METHOD_INVOCATIONS, PROP_METHOD_INVOCATIONS)) {
features.add(Builder.Feature.METHOD_INVOCATIONS);
}
if (getFeatureProperty(profile, properties, Feature.VARARGS, PROP_VAR_ARGS)) {
features.add(Builder.Feature.VARARGS);
}
if (getFeatureProperty(profile, properties, Feature.NULL_PROPERTIES, PROP_NULL_PROPERTIES)) {
features.add(Builder.Feature.NULL_PROPERTIES);
}
builder = createTreeBuilder(properties, features.toArray(new Builder.Feature[0]));
}
// create cache
int cacheSize = defaultCacheSize;
if (properties != null && properties.containsKey(PROP_CACHE_SIZE)) {
try {
cacheSize = Integer.parseInt(properties.getProperty(PROP_CACHE_SIZE));
} catch (NumberFormatException e) {
throw new ELException("Cannot parse EL property " + PROP_CACHE_SIZE, e);
}
}
Cache cache = cacheSize > 0 ? new Cache(cacheSize) : null;
return new TreeStore(builder, cache);
}
/**
* Create the factory's type converter. This implementation takes the
* de.odysseus.el.misc.TypeConverter
property as the name of a class implementing
* the de.odysseus.el.misc.TypeConverter
interface. If the property is not set, the
* default converter (TypeConverter.DEFAULT
) is used.
*/
protected TypeConverter createTypeConverter(Properties properties) {
Class> clazz = load(TypeConverter.class, properties);
if (clazz == null) {
return TypeConverter.DEFAULT;
}
try {
return TypeConverter.class.cast(clazz.newInstance());
} catch (Exception e) {
throw new ELException("TypeConverter " + clazz + " could not be instantiated", e);
}
}
/**
* Create the factory's builder. This implementation takes the
* de.odysseus.el.tree.TreeBuilder
property as a name of a class implementing the
* de.odysseus.el.tree.TreeBuilder
interface. If the property is not set, a plain
* de.odysseus.el.tree.impl.Builder
is used. If the configured class is a subclass
* of de.odysseus.el.tree.impl.Builder
and which provides a constructor taking an
* array of Builder.Feature
, this constructor will be invoked. Otherwise, the
* default constructor will be used.
*/
protected TreeBuilder createTreeBuilder(Properties properties, Feature... features) {
Class> clazz = load(TreeBuilder.class, properties);
if (clazz == null) {
return new Builder(features);
}
try {
if (Builder.class.isAssignableFrom(clazz)) {
Constructor> constructor = clazz.getConstructor(Feature[].class);
if (constructor == null) {
if (features == null || features.length == 0) {
return TreeBuilder.class.cast(clazz.newInstance());
} else {
throw new ELException("Builder " + clazz + " is missing constructor (can't pass features)");
}
} else {
return TreeBuilder.class.cast(constructor.newInstance((Object) features));
}
} else {
return TreeBuilder.class.cast(clazz.newInstance());
}
} catch (Exception e) {
throw new ELException("TreeBuilder " + clazz + " could not be instantiated", e);
}
}
private Class> load(Class> clazz, Properties properties) {
if (properties != null) {
String className = properties.getProperty(clazz.getName());
if (className != null) {
ClassLoader loader;
try {
loader = Thread.currentThread().getContextClassLoader();
} catch (Exception e) {
throw new ELException("Could not get context class loader", e);
}
try {
return loader == null ? Class.forName(className) : loader.loadClass(className);
} catch (ClassNotFoundException e) {
throw new ELException("Class " + className + " not found", e);
} catch (Exception e) {
throw new ELException("Class " + className + " could not be instantiated", e);
}
}
}
return null;
}
@Override
public final Object coerceToType(Object obj, Class> targetType) {
return converter.convert(obj, targetType);
}
@Override
public final ObjectValueExpression createValueExpression(Object instance, Class> expectedType) {
return new ObjectValueExpression(converter, instance, expectedType);
}
@Override
public final TreeValueExpression createValueExpression(ELContext context, String expression, Class> expectedType) {
return new TreeValueExpression(store, context.getFunctionMapper(), context.getVariableMapper(), converter,
expression, expectedType);
}
@Override
public final TreeMethodExpression createMethodExpression(ELContext context, String expression,
Class> expectedReturnType, Class>[] expectedParamTypes) {
return new TreeMethodExpression(store, context.getFunctionMapper(), context.getVariableMapper(), converter,
expression, expectedReturnType, expectedParamTypes);
}
}