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.annotation.Annotation; 020import java.lang.reflect.InvocationHandler; 021import java.lang.reflect.Method; 022import java.lang.reflect.Parameter; 023import java.lang.reflect.Type; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.List; 027import java.util.Map; 028import java.util.concurrent.Callable; 029import java.util.concurrent.ExecutionException; 030import java.util.concurrent.ExecutorService; 031import java.util.concurrent.Future; 032import java.util.concurrent.FutureTask; 033 034import org.apache.camel.Body; 035import org.apache.camel.CamelContext; 036import org.apache.camel.CamelExchangeException; 037import org.apache.camel.Endpoint; 038import org.apache.camel.Exchange; 039import org.apache.camel.ExchangePattern; 040import org.apache.camel.ExchangeProperty; 041import org.apache.camel.Header; 042import org.apache.camel.Headers; 043import org.apache.camel.InvalidPayloadException; 044import org.apache.camel.Producer; 045import org.apache.camel.RuntimeCamelException; 046import org.apache.camel.impl.DefaultExchange; 047import org.apache.camel.util.ObjectHelper; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 050 051public abstract class AbstractCamelInvocationHandler implements InvocationHandler { 052 053 private static final Logger LOG = LoggerFactory.getLogger(CamelInvocationHandler.class); 054 private static final List<Method> EXCLUDED_METHODS = new ArrayList<>(); 055 private static ExecutorService executorService; 056 protected final Endpoint endpoint; 057 protected final Producer producer; 058 059 static { 060 // exclude all java.lang.Object methods as we dont want to invoke them 061 EXCLUDED_METHODS.addAll(Arrays.asList(Object.class.getMethods())); 062 } 063 064 public AbstractCamelInvocationHandler(Endpoint endpoint, Producer producer) { 065 this.endpoint = endpoint; 066 this.producer = producer; 067 } 068 069 private static Object getBody(Exchange exchange, Class<?> type) throws InvalidPayloadException { 070 // get the body from the Exchange from either OUT or IN 071 if (exchange.hasOut()) { 072 if (exchange.getOut().getBody() != null) { 073 return exchange.getOut().getMandatoryBody(type); 074 } else { 075 return null; 076 } 077 } else { 078 if (exchange.getIn().getBody() != null) { 079 return exchange.getIn().getMandatoryBody(type); 080 } else { 081 return null; 082 } 083 } 084 } 085 086 @Override 087 public final Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { 088 if (isValidMethod(method)) { 089 return doInvokeProxy(proxy, method, args); 090 } else { 091 // invalid method then invoke methods on this instead 092 if ("toString".equals(method.getName())) { 093 return this.toString(); 094 } else if ("hashCode".equals(method.getName())) { 095 return this.hashCode(); 096 } else if ("equals".equals(method.getName())) { 097 return Boolean.FALSE; 098 } 099 return null; 100 } 101 } 102 103 public abstract Object doInvokeProxy(Object proxy, Method method, Object[] args) throws Throwable; 104 105 @SuppressWarnings("unchecked") 106 protected Object invokeProxy(final Method method, final ExchangePattern pattern, Object[] args, boolean binding) throws Throwable { 107 final Exchange exchange = new DefaultExchange(endpoint, pattern); 108 109 //Need to check if there are mutiple arguments and the parameters have no annotations for binding, 110 //then use the original bean invocation. 111 112 boolean canUseBinding = method.getParameterCount() == 1; 113 114 if (!canUseBinding) { 115 for (Parameter parameter : method.getParameters()) { 116 if (parameter.isAnnotationPresent(Header.class) 117 || parameter.isAnnotationPresent(Headers.class) 118 || parameter.isAnnotationPresent(ExchangeProperty.class) 119 || parameter.isAnnotationPresent(Body.class)) { 120 canUseBinding = true; 121 } 122 } 123 } 124 125 if (binding && canUseBinding) { 126 // in binding mode we bind the passed in arguments (args) to the created exchange 127 // using the existing Camel @Body, @Header, @Headers, @ExchangeProperty annotations 128 // if no annotation then its bound as the message body 129 int index = 0; 130 for (Annotation[] row : method.getParameterAnnotations()) { 131 Object value = args[index]; 132 if (row == null || row.length == 0) { 133 // assume its message body when there is no annotations 134 exchange.getIn().setBody(value); 135 } else { 136 for (Annotation ann : row) { 137 if (ann.annotationType().isAssignableFrom(Header.class)) { 138 Header header = (Header) ann; 139 String name = header.value(); 140 exchange.getIn().setHeader(name, value); 141 } else if (ann.annotationType().isAssignableFrom(Headers.class)) { 142 Map map = exchange.getContext().getTypeConverter().tryConvertTo(Map.class, exchange, value); 143 if (map != null) { 144 exchange.getIn().getHeaders().putAll(map); 145 } 146 } else if (ann.annotationType().isAssignableFrom(ExchangeProperty.class)) { 147 ExchangeProperty ep = (ExchangeProperty) ann; 148 String name = ep.value(); 149 exchange.setProperty(name, value); 150 } else if (ann.annotationType().isAssignableFrom(Body.class)) { 151 exchange.getIn().setBody(value); 152 } else { 153 // assume its message body when there is no annotations 154 exchange.getIn().setBody(value); 155 } 156 } 157 } 158 index++; 159 } 160 } else { 161 // no binding so use the old behavior with BeanInvocation as the body 162 BeanInvocation invocation = new BeanInvocation(method, args); 163 exchange.getIn().setBody(invocation); 164 } 165 166 if (binding) { 167 LOG.trace("Binding to service interface as @Body,@Header,@ExchangeProperty detected when calling proxy method: {}", method); 168 } else { 169 LOG.trace("No binding to service interface as @Body,@Header,@ExchangeProperty not detected. Using BeanInvocation as message body when calling proxy method: {}"); 170 } 171 172 return doInvoke(method, exchange); 173 } 174 175 protected Object invokeWithBody(final Method method, Object body, final ExchangePattern pattern) throws Throwable { 176 final Exchange exchange = new DefaultExchange(endpoint, pattern); 177 exchange.getIn().setBody(body); 178 179 return doInvoke(method, exchange); 180 } 181 182 protected Object doInvoke(final Method method, final Exchange exchange) throws Throwable { 183 184 // is the return type a future 185 final boolean isFuture = method.getReturnType() == Future.class; 186 187 // create task to execute the proxy and gather the reply 188 FutureTask<Object> task = new FutureTask<>(new Callable<Object>() { 189 public Object call() throws Exception { 190 // process the exchange 191 LOG.trace("Proxied method call {} invoking producer: {}", method.getName(), producer); 192 producer.process(exchange); 193 194 Object answer = afterInvoke(method, exchange, exchange.getPattern(), isFuture); 195 LOG.trace("Proxied method call {} returning: {}", method.getName(), answer); 196 return answer; 197 } 198 }); 199 200 if (isFuture) { 201 // submit task and return future 202 if (LOG.isTraceEnabled()) { 203 LOG.trace("Submitting task for exchange id {}", exchange.getExchangeId()); 204 } 205 getExecutorService(exchange.getContext()).submit(task); 206 return task; 207 } else { 208 // execute task now 209 try { 210 task.run(); 211 return task.get(); 212 } catch (ExecutionException e) { 213 // we don't want the wrapped exception from JDK 214 throw e.getCause(); 215 } 216 } 217 } 218 219 protected Object afterInvoke(Method method, Exchange exchange, ExchangePattern pattern, boolean isFuture) throws Exception { 220 // check if we had an exception 221 Throwable cause = exchange.getException(); 222 if (cause != null) { 223 Throwable found = findSuitableException(cause, method); 224 if (found != null) { 225 if (found instanceof Exception) { 226 throw (Exception)found; 227 } else { 228 // wrap as exception 229 throw new CamelExchangeException("Error processing exchange", exchange, cause); 230 } 231 } 232 // special for runtime camel exceptions as they can be nested 233 if (cause instanceof RuntimeCamelException) { 234 // if the inner cause is a runtime exception we can throw it 235 // directly 236 if (cause.getCause() instanceof RuntimeException) { 237 throw (RuntimeException)((RuntimeCamelException)cause).getCause(); 238 } 239 throw (RuntimeCamelException)cause; 240 } 241 // okay just throw the exception as is 242 if (cause instanceof Exception) { 243 throw (Exception)cause; 244 } else { 245 // wrap as exception 246 throw new CamelExchangeException("Error processing exchange", exchange, cause); 247 } 248 } 249 250 Class<?> to = isFuture ? getGenericType(exchange.getContext(), method.getGenericReturnType()) : method.getReturnType(); 251 252 // do not return a reply if the method is VOID 253 if (to == Void.TYPE) { 254 return null; 255 } 256 257 return getBody(exchange, to); 258 } 259 260 protected static Class<?> getGenericType(CamelContext context, Type type) throws ClassNotFoundException { 261 if (type == null) { 262 // fallback and use object 263 return Object.class; 264 } 265 266 // unfortunately java dont provide a nice api for getting the generic 267 // type of the return type 268 // due type erasure, so we have to gather it based on a String 269 // representation 270 String name = ObjectHelper.between(type.toString(), "<", ">"); 271 if (name != null) { 272 if (name.contains("<")) { 273 // we only need the outer type 274 name = ObjectHelper.before(name, "<"); 275 } 276 return context.getClassResolver().resolveMandatoryClass(name); 277 } else { 278 // fallback and use object 279 return Object.class; 280 } 281 } 282 283 @SuppressWarnings("deprecation") 284 protected static synchronized ExecutorService getExecutorService(CamelContext context) { 285 // CamelContext will shutdown thread pool when it shutdown so we can 286 // lazy create it on demand 287 // but in case of hot-deploy or the likes we need to be able to 288 // re-create it (its a shared static instance) 289 if (executorService == null || executorService.isTerminated() || executorService.isShutdown()) { 290 // try to lookup a pool first based on id/profile 291 executorService = context.getExecutorServiceStrategy().lookup(CamelInvocationHandler.class, "CamelInvocationHandler", "CamelInvocationHandler"); 292 if (executorService == null) { 293 executorService = context.getExecutorServiceStrategy().newDefaultThreadPool(CamelInvocationHandler.class, "CamelInvocationHandler"); 294 } 295 } 296 return executorService; 297 } 298 299 /** 300 * Tries to find the best suited exception to throw. 301 * <p/> 302 * It looks in the exception hierarchy from the caused exception and matches 303 * this against the declared exceptions being thrown on the method. 304 * 305 * @param cause the caused exception 306 * @param method the method 307 * @return the exception to throw, or <tt>null</tt> if not possible to find 308 * a suitable exception 309 */ 310 protected Throwable findSuitableException(Throwable cause, Method method) { 311 if (method.getExceptionTypes() == null || method.getExceptionTypes().length == 0) { 312 return null; 313 } 314 315 // see if there is any exception which matches the declared exception on 316 // the method 317 for (Class<?> type : method.getExceptionTypes()) { 318 Object fault = ObjectHelper.getException(type, cause); 319 if (fault != null) { 320 return Throwable.class.cast(fault); 321 } 322 } 323 324 return null; 325 } 326 327 protected boolean isValidMethod(Method method) { 328 // must not be in the excluded list 329 for (Method excluded : EXCLUDED_METHODS) { 330 if (ObjectHelper.isOverridingMethod(excluded, method)) { 331 // the method is overriding an excluded method so its not valid 332 return false; 333 } 334 } 335 return true; 336 } 337 338}