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