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