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