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