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