001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.language.bean;
018
019 import java.util.List;
020 import java.util.Map;
021
022 import org.apache.camel.CamelContext;
023 import org.apache.camel.Exchange;
024 import org.apache.camel.ExchangePattern;
025 import org.apache.camel.Expression;
026 import org.apache.camel.ExpressionIllegalSyntaxException;
027 import org.apache.camel.Predicate;
028 import org.apache.camel.Processor;
029 import org.apache.camel.component.bean.BeanHolder;
030 import org.apache.camel.component.bean.BeanProcessor;
031 import org.apache.camel.component.bean.ConstantBeanHolder;
032 import org.apache.camel.component.bean.ConstantTypeBeanHolder;
033 import org.apache.camel.component.bean.RegistryBean;
034 import org.apache.camel.util.KeyValueHolder;
035 import org.apache.camel.util.ObjectHelper;
036 import org.apache.camel.util.OgnlHelper;
037 import org.apache.camel.util.StringHelper;
038
039 /**
040 * Evaluates an expression using a bean method invocation
041 */
042 public class BeanExpression implements Expression, Predicate {
043 private final Object bean;
044 private final String beanName;
045 private final Class<?> type;
046 private final String method;
047 private volatile BeanHolder beanHolder;
048
049 public BeanExpression(Object bean, String method) {
050 this.bean = bean;
051 this.method = method;
052 this.beanName = null;
053 this.type = null;
054 }
055
056 public BeanExpression(String beanName, String method) {
057 this.beanName = beanName;
058 this.method = method;
059 this.bean = null;
060 this.type = null;
061 }
062
063 public BeanExpression(Class<?> type, String method) {
064 this.type = type;
065 this.method = method;
066 this.bean = null;
067 this.beanName = null;
068 }
069
070 public BeanExpression(BeanHolder beanHolder, String method) {
071 this.beanHolder = beanHolder;
072 this.method = method;
073 this.bean = null;
074 this.beanName = null;
075 this.type = null;
076 }
077
078 @Override
079 public String toString() {
080 StringBuilder sb = new StringBuilder("BeanExpression[");
081 if (bean != null) {
082 sb.append(bean.toString());
083 } else if (beanName != null) {
084 sb.append(beanName);
085 } else if (type != null) {
086 sb.append(ObjectHelper.className(type));
087 }
088 if (method != null) {
089 sb.append(" method: ").append(method);
090 }
091 sb.append("]");
092 return sb.toString();
093 }
094
095 public Object evaluate(Exchange exchange) {
096
097 // if the bean holder doesn't exist then create it using the context from the exchange
098 if (beanHolder == null) {
099 beanHolder = createBeanHolder(exchange.getContext());
100 }
101
102 // invoking the bean can either be the easy way or using OGNL
103
104 // validate OGNL
105 if (OgnlHelper.isInvalidValidOgnlExpression(method)) {
106 ExpressionIllegalSyntaxException cause = new ExpressionIllegalSyntaxException(method);
107 throw new RuntimeBeanExpressionException(exchange, beanName, method, cause);
108 }
109
110 if (OgnlHelper.isValidOgnlExpression(method)) {
111 // okay the method is an ognl expression
112 OgnlInvokeProcessor ognl = new OgnlInvokeProcessor(beanHolder, method);
113 try {
114 ognl.process(exchange);
115 return ognl.getResult();
116 } catch (Exception e) {
117 throw new RuntimeBeanExpressionException(exchange, beanName, method, e);
118 }
119 } else {
120 // regular non ognl invocation
121 InvokeProcessor invoke = new InvokeProcessor(beanHolder, method);
122 try {
123 invoke.process(exchange);
124 return invoke.getResult();
125 } catch (Exception e) {
126 throw new RuntimeBeanExpressionException(exchange, beanName, method, e);
127 }
128 }
129 }
130
131 public <T> T evaluate(Exchange exchange, Class<T> type) {
132 Object result = evaluate(exchange);
133 return exchange.getContext().getTypeConverter().convertTo(type, result);
134 }
135
136 public boolean matches(Exchange exchange) {
137 Object value = evaluate(exchange);
138 return ObjectHelper.evaluateValuePredicate(value);
139 }
140
141 /**
142 * Optimize to create the bean holder once, so we can reuse it for further
143 * evaluation, which is faster.
144 */
145 private synchronized BeanHolder createBeanHolder(CamelContext context) {
146 // either use registry lookup or a constant bean
147 BeanHolder holder;
148 if (bean != null) {
149 holder = new ConstantBeanHolder(bean, context);
150 } else if (beanName != null) {
151 holder = new RegistryBean(context, beanName);
152 } else if (type != null) {
153 holder = new ConstantTypeBeanHolder(type, context);
154 } else {
155 throw new IllegalArgumentException("Either bean, beanName or type should be set on " + this);
156 }
157 return holder;
158 }
159
160 /**
161 * Invokes a given bean holder. The method name is optional.
162 */
163 private final class InvokeProcessor implements Processor {
164
165 private BeanHolder beanHolder;
166 private String methodName;
167 private Object result;
168
169 private InvokeProcessor(BeanHolder beanHolder, String methodName) {
170 this.beanHolder = beanHolder;
171 this.methodName = methodName;
172 }
173
174 public void process(Exchange exchange) throws Exception {
175 BeanProcessor processor = new BeanProcessor(beanHolder);
176 if (methodName != null) {
177 processor.setMethod(methodName);
178 // enable OGNL like invocation
179 processor.setShorthandMethod(true);
180 }
181 try {
182 // copy the original exchange to avoid side effects on it
183 Exchange resultExchange = exchange.copy();
184 // remove any existing exception in case we do OGNL on the exception
185 resultExchange.setException(null);
186
187 // force to use InOut to retrieve the result on the OUT message
188 resultExchange.setPattern(ExchangePattern.InOut);
189 processor.process(resultExchange);
190 result = resultExchange.getOut().getBody();
191
192 // propagate properties and headers from result
193 if (resultExchange.hasProperties()) {
194 exchange.getProperties().putAll(resultExchange.getProperties());
195 }
196 if (resultExchange.getOut().hasHeaders()) {
197 exchange.getIn().getHeaders().putAll(resultExchange.getOut().getHeaders());
198 }
199
200 // propagate exceptions
201 if (resultExchange.getException() != null) {
202 exchange.setException(resultExchange.getException());
203 }
204 } catch (Exception e) {
205 throw new RuntimeBeanExpressionException(exchange, beanName, methodName, e);
206 }
207 }
208
209 public Object getResult() {
210 return result;
211 }
212 }
213
214 /**
215 * To invoke a bean using a OGNL notation which denotes the chain of methods to invoke.
216 * <p/>
217 * For more advanced OGNL you may have to look for a real framework such as OGNL, Mvel or dynamic
218 * programming language such as Groovy, JuEL, JavaScript.
219 */
220 private final class OgnlInvokeProcessor implements Processor {
221
222 private final String ognl;
223 private final BeanHolder beanHolder;
224 private Object result;
225
226 public OgnlInvokeProcessor(BeanHolder beanHolder, String ognl) {
227 this.beanHolder = beanHolder;
228 this.ognl = ognl;
229 // we must start with having bean as the result
230 this.result = beanHolder.getBean();
231 }
232
233 public void process(Exchange exchange) throws Exception {
234 // copy the original exchange to avoid side effects on it
235 Exchange resultExchange = exchange.copy();
236 // remove any existing exception in case we do OGNL on the exception
237 resultExchange.setException(null);
238 // force to use InOut to retrieve the result on the OUT message
239 resultExchange.setPattern(ExchangePattern.InOut);
240 // do not propagate any method name when using OGNL, as with OGNL we
241 // compute and provide the method name to explicit to invoke
242 resultExchange.getIn().removeHeader(Exchange.BEAN_METHOD_NAME);
243
244 // current ognl path as we go along
245 String ognlPath = "";
246
247 // loop and invoke each method
248 Object beanToCall = beanHolder.getBean();
249 // there must be a bean to call with, we currently does not support OGNL expressions on using purely static methods
250 if (beanToCall == null) {
251 throw new IllegalArgumentException("Bean instance is null. OGNL bean expressions requires bean instances.");
252 }
253
254 // Split ognl except when this is not a Map, Array
255 // and we would like to keep the dots within the key name
256 List<String> methods = OgnlHelper.splitOgnl(ognl);
257
258 for (String methodName : methods) {
259 BeanHolder holder = new ConstantBeanHolder(beanToCall, exchange.getContext());
260
261 // support the null safe operator
262 boolean nullSafe = OgnlHelper.isNullSafeOperator(methodName);
263
264 // keep up with how far are we doing
265 ognlPath += methodName;
266
267 // get rid of leading ?. or . as we only needed that to determine if null safe was enabled or not
268 methodName = OgnlHelper.removeLeadingOperators(methodName);
269
270 // are we doing an index lookup (eg in Map/List/array etc)?
271 String key = null;
272 KeyValueHolder<String, String> index = OgnlHelper.isOgnlIndex(methodName);
273 if (index != null) {
274 methodName = index.getKey();
275 key = index.getValue();
276 }
277
278 // only invoke if we have a method name to use to invoke
279 if (methodName != null) {
280 InvokeProcessor invoke = new InvokeProcessor(holder, methodName);
281 invoke.process(resultExchange);
282
283 // check for exception and rethrow if we failed
284 if (resultExchange.getException() != null) {
285 throw new RuntimeBeanExpressionException(exchange, beanName, methodName, resultExchange.getException());
286 }
287
288 result = invoke.getResult();
289 }
290
291 // if there was a key then we need to lookup using the key
292 if (key != null) {
293 result = lookupResult(resultExchange, key, result, nullSafe, ognlPath, holder.getBean());
294 }
295
296 // check null safe for null results
297 if (result == null && nullSafe) {
298 return;
299 }
300
301 // prepare for next bean to invoke
302 beanToCall = result;
303 }
304 }
305
306 private Object lookupResult(Exchange exchange, String key, Object result, boolean nullSafe, String ognlPath, Object bean) {
307 ObjectHelper.notEmpty(key, "key", "in Simple language ognl path: " + ognlPath);
308
309 // trim key
310 key = key.trim();
311
312 // remove any enclosing quotes
313 key = StringHelper.removeLeadingAndEndingQuotes(key);
314
315 // try map first
316 Map<?, ?> map = exchange.getContext().getTypeConverter().convertTo(Map.class, result);
317 if (map != null) {
318 return map.get(key);
319 }
320
321 // special for list is last keyword
322 Integer num = exchange.getContext().getTypeConverter().tryConvertTo(Integer.class, key);
323 boolean checkList = key.startsWith("last") || num != null;
324
325 if (checkList) {
326 List<?> list = exchange.getContext().getTypeConverter().convertTo(List.class, result);
327 if (list != null) {
328 if (key.startsWith("last")) {
329 num = list.size() - 1;
330
331 // maybe its an expression to subtract a number after last
332 String after = ObjectHelper.after(key, "-");
333 if (after != null) {
334 Integer redux = exchange.getContext().getTypeConverter().tryConvertTo(Integer.class, after.trim());
335 if (redux != null) {
336 num -= redux;
337 } else {
338 throw new ExpressionIllegalSyntaxException(key);
339 }
340 }
341 }
342 if (num != null && num >= 0 && list.size() > num - 1) {
343 return list.get(num);
344 }
345 if (!nullSafe) {
346 // not null safe then its mandatory so thrown out of bounds exception
347 throw new IndexOutOfBoundsException("Index: " + num + ", Size: " + list.size()
348 + " out of bounds with List from bean: " + bean + "using OGNL path [" + ognlPath + "]");
349 }
350 }
351 }
352
353 if (!nullSafe) {
354 throw new IndexOutOfBoundsException("Key: " + key + " not found in bean: " + bean + " of type: "
355 + ObjectHelper.classCanonicalName(bean) + " using OGNL path [" + ognlPath + "]");
356 } else {
357 // null safe so we can return null
358 return null;
359 }
360 }
361
362 public Object getResult() {
363 return result;
364 }
365 }
366
367 }