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.processor.aggregate;
018
019import java.lang.reflect.Method;
020import java.lang.reflect.Modifier;
021import java.lang.reflect.Proxy;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.List;
025
026import org.apache.camel.CamelContext;
027import org.apache.camel.CamelContextAware;
028import org.apache.camel.Exchange;
029import org.apache.camel.support.ServiceSupport;
030import 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 */
038public 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(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}