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.component.bean; 018 019import java.lang.annotation.Annotation; 020import java.lang.reflect.Method; 021import java.lang.reflect.Modifier; 022import java.lang.reflect.Proxy; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.Comparator; 028import java.util.HashMap; 029import java.util.Iterator; 030import java.util.List; 031import java.util.Map; 032 033import org.apache.camel.AttachmentObjects; 034import org.apache.camel.Attachments; 035import org.apache.camel.Body; 036import org.apache.camel.CamelContext; 037import org.apache.camel.Exchange; 038import org.apache.camel.ExchangeException; 039import org.apache.camel.ExchangeProperties; 040import org.apache.camel.ExchangeProperty; 041import org.apache.camel.Expression; 042import org.apache.camel.Handler; 043import org.apache.camel.Header; 044import org.apache.camel.Headers; 045import org.apache.camel.Message; 046import org.apache.camel.OutHeaders; 047import org.apache.camel.Properties; 048import org.apache.camel.Property; 049import org.apache.camel.PropertyInject; 050import org.apache.camel.builder.ExpressionBuilder; 051import org.apache.camel.language.LanguageAnnotation; 052import org.apache.camel.spi.Registry; 053import org.apache.camel.util.CastUtils; 054import org.apache.camel.util.IntrospectionSupport; 055import org.apache.camel.util.ObjectHelper; 056import org.apache.camel.util.StringHelper; 057import org.apache.camel.util.StringQuoteHelper; 058import org.slf4j.Logger; 059import org.slf4j.LoggerFactory; 060 061/** 062 * Represents the metadata about a bean type created via a combination of 063 * introspection and annotations together with some useful sensible defaults 064 */ 065public class BeanInfo { 066 private static final Logger LOG = LoggerFactory.getLogger(BeanInfo.class); 067 private static final String CGLIB_CLASS_SEPARATOR = "$$"; 068 private static final List<Method> EXCLUDED_METHODS = new ArrayList<Method>(); 069 private final CamelContext camelContext; 070 private final BeanComponent component; 071 private final Class<?> type; 072 private final ParameterMappingStrategy strategy; 073 private final MethodInfo defaultMethod; 074 // shared state with details of operations introspected from the bean, created during the constructor 075 private Map<String, List<MethodInfo>> operations = new HashMap<String, List<MethodInfo>>(); 076 private List<MethodInfo> operationsWithBody = new ArrayList<MethodInfo>(); 077 private List<MethodInfo> operationsWithNoBody = new ArrayList<MethodInfo>(); 078 private List<MethodInfo> operationsWithCustomAnnotation = new ArrayList<MethodInfo>(); 079 private List<MethodInfo> operationsWithHandlerAnnotation = new ArrayList<MethodInfo>(); 080 private Map<Method, MethodInfo> methodMap = new HashMap<Method, MethodInfo>(); 081 private boolean publicConstructors; 082 083 static { 084 // exclude all java.lang.Object methods as we dont want to invoke them 085 EXCLUDED_METHODS.addAll(Arrays.asList(Object.class.getMethods())); 086 // exclude all java.lang.reflect.Proxy methods as we dont want to invoke them 087 EXCLUDED_METHODS.addAll(Arrays.asList(Proxy.class.getMethods())); 088 try { 089 // but keep toString as this method is okay 090 EXCLUDED_METHODS.remove(Object.class.getMethod("toString")); 091 EXCLUDED_METHODS.remove(Proxy.class.getMethod("toString")); 092 } catch (Throwable e) { 093 // ignore 094 } 095 } 096 097 public BeanInfo(CamelContext camelContext, Class<?> type) { 098 this(camelContext, type, createParameterMappingStrategy(camelContext)); 099 } 100 101 public BeanInfo(CamelContext camelContext, Method explicitMethod) { 102 this(camelContext, explicitMethod.getDeclaringClass(), explicitMethod, createParameterMappingStrategy(camelContext)); 103 } 104 105 public BeanInfo(CamelContext camelContext, Class<?> type, ParameterMappingStrategy strategy) { 106 this(camelContext, type, null, strategy); 107 } 108 109 public BeanInfo(CamelContext camelContext, Class<?> type, Method explicitMethod, ParameterMappingStrategy strategy) { 110 this.camelContext = camelContext; 111 this.type = type; 112 this.strategy = strategy; 113 this.component = camelContext.getComponent("bean", BeanComponent.class); 114 115 final BeanInfoCacheKey key = new BeanInfoCacheKey(type, explicitMethod); 116 117 // lookup if we have a bean info cache 118 BeanInfo beanInfo = component.getBeanInfoFromCache(key); 119 if (beanInfo != null) { 120 // copy the values from the cache we need 121 defaultMethod = beanInfo.defaultMethod; 122 operations = beanInfo.operations; 123 operationsWithBody = beanInfo.operationsWithBody; 124 operationsWithNoBody = beanInfo.operationsWithNoBody; 125 operationsWithCustomAnnotation = beanInfo.operationsWithCustomAnnotation; 126 operationsWithHandlerAnnotation = beanInfo.operationsWithHandlerAnnotation; 127 methodMap = beanInfo.methodMap; 128 publicConstructors = beanInfo.publicConstructors; 129 return; 130 } 131 132 if (explicitMethod != null) { 133 // must be a valid method 134 if (!isValidMethod(type, explicitMethod)) { 135 throw new IllegalArgumentException("The method " + explicitMethod + " is not valid (for example the method must be public)"); 136 } 137 introspect(getType(), explicitMethod); 138 } else { 139 introspect(getType()); 140 } 141 142 // if there are only 1 method with 1 operation then select it as a default/fallback method 143 MethodInfo method = null; 144 if (operations.size() == 1) { 145 List<MethodInfo> methods = operations.values().iterator().next(); 146 if (methods.size() == 1) { 147 method = methods.get(0); 148 } 149 } 150 defaultMethod = method; 151 152 // mark the operations lists as unmodifiable, as they should not change during runtime 153 // to keep this code thread safe 154 operations = Collections.unmodifiableMap(operations); 155 operationsWithBody = Collections.unmodifiableList(operationsWithBody); 156 operationsWithNoBody = Collections.unmodifiableList(operationsWithNoBody); 157 operationsWithCustomAnnotation = Collections.unmodifiableList(operationsWithCustomAnnotation); 158 operationsWithHandlerAnnotation = Collections.unmodifiableList(operationsWithHandlerAnnotation); 159 methodMap = Collections.unmodifiableMap(methodMap); 160 161 // add new bean info to cache 162 component.addBeanInfoToCache(key, this); 163 } 164 165 public Class<?> getType() { 166 return type; 167 } 168 169 public CamelContext getCamelContext() { 170 return camelContext; 171 } 172 173 public static ParameterMappingStrategy createParameterMappingStrategy(CamelContext camelContext) { 174 // lookup in registry first if there is a user define strategy 175 Registry registry = camelContext.getRegistry(); 176 ParameterMappingStrategy answer = registry.lookupByNameAndType(BeanConstants.BEAN_PARAMETER_MAPPING_STRATEGY, ParameterMappingStrategy.class); 177 if (answer == null) { 178 // no then use the default one 179 answer = new DefaultParameterMappingStrategy(); 180 } 181 182 return answer; 183 } 184 185 public MethodInvocation createInvocation(Object pojo, Exchange exchange) 186 throws AmbiguousMethodCallException, MethodNotFoundException { 187 return createInvocation(pojo, exchange, null); 188 } 189 190 private MethodInvocation createInvocation(Object pojo, Exchange exchange, Method explicitMethod) 191 throws AmbiguousMethodCallException, MethodNotFoundException { 192 MethodInfo methodInfo = null; 193 194 // find the explicit method to invoke 195 if (explicitMethod != null) { 196 for (List<MethodInfo> infos : operations.values()) { 197 for (MethodInfo info : infos) { 198 if (explicitMethod.equals(info.getMethod())) { 199 return info.createMethodInvocation(pojo, info.hasParameters(), exchange); 200 } 201 } 202 } 203 throw new MethodNotFoundException(exchange, pojo, explicitMethod.getName()); 204 } 205 206 String methodName = exchange.getIn().getHeader(Exchange.BEAN_METHOD_NAME, String.class); 207 if (methodName != null) { 208 209 // do not use qualifier for name 210 String name = methodName; 211 if (methodName.contains("(")) { 212 name = StringHelper.before(methodName, "("); 213 // the must be a ending parenthesis 214 if (!methodName.endsWith(")")) { 215 throw new IllegalArgumentException("Method should end with parenthesis, was " + methodName); 216 } 217 // and there must be an even number of parenthesis in the syntax 218 // (we can use betweenOuterPair as it return null if the syntax is invalid) 219 if (StringHelper.betweenOuterPair(methodName, '(', ')') == null) { 220 throw new IllegalArgumentException("Method should have even pair of parenthesis, was " + methodName); 221 } 222 } 223 boolean emptyParameters = methodName.endsWith("()"); 224 225 // special for getClass, as we want the user to be able to invoke this method 226 // for example to log the class type or the likes 227 if ("class".equals(name) || "getClass".equals(name)) { 228 try { 229 Method method = pojo.getClass().getMethod("getClass"); 230 methodInfo = new MethodInfo(exchange.getContext(), pojo.getClass(), method, Collections.<ParameterInfo>emptyList(), Collections.<ParameterInfo>emptyList(), false, false); 231 } catch (NoSuchMethodException e) { 232 throw new MethodNotFoundException(exchange, pojo, "getClass"); 233 } 234 // special for length on an array type 235 } else if ("length".equals(name) && pojo.getClass().isArray()) { 236 try { 237 // need to use arrayLength method from ObjectHelper as Camel's bean OGNL support is method invocation based 238 // and not for accessing fields. And hence we need to create a MethodInfo instance with a method to call 239 // and therefore use arrayLength from ObjectHelper to return the array length field. 240 Method method = ObjectHelper.class.getMethod("arrayLength", Object[].class); 241 ParameterInfo pi = new ParameterInfo(0, Object[].class, null, ExpressionBuilder.mandatoryBodyExpression(Object[].class, true)); 242 List<ParameterInfo> lpi = new ArrayList<ParameterInfo>(1); 243 lpi.add(pi); 244 methodInfo = new MethodInfo(exchange.getContext(), pojo.getClass(), method, lpi, lpi, false, false); 245 // Need to update the message body to be pojo for the invocation 246 exchange.getIn().setBody(pojo); 247 } catch (NoSuchMethodException e) { 248 throw new MethodNotFoundException(exchange, pojo, "getClass"); 249 } 250 } else { 251 List<MethodInfo> methods = getOperations(name); 252 if (methods != null && methods.size() == 1) { 253 // only one method then choose it 254 methodInfo = methods.get(0); 255 256 // validate that if we want an explicit no-arg method, then that's what we get 257 if (emptyParameters && methodInfo.hasParameters()) { 258 throw new MethodNotFoundException(exchange, pojo, methodName, "(with no parameters)"); 259 } 260 } else if (methods != null) { 261 // there are more methods with that name so we cannot decide which to use 262 263 // but first let's try to choose a method and see if that complies with the name 264 // must use the method name which may have qualifiers 265 methodInfo = chooseMethod(pojo, exchange, methodName); 266 267 // validate that if we want an explicit no-arg method, then that's what we get 268 if (emptyParameters) { 269 if (methodInfo == null || methodInfo.hasParameters()) { 270 // we could not find a no-arg method with that name 271 throw new MethodNotFoundException(exchange, pojo, methodName, "(with no parameters)"); 272 } 273 } 274 275 if (methodInfo == null || (name != null && !name.equals(methodInfo.getMethod().getName()))) { 276 throw new AmbiguousMethodCallException(exchange, methods); 277 } 278 } else { 279 // a specific method was given to invoke but not found 280 throw new MethodNotFoundException(exchange, pojo, methodName); 281 } 282 } 283 } 284 285 if (methodInfo == null && methodMap.size() >= 2) { 286 // only try to choose if there is at least 2 methods 287 methodInfo = chooseMethod(pojo, exchange, null); 288 } 289 if (methodInfo == null) { 290 methodInfo = defaultMethod; 291 } 292 if (methodInfo != null) { 293 LOG.trace("Chosen method to invoke: {} on bean: {}", methodInfo, pojo); 294 return methodInfo.createMethodInvocation(pojo, methodInfo.hasParameters(), exchange); 295 } 296 297 LOG.debug("Cannot find suitable method to invoke on bean: {}", pojo); 298 return null; 299 } 300 301 /** 302 * Introspects the given class 303 * 304 * @param clazz the class 305 */ 306 private void introspect(Class<?> clazz) { 307 308 // does the class have any public constructors? 309 publicConstructors = clazz.getConstructors().length > 0; 310 311 MethodsFilter methods = new MethodsFilter(getType()); 312 introspect(clazz, methods); 313 314 // now introspect the methods and filter non valid methods 315 for (Method method : methods.asReadOnlyList()) { 316 boolean valid = isValidMethod(clazz, method); 317 LOG.trace("Method: {} is valid: {}", method, valid); 318 if (valid) { 319 introspect(clazz, method); 320 } 321 } 322 } 323 324 private void introspect(Class<?> clazz, MethodsFilter filteredMethods) { 325 // get the target clazz as it could potentially have been enhanced by 326 // CGLIB etc. 327 clazz = getTargetClass(clazz); 328 ObjectHelper.notNull(clazz, "clazz", this); 329 330 LOG.trace("Introspecting class: {}", clazz); 331 332 for (Method m : Arrays.asList(clazz.getDeclaredMethods())) { 333 filteredMethods.filterMethod(m); 334 } 335 336 Class<?> superClass = clazz.getSuperclass(); 337 if (superClass != null && !superClass.equals(Object.class)) { 338 introspect(superClass, filteredMethods); 339 } 340 for (Class<?> superInterface : clazz.getInterfaces()) { 341 introspect(superInterface, filteredMethods); 342 } 343 } 344 345 /** 346 * Introspects the given method 347 * 348 * @param clazz the class 349 * @param method the method 350 * @return the method info, is newer <tt>null</tt> 351 */ 352 private MethodInfo introspect(Class<?> clazz, Method method) { 353 LOG.trace("Introspecting class: {}, method: {}", clazz, method); 354 String opName = method.getName(); 355 356 MethodInfo methodInfo = createMethodInfo(clazz, method); 357 358 // Foster the use of a potentially already registered most specific override 359 MethodInfo existingMethodInfo = findMostSpecificOverride(methodInfo); 360 if (existingMethodInfo != null) { 361 LOG.trace("This method is already overridden in a subclass, so the method from the sub class is preferred: {}", existingMethodInfo); 362 return existingMethodInfo; 363 } 364 365 LOG.trace("Adding operation: {} for method: {}", opName, methodInfo); 366 367 List<MethodInfo> existing = getOperations(opName); 368 if (existing != null) { 369 // we have an overloaded method so add the method info to the same key 370 existing.add(methodInfo); 371 } else { 372 // its a new method we have not seen before so wrap it in a list and add it 373 List<MethodInfo> methods = new ArrayList<MethodInfo>(); 374 methods.add(methodInfo); 375 operations.put(opName, methods); 376 } 377 378 if (methodInfo.hasCustomAnnotation()) { 379 operationsWithCustomAnnotation.add(methodInfo); 380 } else if (methodInfo.hasBodyParameter()) { 381 operationsWithBody.add(methodInfo); 382 } else { 383 operationsWithNoBody.add(methodInfo); 384 } 385 386 if (methodInfo.hasHandlerAnnotation()) { 387 operationsWithHandlerAnnotation.add(methodInfo); 388 } 389 390 // must add to method map last otherwise we break stuff 391 methodMap.put(method, methodInfo); 392 393 return methodInfo; 394 } 395 396 /** 397 * Returns the {@link MethodInfo} for the given method if it exists or null 398 * if there is no metadata available for the given method 399 */ 400 public MethodInfo getMethodInfo(Method method) { 401 MethodInfo answer = methodMap.get(method); 402 if (answer == null) { 403 // maybe the method overrides, and the method map keeps info of the source override we can use 404 for (Method source : methodMap.keySet()) { 405 if (ObjectHelper.isOverridingMethod(getType(), source, method, false)) { 406 answer = methodMap.get(source); 407 break; 408 } 409 } 410 } 411 412 if (answer == null) { 413 // maybe the method is defined on a base class? 414 if (type != Object.class) { 415 Class<?> superclass = type.getSuperclass(); 416 if (superclass != null && superclass != Object.class) { 417 BeanInfo superBeanInfo = new BeanInfo(camelContext, superclass, strategy); 418 return superBeanInfo.getMethodInfo(method); 419 } 420 } 421 } 422 return answer; 423 } 424 425 protected MethodInfo createMethodInfo(Class<?> clazz, Method method) { 426 Class<?>[] parameterTypes = method.getParameterTypes(); 427 List<Annotation>[] parametersAnnotations = collectParameterAnnotations(clazz, method); 428 429 List<ParameterInfo> parameters = new ArrayList<ParameterInfo>(); 430 List<ParameterInfo> bodyParameters = new ArrayList<ParameterInfo>(); 431 432 boolean hasCustomAnnotation = false; 433 boolean hasHandlerAnnotation = ObjectHelper.hasAnnotation(method.getAnnotations(), Handler.class); 434 435 int size = parameterTypes.length; 436 if (LOG.isTraceEnabled()) { 437 LOG.trace("Creating MethodInfo for class: {} method: {} having {} parameters", new Object[]{clazz, method, size}); 438 } 439 440 for (int i = 0; i < size; i++) { 441 Class<?> parameterType = parameterTypes[i]; 442 Annotation[] parameterAnnotations = parametersAnnotations[i].toArray(new Annotation[parametersAnnotations[i].size()]); 443 Expression expression = createParameterUnmarshalExpression(clazz, method, parameterType, parameterAnnotations); 444 hasCustomAnnotation |= expression != null; 445 446 ParameterInfo parameterInfo = new ParameterInfo(i, parameterType, parameterAnnotations, expression); 447 LOG.trace("Parameter #{}: {}", i, parameterInfo); 448 parameters.add(parameterInfo); 449 if (expression == null) { 450 boolean bodyAnnotation = ObjectHelper.hasAnnotation(parameterAnnotations, Body.class); 451 LOG.trace("Parameter #{} has @Body annotation", i); 452 hasCustomAnnotation |= bodyAnnotation; 453 if (bodyParameters.isEmpty()) { 454 // okay we have not yet set the body parameter and we have found 455 // the candidate now to use as body parameter 456 if (Exchange.class.isAssignableFrom(parameterType)) { 457 // use exchange 458 expression = ExpressionBuilder.exchangeExpression(); 459 } else { 460 // assume it's the body and it must be mandatory convertible to the parameter type 461 // but we allow null bodies in case the message really contains a null body 462 expression = ExpressionBuilder.mandatoryBodyExpression(parameterType, true); 463 } 464 LOG.trace("Parameter #{} is the body parameter using expression {}", i, expression); 465 parameterInfo.setExpression(expression); 466 bodyParameters.add(parameterInfo); 467 } else { 468 // will ignore the expression for parameter evaluation 469 } 470 } 471 LOG.trace("Parameter #{} has parameter info: ", i, parameterInfo); 472 } 473 474 // now let's add the method to the repository 475 return new MethodInfo(camelContext, clazz, method, parameters, bodyParameters, hasCustomAnnotation, hasHandlerAnnotation); 476 } 477 478 @SuppressWarnings("unchecked") 479 protected List<Annotation>[] collectParameterAnnotations(Class<?> c, Method m) { 480 List<Annotation>[] annotations = new List[m.getParameterCount()]; 481 for (int i = 0; i < annotations.length; i++) { 482 annotations[i] = new ArrayList<Annotation>(); 483 } 484 collectParameterAnnotations(c, m, annotations); 485 return annotations; 486 } 487 488 protected void collectParameterAnnotations(Class<?> c, Method m, List<Annotation>[] a) { 489 try { 490 Annotation[][] pa = c.getDeclaredMethod(m.getName(), m.getParameterTypes()).getParameterAnnotations(); 491 for (int i = 0; i < pa.length; i++) { 492 a[i].addAll(Arrays.asList(pa[i])); 493 } 494 } catch (NoSuchMethodException e) { 495 // no method with signature of m declared on c 496 } 497 for (Class<?> i : c.getInterfaces()) { 498 collectParameterAnnotations(i, m, a); 499 } 500 if (!c.isInterface() && c.getSuperclass() != null) { 501 collectParameterAnnotations(c.getSuperclass(), m, a); 502 } 503 } 504 505 /** 506 * Choose one of the available methods to invoke if we can match 507 * the message body to the body parameter 508 * 509 * @param pojo the bean to invoke a method on 510 * @param exchange the message exchange 511 * @param name an optional name of the method that must match, use <tt>null</tt> to indicate all methods 512 * @return the method to invoke or null if no definitive method could be matched 513 * @throws AmbiguousMethodCallException is thrown if cannot choose method due to ambiguity 514 */ 515 protected MethodInfo chooseMethod(Object pojo, Exchange exchange, String name) throws AmbiguousMethodCallException { 516 // @Handler should be select first 517 // then any single method that has a custom @annotation 518 // or any single method that has a match parameter type that matches the Exchange payload 519 // and last then try to select the best among the rest 520 521 // must use defensive copy, to avoid altering the shared lists 522 // and we want to remove unwanted operations from these local lists 523 List<MethodInfo> localOperationsWithBody = null; 524 if (!operationsWithBody.isEmpty()) { 525 localOperationsWithBody = new ArrayList<>(operationsWithBody); 526 } 527 List<MethodInfo> localOperationsWithNoBody = null; 528 if (!operationsWithNoBody.isEmpty()) { 529 localOperationsWithNoBody = new ArrayList<>(operationsWithNoBody); 530 } 531 List<MethodInfo> localOperationsWithCustomAnnotation = null; 532 if (!operationsWithCustomAnnotation.isEmpty()) { 533 localOperationsWithCustomAnnotation = new ArrayList<>(operationsWithCustomAnnotation); 534 } 535 List<MethodInfo> localOperationsWithHandlerAnnotation = null; 536 if (!operationsWithHandlerAnnotation.isEmpty()) { 537 localOperationsWithHandlerAnnotation = new ArrayList<>(operationsWithHandlerAnnotation); 538 } 539 540 // remove all abstract methods 541 if (localOperationsWithBody != null) { 542 removeAllAbstractMethods(localOperationsWithBody); 543 } 544 if (localOperationsWithNoBody != null) { 545 removeAllAbstractMethods(localOperationsWithNoBody); 546 } 547 if (localOperationsWithCustomAnnotation != null) { 548 removeAllAbstractMethods(localOperationsWithCustomAnnotation); 549 } 550 if (localOperationsWithHandlerAnnotation != null) { 551 removeAllAbstractMethods(localOperationsWithHandlerAnnotation); 552 } 553 554 if (name != null) { 555 // filter all lists to only include methods with this name 556 if (localOperationsWithHandlerAnnotation != null) { 557 removeNonMatchingMethods(localOperationsWithHandlerAnnotation, name); 558 } 559 if (localOperationsWithCustomAnnotation != null) { 560 removeNonMatchingMethods(localOperationsWithCustomAnnotation, name); 561 } 562 if (localOperationsWithBody != null) { 563 removeNonMatchingMethods(localOperationsWithBody, name); 564 } 565 if (localOperationsWithNoBody != null) { 566 removeNonMatchingMethods(localOperationsWithNoBody, name); 567 } 568 } else { 569 // remove all getter/setter as we do not want to consider these methods 570 if (localOperationsWithHandlerAnnotation != null) { 571 removeAllSetterOrGetterMethods(localOperationsWithHandlerAnnotation); 572 } 573 if (localOperationsWithCustomAnnotation != null) { 574 removeAllSetterOrGetterMethods(localOperationsWithCustomAnnotation); 575 } 576 if (localOperationsWithBody != null) { 577 removeAllSetterOrGetterMethods(localOperationsWithBody); 578 } 579 if (localOperationsWithNoBody != null) { 580 removeAllSetterOrGetterMethods(localOperationsWithNoBody); 581 } 582 } 583 584 if (localOperationsWithHandlerAnnotation != null && localOperationsWithHandlerAnnotation.size() > 1) { 585 // if we have more than 1 @Handler then its ambiguous 586 throw new AmbiguousMethodCallException(exchange, localOperationsWithHandlerAnnotation); 587 } 588 589 if (localOperationsWithHandlerAnnotation != null && localOperationsWithHandlerAnnotation.size() == 1) { 590 // methods with handler should be preferred 591 return localOperationsWithHandlerAnnotation.get(0); 592 } else if (localOperationsWithCustomAnnotation != null && localOperationsWithCustomAnnotation.size() == 1) { 593 // if there is one method with an annotation then use that one 594 return localOperationsWithCustomAnnotation.get(0); 595 } 596 597 // named method and with no parameters 598 boolean noParameters = name != null && name.endsWith("()"); 599 if (noParameters && localOperationsWithNoBody != null && localOperationsWithNoBody.size() == 1) { 600 // if there was a method name configured and it has no parameters, then use the method with no body (eg no parameters) 601 return localOperationsWithNoBody.get(0); 602 } else if (!noParameters && (localOperationsWithBody != null && localOperationsWithBody.size() == 1 && localOperationsWithCustomAnnotation == null)) { 603 // if there is one method with body then use that one 604 return localOperationsWithBody.get(0); 605 } 606 607 if (localOperationsWithBody != null || localOperationsWithCustomAnnotation != null) { 608 Collection<MethodInfo> possibleOperations = new ArrayList<>(); 609 if (localOperationsWithBody != null) { 610 possibleOperations.addAll(localOperationsWithBody); 611 } 612 if (localOperationsWithCustomAnnotation != null) { 613 possibleOperations.addAll(localOperationsWithCustomAnnotation); 614 } 615 616 if (!possibleOperations.isEmpty()) { 617 MethodInfo answer = null; 618 619 if (name != null) { 620 // do we have hardcoded parameters values provided from the method name then use that for matching 621 String parameters = StringHelper.between(name, "(", ")"); 622 if (parameters != null) { 623 // special as we have hardcoded parameters, so we need to choose method that matches those parameters the best 624 LOG.trace("Choosing best matching method matching parameters: {}", parameters); 625 answer = chooseMethodWithMatchingParameters(exchange, parameters, possibleOperations); 626 } 627 } 628 if (answer == null) { 629 // multiple possible operations so find the best suited if possible 630 answer = chooseMethodWithMatchingBody(exchange, possibleOperations, localOperationsWithCustomAnnotation); 631 } 632 if (answer == null && possibleOperations.size() > 1) { 633 answer = getSingleCovariantMethod(possibleOperations); 634 } 635 636 if (answer == null) { 637 throw new AmbiguousMethodCallException(exchange, possibleOperations); 638 } else { 639 return answer; 640 } 641 } 642 } 643 644 // not possible to determine 645 return null; 646 } 647 648 private MethodInfo chooseMethodWithMatchingParameters(Exchange exchange, String parameters, Collection<MethodInfo> operationList) 649 throws AmbiguousMethodCallException { 650 // we have hardcoded parameters so need to match that with the given operations 651 Iterator<?> it = ObjectHelper.createIterator(parameters); 652 int count = 0; 653 while (it.hasNext()) { 654 it.next(); 655 count++; 656 } 657 658 List<MethodInfo> operations = new ArrayList<MethodInfo>(); 659 for (MethodInfo info : operationList) { 660 if (info.getParameters().size() == count) { 661 operations.add(info); 662 } 663 } 664 665 if (operations.isEmpty()) { 666 return null; 667 } else if (operations.size() == 1) { 668 return operations.get(0); 669 } 670 671 // okay we still got multiple operations, so need to match the best one 672 List<MethodInfo> candidates = new ArrayList<MethodInfo>(); 673 MethodInfo fallbackCandidate = null; 674 for (MethodInfo info : operations) { 675 it = ObjectHelper.createIterator(parameters, ",", false); 676 int index = 0; 677 boolean matches = true; 678 while (it.hasNext()) { 679 String parameter = (String) it.next(); 680 if (parameter != null) { 681 // must trim 682 parameter = parameter.trim(); 683 } 684 685 Class<?> parameterType = BeanHelper.getValidParameterType(parameter); 686 Class<?> expectedType = info.getParameters().get(index).getType(); 687 688 if (parameterType != null && expectedType != null) { 689 690 // if its a simple language then we need to evaluate the expression 691 // so we have the result and can find out what type the parameter actually is 692 if (StringHelper.hasStartToken(parameter, "simple")) { 693 LOG.trace("Evaluating simple expression for parameter #{}: {} to determine the class type of the parameter", index, parameter); 694 Object out = getCamelContext().resolveLanguage("simple").createExpression(parameter).evaluate(exchange, Object.class); 695 if (out != null) { 696 parameterType = out.getClass(); 697 } 698 } 699 700 // skip java.lang.Object type, when we have multiple possible methods we want to avoid it if possible 701 if (Object.class.equals(expectedType)) { 702 fallbackCandidate = info; 703 matches = false; 704 break; 705 } 706 707 boolean matchingTypes = isParameterMatchingType(parameterType, expectedType); 708 if (!matchingTypes) { 709 matches = false; 710 break; 711 } 712 } 713 714 index++; 715 } 716 717 if (matches) { 718 candidates.add(info); 719 } 720 } 721 722 if (candidates.size() > 1) { 723 MethodInfo answer = getSingleCovariantMethod(candidates); 724 if (answer != null) { 725 return answer; 726 } 727 } 728 return candidates.size() == 1 ? candidates.get(0) : fallbackCandidate; 729 } 730 731 private boolean isParameterMatchingType(Class<?> parameterType, Class<?> expectedType) { 732 if (Number.class.equals(parameterType)) { 733 // number should match long/int/etc. 734 if (Integer.class.isAssignableFrom(expectedType) || Long.class.isAssignableFrom(expectedType) 735 || int.class.isAssignableFrom(expectedType) || long.class.isAssignableFrom(expectedType)) { 736 return true; 737 } 738 } 739 return parameterType.isAssignableFrom(expectedType); 740 } 741 742 private MethodInfo getSingleCovariantMethod(Collection<MethodInfo> candidates) { 743 // if all the candidates are actually covariant, it doesn't matter which one we call 744 MethodInfo firstCandidate = candidates.iterator().next(); 745 for (MethodInfo candidate : candidates) { 746 if (!firstCandidate.isCovariantWith(candidate)) { 747 return null; 748 } 749 } 750 return firstCandidate; 751 } 752 753 private MethodInfo chooseMethodWithMatchingBody(Exchange exchange, Collection<MethodInfo> operationList, 754 List<MethodInfo> operationsWithCustomAnnotation) 755 throws AmbiguousMethodCallException { 756 // see if we can find a method whose body param type matches the message body 757 Message in = exchange.getIn(); 758 Object body = in.getBody(); 759 if (body != null) { 760 Class<?> bodyType = body.getClass(); 761 if (LOG.isTraceEnabled()) { 762 LOG.trace("Matching for method with a single parameter that matches type: {}", bodyType.getCanonicalName()); 763 } 764 765 List<MethodInfo> possibles = new ArrayList<>(); 766 List<MethodInfo> possiblesWithException = null; 767 for (MethodInfo methodInfo : operationList) { 768 // test for MEP pattern matching 769 boolean out = exchange.getPattern().isOutCapable(); 770 if (out && methodInfo.isReturnTypeVoid()) { 771 // skip this method as the MEP is Out so the method must return something 772 continue; 773 } 774 775 // try to match the arguments 776 if (methodInfo.bodyParameterMatches(bodyType)) { 777 LOG.trace("Found a possible method: {}", methodInfo); 778 if (methodInfo.hasExceptionParameter()) { 779 // methods with accepts exceptions 780 if (possiblesWithException == null) { 781 possiblesWithException = new ArrayList<>(); 782 } 783 possiblesWithException.add(methodInfo); 784 } else { 785 // regular methods with no exceptions 786 possibles.add(methodInfo); 787 } 788 } 789 } 790 791 // find best suited method to use 792 return chooseBestPossibleMethodInfo(exchange, operationList, body, possibles, possiblesWithException, operationsWithCustomAnnotation); 793 } 794 795 // no match so return null 796 return null; 797 } 798 799 private MethodInfo chooseBestPossibleMethodInfo(Exchange exchange, Collection<MethodInfo> operationList, Object body, 800 List<MethodInfo> possibles, List<MethodInfo> possiblesWithException, 801 List<MethodInfo> possibleWithCustomAnnotation) 802 throws AmbiguousMethodCallException { 803 804 Exception exception = ExpressionBuilder.exchangeExceptionExpression().evaluate(exchange, Exception.class); 805 if (exception != null && possiblesWithException != null && possiblesWithException.size() == 1) { 806 LOG.trace("Exchange has exception set so we prefer method that also has exception as parameter"); 807 // prefer the method that accepts exception in case we have an exception also 808 return possiblesWithException.get(0); 809 } else if (possibles.size() == 1) { 810 return possibles.get(0); 811 } else if (possibles.isEmpty()) { 812 LOG.trace("No possible methods so now trying to convert body to parameter types"); 813 814 // let's try converting 815 Object newBody = null; 816 MethodInfo matched = null; 817 int matchCounter = 0; 818 for (MethodInfo methodInfo : operationList) { 819 if (methodInfo.getBodyParameterType() != null) { 820 if (methodInfo.getBodyParameterType().isInstance(body)) { 821 return methodInfo; 822 } 823 824 // we should only try to convert, as we are looking for best match 825 Object value = exchange.getContext().getTypeConverter().tryConvertTo(methodInfo.getBodyParameterType(), exchange, body); 826 if (value != null) { 827 if (LOG.isTraceEnabled()) { 828 LOG.trace("Converted body from: {} to: {}", 829 body.getClass().getCanonicalName(), methodInfo.getBodyParameterType().getCanonicalName()); 830 } 831 matchCounter++; 832 newBody = value; 833 matched = methodInfo; 834 } 835 } 836 } 837 if (matchCounter > 1) { 838 throw new AmbiguousMethodCallException(exchange, Arrays.asList(matched, matched)); 839 } 840 if (matched != null) { 841 LOG.trace("Setting converted body: {}", body); 842 Message in = exchange.getIn(); 843 in.setBody(newBody); 844 return matched; 845 } 846 } else { 847 // if we only have a single method with custom annotations, let's use that one 848 if (possibleWithCustomAnnotation != null && possibleWithCustomAnnotation.size() == 1) { 849 MethodInfo answer = possibleWithCustomAnnotation.get(0); 850 LOG.trace("There are only one method with annotations so we choose it: {}", answer); 851 return answer; 852 } 853 // try to choose among multiple methods with annotations 854 MethodInfo chosen = chooseMethodWithCustomAnnotations(possibles); 855 if (chosen != null) { 856 return chosen; 857 } 858 // just make sure the methods aren't all actually the same 859 chosen = getSingleCovariantMethod(possibles); 860 if (chosen != null) { 861 return chosen; 862 } 863 throw new AmbiguousMethodCallException(exchange, possibles); 864 } 865 866 // cannot find a good method to use 867 return null; 868 } 869 870 /** 871 * Validates whether the given method is a valid candidate for Camel Bean Binding. 872 * 873 * @param clazz the class 874 * @param method the method 875 * @return true if valid, false to skip the method 876 */ 877 protected boolean isValidMethod(Class<?> clazz, Method method) { 878 // must not be in the excluded list 879 for (Method excluded : EXCLUDED_METHODS) { 880 if (ObjectHelper.isOverridingMethod(excluded, method)) { 881 // the method is overriding an excluded method so its not valid 882 return false; 883 } 884 } 885 886 // must be a public method 887 if (!Modifier.isPublic(method.getModifiers())) { 888 return false; 889 } 890 891 // return type must not be an Exchange and it should not be a bridge method 892 if ((method.getReturnType() != null && Exchange.class.isAssignableFrom(method.getReturnType())) || method.isBridge()) { 893 return false; 894 } 895 896 return true; 897 } 898 899 /** 900 * Gets the most specific override of a given method, if any. Indeed, 901 * overrides may have already been found while inspecting sub classes. Or 902 * the given method could override an interface extra method. 903 * 904 * @param proposedMethodInfo the method for which a more specific override is 905 * searched 906 * @return The already registered most specific override if any, otherwise 907 * <code>null</code> 908 */ 909 private MethodInfo findMostSpecificOverride(MethodInfo proposedMethodInfo) { 910 for (MethodInfo alreadyRegisteredMethodInfo : methodMap.values()) { 911 Method alreadyRegisteredMethod = alreadyRegisteredMethodInfo.getMethod(); 912 Method proposedMethod = proposedMethodInfo.getMethod(); 913 914 if (ObjectHelper.isOverridingMethod(getType(), proposedMethod, alreadyRegisteredMethod, false)) { 915 return alreadyRegisteredMethodInfo; 916 } else if (ObjectHelper.isOverridingMethod(getType(), alreadyRegisteredMethod, proposedMethod, false)) { 917 return proposedMethodInfo; 918 } 919 } 920 921 return null; 922 } 923 924 private MethodInfo chooseMethodWithCustomAnnotations(Collection<MethodInfo> possibles) { 925 // if we have only one method with custom annotations let's choose that 926 MethodInfo chosen = null; 927 for (MethodInfo possible : possibles) { 928 if (possible.hasCustomAnnotation()) { 929 if (chosen != null) { 930 chosen = null; 931 break; 932 } else { 933 chosen = possible; 934 } 935 } 936 } 937 return chosen; 938 } 939 940 /** 941 * Creates an expression for the given parameter type if the parameter can 942 * be mapped automatically or null if the parameter cannot be mapped due to 943 * insufficient annotations or not fitting with the default type 944 * conventions. 945 */ 946 private Expression createParameterUnmarshalExpression(Class<?> clazz, Method method, 947 Class<?> parameterType, Annotation[] parameterAnnotation) { 948 949 // look for a parameter annotation that converts into an expression 950 for (Annotation annotation : parameterAnnotation) { 951 Expression answer = createParameterUnmarshalExpressionForAnnotation(clazz, method, parameterType, annotation); 952 if (answer != null) { 953 return answer; 954 } 955 } 956 // no annotations then try the default parameter mappings 957 return strategy.getDefaultParameterTypeExpression(parameterType); 958 } 959 960 private Expression createParameterUnmarshalExpressionForAnnotation(Class<?> clazz, Method method, 961 Class<?> parameterType, Annotation annotation) { 962 if (annotation instanceof AttachmentObjects) { 963 return ExpressionBuilder.attachmentObjectsExpression(); 964 } else if (annotation instanceof Attachments) { 965 return ExpressionBuilder.attachmentsExpression(); 966 } else if (annotation instanceof Property) { 967 Property propertyAnnotation = (Property)annotation; 968 return ExpressionBuilder.exchangePropertyExpression(propertyAnnotation.value()); 969 } else if (annotation instanceof ExchangeProperty) { 970 ExchangeProperty propertyAnnotation = (ExchangeProperty)annotation; 971 return ExpressionBuilder.exchangePropertyExpression(propertyAnnotation.value()); 972 } else if (annotation instanceof Properties) { 973 return ExpressionBuilder.exchangePropertiesExpression(); 974 } else if (annotation instanceof ExchangeProperties) { 975 return ExpressionBuilder.exchangePropertiesExpression(); 976 } else if (annotation instanceof Header) { 977 Header headerAnnotation = (Header)annotation; 978 return ExpressionBuilder.headerExpression(headerAnnotation.value()); 979 } else if (annotation instanceof Headers) { 980 return ExpressionBuilder.headersExpression(); 981 } else if (annotation instanceof OutHeaders) { 982 return ExpressionBuilder.outHeadersExpression(); 983 } else if (annotation instanceof ExchangeException) { 984 return ExpressionBuilder.exchangeExceptionExpression(CastUtils.cast(parameterType, Exception.class)); 985 } else if (annotation instanceof PropertyInject) { 986 PropertyInject propertyAnnotation = (PropertyInject) annotation; 987 Expression inject = ExpressionBuilder.propertiesComponentExpression(propertyAnnotation.value(), null, propertyAnnotation.defaultValue()); 988 return ExpressionBuilder.convertToExpression(inject, parameterType); 989 } else { 990 LanguageAnnotation languageAnnotation = annotation.annotationType().getAnnotation(LanguageAnnotation.class); 991 if (languageAnnotation != null) { 992 Class<?> type = languageAnnotation.factory(); 993 Object object = camelContext.getInjector().newInstance(type); 994 if (object instanceof AnnotationExpressionFactory) { 995 AnnotationExpressionFactory expressionFactory = (AnnotationExpressionFactory) object; 996 return expressionFactory.createExpression(camelContext, annotation, languageAnnotation, parameterType); 997 } else { 998 LOG.warn("Ignoring bad annotation: " + languageAnnotation + "on method: " + method 999 + " which declares a factory: " + type.getName() 1000 + " which does not implement " + AnnotationExpressionFactory.class.getName()); 1001 } 1002 } 1003 } 1004 1005 return null; 1006 } 1007 1008 private static void removeAllSetterOrGetterMethods(List<MethodInfo> methods) { 1009 Iterator<MethodInfo> it = methods.iterator(); 1010 while (it.hasNext()) { 1011 MethodInfo info = it.next(); 1012 if (IntrospectionSupport.isGetter(info.getMethod())) { 1013 // skip getters 1014 it.remove(); 1015 } else if (IntrospectionSupport.isSetter(info.getMethod())) { 1016 // skip setters 1017 it.remove(); 1018 } 1019 } 1020 } 1021 1022 private void removeNonMatchingMethods(List<MethodInfo> methods, String name) { 1023 // method does not match so remove it 1024 methods.removeIf(info -> !matchMethod(info.getMethod(), name)); 1025 } 1026 1027 private void removeAllAbstractMethods(List<MethodInfo> methods) { 1028 Iterator<MethodInfo> it = methods.iterator(); 1029 while (it.hasNext()) { 1030 MethodInfo info = it.next(); 1031 // if the class is an interface then keep the method 1032 boolean isFromInterface = Modifier.isInterface(info.getMethod().getDeclaringClass().getModifiers()); 1033 if (!isFromInterface && Modifier.isAbstract(info.getMethod().getModifiers())) { 1034 // we cannot invoke an abstract method 1035 it.remove(); 1036 } 1037 } 1038 } 1039 1040 private boolean matchMethod(Method method, String methodName) { 1041 if (methodName == null) { 1042 return true; 1043 } 1044 1045 if (methodName.contains("(") && !methodName.endsWith(")")) { 1046 throw new IllegalArgumentException("Name must have both starting and ending parenthesis, was: " + methodName); 1047 } 1048 1049 // do not use qualifier for name matching 1050 String name = methodName; 1051 if (name.contains("(")) { 1052 name = StringHelper.before(name, "("); 1053 } 1054 1055 // must match name 1056 if (name != null && !name.equals(method.getName())) { 1057 return false; 1058 } 1059 1060 // is it a method with no parameters 1061 boolean noParameters = methodName.endsWith("()"); 1062 if (noParameters) { 1063 return method.getParameterCount() == 0; 1064 } 1065 1066 // match qualifier types which is used to select among overloaded methods 1067 String types = StringHelper.between(methodName, "(", ")"); 1068 if (ObjectHelper.isNotEmpty(types)) { 1069 // we must qualify based on types to match method 1070 String[] parameters = StringQuoteHelper.splitSafeQuote(types, ','); 1071 Class<?>[] parameterTypes = null; 1072 Iterator<?> it = ObjectHelper.createIterator(parameters); 1073 for (int i = 0; i < method.getParameterCount(); i++) { 1074 if (it.hasNext()) { 1075 if (parameterTypes == null) { 1076 parameterTypes = method.getParameterTypes(); 1077 } 1078 Class<?> parameterType = parameterTypes[i]; 1079 1080 String qualifyType = (String) it.next(); 1081 if (ObjectHelper.isEmpty(qualifyType)) { 1082 continue; 1083 } 1084 // trim the type 1085 qualifyType = qualifyType.trim(); 1086 1087 if ("*".equals(qualifyType)) { 1088 // * is a wildcard so we accept and match that parameter type 1089 continue; 1090 } 1091 1092 if (BeanHelper.isValidParameterValue(qualifyType)) { 1093 // its a parameter value, so continue to next parameter 1094 // as we should only check for FQN/type parameters 1095 continue; 1096 } 1097 1098 // if qualify type indeed is a class, then it must be assignable with the parameter type 1099 Boolean assignable = BeanHelper.isAssignableToExpectedType(getCamelContext().getClassResolver(), qualifyType, parameterType); 1100 // the method will return null if the qualifyType is not a class 1101 if (assignable != null && !assignable) { 1102 return false; 1103 } 1104 1105 } else { 1106 // there method has more parameters than was specified in the method name qualifiers 1107 return false; 1108 } 1109 } 1110 1111 // if the method has no more types then we can only regard it as matched 1112 // if there are no more qualifiers 1113 if (it.hasNext()) { 1114 return false; 1115 } 1116 } 1117 1118 // the method matched 1119 return true; 1120 } 1121 1122 private static Class<?> getTargetClass(Class<?> clazz) { 1123 if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) { 1124 Class<?> superClass = clazz.getSuperclass(); 1125 if (superClass != null && !Object.class.equals(superClass)) { 1126 return superClass; 1127 } 1128 } 1129 return clazz; 1130 } 1131 1132 /** 1133 * Do we have a method with the given name. 1134 * <p/> 1135 * Shorthand method names for getters is supported, so you can pass in eg 'name' and Camel 1136 * will can find the real 'getName' method instead. 1137 * 1138 * @param methodName the method name 1139 * @return <tt>true</tt> if we have such a method. 1140 */ 1141 public boolean hasMethod(String methodName) { 1142 return getOperations(methodName) != null; 1143 } 1144 1145 /** 1146 * Do we have a static method with the given name. 1147 * <p/> 1148 * Shorthand method names for getters is supported, so you can pass in eg 'name' and Camel 1149 * will can find the real 'getName' method instead. 1150 * 1151 * @param methodName the method name 1152 * @return <tt>true</tt> if we have such a static method. 1153 */ 1154 public boolean hasStaticMethod(String methodName) { 1155 List<MethodInfo> methods = getOperations(methodName); 1156 if (methods == null || methods.isEmpty()) { 1157 return false; 1158 } 1159 for (MethodInfo method : methods) { 1160 if (method.isStaticMethod()) { 1161 return true; 1162 } 1163 } 1164 return false; 1165 } 1166 1167 /** 1168 * Returns whether the bean class has any public constructors. 1169 */ 1170 public boolean hasPublicConstructors() { 1171 return publicConstructors; 1172 } 1173 1174 /** 1175 * Gets the list of methods sorted by A..Z method name. 1176 * 1177 * @return the methods. 1178 */ 1179 public List<MethodInfo> getMethods() { 1180 if (operations.isEmpty()) { 1181 return Collections.emptyList(); 1182 } 1183 1184 List<MethodInfo> methods = new ArrayList<MethodInfo>(); 1185 for (Collection<MethodInfo> col : operations.values()) { 1186 methods.addAll(col); 1187 } 1188 1189 if (methods.size() > 1) { 1190 // sort the methods by name A..Z 1191 methods.sort(Comparator.comparing(o -> o.getMethod().getName())); 1192 } 1193 return methods; 1194 } 1195 1196 /** 1197 * Does any of the methods have a Canel @Handler annotation. 1198 */ 1199 public boolean hasAnyMethodHandlerAnnotation() { 1200 return !operationsWithHandlerAnnotation.isEmpty(); 1201 } 1202 1203 /** 1204 * Get the operation(s) with the given name. We can have multiple when methods is overloaded. 1205 * <p/> 1206 * Shorthand method names for getters is supported, so you can pass in eg 'name' and Camel 1207 * will can find the real 'getName' method instead. 1208 * 1209 * @param methodName the method name 1210 * @return the found method, or <tt>null</tt> if not found 1211 */ 1212 private List<MethodInfo> getOperations(String methodName) { 1213 // do not use qualifier for name 1214 if (methodName.contains("(")) { 1215 methodName = StringHelper.before(methodName, "("); 1216 } 1217 1218 List<MethodInfo> answer = operations.get(methodName); 1219 if (answer != null) { 1220 return answer; 1221 } 1222 1223 // now try all getters to see if any of those matched the methodName 1224 for (Method method : methodMap.keySet()) { 1225 if (IntrospectionSupport.isGetter(method)) { 1226 String shorthandMethodName = IntrospectionSupport.getGetterShorthandName(method); 1227 // if the two names matches then see if we can find it using that name 1228 if (methodName != null && methodName.equals(shorthandMethodName)) { 1229 return operations.get(method.getName()); 1230 } 1231 } 1232 } 1233 1234 return null; 1235 } 1236 1237}