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