/* * Copyright 2013 <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a> * * 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.ocpsoft.rewrite.el; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.ocpsoft.common.pattern.WeightedComparator; import org.ocpsoft.common.services.ServiceLoader; import org.ocpsoft.common.util.Iterators; import org.ocpsoft.logging.Logger; import org.ocpsoft.rewrite.bind.Binding; import org.ocpsoft.rewrite.bind.Retrieval; import org.ocpsoft.rewrite.context.EvaluationContext; import org.ocpsoft.rewrite.el.spi.ExpressionLanguageProvider; import org.ocpsoft.rewrite.event.Rewrite; import org.ocpsoft.rewrite.exception.RewriteException; /** * Responsible for binding to EL expressions. * * @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a> */ public abstract class El implements Binding, Retrieval { private static final Logger log = Logger.getLogger(El.class); private static volatile List<ExpressionLanguageProvider> _providers; /** * Create a new EL Method binding using distinct expressions to submit and retrieve values. The method intended for * use in submission must accept a single parameter of the expected type. */ public static El method(final String retrieve, final String submit) { return new ElMethod(new ConstantExpression(retrieve), new ConstantExpression(submit)); } /** * Create a new EL Method binding to retrieve values. The method must return a value of the expected type. */ public static El retrievalMethod(final String expression) { return new ElMethod(new ConstantExpression(expression), null); } /** * Create a new EL Method binding to retrieve values. This method allows the caller to supply {@link Method} instance * to refer to the method that should be invoked. */ public static El retrievalMethod(final Method method) { return retrievalMethod(method.getDeclaringClass(), method.getName()); } /** * Create a new EL Method binding to retrieve values. This method allows the caller to supply the {@link Class} * representing the type of the target object and the method name to refer to the method that should be invoked. */ public static El retrievalMethod(final Class<?> clazz, final String methodName) { return new ElMethod(new TypeBasedExpression(clazz, methodName), null); } /** * Create a new EL Method binding to submit values. The method must accept a single parameter of the expected type. */ public static El submissionMethod(final String expression) { return new ElMethod(null, new ConstantExpression(expression)); } /** * Create a new EL Value binding using a single expression to submit and retrieve values. The specified property must * either be public, or have a publicly defined getter/setter. */ public static El property(final String expression) { return new ElProperty(new ConstantExpression(expression)); } /** * Create a new EL Value binding using a single expression to submit and retrieve values. The specified property must * either be public, or have a publicly defined getter/setter. Instead of an EL expression this method expects a * {@link Field} argument. The EL expression will be automatically created at runtime. */ public static El property(final Field field) { return property(field.getDeclaringClass(), field.getName()); } /** * Create a new EL Value binding using a single expression to submit and retrieve values. The specified property must * either be public, or have a publicly defined getter/setter. Instead of an EL expression this method expects the * {@link Class} representing the type of the target object and the field name. The EL expression will be * automatically created at runtime. */ public static El property(final Class<?> clazz, final String fieldName) { return new ElProperty(new TypeBasedExpression(clazz, fieldName)); } /** * Create a new EL Value binding using a single expression to submit and retrieve values. The specified property must * either be public, or have a publicly defined getter/setter. */ public static El properties(final String submit, final String retrieve) { return new ElProperties(new ConstantExpression(submit), new ConstantExpression(retrieve)); } /** * Create a new EL Value binding using a single expression to submit and retrieve values. The specified property must * either be public, or have a publicly defined getter/setter. Instead of an EL expression this method expects a * {@link Field} argument. The EL expression will be automatically created at runtime. */ public static El properties(final Field submit, final Field retrieve) { return new ElProperties(new TypeBasedExpression(submit.getDeclaringClass(), submit.getName()), new TypeBasedExpression(retrieve.getDeclaringClass(), retrieve.getName())); } @SuppressWarnings("unchecked") private static List<ExpressionLanguageProvider> getProviders() { if (_providers == null) { synchronized (El.class) { if (_providers == null) { _providers = Iterators.asList(ServiceLoader.load(ExpressionLanguageProvider.class)); Collections.sort(_providers, new WeightedComparator()); if (_providers.isEmpty()) { log.warn("No instances of [{}] were configured. EL support is disabled.", ExpressionLanguageProvider.class.getName()); } } } } return _providers; } private static Object executeProviderCallable(Rewrite event, EvaluationContext context, ProviderCallable<Object> providerCallable) { List<Exception> exceptions = new ArrayList<Exception>(); for (ExpressionLanguageProvider provider : getProviders()) { try { return providerCallable.call(event, context, provider); } catch (RuntimeException e) { throw e; } catch (Exception e) { exceptions.add(e); } } for (Exception exception : exceptions) { log.error("DEFERRED EXCEPTION", exception); } throw new RewriteException("No registered " + ExpressionLanguageProvider.class.getName() + " could handle the Expression [" + providerCallable.getExpression() + "]"); } /** * Handle EL Method Invocation and Value Extraction */ public static class ElMethod extends El { private final Expression getExpression; private final Expression setExpression; public ElMethod(final Expression getExpression, final Expression setExpression) { this.getExpression = getExpression; this.setExpression = setExpression; } @Override public Object retrieve(final Rewrite event, final EvaluationContext context) { if (!supportsRetrieval()) throw new RewriteException("Method binding expression supports submission only [" + setExpression + "], no value retrieval expression was defined"); return executeProviderCallable(event, context, new ProviderCallable<Object>() { @Override public Object call(Rewrite event, EvaluationContext context, ExpressionLanguageProvider provider) throws Exception { return provider.evaluateMethodExpression(getExpression.getExpression()); } @Override public String getExpression() { return ElMethod.this.getExpression.getExpression(); } }); } @Override public Object submit(final Rewrite event, final EvaluationContext context, final Object value) { if (!supportsSubmission()) throw new RewriteException("Method binding expression supports retrieval only [" + getExpression + "], no value submission expression was defined"); return executeProviderCallable(event, context, new ProviderCallable<Object>() { @Override public Object call(Rewrite event, EvaluationContext context, ExpressionLanguageProvider provider) throws Exception { return provider.evaluateMethodExpression(setExpression.getExpression(), value); } @Override public String getExpression() { return ElMethod.this.setExpression.getExpression(); } }); } @Override public boolean supportsRetrieval() { return getExpression != null; } @Override public boolean supportsSubmission() { return setExpression != null; } @Override public String toString() { return "ElMethod [retrieve= [ " + getExpression + " }, submit= [ " + setExpression + " ]"; } } /** * Handle EL Property Injection and Extraction */ public static class ElProperty extends El { private final Expression expression; public ElProperty(final Expression expression) { this.expression = expression; } @Override public Object retrieve(final Rewrite event, final EvaluationContext context) { return executeProviderCallable(event, context, new ProviderCallable<Object>() { @Override public Object call(Rewrite event, EvaluationContext context, ExpressionLanguageProvider provider) throws Exception { return provider.retrieveValue(expression.getExpression()); } @Override public String getExpression() { return ElProperty.this.expression.getExpression(); } }); } @Override public Object submit(final Rewrite event, final EvaluationContext context, final Object value) { return executeProviderCallable(event, context, new ProviderCallable<Object>() { @Override public Object call(Rewrite event, EvaluationContext context, ExpressionLanguageProvider provider) throws Exception { provider.submitValue(expression.getExpression(), value); return null; } @Override public String getExpression() { return ElProperty.this.expression.getExpression(); } }); } @Override public boolean supportsRetrieval() { return true; } @Override public boolean supportsSubmission() { return true; } @Override public String toString() { return "ElProperty [ " + expression + " ]"; } } public static class ElProperties extends El { private Expression submit; private Expression retrieve; public ElProperties(final Expression submit, final Expression retrieve) { this.submit = submit; this.retrieve = retrieve; } @Override public Object retrieve(final Rewrite event, final EvaluationContext context) { return executeProviderCallable(event, context, new ProviderCallable<Object>() { @Override public Object call(Rewrite event, EvaluationContext context, ExpressionLanguageProvider provider) throws Exception { return provider.retrieveValue(retrieve.getExpression()); } @Override public String getExpression() { return retrieve.getExpression(); } }); } @Override public Object submit(final Rewrite event, final EvaluationContext context, final Object value) { return executeProviderCallable(event, context, new ProviderCallable<Object>() { @Override public Object call(Rewrite event, EvaluationContext context, ExpressionLanguageProvider provider) throws Exception { provider.submitValue(submit.getExpression(), value); return null; } @Override public String getExpression() { return submit.getExpression(); } }); } @Override public boolean supportsRetrieval() { return true; } @Override public boolean supportsSubmission() { return true; } @Override public String toString() { return "ElProperties [ submitTo =>" + submit + ", retrieveFrom=>" + retrieve + " ]"; } } }