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