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