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