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