Coverage Report - org.apache.camel.component.bean.BeanInfo
 
Classes in this File Line Coverage Branch Coverage Complexity
BeanInfo
43% 
37% 
0
 
 1  
 /**
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *      http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.apache.camel.component.bean;
 18  
 
 19  
 import java.lang.annotation.Annotation;
 20  
 import java.lang.reflect.Method;
 21  
 import java.lang.reflect.Modifier;
 22  
 import java.util.ArrayList;
 23  
 import java.util.Arrays;
 24  
 import java.util.Collection;
 25  
 import java.util.List;
 26  
 import java.util.Map;
 27  
 import java.util.concurrent.ConcurrentHashMap;
 28  
 
 29  
 import org.apache.camel.Body;
 30  
 import org.apache.camel.Exchange;
 31  
 import org.apache.camel.Expression;
 32  
 import org.apache.camel.Header;
 33  
 import org.apache.camel.Message;
 34  
 import org.apache.camel.Property;
 35  
 import org.apache.camel.RuntimeCamelException;
 36  
 import org.apache.camel.builder.ExpressionBuilder;
 37  
 import org.apache.commons.logging.Log;
 38  
 import org.apache.commons.logging.LogFactory;
 39  
 
 40  
 import static org.apache.camel.util.ExchangeHelper.convertToType;
 41  
 
 42  
 /**
 43  
  * Represents the metadata about a bean type created via a combination of
 44  
  * introspection and annotations together with some useful sensible defaults
 45  
  * 
 46  
  * @version $Revision: $
 47  
  */
 48  
 public class BeanInfo {
 49  3
     private static final transient Log LOG = LogFactory.getLog(BeanInfo.class);
 50  
     private Class type;
 51  
     private ParameterMappingStrategy strategy;
 52  6
     private Map<String, MethodInfo> operations = new ConcurrentHashMap<String, MethodInfo>();
 53  
     private MethodInfo defaultMethod;
 54  6
     private List<MethodInfo> operationsWithBody = new ArrayList<MethodInfo>();
 55  
 
 56  6
     public BeanInfo(Class type, ParameterMappingStrategy strategy) {
 57  6
         this.type = type;
 58  6
         this.strategy = strategy;
 59  6
         introspect(getType());
 60  6
         if (operations.size() == 1) {
 61  3
             Collection<MethodInfo> methodInfos = operations.values();
 62  3
             for (MethodInfo methodInfo : methodInfos) {
 63  3
                 defaultMethod = methodInfo;
 64  3
             }
 65  
         }
 66  6
     }
 67  
 
 68  
     public Class getType() {
 69  6
         return type;
 70  
     }
 71  
 
 72  
     public MethodInvocation createInvocation(Method method, Object pojo, Exchange exchange)
 73  
         throws RuntimeCamelException {
 74  0
         MethodInfo methodInfo = introspect(type, method);
 75  0
         if (methodInfo != null) {
 76  0
             return methodInfo.createMethodInvocation(pojo, exchange);
 77  
         }
 78  0
         return null;
 79  
     }
 80  
 
 81  
     public MethodInvocation createInvocation(Object pojo, Exchange exchange) throws RuntimeCamelException,
 82  
         AmbiguousMethodCallException {
 83  0
         MethodInfo methodInfo = null;
 84  
 
 85  
         // TODO use some other mechanism?
 86  0
         String name = exchange.getIn().getHeader(BeanProcessor.METHOD_NAME, String.class);
 87  0
         if (name != null) {
 88  0
             methodInfo = operations.get(name);
 89  
         }
 90  0
         if (methodInfo == null) {
 91  0
             methodInfo = chooseMethod(pojo, exchange);
 92  
         }
 93  0
         if (methodInfo == null) {
 94  0
             methodInfo = defaultMethod;
 95  
         }
 96  0
         if (methodInfo != null) {
 97  0
             return methodInfo.createMethodInvocation(pojo, exchange);
 98  
         }
 99  0
         return null;
 100  
     }
 101  
 
 102  
     protected void introspect(Class clazz) {
 103  6
         Method[] methods = clazz.getDeclaredMethods();
 104  18
         for (Method method : methods) {
 105  12
             if (isValidMethod(clazz, method)) {
 106  12
                 introspect(clazz, method);
 107  
             }
 108  
         }
 109  6
         Class superclass = clazz.getSuperclass();
 110  6
         if (superclass != null && !superclass.equals(Object.class)) {
 111  0
             introspect(superclass);
 112  
         }
 113  6
     }
 114  
 
 115  
     protected MethodInfo introspect(Class clazz, Method method) {
 116  12
         Class[] parameterTypes = method.getParameterTypes();
 117  12
         Annotation[][] parametersAnnotations = method.getParameterAnnotations();
 118  12
         final Expression[] parameterExpressions = new Expression[parameterTypes.length];
 119  
 
 120  12
         List<ParameterInfo> parameters = new ArrayList<ParameterInfo>();
 121  12
         List<ParameterInfo> bodyParameters = new ArrayList<ParameterInfo>();
 122  
 
 123  15
         for (int i = 0; i < parameterTypes.length; i++) {
 124  3
             Class parameterType = parameterTypes[i];
 125  3
             Annotation[] parameterAnnotations = parametersAnnotations[i];
 126  3
             Expression expression = createParameterUnmarshalExpression(clazz, method, parameterType,
 127  
                                                                        parameterAnnotations);
 128  3
             if (expression == null) {
 129  3
                 if (parameterTypes.length == 1 && bodyParameters.isEmpty()) {
 130  
                     // lets assume its the body
 131  3
                     expression = ExpressionBuilder.bodyExpression(parameterType);
 132  3
                 } else {
 133  0
                     if (LOG.isDebugEnabled()) {
 134  0
                         LOG.debug("No expression available for method: " + method.toString()
 135  
                                   + " which already has a body so ignoring parameter: " + i
 136  
                                   + " so ignoring method");
 137  
                     }
 138  0
                     return null;
 139  
                 }
 140  
             }
 141  
 
 142  3
             ParameterInfo parameterInfo = new ParameterInfo(i, parameterType, parameterAnnotations,
 143  
                                                             expression);
 144  3
             parameters.add(parameterInfo);
 145  3
             if (isPossibleBodyParameter(parameterAnnotations)) {
 146  3
                 bodyParameters.add(parameterInfo);
 147  
             }
 148  
         }
 149  
 
 150  
         // now lets add the method to the repository
 151  12
         String opName = method.getName();
 152  
 
 153  
         /*
 154  
          * 
 155  
          * TODO allow an annotation to expose the operation name to use
 156  
          * 
 157  
          * if (method.getAnnotation(Operation.class) != null) { String name =
 158  
          * method.getAnnotation(Operation.class).name(); if (name != null &&
 159  
          * name.length() > 0) { opName = name; } }
 160  
          */
 161  12
         MethodInfo methodInfo = new MethodInfo(clazz, method, parameters, bodyParameters);
 162  12
         operations.put(opName, methodInfo);
 163  12
         if (methodInfo.hasBodyParameter()) {
 164  3
             operationsWithBody.add(methodInfo);
 165  
         }
 166  12
         return methodInfo;
 167  
     }
 168  
 
 169  
     /**
 170  
      * Lets try choose one of the available methods to invoke if we can match
 171  
      * the message body to the body parameter
 172  
      * 
 173  
      * @param pojo the bean to invoke a method on
 174  
      * @param exchange the message exchange
 175  
      * @return the method to invoke or null if no definitive method could be
 176  
      *         matched
 177  
      */
 178  
     protected MethodInfo chooseMethod(Object pojo, Exchange exchange) throws AmbiguousMethodCallException {
 179  0
         if (operationsWithBody.size() == 1) {
 180  0
             return operationsWithBody.get(0);
 181  0
         } else if (!operationsWithBody.isEmpty()) {
 182  
             // lets see if we can find a method who's body param type matches
 183  
             // the message body
 184  0
             Message in = exchange.getIn();
 185  0
             Object body = in.getBody();
 186  0
             if (body != null) {
 187  0
                 Class bodyType = body.getClass();
 188  
 
 189  0
                 List<MethodInfo> possibles = new ArrayList<MethodInfo>();
 190  0
                 for (MethodInfo methodInfo : operationsWithBody) {
 191  0
                     if (methodInfo.bodyParameterMatches(bodyType)) {
 192  0
                         possibles.add(methodInfo);
 193  
                     }
 194  0
                 }
 195  0
                 if (possibles.size() == 1) {
 196  0
                     return possibles.get(0);
 197  0
                 } else if (possibles.isEmpty()) {
 198  
                     // lets try converting
 199  0
                     Object newBody = null;
 200  0
                     MethodInfo matched = null;
 201  0
                     for (MethodInfo methodInfo : operationsWithBody) {
 202  0
                         Object value = convertToType(exchange, methodInfo.getBodyParameterType(), body);
 203  0
                         if (value != null) {
 204  0
                             if (newBody != null) {
 205  0
                                 throw new AmbiguousMethodCallException(exchange, Arrays.asList(matched,
 206  
                                                                                                methodInfo));
 207  
                             } else {
 208  0
                                 newBody = value;
 209  0
                                 matched = methodInfo;
 210  
                             }
 211  
                         }
 212  0
                     }
 213  0
                     if (matched != null) {
 214  0
                         in.setBody(newBody);
 215  0
                         return matched;
 216  
                     }
 217  0
                 } else {
 218  0
                     throw new AmbiguousMethodCallException(exchange, possibles);
 219  
                 }
 220  
             }
 221  0
             return null;
 222  
         }
 223  0
         return null;
 224  
     }
 225  
 
 226  
     /**
 227  
      * Creates an expression for the given parameter type if the parameter can
 228  
      * be mapped automatically or null if the parameter cannot be mapped due to
 229  
      * unsufficient annotations or not fitting with the default type
 230  
      * conventions.
 231  
      */
 232  
     protected Expression createParameterUnmarshalExpression(Class clazz, Method method, Class parameterType,
 233  
                                                             Annotation[] parameterAnnotation) {
 234  
 
 235  
         // TODO look for a parameter annotation that converts into an expression
 236  3
         for (Annotation annotation : parameterAnnotation) {
 237  0
             Expression answer = createParameterUnmarshalExpressionForAnnotation(clazz, method, parameterType,
 238  
                                                                                 annotation);
 239  0
             if (answer != null) {
 240  0
                 return answer;
 241  
             }
 242  
         }
 243  3
         return strategy.getDefaultParameterTypeExpression(parameterType);
 244  
     }
 245  
 
 246  
     protected boolean isPossibleBodyParameter(Annotation[] annotations) {
 247  3
         if (annotations != null) {
 248  3
             for (Annotation annotation : annotations) {
 249  0
                 if ((annotation instanceof Property) || (annotation instanceof Header)) {
 250  0
                     return false;
 251  
                 }
 252  
             }
 253  
         }
 254  3
         return true;
 255  
     }
 256  
 
 257  
     protected Expression createParameterUnmarshalExpressionForAnnotation(Class clazz, Method method,
 258  
                                                                          Class parameterType,
 259  
                                                                          Annotation annotation) {
 260  0
         if (annotation instanceof Property) {
 261  0
             Property propertyAnnotation = (Property)annotation;
 262  0
             return ExpressionBuilder.propertyExpression(propertyAnnotation.name());
 263  0
         } else if (annotation instanceof Header) {
 264  0
             Header headerAnnotation = (Header)annotation;
 265  0
             return ExpressionBuilder.headerExpression(headerAnnotation.name());
 266  0
         } else if (annotation instanceof Body) {
 267  0
             Body content = (Body)annotation;
 268  0
             return ExpressionBuilder.bodyExpression(parameterType);
 269  
 
 270  
             // TODO allow annotations to be used to create expressions?
 271  
             /*
 272  
              * } else if (annotation instanceof XPath) { XPath xpathAnnotation =
 273  
              * (XPath) annotation; return new
 274  
              * JAXPStringXPathExpression(xpathAnnotation.xpath()); }
 275  
              */
 276  
         }
 277  0
         return null;
 278  
     }
 279  
 
 280  
     protected boolean isValidMethod(Class clazz, Method method) {
 281  12
         return Modifier.isPublic(method.getModifiers());
 282  
     }
 283  
 }