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    }