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     */
017    package org.apache.camel.spring.util;
018    
019    import org.aopalliance.intercept.MethodInvocation;
020    import org.apache.camel.Body;
021    import org.apache.camel.Exchange;
022    import org.apache.camel.Expression;
023    import org.apache.camel.Header;
024    import org.apache.camel.Property;
025    import org.apache.camel.RuntimeCamelException;
026    import org.apache.camel.builder.ExpressionBuilder;
027    import org.apache.commons.logging.Log;
028    import org.apache.commons.logging.LogFactory;
029    
030    import java.lang.annotation.Annotation;
031    import java.lang.reflect.Method;
032    import java.util.Arrays;
033    import java.util.Collection;
034    import java.util.Map;
035    import java.util.concurrent.ConcurrentHashMap;
036    
037    /**
038     * Represents the metadata about a bean type created via a combination of
039     * introspection and annotations together with some useful sensible defaults
040     *
041     * @version $Revision: $
042     */
043    public class BeanInfo {
044        private static final transient Log log = LogFactory.getLog(BeanInfo.class);
045    
046        private Class type;
047        private MethodInvocationStrategy strategy;
048        private Map<String, MethodInfo> operations = new ConcurrentHashMap<String, MethodInfo>();
049        private MethodInfo defaultExpression;
050    
051        public BeanInfo(Class type, MethodInvocationStrategy strategy) {
052            this.type = type;
053            this.strategy = strategy;
054        }
055    
056        public Class getType() {
057            return type;
058        }
059    
060        public void introspect() {
061            introspect(getType());
062            if (operations.size() == 1) {
063                Collection<MethodInfo> methodInfos = operations.values();
064                for (MethodInfo methodInfo : methodInfos) {
065                    defaultExpression = methodInfo;
066                }
067            }
068        }
069    
070        public MethodInvocation createInvocation(Method method, Object pojo, Exchange messageExchange) throws RuntimeCamelException {
071            MethodInfo methodInfo = introspect(type, method);
072            return methodInfo.createMethodInvocation(pojo, messageExchange);
073        }
074    
075        public MethodInvocation createInvocation(Object pojo, Exchange messageExchange) throws RuntimeCamelException {
076            MethodInfo methodInfo = null;
077    
078            // TODO use some other mechanism?
079            String name = messageExchange.getIn().getHeader("org.apache.camel.MethodName", String.class);
080            if (name != null) {
081                methodInfo = operations.get(name);
082            }
083            if (methodInfo == null) {
084                methodInfo = defaultExpression;
085            }
086            if (methodInfo != null) {
087                return methodInfo.createMethodInvocation(pojo, messageExchange);
088            }
089            return null;
090        }
091    
092        protected void introspect(Class clazz) {
093            Method[] methods = clazz.getDeclaredMethods();
094            for (Method method : methods) {
095                introspect(clazz, method);
096            }
097            Class superclass = clazz.getSuperclass();
098            if (superclass != null && !superclass.equals(Object.class)) {
099                introspect(superclass);
100            }
101        }
102    
103        protected MethodInfo introspect(Class clazz, Method method) {
104            Class[] parameterTypes = method.getParameterTypes();
105            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
106            final Expression[] parameterExpressions = new Expression[parameterTypes.length];
107            for (int i = 0; i < parameterTypes.length; i++) {
108                Class parameterType = parameterTypes[i];
109                Expression expression = createParameterUnmarshalExpression(clazz, method,
110                        parameterType, parameterAnnotations[i]);
111                if (expression == null) {
112                    if (log.isDebugEnabled()) {
113                        log.debug("No expression available for method: "
114                                + method.toString() + " parameter: " + i + " so ignoring method");
115                    }
116                    if (parameterTypes.length == 1) {
117                            // lets assume its the body
118                            expression = ExpressionBuilder.bodyExpression(parameterType);
119                    }
120                    else {
121                            return null;
122                    }
123                }
124                parameterExpressions[i] = expression;
125            }
126    
127            // now lets add the method to the repository
128            String opName = method.getName();
129    
130            /*
131    
132            TODO allow an annotation to expose the operation name to use
133    
134            if (method.getAnnotation(Operation.class) != null) {
135                String name = method.getAnnotation(Operation.class).name();
136                if (name != null && name.length() > 0) {
137                    opName = name;
138                }
139            }
140            */
141            Expression parametersExpression = createMethodParametersExpression(parameterExpressions);
142            MethodInfo methodInfo = new MethodInfo(clazz, method, parametersExpression);
143            operations.put(opName, methodInfo);
144            return methodInfo;
145        }
146    
147        protected Expression createMethodParametersExpression(final Expression[] parameterExpressions) {
148            return new Expression<Exchange>() {
149                public Object evaluate(Exchange exchange) {
150                    Object[] answer = new Object[parameterExpressions.length];
151                    for (int i = 0; i < parameterExpressions.length; i++) {
152                        Expression parameterExpression = parameterExpressions[i];
153                        answer[i] = parameterExpression.evaluate(exchange);
154                    }
155                    return answer;
156                }
157    
158                @Override
159                public String toString() {
160                    return "parametersExpression" + Arrays.asList(parameterExpressions);
161                }
162            };
163        }
164    
165        /**
166         * Creates an expression for the given parameter type if the parameter can be mapped
167         * automatically or null if the parameter cannot be mapped due to unsufficient
168         * annotations or not fitting with the default type conventions.
169         */
170        protected Expression createParameterUnmarshalExpression(Class clazz, Method method, Class parameterType, Annotation[] parameterAnnotation) {
171    
172            // TODO look for a parameter annotation that converts into an expression
173            for (Annotation annotation : parameterAnnotation) {
174                Expression answer = createParameterUnmarshalExpressionForAnnotation(
175                        clazz, method, parameterType, annotation);
176                if (answer != null) {
177                    return answer;
178                }
179            }
180            return strategy.getDefaultParameterTypeExpression(parameterType);
181        }
182    
183        protected Expression createParameterUnmarshalExpressionForAnnotation(Class clazz, Method method, Class parameterType, Annotation annotation) {
184            if (annotation instanceof Property) {
185                Property propertyAnnotation = (Property) annotation;
186                return ExpressionBuilder.propertyExpression(propertyAnnotation.name());
187            }
188            else if (annotation instanceof Header) {
189                Header headerAnnotation = (Header) annotation;
190                return ExpressionBuilder.headerExpression(headerAnnotation.name());
191            }
192            else if (annotation instanceof Body) {
193                Body content = (Body) annotation;
194                return ExpressionBuilder.bodyExpression(parameterType);
195    
196                // TODO allow annotations to be used to create expressions?
197    /*
198            } else if (annotation instanceof XPath) {
199                XPath xpathAnnotation = (XPath) annotation;
200                return new JAXPStringXPathExpression(xpathAnnotation.xpath());
201            }
202    */
203            }
204            return null;
205        }
206    }