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.servicemix.bean;
018
019 import java.lang.reflect.Field;
020 import java.lang.reflect.InvocationTargetException;
021 import java.lang.reflect.Method;
022 import java.util.Map;
023 import java.util.MissingResourceException;
024 import java.util.concurrent.ConcurrentHashMap;
025 import java.util.concurrent.Future;
026 import java.util.logging.Logger;
027
028 import javax.annotation.PostConstruct;
029 import javax.annotation.PreDestroy;
030 import javax.annotation.Resource;
031 import javax.jbi.JBIException;
032 import javax.jbi.component.ComponentContext;
033 import javax.jbi.management.MBeanNames;
034 import javax.jbi.messaging.*;
035 import javax.jbi.messaging.MessageExchange.Role;
036 import javax.jbi.servicedesc.ServiceEndpoint;
037 import javax.management.MBeanServer;
038 import javax.naming.InitialContext;
039 import javax.xml.namespace.QName;
040
041 import org.aopalliance.intercept.MethodInvocation;
042 import org.apache.commons.jexl.Expression;
043 import org.apache.commons.jexl.ExpressionFactory;
044 import org.apache.commons.jexl.JexlContext;
045 import org.apache.commons.jexl.JexlHelper;
046 import org.apache.servicemix.bean.support.BeanInfo;
047 import org.apache.servicemix.bean.support.DefaultMethodInvocationStrategy;
048 import org.apache.servicemix.bean.support.DestinationImpl;
049 import org.apache.servicemix.bean.support.Holder;
050 import org.apache.servicemix.bean.support.MethodInvocationStrategy;
051 import org.apache.servicemix.bean.support.ReflectionUtils;
052 import org.apache.servicemix.bean.support.Request;
053 import org.apache.servicemix.common.endpoints.ProviderEndpoint;
054 import org.apache.servicemix.common.util.MessageUtil;
055 import org.apache.servicemix.common.util.URIResolver;
056 import org.apache.servicemix.expression.JAXPStringXPathExpression;
057 import org.apache.servicemix.expression.PropertyExpression;
058 import org.apache.servicemix.jbi.listener.MessageExchangeListener;
059 import org.springframework.beans.BeansException;
060 import org.springframework.context.ApplicationContext;
061 import org.springframework.context.ApplicationContextAware;
062 import org.w3c.dom.Document;
063 import org.w3c.dom.DocumentFragment;
064
065 /**
066 * Represents a bean endpoint which consists of a together with a {@link MethodInvocationStrategy}
067 * so that JBI message exchanges can be invoked on the bean.
068 *
069 * @version $Revision: $
070 * @org.apache.xbean.XBean element="endpoint"
071 */
072 public class BeanEndpoint extends ProviderEndpoint implements ApplicationContextAware {
073
074 /**
075 * Property name for the correlation id that is being set on exchanges by the BeanEndpoint
076 */
077 public static final String CORRELATION_ID = BeanEndpoint.class.getName().replaceAll("\\.", "_") + "_correlation";
078
079 private ApplicationContext applicationContext;
080 private String beanName;
081 private Object bean;
082 private BeanInfo beanInfo;
083 private Class<?> beanType;
084 private String beanClassName;
085 private MethodInvocationStrategy methodInvocationStrategy;
086 private org.apache.servicemix.expression.Expression correlationExpression;
087
088 private Map<String, Holder> exchanges = new ConcurrentHashMap<String, Holder>();
089 private Map<Object, Request> requests = new ConcurrentHashMap<Object, Request>();
090 private ThreadLocal<Request> currentRequest = new ThreadLocal<Request>();
091 private ServiceEndpoint serviceEndpoint;
092
093 public BeanEndpoint() {
094 }
095
096 public BeanEndpoint(BeanComponent component, ServiceEndpoint serviceEndpoint) {
097 super(component, serviceEndpoint);
098 this.applicationContext = component.getApplicationContext();
099 this.serviceEndpoint = serviceEndpoint;
100 }
101
102 public void start() throws Exception {
103 super.start();
104 if (serviceEndpoint == null) {
105 serviceEndpoint = getContext().getEndpoint(getService(), getEndpoint());
106 }
107 Object pojo = getBean();
108 if (pojo != null) {
109 beanType = pojo.getClass();
110 injectBean(pojo);
111 ReflectionUtils.callLifecycleMethod(pojo, PostConstruct.class);
112 } else {
113 beanType = createBean().getClass();
114 }
115 if (getMethodInvocationStrategy() == null) {
116 throw new IllegalArgumentException("No 'methodInvocationStrategy' property set");
117 }
118 }
119
120
121 public void stop() throws Exception {
122 super.stop();
123 Object pojo = getBean();
124 if (pojo != null) {
125 ReflectionUtils.callLifecycleMethod(pojo, PreDestroy.class);
126 }
127 }
128
129
130 public ApplicationContext getApplicationContext() {
131 return applicationContext;
132 }
133
134 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
135 this.applicationContext = applicationContext;
136 }
137
138 public String getBeanName() {
139 return beanName;
140 }
141
142 public void setBeanName(String beanName) {
143 this.beanName = beanName;
144 }
145
146 public Object getBean() {
147 return bean;
148 }
149
150 public void setBean(Object bean) {
151 this.bean = bean;
152 }
153
154 /**
155 * @return the beanType
156 */
157 public Class<?> getBeanType() {
158 return beanType;
159 }
160
161 /**
162 * @param beanType the beanType to set
163 */
164 public void setBeanType(Class<?> beanType) {
165 this.beanType = beanType;
166 }
167
168 /**
169 * @return the beanClassName
170 */
171 public String getBeanClassName() {
172 return beanClassName;
173 }
174
175 /**
176 * @param beanClassName the beanClassName to set
177 */
178 public void setBeanClassName(String beanClassName) {
179 this.beanClassName = beanClassName;
180 }
181
182 public BeanInfo getBeanInfo() {
183 if (beanInfo == null) {
184 beanInfo = new BeanInfo(beanType, getMethodInvocationStrategy());
185 beanInfo.introspect();
186 }
187 return beanInfo;
188 }
189
190 public void setBeanInfo(BeanInfo beanInfo) {
191 this.beanInfo = beanInfo;
192 }
193
194 public MethodInvocationStrategy getMethodInvocationStrategy() {
195 if (methodInvocationStrategy == null) {
196 methodInvocationStrategy = createMethodInvocationStrategy();
197 }
198 return methodInvocationStrategy;
199 }
200
201 public void setMethodInvocationStrategy(MethodInvocationStrategy methodInvocationStrategy) {
202 this.methodInvocationStrategy = methodInvocationStrategy;
203 }
204
205
206 @Override
207 public void process(MessageExchange exchange) throws Exception {
208 if (exchange.getRole() == Role.CONSUMER) {
209 onConsumerExchange(exchange);
210 } else {
211 onProviderExchange(exchange);
212 }
213 }
214
215 protected void onProviderExchange(MessageExchange exchange) throws Exception {
216 Request req = getOrCreateCurrentRequest(exchange);
217 currentRequest.set(req);
218 try {
219 // Find or create the request for this provider exchange
220 synchronized (req) {
221 // If the bean implements MessageExchangeListener,
222 // just call the method
223 if (req.getBean() instanceof MessageExchangeListener) {
224 ((MessageExchangeListener) req.getBean()).onMessageExchange(exchange);
225 } else {
226 // Exchange is finished
227 if (exchange.getStatus() == ExchangeStatus.DONE) {
228 return;
229 // Exchange has been aborted with an exception
230 } else if (exchange.getStatus() == ExchangeStatus.ERROR) {
231 return;
232 // Fault message
233 } else if (exchange.getFault() != null) {
234 // TODO: find a way to send it back to the bean before setting the DONE status
235 done(exchange);
236 } else {
237 MethodInvocation invocation = getMethodInvocationStrategy().createInvocation(
238 req.getBean(), getBeanInfo(), exchange, this);
239 if (invocation == null) {
240 throw new UnknownMessageExchangeTypeException(exchange, this);
241 }
242 try {
243 invocation.proceed();
244 } catch (InvocationTargetException e) {
245 throw new MethodInvocationFailedException(req.getBean(), invocation, exchange, this, e.getCause());
246 } catch (Exception e) {
247 throw e;
248 } catch (Throwable throwable) {
249 throw new MethodInvocationFailedException(req.getBean(), invocation, exchange, this, throwable);
250 }
251 if (exchange.getStatus() == ExchangeStatus.ERROR) {
252 send(exchange);
253 }
254 if (exchange.getFault() == null && exchange.getMessage("out") == null) {
255 //TODO: Only send DONE if(onProviderExchange(exchange)) as soon as we find a way to solve
256 // the TODO in evaluateCallbacks(Request)
257 done(exchange);
258 }
259 }
260 }
261 }
262 } finally {
263 checkEndOfRequest(req);
264 currentRequest.set(null);
265 }
266 }
267
268 /*
269 * Check if the incoming provider exchange should be marked DONE
270 */
271 protected boolean onProviderExchangeDone(MessageExchange exchange) {
272 return (exchange instanceof InOnly) ||
273 (exchange instanceof RobustInOnly && exchange.getFault() == null) ||
274 (exchange instanceof InOptionalOut && exchange.getFault() == null && exchange.getMessage("out") == null);
275 }
276
277 protected Request getOrCreateCurrentRequest(MessageExchange exchange) throws ClassNotFoundException, InstantiationException, IllegalAccessException, MessagingException {
278 if (currentRequest.get() != null) {
279 return currentRequest.get();
280 }
281 Request req = getRequest(exchange);
282 if (req == null) {
283 Object pojo = getBean();
284 if (pojo == null) {
285 pojo = createBean();
286 injectBean(pojo);
287 ReflectionUtils.callLifecycleMethod(pojo, PostConstruct.class);
288 }
289 req = new Request(getCorrelation(exchange), pojo, exchange);
290 requests.put(req.getCorrelationId(), req);
291 }
292 return req;
293 }
294
295 protected Request getRequest(MessageExchange exchange) throws MessagingException {
296 Object correlation = getCorrelation(exchange);
297 return correlation == null ? null : requests.get(correlation);
298 }
299
300 protected void onConsumerExchange(MessageExchange exchange) throws Exception {
301 Request req = getOrCreateCurrentRequest(exchange);
302 if (req == null) {
303 throw new IllegalStateException("Receiving unknown consumer exchange: " + exchange);
304 }
305 currentRequest.set(req);
306
307 // if there's a holder for this exchange, act upon that
308 // else invoke the MessageExchangeListener interface
309 if (exchanges.containsKey(exchange.getExchangeId())) {
310 exchanges.remove(exchange.getExchangeId()).set(exchange);
311 evaluateCallbacks(req);
312
313 //we should done() the consumer exchange here on behalf of the Destination who sent it
314 if (exchange instanceof InOut && ExchangeStatus.ACTIVE.equals(exchange.getStatus())) {
315 done(exchange);
316 }
317 } else if (req.getBean() instanceof MessageExchangeListener) {
318 ((MessageExchangeListener) req.getBean()).onMessageExchange(exchange);
319 } else {
320 throw new IllegalStateException("No known consumer exchange found and bean does not implement MessageExchangeListener");
321 }
322 checkEndOfRequest(req);
323 currentRequest.set(null);
324 }
325
326 protected Object getCorrelation(MessageExchange exchange) throws MessagingException {
327 return getCorrelationExpression().evaluate(exchange, exchange.getMessage("in"));
328 }
329
330 protected Object createBean() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
331 if (beanType == null && beanClassName != null) {
332 beanType = Class.forName(beanClassName, true, getServiceUnit().getConfigurationClassLoader());
333 }
334 if (beanName == null && beanType == null) {
335 throw new IllegalArgumentException("Property 'bean', 'beanName' or 'beanClassName' has not been set!");
336 }
337 if (beanName != null && applicationContext == null) {
338 throw new IllegalArgumentException("Property 'beanName' specified, but no BeanFactory set!");
339 }
340 if (beanType != null) {
341 return beanType.newInstance();
342 } else {
343 Object answer = applicationContext.getBean(beanName);
344 if (answer == null) {
345 throw new NoSuchBeanException(beanName, this);
346 }
347 return answer;
348 }
349 }
350
351 protected MethodInvocationStrategy createMethodInvocationStrategy() {
352 DefaultMethodInvocationStrategy st = new DefaultMethodInvocationStrategy();
353 st.loadDefaultRegistry();
354 return st;
355 }
356
357 /**
358 * A strategy method to allow implementations to perform some custom JBI based injection of the POJO
359 *
360 * @param target the bean to be injected
361 */
362 protected void injectBean(final Object target) {
363 final PojoContext ctx = new PojoContext();
364 final DeliveryChannel ch = ctx.channel;
365 // Inject fields
366 ReflectionUtils.doWithFields(target.getClass(), new ReflectionUtils.FieldCallback() {
367 public void doWith(Field f) throws IllegalArgumentException, IllegalAccessException {
368 ExchangeTarget et = f.getAnnotation(ExchangeTarget.class);
369 if (et != null) {
370 ReflectionUtils.setField(f, target, new DestinationImpl(et.uri(), BeanEndpoint.this));
371 }
372 if (f.getAnnotation(Resource.class) != null) {
373 if (ComponentContext.class.isAssignableFrom(f.getType())) {
374 ReflectionUtils.setField(f, target, ctx);
375 } else if (DeliveryChannel.class.isAssignableFrom(f.getType())) {
376 ReflectionUtils.setField(f, target, ch);
377 } else if (ServiceEndpoint.class.isAssignableFrom(f.getType())) {
378 ReflectionUtils.setField(f, target, BeanEndpoint.this.serviceEndpoint);
379 }
380 }
381 }
382 });
383 }
384
385 protected void evaluateCallbacks(final Request req) {
386 final Object obj = req.getBean();
387 ReflectionUtils.doWithMethods(obj.getClass(), new ReflectionUtils.MethodCallback() {
388 @SuppressWarnings("unchecked")
389 public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
390 if (method.getAnnotation(Callback.class) != null) {
391 try {
392 Expression e = ExpressionFactory.createExpression(
393 method.getAnnotation(Callback.class).condition());
394 JexlContext jc = JexlHelper.createContext();
395 jc.getVars().put("this", obj);
396 Object r = e.evaluate(jc);
397 if (!(r instanceof Boolean)) {
398 throw new RuntimeException("Expression did not returned a boolean value but: " + r);
399 }
400 Boolean oldVal = req.getCallbacks().get(method);
401 Boolean newVal = (Boolean) r;
402 if ((oldVal == null || !oldVal) && newVal) {
403 req.getCallbacks().put(method, newVal);
404 method.invoke(obj, new Object[0]);
405 // TODO: handle return value and sent it as the answer
406 }
407 } catch (Exception e) {
408 throw new RuntimeException("Unable to invoke callback", e);
409 }
410 }
411 }
412 });
413 }
414
415 /**
416 * Used by POJOs acting as a consumer
417 * @param uri
418 * @param message
419 * @return
420 */
421 public Future<NormalizedMessage> send(String uri, NormalizedMessage message) {
422 try {
423 InOut me = getExchangeFactory().createInOutExchange();
424 URIResolver.configureExchange(me, getServiceUnit().getComponent().getComponentContext(), uri);
425 MessageUtil.transferTo(message, me, "in");
426 final Holder h = new Holder();
427 getOrCreateCurrentRequest(me).addExchange(me);
428 exchanges.put(me.getExchangeId(), h);
429 BeanEndpoint.this.send(me);
430 return h;
431 } catch (Exception e) {
432 throw new RuntimeException(e);
433 }
434 }
435
436 @Override
437 protected void send(MessageExchange me) throws MessagingException {
438 checkEndOfRequest(me);
439 super.send(me);
440 }
441
442 /*
443 * Checks if the request has ended with the given MessageExchange. It will only perform the check on non-ACTIVE exchanges
444 */
445 private void checkEndOfRequest(MessageExchange me) throws MessagingException {
446 if (!ExchangeStatus.ACTIVE.equals(me.getStatus())) {
447 Request request = getRequest(me);
448 if (request != null) {
449 checkEndOfRequest(request);
450 }
451 }
452 }
453
454 /**
455 * Checks if the request has ended. If the request has ended,
456 * <ul>
457 * <li>the request object is being removed from the list of pending requests</li>
458 * <li>if the bean was created for that request, it is now being destroyed</li>
459 * </ul>
460 *
461 * @param req the Request instance to check
462 */
463 protected void checkEndOfRequest(Request req) {
464 if (req.isFinished()) {
465 requests.remove(req.getCorrelationId());
466 if (req.getBean() != bean) {
467 ReflectionUtils.callLifecycleMethod(req.getBean(), PreDestroy.class);
468 }
469 }
470 }
471
472 /**
473 * @return the correlationExpression
474 */
475 public org.apache.servicemix.expression.Expression getCorrelationExpression() {
476 if (correlationExpression == null) {
477 // Find correlation expression
478 Correlation cor = beanType.getAnnotation(Correlation.class);
479 if (cor != null) {
480 if (cor.property() != null) {
481 correlationExpression = new PropertyExpression(cor.property());
482 } else if (cor.xpath() != null) {
483 correlationExpression = new JAXPStringXPathExpression(cor.xpath());
484 }
485 }
486 if (correlationExpression == null) {
487 correlationExpression = new org.apache.servicemix.expression.Expression() {
488 public Object evaluate(MessageExchange exchange, NormalizedMessage message)
489 throws MessagingException {
490 if (exchange.getProperty(CORRELATION_ID) != null) {
491 return exchange.getProperty(CORRELATION_ID);
492 }
493 return exchange.getExchangeId();
494 }
495 };
496 }
497 }
498 return correlationExpression;
499 }
500
501 /**
502 * @param correlationExpression the correlationExpression to set
503 */
504 public void setCorrelationExpression(org.apache.servicemix.expression.Expression correlationExpression) {
505 this.correlationExpression = correlationExpression;
506 }
507
508 protected class PojoContext implements ComponentContext {
509
510 private DeliveryChannel channel = new PojoChannel();
511
512 public ServiceEndpoint activateEndpoint(QName qName, String s) throws JBIException {
513 return getContext().activateEndpoint(qName, s);
514 }
515
516 public void deactivateEndpoint(ServiceEndpoint serviceEndpoint) throws JBIException {
517 getContext().deactivateEndpoint(serviceEndpoint);
518 }
519
520 public void registerExternalEndpoint(ServiceEndpoint serviceEndpoint) throws JBIException {
521 getContext().registerExternalEndpoint(serviceEndpoint);
522 }
523
524 public void deregisterExternalEndpoint(ServiceEndpoint serviceEndpoint) throws JBIException {
525 getContext().deregisterExternalEndpoint(serviceEndpoint);
526 }
527
528 public ServiceEndpoint resolveEndpointReference(DocumentFragment documentFragment) {
529 return getContext().resolveEndpointReference(documentFragment);
530 }
531
532 public String getComponentName() {
533 return getContext().getComponentName();
534 }
535
536 public DeliveryChannel getDeliveryChannel() throws MessagingException {
537 return channel;
538 }
539
540 public ServiceEndpoint getEndpoint(QName qName, String s) {
541 return getContext().getEndpoint(qName, s);
542 }
543
544 public Document getEndpointDescriptor(ServiceEndpoint serviceEndpoint) throws JBIException {
545 return getContext().getEndpointDescriptor(serviceEndpoint);
546 }
547
548 public ServiceEndpoint[] getEndpoints(QName qName) {
549 return getContext().getEndpoints(qName);
550 }
551
552 public ServiceEndpoint[] getEndpointsForService(QName qName) {
553 return getContext().getEndpointsForService(qName);
554 }
555
556 public ServiceEndpoint[] getExternalEndpoints(QName qName) {
557 return getContext().getExternalEndpoints(qName);
558 }
559
560 public ServiceEndpoint[] getExternalEndpointsForService(QName qName) {
561 return getContext().getExternalEndpointsForService(qName);
562 }
563
564 public String getInstallRoot() {
565 return getContext().getInstallRoot();
566 }
567
568 public Logger getLogger(String s, String s1) throws MissingResourceException, JBIException {
569 return getContext().getLogger(s, s1);
570 }
571
572 public MBeanNames getMBeanNames() {
573 return getContext().getMBeanNames();
574 }
575
576 public MBeanServer getMBeanServer() {
577 return getContext().getMBeanServer();
578 }
579
580 public InitialContext getNamingContext() {
581 return getContext().getNamingContext();
582 }
583
584 public Object getTransactionManager() {
585 return getContext().getTransactionManager();
586 }
587
588 public String getWorkspaceRoot() {
589 return getContext().getWorkspaceRoot();
590 }
591 }
592
593 protected class PojoChannel implements DeliveryChannel {
594
595 public void close() throws MessagingException {
596 }
597
598 public MessageExchangeFactory createExchangeFactory() {
599 return getChannel().createExchangeFactory();
600 }
601
602 public MessageExchangeFactory createExchangeFactory(QName qName) {
603 return getChannel().createExchangeFactory(qName);
604 }
605
606 public MessageExchangeFactory createExchangeFactoryForService(QName qName) {
607 return getChannel().createExchangeFactoryForService(qName);
608 }
609
610 public MessageExchangeFactory createExchangeFactory(ServiceEndpoint serviceEndpoint) {
611 return getChannel().createExchangeFactory(serviceEndpoint);
612 }
613
614 public MessageExchange accept() throws MessagingException {
615 return getChannel().accept();
616 }
617
618 public MessageExchange accept(long l) throws MessagingException {
619 return getChannel().accept(l);
620 }
621
622 public void send(MessageExchange messageExchange) throws MessagingException {
623 try {
624 Request req = getOrCreateCurrentRequest(messageExchange);
625 if (messageExchange.getRole() == MessageExchange.Role.CONSUMER
626 && messageExchange.getStatus() == ExchangeStatus.ACTIVE) {
627 if (!(req.getBean() instanceof MessageExchangeListener)) {
628 throw new IllegalStateException("A bean acting as a consumer and using the channel to send exchanges must implement the MessageExchangeListener interface");
629 }
630 req.addExchange(messageExchange);
631 }
632 if (messageExchange.getStatus() != ExchangeStatus.ACTIVE) {
633 checkEndOfRequest(req);
634 }
635 getChannel().send(messageExchange);
636 } catch (MessagingException e) {
637 throw e;
638 } catch (Exception e) {
639 throw new MessagingException(e);
640 }
641 }
642
643 public boolean sendSync(MessageExchange messageExchange) throws MessagingException {
644 checkEndOfRequest(messageExchange);
645 return getChannel().sendSync(messageExchange);
646 }
647
648 public boolean sendSync(MessageExchange messageExchange, long l) throws MessagingException {
649 checkEndOfRequest(messageExchange);
650 return getChannel().sendSync(messageExchange, l);
651 }
652 }
653 }