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