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