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.MessageExchange.Role;
038 import javax.jbi.messaging.MessageExchangeFactory;
039 import javax.jbi.messaging.MessagingException;
040 import javax.jbi.messaging.NormalizedMessage;
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.w3c.dom.Document;
047 import org.w3c.dom.DocumentFragment;
048
049 import org.aopalliance.intercept.MethodInvocation;
050 import org.apache.commons.jexl.Expression;
051 import org.apache.commons.jexl.ExpressionFactory;
052 import org.apache.commons.jexl.JexlContext;
053 import org.apache.commons.jexl.JexlHelper;
054 import org.apache.servicemix.bean.support.BeanInfo;
055 import org.apache.servicemix.bean.support.DefaultMethodInvocationStrategy;
056 import org.apache.servicemix.bean.support.DestinationImpl;
057 import org.apache.servicemix.bean.support.Holder;
058 import org.apache.servicemix.bean.support.MethodInvocationStrategy;
059 import org.apache.servicemix.bean.support.ReflectionUtils;
060 import org.apache.servicemix.bean.support.Request;
061 import org.apache.servicemix.common.endpoints.ProviderEndpoint;
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.apache.servicemix.jbi.resolver.URIResolver;
066 import org.apache.servicemix.jbi.util.MessageUtil;
067 import org.springframework.beans.BeansException;
068 import org.springframework.context.ApplicationContext;
069 import org.springframework.context.ApplicationContextAware;
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 // Find or create the request for this provider exchange
217 } else if (exchange.getRole() == MessageExchange.Role.PROVIDER) {
218 onProviderExchange(exchange);
219 } else {
220 throw new IllegalStateException("Unknown role: " + exchange.getRole());
221 }
222 }
223
224 protected void onProviderExchange(MessageExchange exchange) throws Exception {
225 Request req = getOrCreateCurrentRequest(exchange);
226 currentRequest.set(req);
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 (Exception e) {
252 throw e;
253 } catch (Throwable throwable) {
254 throw new MethodInvocationFailedException(req.getBean(), invocation, exchange, this, throwable);
255 }
256 if (exchange.getStatus() == ExchangeStatus.ERROR) {
257 send(exchange);
258 }
259 if (exchange.getFault() == null && exchange.getMessage("out") == null) {
260 // TODO: handle MEP correctly (DONE should only be sent for InOnly)
261 done(exchange);
262 }
263 }
264 }
265 checkEndOfRequest(req);
266 currentRequest.set(null);
267 }
268 }
269
270 protected Request getOrCreateCurrentRequest(MessageExchange exchange) throws Exception {
271 if (currentRequest.get() != null) {
272 return currentRequest.get();
273 }
274 Request req = getRequest(exchange);
275 if (req == null) {
276 Object pojo = getBean();
277 if (pojo == null) {
278 pojo = createBean();
279 injectBean(pojo);
280 ReflectionUtils.callLifecycleMethod(pojo, PostConstruct.class);
281 }
282 req = new Request(getCorrelation(exchange), pojo, exchange);
283 requests.put(req.getCorrelationId(), req);
284 }
285 return req;
286 }
287
288 protected Request getRequest(MessageExchange exchange) throws MessagingException {
289 Object correlation = getCorrelation(exchange);
290 return correlation == null ? null : requests.get(correlation);
291 }
292
293 protected void onConsumerExchange(MessageExchange exchange) throws Exception {
294 Request req = getOrCreateCurrentRequest(exchange);
295 if (req == null) {
296 throw new IllegalStateException("Receiving unknown consumer exchange: " + exchange);
297 }
298 currentRequest.set(req);
299
300 // if there's a holder for this exchange, act upon that
301 // else invoke the MessageExchangeListener interface
302 if (exchanges.containsKey(exchange.getExchangeId())) {
303 exchanges.remove(exchange.getExchangeId()).set(exchange);
304 evaluateCallbacks(req);
305
306 //we should done() the consumer exchange here on behalf of the Destination who sent it
307 if (exchange instanceof InOut && ExchangeStatus.ACTIVE.equals(exchange.getStatus())) {
308 done(exchange);
309 }
310 } else if (req.getBean() instanceof MessageExchangeListener) {
311 ((MessageExchangeListener) req.getBean()).onMessageExchange(exchange);
312 } else {
313 throw new IllegalStateException("No known consumer exchange found and bean does not implement MessageExchangeListener");
314 }
315 checkEndOfRequest(req);
316 currentRequest.set(null);
317 }
318
319 protected Object getCorrelation(MessageExchange exchange) throws MessagingException {
320 return getCorrelationExpression().evaluate(exchange, exchange.getMessage("in"));
321 }
322
323 protected Object createBean() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
324 if (beanType == null && beanClassName != null) {
325 beanType = Class.forName(beanClassName, true, getServiceUnit().getConfigurationClassLoader());
326 }
327 if (beanName == null && beanType == null) {
328 throw new IllegalArgumentException("Property 'bean', 'beanName' or 'beanClassName' has not been set!");
329 }
330 if (beanType != null) {
331 return beanType.newInstance();
332 } else if (beanName == null) {
333 throw new IllegalArgumentException("Property 'beanName', 'beanType' or 'beanClassName' must be set!");
334 } else if (applicationContext == null) {
335 throw new IllegalArgumentException("Property 'beanName' specified, but no BeanFactory set!");
336 } else {
337 Object answer = applicationContext.getBean(beanName);
338 if (answer == null) {
339 throw new NoSuchBeanException(beanName, this);
340 }
341 return answer;
342 }
343 }
344
345 protected MethodInvocationStrategy createMethodInvocationStrategy() {
346 DefaultMethodInvocationStrategy st = new DefaultMethodInvocationStrategy();
347 st.loadDefaultRegistry();
348 return st;
349 }
350
351 /**
352 * A strategy method to allow implementations to perform some custom JBI based injection of the POJO
353 *
354 * @param target the bean to be injected
355 */
356 protected void injectBean(final Object target) {
357 final PojoContext ctx = new PojoContext();
358 final DeliveryChannel ch = ctx.channel;
359 // Inject fields
360 ReflectionUtils.doWithFields(target.getClass(), new ReflectionUtils.FieldCallback() {
361 public void doWith(Field f) throws IllegalArgumentException, IllegalAccessException {
362 ExchangeTarget et = f.getAnnotation(ExchangeTarget.class);
363 if (et != null) {
364 ReflectionUtils.setField(f, target, new DestinationImpl(et.uri(), BeanEndpoint.this));
365 }
366 if (f.getAnnotation(Resource.class) != null) {
367 if (ComponentContext.class.isAssignableFrom(f.getType())) {
368 ReflectionUtils.setField(f, target, ctx);
369 } else if (DeliveryChannel.class.isAssignableFrom(f.getType())) {
370 ReflectionUtils.setField(f, target, ch);
371 } else if (ServiceEndpoint.class.isAssignableFrom(f.getType())) {
372 ReflectionUtils.setField(f, target, BeanEndpoint.this.serviceEndpoint);
373 }
374 }
375 }
376 });
377 }
378
379 protected void evaluateCallbacks(final Request req) {
380 final Object obj = req.getBean();
381 ReflectionUtils.doWithMethods(obj.getClass(), new ReflectionUtils.MethodCallback() {
382 @SuppressWarnings("unchecked")
383 public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
384 if (method.getAnnotation(Callback.class) != null) {
385 try {
386 Expression e = ExpressionFactory.createExpression(
387 method.getAnnotation(Callback.class).condition());
388 JexlContext jc = JexlHelper.createContext();
389 jc.getVars().put("this", obj);
390 Object r = e.evaluate(jc);
391 if (!(r instanceof Boolean)) {
392 throw new RuntimeException("Expression did not returned a boolean value but: " + r);
393 }
394 Boolean oldVal = req.getCallbacks().get(method);
395 Boolean newVal = (Boolean) r;
396 if ((oldVal == null || !oldVal) && newVal) {
397 req.getCallbacks().put(method, newVal);
398 method.invoke(obj, new Object[0]);
399 // TODO: handle return value and sent it as the answer
400 }
401 } catch (Exception e) {
402 throw new RuntimeException("Unable to invoke callback", e);
403 }
404 }
405 }
406 });
407 }
408
409 /**
410 * Used by POJOs acting as a consumer
411 * @param uri
412 * @param message
413 * @return
414 */
415 public Future<NormalizedMessage> send(String uri, NormalizedMessage message) {
416 try {
417 InOut me = getExchangeFactory().createInOutExchange();
418 URIResolver.configureExchange(me, getServiceUnit().getComponent().getComponentContext(), uri);
419 MessageUtil.transferTo(message, me, "in");
420 final Holder h = new Holder();
421 getOrCreateCurrentRequest(me).addExchange(me);
422 exchanges.put(me.getExchangeId(), h);
423 BeanEndpoint.this.send(me);
424 return h;
425 } catch (Exception e) {
426 throw new RuntimeException(e);
427 }
428 }
429
430 @Override
431 protected void send(MessageExchange me) throws MessagingException {
432 checkEndOfRequest(me);
433 super.send(me);
434 }
435
436 /*
437 * Checks if the request has ended with the given MessageExchange. It will only perform the check on non-ACTIVE exchanges
438 */
439 private void checkEndOfRequest(MessageExchange me) throws MessagingException {
440 if (!ExchangeStatus.ACTIVE.equals(me.getStatus())) {
441 Request request = getRequest(me);
442 if (request != null) {
443 checkEndOfRequest(request);
444 }
445 }
446 }
447
448 /**
449 * Checks if the request has ended. If the request has ended,
450 * <ul>
451 * <li>the request object is being removed from the list of pending requests</li>
452 * <li>if the bean was created for that request, it is now being destroyed</li>
453 * </ul>
454 *
455 * @param req the Request instance to check
456 */
457 protected void checkEndOfRequest(Request req) {
458 if (req.isFinished()) {
459 requests.remove(req.getCorrelationId());
460 if (req.getBean() != bean) {
461 ReflectionUtils.callLifecycleMethod(req.getBean(), PreDestroy.class);
462 }
463 }
464 }
465
466 /**
467 * @return the correlationExpression
468 */
469 public org.apache.servicemix.expression.Expression getCorrelationExpression() {
470 if (correlationExpression == null) {
471 // Find correlation expression
472 Correlation cor = beanType.getAnnotation(Correlation.class);
473 if (cor != null) {
474 if (cor.property() != null) {
475 correlationExpression = new PropertyExpression(cor.property());
476 } else if (cor.xpath() != null) {
477 correlationExpression = new JAXPStringXPathExpression(cor.xpath());
478 }
479 }
480 if (correlationExpression == null) {
481 correlationExpression = new org.apache.servicemix.expression.Expression() {
482 public Object evaluate(MessageExchange exchange, NormalizedMessage message)
483 throws MessagingException {
484 if (exchange.getProperty(CORRELATION_ID) != null) {
485 return exchange.getProperty(CORRELATION_ID);
486 }
487 return exchange.getExchangeId();
488 }
489 };
490 }
491 }
492 return correlationExpression;
493 }
494
495 /**
496 * @param correlationExpression the correlationExpression to set
497 */
498 public void setCorrelationExpression(org.apache.servicemix.expression.Expression correlationExpression) {
499 this.correlationExpression = correlationExpression;
500 }
501
502 protected class PojoContext implements ComponentContext {
503
504 private DeliveryChannel channel = new PojoChannel();
505
506 public ServiceEndpoint activateEndpoint(QName qName, String s) throws JBIException {
507 return getContext().activateEndpoint(qName, s);
508 }
509
510 public void deactivateEndpoint(ServiceEndpoint se) throws JBIException {
511 getContext().deactivateEndpoint(se);
512 }
513
514 public void registerExternalEndpoint(ServiceEndpoint se) throws JBIException {
515 getContext().registerExternalEndpoint(se);
516 }
517
518 public void deregisterExternalEndpoint(ServiceEndpoint se) throws JBIException {
519 getContext().deregisterExternalEndpoint(se);
520 }
521
522 public ServiceEndpoint resolveEndpointReference(DocumentFragment documentFragment) {
523 return getContext().resolveEndpointReference(documentFragment);
524 }
525
526 public String getComponentName() {
527 return getContext().getComponentName();
528 }
529
530 public DeliveryChannel getDeliveryChannel() throws MessagingException {
531 return channel;
532 }
533
534 public ServiceEndpoint getEndpoint(QName qName, String s) {
535 return getContext().getEndpoint(qName, s);
536 }
537
538 public Document getEndpointDescriptor(ServiceEndpoint se) throws JBIException {
539 return getContext().getEndpointDescriptor(se);
540 }
541
542 public ServiceEndpoint[] getEndpoints(QName qName) {
543 return getContext().getEndpoints(qName);
544 }
545
546 public ServiceEndpoint[] getEndpointsForService(QName qName) {
547 return getContext().getEndpointsForService(qName);
548 }
549
550 public ServiceEndpoint[] getExternalEndpoints(QName qName) {
551 return getContext().getExternalEndpoints(qName);
552 }
553
554 public ServiceEndpoint[] getExternalEndpointsForService(QName qName) {
555 return getContext().getExternalEndpointsForService(qName);
556 }
557
558 public String getInstallRoot() {
559 return getContext().getInstallRoot();
560 }
561
562 public Logger getLogger(String s, String s1) throws MissingResourceException, JBIException {
563 return getContext().getLogger(s, s1);
564 }
565
566 public MBeanNames getMBeanNames() {
567 return getContext().getMBeanNames();
568 }
569
570 public MBeanServer getMBeanServer() {
571 return getContext().getMBeanServer();
572 }
573
574 public InitialContext getNamingContext() {
575 return getContext().getNamingContext();
576 }
577
578 public Object getTransactionManager() {
579 return getContext().getTransactionManager();
580 }
581
582 public String getWorkspaceRoot() {
583 return getContext().getWorkspaceRoot();
584 }
585 }
586
587 protected class PojoChannel implements DeliveryChannel {
588
589 public void close() throws MessagingException {
590 }
591
592 public MessageExchangeFactory createExchangeFactory() {
593 return getChannel().createExchangeFactory();
594 }
595
596 public MessageExchangeFactory createExchangeFactory(QName qName) {
597 return getChannel().createExchangeFactory(qName);
598 }
599
600 public MessageExchangeFactory createExchangeFactoryForService(QName qName) {
601 return getChannel().createExchangeFactoryForService(qName);
602 }
603
604 public MessageExchangeFactory createExchangeFactory(ServiceEndpoint se) {
605 return getChannel().createExchangeFactory(se);
606 }
607
608 public MessageExchange accept() throws MessagingException {
609 return getChannel().accept();
610 }
611
612 public MessageExchange accept(long l) throws MessagingException {
613 return getChannel().accept(l);
614 }
615
616 public void send(MessageExchange messageExchange) throws MessagingException {
617 try {
618 Request request = getOrCreateCurrentRequest(messageExchange);
619 if (messageExchange.getRole() == MessageExchange.Role.CONSUMER
620 && messageExchange.getStatus() == ExchangeStatus.ACTIVE) {
621 Request req = getOrCreateCurrentRequest(messageExchange);
622 if (!(req.getBean() instanceof MessageExchangeListener)) {
623 throw new IllegalStateException("A bean acting as a consumer and using the channel "
624 + "to send exchanges must implement the MessageExchangeListener interface");
625 }
626 req.addExchange(messageExchange);
627 }
628 if (messageExchange.getStatus() != ExchangeStatus.ACTIVE) {
629 checkEndOfRequest(request);
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 }