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 return exchange.getContext().getTypeConverter().convertTo(type, exchange, 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}