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