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 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 // Find or create the request for this provider exchange
211 } else if (exchange.getRole() == MessageExchange.Role.PROVIDER) {
212 onProviderExchange(exchange);
213 } else {
214 throw new IllegalStateException("Unknown role: " + exchange.getRole());
215 }
216 }
217
218 protected void onProviderExchange(MessageExchange exchange) throws Exception {
219 Object corId = getCorrelation(exchange);
220 Request req = getOrCreateCurrentRequest(exchange);
221 currentRequest.set(req);
222 synchronized (req) {
223 // If the bean implements MessageExchangeListener,
224 // just call the method
225 if (req.getBean() instanceof MessageExchangeListener) {
226 ((MessageExchangeListener) req.getBean()).onMessageExchange(exchange);
227 } else {
228 // Exchange is finished
229 if (exchange.getStatus() == ExchangeStatus.DONE) {
230 return;
231 // Exchange has been aborted with an exception
232 } else if (exchange.getStatus() == ExchangeStatus.ERROR) {
233 return;
234 // Fault message
235 } else if (exchange.getFault() != null) {
236 // TODO: find a way to send it back to the bean before setting the DONE status
237 done(exchange);
238 } else {
239 MethodInvocation invocation = getMethodInvocationStrategy().createInvocation(
240 req.getBean(), getBeanInfo(), exchange, this);
241 if (invocation == null) {
242 throw new UnknownMessageExchangeTypeException(exchange, this);
243 }
244 try {
245 invocation.proceed();
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: handle MEP correctly (DONE should only be sent for InOnly)
256 done(exchange);
257 }
258 }
259 }
260 checkEndOfRequest(req, corId);
261 currentRequest.set(null);
262 }
263 }
264
265 protected Request getOrCreateCurrentRequest(MessageExchange exchange) throws ClassNotFoundException, InstantiationException, IllegalAccessException, MessagingException {
266 Object corId = getCorrelation(exchange);
267 Request req = requests.get(corId);
268 if (req == null) {
269 Object pojo = getBean();
270 if (pojo == null) {
271 pojo = createBean();
272 injectBean(pojo);
273 ReflectionUtils.callLifecycleMethod(pojo, PostConstruct.class);
274 }
275 req = new Request(pojo, exchange);
276 requests.put(corId, req);
277 }
278 return req;
279 }
280
281 protected void onConsumerExchange(MessageExchange exchange) throws Exception {
282 Object corId = exchange.getExchangeId();
283 Request req = requests.remove(corId);
284 if (req == null) {
285 throw new IllegalStateException("Receiving unknown consumer exchange: " + exchange);
286 }
287 currentRequest.set(req);
288 // If the bean implements MessageExchangeListener,
289 // just call the method
290 if (req.getBean() instanceof MessageExchangeListener) {
291 ((MessageExchangeListener) req.getBean()).onMessageExchange(exchange);
292 } else {
293 Holder me = exchanges.get(exchange.getExchangeId());
294 if (me == null) {
295 throw new IllegalStateException("Consumer exchange not found");
296 }
297 me.set(exchange);
298 evaluateCallbacks(req);
299 }
300 checkEndOfRequest(req, corId);
301 currentRequest.set(null);
302 }
303
304 protected Object getCorrelation(MessageExchange exchange) throws MessagingException {
305 return getCorrelationExpression().evaluate(exchange, exchange.getMessage("in"));
306 }
307
308 protected Object createBean() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
309 if (beanType == null && beanClassName != null) {
310 beanType = Class.forName(beanClassName, true, getServiceUnit().getConfigurationClassLoader());
311 }
312 if (beanName == null && beanType == null) {
313 throw new IllegalArgumentException("Property 'bean', 'beanName' or 'beanClassName' has not been set!");
314 }
315 if (beanType != null) {
316 return beanType.newInstance();
317 } else if (beanName == null) {
318 throw new IllegalArgumentException("Property 'beanName', 'beanType' or 'beanClassName' must be set!");
319 } else if (applicationContext == null) {
320 throw new IllegalArgumentException("Property 'beanName' specified, but no BeanFactory set!");
321 } else {
322 Object answer = applicationContext.getBean(beanName);
323 if (answer == null) {
324 throw new NoSuchBeanException(beanName, this);
325 }
326 return answer;
327 }
328 }
329
330 protected MethodInvocationStrategy createMethodInvocationStrategy() {
331 DefaultMethodInvocationStrategy st = new DefaultMethodInvocationStrategy();
332 st.loadDefaultRegistry();
333 return st;
334 }
335
336 /**
337 * A strategy method to allow implementations to perform some custom JBI based injection of the POJO
338 *
339 * @param target the bean to be injected
340 */
341 protected void injectBean(final Object target) {
342 final PojoContext ctx = new PojoContext();
343 final DeliveryChannel ch = ctx.channel;
344 // Inject fields
345 ReflectionUtils.doWithFields(target.getClass(), new ReflectionUtils.FieldCallback() {
346 public void doWith(Field f) throws IllegalArgumentException, IllegalAccessException {
347 ExchangeTarget et = f.getAnnotation(ExchangeTarget.class);
348 if (et != null) {
349 ReflectionUtils.setField(f, target, new DestinationImpl(et.uri(), BeanEndpoint.this));
350 }
351 if (f.getAnnotation(Resource.class) != null) {
352 if (ComponentContext.class.isAssignableFrom(f.getType())) {
353 ReflectionUtils.setField(f, target, ctx);
354 } else if (DeliveryChannel.class.isAssignableFrom(f.getType())) {
355 ReflectionUtils.setField(f, target, ch);
356 } else if (ServiceEndpoint.class.isAssignableFrom(f.getType())) {
357 ReflectionUtils.setField(f, target, BeanEndpoint.this.serviceEndpoint);
358 }
359 }
360 }
361 });
362 }
363
364 protected void evaluateCallbacks(final Request req) {
365 final Object obj = req.getBean();
366 ReflectionUtils.doWithMethods(obj.getClass(), new ReflectionUtils.MethodCallback() {
367 @SuppressWarnings("unchecked")
368 public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
369 if (method.getAnnotation(Callback.class) != null) {
370 try {
371 Expression e = ExpressionFactory.createExpression(
372 method.getAnnotation(Callback.class).condition());
373 JexlContext jc = JexlHelper.createContext();
374 jc.getVars().put("this", obj);
375 Object r = e.evaluate(jc);
376 if (!(r instanceof Boolean)) {
377 throw new RuntimeException("Expression did not returned a boolean value but: " + r);
378 }
379 Boolean oldVal = req.getCallbacks().get(method);
380 Boolean newVal = (Boolean) r;
381 if ((oldVal == null || !oldVal) && newVal) {
382 req.getCallbacks().put(method, newVal);
383 method.invoke(obj, new Object[0]);
384 // TODO: handle return value and sent it as the answer
385 }
386 } catch (Exception e) {
387 throw new RuntimeException("Unable to invoke callback", e);
388 }
389 }
390 }
391 });
392 }
393
394 /**
395 * Used by POJOs acting as a consumer
396 * @param uri
397 * @param message
398 * @return
399 */
400 public Future<NormalizedMessage> send(String uri, NormalizedMessage message) {
401 try {
402 InOut me = getExchangeFactory().createInOutExchange();
403 URIResolver.configureExchange(me, getServiceUnit().getComponent().getComponentContext(), uri);
404 MessageUtil.transferTo(message, me, "in");
405 final Holder h = new Holder();
406 requests.put(me.getExchangeId(), currentRequest.get());
407 exchanges.put(me.getExchangeId(), h);
408 BeanEndpoint.this.send(me);
409 return h;
410 } catch (Exception e) {
411 throw new RuntimeException(e);
412 }
413 }
414
415 protected void checkEndOfRequest(Request request, Object corId) {
416 if (request.getExchange().getStatus() != ExchangeStatus.ACTIVE) {
417 Object beanFromRequest = request.getBean();
418 if (beanFromRequest != bean) {
419 ReflectionUtils.callLifecycleMethod(beanFromRequest, PreDestroy.class);
420 }
421 //request.setBean(null);
422 //request.setExchange(null);
423 requests.remove(corId);
424 }
425 }
426
427 /**
428 * @return the correlationExpression
429 */
430 public org.apache.servicemix.expression.Expression getCorrelationExpression() {
431 if (correlationExpression == null) {
432 // Find correlation expression
433 Correlation cor = beanType.getAnnotation(Correlation.class);
434 if (cor != null) {
435 if (cor.property() != null) {
436 correlationExpression = new PropertyExpression(cor.property());
437 } else if (cor.xpath() != null) {
438 correlationExpression = new JAXPStringXPathExpression(cor.xpath());
439 }
440 }
441 if (correlationExpression == null) {
442 correlationExpression = new org.apache.servicemix.expression.Expression() {
443 public Object evaluate(MessageExchange exchange, NormalizedMessage message)
444 throws MessagingException {
445 return exchange.getExchangeId();
446 }
447 };
448 }
449 }
450 return correlationExpression;
451 }
452
453 /**
454 * @param correlationExpression the correlationExpression to set
455 */
456 public void setCorrelationExpression(org.apache.servicemix.expression.Expression correlationExpression) {
457 this.correlationExpression = correlationExpression;
458 }
459
460 protected class PojoContext implements ComponentContext {
461
462 private DeliveryChannel channel = new PojoChannel();
463
464 public ServiceEndpoint activateEndpoint(QName qName, String s) throws JBIException {
465 return getContext().activateEndpoint(qName, s);
466 }
467
468 public void deactivateEndpoint(ServiceEndpoint serviceEndpoint) throws JBIException {
469 getContext().deactivateEndpoint(serviceEndpoint);
470 }
471
472 public void registerExternalEndpoint(ServiceEndpoint serviceEndpoint) throws JBIException {
473 getContext().registerExternalEndpoint(serviceEndpoint);
474 }
475
476 public void deregisterExternalEndpoint(ServiceEndpoint serviceEndpoint) throws JBIException {
477 getContext().deregisterExternalEndpoint(serviceEndpoint);
478 }
479
480 public ServiceEndpoint resolveEndpointReference(DocumentFragment documentFragment) {
481 return getContext().resolveEndpointReference(documentFragment);
482 }
483
484 public String getComponentName() {
485 return getContext().getComponentName();
486 }
487
488 public DeliveryChannel getDeliveryChannel() throws MessagingException {
489 return channel;
490 }
491
492 public ServiceEndpoint getEndpoint(QName qName, String s) {
493 return getContext().getEndpoint(qName, s);
494 }
495
496 public Document getEndpointDescriptor(ServiceEndpoint serviceEndpoint) throws JBIException {
497 return getContext().getEndpointDescriptor(serviceEndpoint);
498 }
499
500 public ServiceEndpoint[] getEndpoints(QName qName) {
501 return getContext().getEndpoints(qName);
502 }
503
504 public ServiceEndpoint[] getEndpointsForService(QName qName) {
505 return getContext().getEndpointsForService(qName);
506 }
507
508 public ServiceEndpoint[] getExternalEndpoints(QName qName) {
509 return getContext().getExternalEndpoints(qName);
510 }
511
512 public ServiceEndpoint[] getExternalEndpointsForService(QName qName) {
513 return getContext().getExternalEndpointsForService(qName);
514 }
515
516 public String getInstallRoot() {
517 return getContext().getInstallRoot();
518 }
519
520 public Logger getLogger(String s, String s1) throws MissingResourceException, JBIException {
521 return getContext().getLogger(s, s1);
522 }
523
524 public MBeanNames getMBeanNames() {
525 return getContext().getMBeanNames();
526 }
527
528 public MBeanServer getMBeanServer() {
529 return getContext().getMBeanServer();
530 }
531
532 public InitialContext getNamingContext() {
533 return getContext().getNamingContext();
534 }
535
536 public Object getTransactionManager() {
537 return getContext().getTransactionManager();
538 }
539
540 public String getWorkspaceRoot() {
541 return getContext().getWorkspaceRoot();
542 }
543 }
544
545 protected class PojoChannel implements DeliveryChannel {
546
547 public void close() throws MessagingException {
548 }
549
550 public MessageExchangeFactory createExchangeFactory() {
551 return getChannel().createExchangeFactory();
552 }
553
554 public MessageExchangeFactory createExchangeFactory(QName qName) {
555 return getChannel().createExchangeFactory(qName);
556 }
557
558 public MessageExchangeFactory createExchangeFactoryForService(QName qName) {
559 return getChannel().createExchangeFactoryForService(qName);
560 }
561
562 public MessageExchangeFactory createExchangeFactory(ServiceEndpoint serviceEndpoint) {
563 return getChannel().createExchangeFactory(serviceEndpoint);
564 }
565
566 public MessageExchange accept() throws MessagingException {
567 return getChannel().accept();
568 }
569
570 public MessageExchange accept(long l) throws MessagingException {
571 return getChannel().accept(l);
572 }
573
574 public void send(MessageExchange messageExchange) throws MessagingException {
575 try {
576 if (messageExchange.getRole() == MessageExchange.Role.CONSUMER
577 && messageExchange.getStatus() == ExchangeStatus.ACTIVE) {
578 Request req = getOrCreateCurrentRequest(messageExchange);
579 if (!(req.getBean() instanceof MessageExchangeListener)) {
580 throw new IllegalStateException("A bean acting as a consumer and using the channel to send exchanges must implement the MessageExchangeListener interface");
581 }
582 }
583 getChannel().send(messageExchange);
584 } catch (MessagingException e) {
585 throw e;
586 } catch (Exception e) {
587 throw new MessagingException(e);
588 }
589 }
590
591 public boolean sendSync(MessageExchange messageExchange) throws MessagingException {
592 return getChannel().sendSync(messageExchange);
593 }
594
595 public boolean sendSync(MessageExchange messageExchange, long l) throws MessagingException {
596 return getChannel().sendSync(messageExchange, l);
597 }
598
599 }
600 }