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