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