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.processor.aggregate;
018    
019    import java.lang.reflect.Method;
020    import java.lang.reflect.Modifier;
021    import java.lang.reflect.Proxy;
022    import java.util.ArrayList;
023    import java.util.Arrays;
024    import java.util.List;
025    
026    import org.apache.camel.CamelContext;
027    import org.apache.camel.CamelContextAware;
028    import org.apache.camel.Exchange;
029    import org.apache.camel.support.ServiceSupport;
030    import org.apache.camel.util.ServiceHelper;
031    
032    /**
033     * An {@link AggregationStrategy} that adapts to a POJO.
034     * <p/>
035     * This allows end users to use POJOs for the aggregation logic, instead of having to implement the
036     * Camel API {@link AggregationStrategy}.
037     */
038    public final class AggregationStrategyBeanAdapter extends ServiceSupport implements AggregationStrategy, CamelContextAware {
039    
040        private static final List<Method> EXCLUDED_METHODS = new ArrayList<Method>();
041        private CamelContext camelContext;
042        private Object pojo;
043        private final Class<?> type;
044        private String methodName;
045        private boolean allowNullOldExchange;
046        private boolean allowNullNewExchange;
047        private volatile AggregationStrategyMethodInfo mi;
048    
049        static {
050            // exclude all java.lang.Object methods as we dont want to invoke them
051            EXCLUDED_METHODS.addAll(Arrays.asList(Object.class.getMethods()));
052            // exclude all java.lang.reflect.Proxy methods as we dont want to invoke them
053            EXCLUDED_METHODS.addAll(Arrays.asList(Proxy.class.getMethods()));
054        }
055    
056        /**
057         * Creates this adapter.
058         *
059         * @param pojo the pojo to use.
060         */
061        public AggregationStrategyBeanAdapter(Object pojo) {
062            this(pojo, null);
063        }
064    
065        /**
066         * Creates this adapter.
067         *
068         * @param type the class type of the pojo
069         */
070        public AggregationStrategyBeanAdapter(Class<?> type) {
071            this(type, null);
072        }
073    
074        /**
075         * Creates this adapter.
076         *
077         * @param pojo the pojo to use.
078         * @param methodName the name of the method to call
079         */
080        public AggregationStrategyBeanAdapter(Object pojo, String methodName) {
081            this.pojo = pojo;
082            this.type = pojo.getClass();
083            this.methodName = methodName;
084        }
085    
086        /**
087         * Creates this adapter.
088         *
089         * @param type the class type of the pojo
090         * @param methodName the name of the method to call
091         */
092        public AggregationStrategyBeanAdapter(Class<?> type, String methodName) {
093            this.type = type;
094            this.pojo = null;
095            this.methodName = methodName;
096        }
097    
098        public CamelContext getCamelContext() {
099            return camelContext;
100        }
101    
102        public void setCamelContext(CamelContext camelContext) {
103            this.camelContext = camelContext;
104        }
105    
106        public String getMethodName() {
107            return methodName;
108        }
109    
110        public void setMethodName(String methodName) {
111            this.methodName = methodName;
112        }
113    
114        public boolean isAllowNullOldExchange() {
115            return allowNullOldExchange;
116        }
117    
118        public void setAllowNullOldExchange(boolean allowNullOldExchange) {
119            this.allowNullOldExchange = allowNullOldExchange;
120        }
121    
122        public boolean isAllowNullNewExchange() {
123            return allowNullNewExchange;
124        }
125    
126        public void setAllowNullNewExchange(boolean allowNullNewExchange) {
127            this.allowNullNewExchange = allowNullNewExchange;
128        }
129    
130        @Override
131        public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
132            if (!allowNullOldExchange && oldExchange == null) {
133                return newExchange;
134            }
135            if (!allowNullNewExchange && newExchange == null) {
136                return oldExchange;
137            }
138    
139            try {
140                Object out = mi.invoke(pojo, oldExchange, newExchange);
141                if (out != null) {
142                    if (oldExchange != null) {
143                        oldExchange.getIn().setBody(out);
144                    } else {
145                        newExchange.getIn().setBody(out);
146                    }
147                }
148            } catch (Exception e) {
149                if (oldExchange != null) {
150                    oldExchange.setException(e);
151                } else {
152                    newExchange.setException(e);
153                }
154            }
155            return oldExchange != null ? oldExchange : newExchange;
156        }
157    
158        /**
159         * Validates whether the given method is valid.
160         *
161         * @param method  the method
162         * @return true if valid, false to skip the method
163         */
164        protected boolean isValidMethod(Method method) {
165            // must not be in the excluded list
166            for (Method excluded : EXCLUDED_METHODS) {
167                if (method.equals(excluded)) {
168                    return false;
169                }
170            }
171    
172            // must be a public method
173            if (!Modifier.isPublic(method.getModifiers())) {
174                return false;
175            }
176    
177            // return type must not be void and it should not be a bridge method
178            if (method.getReturnType().equals(Void.TYPE) || method.isBridge()) {
179                return false;
180            }
181    
182            return true;
183        }
184    
185        private static boolean isStaticMethod(Method method) {
186            return Modifier.isStatic(method.getModifiers());
187        }
188    
189        @Override
190        protected void doStart() throws Exception {
191            Method found = null;
192            if (methodName != null) {
193                for (Method method : type.getMethods()) {
194                    if (isValidMethod(method) && method.getName().equals(methodName)) {
195                        if (found == null) {
196                            found = method;
197                        } else {
198                            throw new IllegalArgumentException("The bean " + type + " has 2 or more methods with the name " + methodName);
199                        }
200                    }
201                }
202            } else {
203                for (Method method : type.getMethods()) {
204                    if (isValidMethod(method)) {
205                        if (found == null) {
206                            found = method;
207                        } else {
208                            throw new IllegalArgumentException("The bean " + type + " has 2 or more methods and no explicit method name was configured.");
209                        }
210                    }
211                }
212            }
213    
214            if (found == null) {
215                throw new UnsupportedOperationException("Cannot find a valid method with name: " + methodName + " on bean type: " + type);
216            }
217    
218            // if its not a static method then we must have an instance of the pojo
219            if (!isStaticMethod(found) && pojo == null) {
220                pojo = camelContext.getInjector().newInstance(type);
221            }
222    
223            // create the method info which has adapted to the pojo
224            AggregationStrategyBeanInfo bi = new AggregationStrategyBeanInfo(getCamelContext(), type, found);
225            mi = bi.createMethodInfo();
226    
227            // in case the POJO is CamelContextAware
228            if (pojo != null && pojo instanceof CamelContextAware) {
229                ((CamelContextAware) pojo).setCamelContext(getCamelContext());
230            }
231    
232            // in case the pojo is a service
233            ServiceHelper.startService(pojo);
234        }
235    
236        @Override
237        protected void doStop() throws Exception {
238            ServiceHelper.stopService(pojo);
239        }
240    }