001: /*
002: * Copyright 2006, 2007 Odysseus Software GmbH
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package de.odysseus.el;
017:
018: import java.io.File;
019: import java.io.FileInputStream;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.util.EnumSet;
023: import java.util.Properties;
024:
025: import javax.el.ELContext;
026: import javax.el.ELException;
027: import javax.el.ExpressionFactory;
028:
029: import de.odysseus.el.misc.TypeConversions;
030: import de.odysseus.el.tree.TreeStore;
031:
032: import de.odysseus.el.tree.impl.Builder;
033: import de.odysseus.el.tree.impl.Cache;
034:
035: /**
036: * Expression factory implementation.
037: *
038: * This class is also used as an EL "service provider".
039: * The <em>JUEL</em> jar files specify this class as their expression factory
040: * implementation in <code>META-INF/services/javax.el.ExpressionFactory</code>.
041: * Calling {@link ExpressionFactory#getExpressionFactory()} will then return
042: * an instance of this class, configured as described below.
043: *
044: * The default constructor loads properties from resources
045: * <ol>
046: * <li>
047: * <code>JAVA_HOME/lib/el.properties</code> -
048: * If this file exists and if it contains property <code>javax.el.ExpressionFactory</code>
049: * whose value is the name of this class, these properties are taken as default properties.
050: * </li>
051: * <li>
052: * <code>el.properties</code> on your classpath. These properties override the
053: * properties from <code>JAVA_HOME/lib/el.properties</code>.
054: * </li>
055: * </ol>
056: * There's also a constructor to explicitly pass in an instance of {@link Properties}.
057: * Having this, the following properties are read:
058: * <ul>
059: * <li>
060: * <code>javax.el.cacheSize</code> - cache size (int, default is 1000)
061: * </li>
062: * <li>
063: * <code>javax.el.methodInvocations</code> - allow method invocations
064: * as in <code>${foo.bar(baz)}</code> (boolean, default is <code>false</code>).
065: * </li>
066: * <li>
067: * <code>javax.el.nullProperties</code> - resolve <code>null</code> properties
068: * as in <code>${foo[null]}</code> (boolean, default is <code>false</code>).
069: * </li>
070: * </ul>
071: *
072: * @author Christoph Beck
073: */
074: public class ExpressionFactoryImpl extends javax.el.ExpressionFactory {
075: private final TreeStore store;
076:
077: /**
078: * Create a new expression factory using the default parser and tree cache implementations.
079: * The builder and cache are configured from <code>el.properties</code> (see above).
080: * The maximum cache size will be 1000 unless overridden in <code>el.properties</code>.
081: */
082: public ExpressionFactoryImpl() {
083: store = createTreeStore(1000, loadProperties("el.properties"));
084: }
085:
086: /**
087: * Create a new expression factory using the default builder and cache implementations.
088: * The builder and cache are configured using the specified properties.
089: * The maximum cache size will be 1000 unless overridden by property <code>javax.el.cacheSize</code>.
090: * @param properties used to initialize builder and cache (may be <code>null</code>)
091: */
092: public ExpressionFactoryImpl(Properties properties) {
093: store = createTreeStore(1000, properties);
094: }
095:
096: /**
097: * Create a new expression factory.
098: * @param store the tree store used to parse and cache parse trees.
099: */
100: public ExpressionFactoryImpl(TreeStore store) {
101: this .store = store;
102: }
103:
104: private Properties loadDefaultProperties() {
105: String home = System.getProperty("java.home");
106: String path = home + File.separator + "lib" + File.separator
107: + "el.properties";
108: File file = new File(path);
109: if (file.exists()) {
110: Properties properties = new Properties();
111: InputStream input = null;
112: try {
113: properties.load(input = new FileInputStream(file));
114: } catch (IOException e) {
115: throw new ELException(
116: "Cannot read default EL properties", e);
117: } finally {
118: try {
119: input.close();
120: } catch (IOException e) {
121: // ignore...
122: }
123: }
124: String clazz = properties
125: .getProperty("javax.el.ExpressionFactory");
126: if (getClass().getName().equals(clazz)) {
127: return properties;
128: }
129: }
130: return null;
131: }
132:
133: private Properties loadProperties(String path) {
134: Properties properties = new Properties(loadDefaultProperties());
135:
136: // try to find and load properties
137: InputStream input = null;
138: try {
139: input = Thread.currentThread().getContextClassLoader()
140: .getResourceAsStream(path);
141: } catch (SecurityException e) {
142: input = ClassLoader.getSystemResourceAsStream(path);
143: }
144: if (input != null) {
145: try {
146: properties.load(input);
147: } catch (IOException e) {
148: throw new ELException("Cannot read EL properties", e);
149: } finally {
150: try {
151: input.close();
152: } catch (IOException e) {
153: // ignore...
154: }
155: }
156: }
157:
158: return properties;
159: }
160:
161: /**
162: * Create the factory's tree store.
163: * This implementation creates a new tree store using the default builder and cache
164: * implementations. The builder and cache are configured using the specified properties.
165: * The maximum cache size will be as specified unless overridden by property
166: * <code>javax.el.cacheSize</code>.
167: */
168: protected TreeStore createTreeStore(int defaultCacheSize,
169: Properties properties) {
170: // create builder
171: EnumSet<Builder.Feature> features = EnumSet
172: .noneOf(Builder.Feature.class);
173: if (properties != null) {
174: if (Boolean.valueOf(properties
175: .getProperty("javax.el.methodInvocations"))) {
176: features.add(Builder.Feature.METHOD_INVOCATIONS);
177: }
178: if (Boolean.valueOf(properties
179: .getProperty("javax.el.nullProperties"))) {
180: features.add(Builder.Feature.NULL_PROPERTIES);
181: }
182: }
183: Builder builder = new Builder(features
184: .toArray(new Builder.Feature[features.size()]));
185:
186: // create cache
187: int cacheSize = defaultCacheSize;
188: if (properties != null
189: && properties.containsKey("javax.el.cacheSize")) {
190: try {
191: cacheSize = Integer.parseInt(properties
192: .getProperty("javax.el.cacheSize"));
193: } catch (NumberFormatException e) {
194: throw new ELException(
195: "Cannot parse EL property javax.el.cacheSize",
196: e);
197: }
198: }
199: Cache cache = cacheSize > 0 ? new Cache(cacheSize) : null;
200:
201: return new TreeStore(builder, cache);
202: }
203:
204: @Override
205: public final Object coerceToType(Object obj, Class<?> targetType) {
206: return TypeConversions.coerceToType(obj, targetType);
207: }
208:
209: @Override
210: public final ObjectValueExpression createValueExpression(
211: Object instance, Class<?> expectedType) {
212: return new ObjectValueExpression(instance, expectedType);
213: }
214:
215: @Override
216: public final TreeValueExpression createValueExpression(
217: ELContext context, String expression, Class<?> expectedType) {
218: return new TreeValueExpression(store, context
219: .getFunctionMapper(), context.getVariableMapper(),
220: expression, expectedType);
221: }
222:
223: @Override
224: public final TreeMethodExpression createMethodExpression(
225: ELContext context, String expression,
226: Class<?> expectedReturnType, Class<?>[] expectedParamTypes) {
227: return new TreeMethodExpression(store, context
228: .getFunctionMapper(), context.getVariableMapper(),
229: expression, expectedReturnType, expectedParamTypes);
230: }
231: }
|