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 }