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.component.bean;
018
019import java.lang.reflect.InvocationHandler;
020import java.lang.reflect.Method;
021import java.lang.reflect.Type;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.List;
025import java.util.concurrent.Callable;
026import java.util.concurrent.ExecutionException;
027import java.util.concurrent.ExecutorService;
028import java.util.concurrent.Future;
029import java.util.concurrent.FutureTask;
030
031import org.apache.camel.CamelContext;
032import org.apache.camel.CamelExchangeException;
033import org.apache.camel.Endpoint;
034import org.apache.camel.Exchange;
035import org.apache.camel.ExchangePattern;
036import org.apache.camel.InvalidPayloadException;
037import org.apache.camel.Producer;
038import org.apache.camel.RuntimeCamelException;
039import org.apache.camel.impl.DefaultExchange;
040import org.apache.camel.util.ObjectHelper;
041import org.slf4j.Logger;
042import org.slf4j.LoggerFactory;
043
044public abstract class AbstractCamelInvocationHandler implements InvocationHandler {
045
046    private static final Logger LOG = LoggerFactory.getLogger(CamelInvocationHandler.class);
047    private static final List<Method> EXCLUDED_METHODS = new ArrayList<Method>();
048    private static ExecutorService executorService;
049    protected final Endpoint endpoint;
050    protected final Producer producer;
051
052    static {
053        // exclude all java.lang.Object methods as we dont want to invoke them
054        EXCLUDED_METHODS.addAll(Arrays.asList(Object.class.getMethods()));
055    }
056
057    public AbstractCamelInvocationHandler(Endpoint endpoint, Producer producer) {
058        this.endpoint = endpoint;
059        this.producer = producer;
060    }
061
062    private static Object getBody(Exchange exchange, Class<?> type) throws InvalidPayloadException {
063        // get the body from the Exchange from either OUT or IN
064        if (exchange.hasOut()) {
065            if (exchange.getOut().getBody() != null) {
066                return exchange.getOut().getMandatoryBody(type);
067            } else {
068                return null;
069            }
070        } else {
071            if (exchange.getIn().getBody() != null) {
072                return exchange.getIn().getMandatoryBody(type);
073            } else {
074                return null;
075            }
076        }
077    }
078
079    @Override
080    public final Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
081        if (isValidMethod(method)) {
082            return doInvokeProxy(proxy, method, args);
083        } else {
084            // invalid method then invoke methods on this instead
085            if ("toString".equals(method.getName())) {
086                return this.toString();
087            } else if ("hashCode".equals(method.getName())) {
088                return this.hashCode();
089            } else if ("equals".equals(method.getName())) {
090                return Boolean.FALSE;
091            }
092            return null;
093        }
094    }
095
096    public abstract Object doInvokeProxy(final Object proxy, final Method method, final Object[] args) throws Throwable;
097
098    protected Object invokeWithBody(final Method method, Object body, final ExchangePattern pattern) throws Throwable {
099        final Exchange exchange = new DefaultExchange(endpoint, pattern);
100        exchange.getIn().setBody(body);
101
102        // is the return type a future
103        final boolean isFuture = method.getReturnType() == Future.class;
104
105        // create task to execute the proxy and gather the reply
106        FutureTask<Object> task = new FutureTask<Object>(new Callable<Object>() {
107            public Object call() throws Exception {
108                // process the exchange
109                LOG.trace("Proxied method call {} invoking producer: {}", method.getName(), producer);
110                producer.process(exchange);
111
112                Object answer = afterInvoke(method, exchange, pattern, isFuture);
113                LOG.trace("Proxied method call {} returning: {}", method.getName(), answer);
114                return answer;
115            }
116        });
117
118        if (isFuture) {
119            // submit task and return future
120            if (LOG.isTraceEnabled()) {
121                LOG.trace("Submitting task for exchange id {}", exchange.getExchangeId());
122            }
123            getExecutorService(exchange.getContext()).submit(task);
124            return task;
125        } else {
126            // execute task now
127            try {
128                task.run();
129                return task.get();
130            } catch (ExecutionException e) {
131                // we don't want the wrapped exception from JDK
132                throw e.getCause();
133            }
134        }
135    }
136
137    protected Object afterInvoke(Method method, Exchange exchange, ExchangePattern pattern, boolean isFuture) throws Exception {
138        // check if we had an exception
139        Throwable cause = exchange.getException();
140        if (cause != null) {
141            Throwable found = findSuitableException(cause, method);
142            if (found != null) {
143                if (found instanceof Exception) {
144                    throw (Exception)found;
145                } else {
146                    // wrap as exception
147                    throw new CamelExchangeException("Error processing exchange", exchange, cause);
148                }
149            }
150            // special for runtime camel exceptions as they can be nested
151            if (cause instanceof RuntimeCamelException) {
152                // if the inner cause is a runtime exception we can throw it
153                // directly
154                if (cause.getCause() instanceof RuntimeException) {
155                    throw (RuntimeException)((RuntimeCamelException)cause).getCause();
156                }
157                throw (RuntimeCamelException)cause;
158            }
159            // okay just throw the exception as is
160            if (cause instanceof Exception) {
161                throw (Exception)cause;
162            } else {
163                // wrap as exception
164                throw new CamelExchangeException("Error processing exchange", exchange, cause);
165            }
166        }
167
168        Class<?> to = isFuture ? getGenericType(exchange.getContext(), method.getGenericReturnType()) : method.getReturnType();
169
170        // do not return a reply if the method is VOID
171        if (to == Void.TYPE) {
172            return null;
173        }
174
175        return getBody(exchange, to);
176    }
177
178    protected static Class<?> getGenericType(CamelContext context, Type type) throws ClassNotFoundException {
179        if (type == null) {
180            // fallback and use object
181            return Object.class;
182        }
183
184        // unfortunately java dont provide a nice api for getting the generic
185        // type of the return type
186        // due type erasure, so we have to gather it based on a String
187        // representation
188        String name = ObjectHelper.between(type.toString(), "<", ">");
189        if (name != null) {
190            if (name.contains("<")) {
191                // we only need the outer type
192                name = ObjectHelper.before(name, "<");
193            }
194            return context.getClassResolver().resolveMandatoryClass(name);
195        } else {
196            // fallback and use object
197            return Object.class;
198        }
199    }
200
201    @SuppressWarnings("deprecation")
202    protected static synchronized ExecutorService getExecutorService(CamelContext context) {
203        // CamelContext will shutdown thread pool when it shutdown so we can
204        // lazy create it on demand
205        // but in case of hot-deploy or the likes we need to be able to
206        // re-create it (its a shared static instance)
207        if (executorService == null || executorService.isTerminated() || executorService.isShutdown()) {
208            // try to lookup a pool first based on id/profile
209            executorService = context.getExecutorServiceStrategy().lookup(CamelInvocationHandler.class, "CamelInvocationHandler", "CamelInvocationHandler");
210            if (executorService == null) {
211                executorService = context.getExecutorServiceStrategy().newDefaultThreadPool(CamelInvocationHandler.class, "CamelInvocationHandler");
212            }
213        }
214        return executorService;
215    }
216
217    /**
218     * Tries to find the best suited exception to throw.
219     * <p/>
220     * It looks in the exception hierarchy from the caused exception and matches
221     * this against the declared exceptions being thrown on the method.
222     * 
223     * @param cause the caused exception
224     * @param method the method
225     * @return the exception to throw, or <tt>null</tt> if not possible to find
226     *         a suitable exception
227     */
228    protected Throwable findSuitableException(Throwable cause, Method method) {
229        if (method.getExceptionTypes() == null || method.getExceptionTypes().length == 0) {
230            return null;
231        }
232
233        // see if there is any exception which matches the declared exception on
234        // the method
235        for (Class<?> type : method.getExceptionTypes()) {
236            Object fault = ObjectHelper.getException(type, cause);
237            if (fault != null) {
238                return Throwable.class.cast(fault);
239            }
240        }
241
242        return null;
243    }
244
245    protected boolean isValidMethod(Method method) {
246        // must not be in the excluded list
247        for (Method excluded : EXCLUDED_METHODS) {
248            if (ObjectHelper.isOverridingMethod(excluded, method)) {
249                // the method is overriding an excluded method so its not valid
250                return false;
251            }
252        }
253        return true;
254    }
255
256}