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