blob: 93e1ee627c94b8719bc30fba6a0760680601d064 [file] [log] [blame]
/**
* 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.camel.language.bean;
import java.util.List;
import java.util.Map;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.Expression;
import org.apache.camel.ExpressionIllegalSyntaxException;
import org.apache.camel.Predicate;
import org.apache.camel.Processor;
import org.apache.camel.component.bean.BeanHolder;
import org.apache.camel.component.bean.BeanProcessor;
import org.apache.camel.component.bean.ConstantBeanHolder;
import org.apache.camel.component.bean.RegistryBean;
import org.apache.camel.util.KeyValueHolder;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.OgnlHelper;
/**
* Evaluates an expression using a bean method invocation
*
* @version
*/
public class BeanExpression implements Expression, Predicate {
private String beanName;
private String method;
private Object bean;
public BeanExpression(Object bean, String method) {
this.bean = bean;
this.method = method;
}
public BeanExpression(String beanName, String method) {
this.beanName = beanName;
this.method = method;
}
@Override
public String toString() {
return "BeanExpression[bean:" + (bean == null ? beanName : bean) + " method: " + method + "]";
}
public Object evaluate(Exchange exchange) {
// either use registry lookup or a constant bean
BeanHolder holder;
if (bean == null) {
holder = new RegistryBean(exchange.getContext(), beanName);
} else {
holder = new ConstantBeanHolder(bean, exchange.getContext());
}
// invoking the bean can either be the easy way or using OGNL
// validate OGNL
if (OgnlHelper.isInvalidValidOgnlExpression(method)) {
ExpressionIllegalSyntaxException cause = new ExpressionIllegalSyntaxException(method);
throw new RuntimeBeanExpressionException(exchange, beanName, method, cause);
}
if (OgnlHelper.isValidOgnlExpression(method)) {
// okay the method is an ognl expression
Object beanToCall = holder.getBean();
OgnlInvokeProcessor ognl = new OgnlInvokeProcessor(beanToCall, method);
try {
ognl.process(exchange);
return ognl.getResult();
} catch (Exception e) {
throw new RuntimeBeanExpressionException(exchange, beanName, method, e);
}
} else {
// regular non ognl invocation
InvokeProcessor invoke = new InvokeProcessor(holder, method);
try {
invoke.process(exchange);
return invoke.getResult();
} catch (Exception e) {
throw new RuntimeBeanExpressionException(exchange, beanName, method, e);
}
}
}
public <T> T evaluate(Exchange exchange, Class<T> type) {
Object result = evaluate(exchange);
return exchange.getContext().getTypeConverter().convertTo(type, result);
}
public boolean matches(Exchange exchange) {
Object value = evaluate(exchange);
return ObjectHelper.evaluateValuePredicate(value);
}
/**
* Invokes a given bean holder. The method name is optional.
*/
private final class InvokeProcessor implements Processor {
private BeanHolder beanHolder;
private String methodName;
private Object result;
private InvokeProcessor(BeanHolder beanHolder, String methodName) {
this.beanHolder = beanHolder;
this.methodName = methodName;
}
public void process(Exchange exchange) throws Exception {
BeanProcessor processor = new BeanProcessor(beanHolder);
if (methodName != null) {
processor.setMethod(methodName);
// enable OGNL like invocation
processor.setShorthandMethod(true);
}
try {
// copy the original exchange to avoid side effects on it
Exchange resultExchange = exchange.copy();
// force to use InOut to retrieve the result on the OUT message
resultExchange.setPattern(ExchangePattern.InOut);
processor.process(resultExchange);
result = resultExchange.getOut().getBody();
// propagate exceptions
if (resultExchange.getException() != null) {
exchange.setException(resultExchange.getException());
}
} catch (Exception e) {
throw new RuntimeBeanExpressionException(exchange, beanName, methodName, e);
}
}
public Object getResult() {
return result;
}
}
/**
* To invoke a bean using a OGNL notation which denotes the chain of methods to invoke.
* <p/>
* For more advanced OGNL you may have to look for a real framework such as OGNL, Mvel or dynamic
* programming language such as Groovy, JuEL, JavaScript.
*/
private final class OgnlInvokeProcessor implements Processor {
private final Object bean;
private final String ognl;
private Object result;
public OgnlInvokeProcessor(Object bean, String ognl) {
this.bean = bean;
this.ognl = ognl;
// we must start with having bean as the result
this.result = bean;
}
public void process(Exchange exchange) throws Exception {
// copy the original exchange to avoid side effects on it
Exchange resultExchange = exchange.copy();
// force to use InOut to retrieve the result on the OUT message
resultExchange.setPattern(ExchangePattern.InOut);
// current ognl path as we go along
String ognlPath = "";
// loop and invoke each method
Object beanToCall = bean;
// Split ognl except when this is not a Map, Array
// and we would like to keep the dots within the key name
List<String> methods = OgnlHelper.splitOgnl(ognl);
for (String methodName : methods) {
BeanHolder holder = new ConstantBeanHolder(beanToCall, exchange.getContext());
// support the null safe operator
boolean nullSafe = OgnlHelper.isNullSafeOperator(methodName);
// keep up with how far are we doing
ognlPath += methodName;
// get rid of leading ?. or . as we only needed that to determine if null safe was enabled or not
methodName = OgnlHelper.removeLeadingOperators(methodName);
// are we doing an index lookup (eg in Map/List/array etc)?
String key = null;
KeyValueHolder<String, String> index = OgnlHelper.isOgnlIndex(methodName);
if (index != null) {
methodName = index.getKey();
key = index.getValue();
}
// only invoke if we have a method name to use to invoke
if (methodName != null) {
InvokeProcessor invoke = new InvokeProcessor(holder, methodName);
invoke.process(resultExchange);
// check for exception and rethrow if we failed
if (resultExchange.getException() != null) {
throw new RuntimeBeanExpressionException(exchange, beanName, methodName, resultExchange.getException());
}
result = invoke.getResult();
}
// if there was a key then we need to lookup using the key
if (key != null) {
result = lookupResult(resultExchange, key, result, nullSafe, ognlPath, holder.getBean());
}
// check null safe for null results
if (result == null && nullSafe) {
return;
}
// prepare for next bean to invoke
beanToCall = result;
}
}
private Object lookupResult(Exchange exchange, String key, Object result, boolean nullSafe, String ognlPath, Object bean) {
// trim key
key = key.trim();
// try map first
Map map = exchange.getContext().getTypeConverter().convertTo(Map.class, result);
if (map != null) {
return map.get(key);
}
// special for list is last keyword
Integer num = exchange.getContext().getTypeConverter().convertTo(Integer.class, key);
boolean checkList = key.startsWith("last") || num != null;
if (checkList) {
List list = exchange.getContext().getTypeConverter().convertTo(List.class, result);
if (list != null) {
if (key.startsWith("last")) {
num = list.size() - 1;
// maybe its an expression to subtract a number after last
String after = ObjectHelper.after(key, "-");
if (after != null) {
Integer redux = exchange.getContext().getTypeConverter().convertTo(Integer.class, after.trim());
if (redux != null) {
num -= redux;
} else {
throw new ExpressionIllegalSyntaxException(key);
}
}
}
if (num != null && num >= 0 && list.size() > num - 1) {
return list.get(num);
}
if (!nullSafe) {
// not null safe then its mandatory so thrown out of bounds exception
throw new IndexOutOfBoundsException("Index: " + num + ", Size: " + list.size()
+ " out of bounds with List from bean: " + bean + "using OGNL path [" + ognlPath + "]");
}
}
}
if (!nullSafe) {
throw new IndexOutOfBoundsException("Key: " + key + " not found in bean: " + bean + " of type: "
+ ObjectHelper.classCanonicalName(bean) + " using OGNL path [" + ognlPath + "]");
} else {
// null safe so we can return null
return null;
}
}
public Object getResult() {
return result;
}
}
}