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