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.concurrent.ConcurrentHashMap;
023    import java.util.concurrent.Future;
024    
025    import javax.annotation.PostConstruct;
026    import javax.annotation.PreDestroy;
027    import javax.annotation.Resource;
028    import javax.jbi.component.ComponentContext;
029    import javax.jbi.messaging.DeliveryChannel;
030    import javax.jbi.messaging.ExchangeStatus;
031    import javax.jbi.messaging.InOut;
032    import javax.jbi.messaging.MessageExchange;
033    import javax.jbi.messaging.MessageExchange.Role;
034    import javax.jbi.messaging.MessagingException;
035    import javax.jbi.messaging.NormalizedMessage;
036    import javax.jbi.servicedesc.ServiceEndpoint;
037    
038    import org.aopalliance.intercept.MethodInvocation;
039    import org.apache.commons.jexl.Expression;
040    import org.apache.commons.jexl.ExpressionFactory;
041    import org.apache.commons.jexl.JexlContext;
042    import org.apache.commons.jexl.JexlHelper;
043    import org.apache.servicemix.MessageExchangeListener;
044    import org.apache.servicemix.bean.support.BeanInfo;
045    import org.apache.servicemix.bean.support.DefaultMethodInvocationStrategy;
046    import org.apache.servicemix.bean.support.DestinationImpl;
047    import org.apache.servicemix.bean.support.Holder;
048    import org.apache.servicemix.bean.support.MethodInvocationStrategy;
049    import org.apache.servicemix.bean.support.ReflectionUtils;
050    import org.apache.servicemix.bean.support.Request;
051    import org.apache.servicemix.common.EndpointComponentContext;
052    import org.apache.servicemix.common.endpoints.ProviderEndpoint;
053    import org.apache.servicemix.expression.JAXPStringXPathExpression;
054    import org.apache.servicemix.expression.PropertyExpression;
055    import org.apache.servicemix.jbi.resolver.URIResolver;
056    import org.apache.servicemix.jbi.util.MessageUtil;
057    import org.springframework.beans.BeansException;
058    import org.springframework.context.ApplicationContext;
059    import org.springframework.context.ApplicationContextAware;
060    
061    /**
062     * Represents a bean endpoint which consists of a together with a {@link MethodInvocationStrategy}
063     * so that JBI message exchanges can be invoked on the bean.
064     *
065     * @version $Revision: $
066     * @org.apache.xbean.XBean element="endpoint"
067     */
068    public class BeanEndpoint extends ProviderEndpoint implements ApplicationContextAware {
069    
070        private ApplicationContext applicationContext;
071        private String beanName;
072        private Object bean;
073        private BeanInfo beanInfo;
074        private Class<?> beanType;
075        private String beanClassName;
076        private MethodInvocationStrategy methodInvocationStrategy;
077        private org.apache.servicemix.expression.Expression correlationExpression;
078        
079        private Map<String, Holder> exchanges = new ConcurrentHashMap<String, Holder>();
080        private Map<Object, Request> requests = new ConcurrentHashMap<Object, Request>();
081        private ThreadLocal<Request> currentRequest = new ThreadLocal<Request>();
082        private ComponentContext context;
083        private DeliveryChannel channel;
084    
085        public BeanEndpoint() {
086        }
087    
088        public BeanEndpoint(BeanComponent component, ServiceEndpoint serviceEndpoint) {
089            super(component, serviceEndpoint);
090            this.applicationContext = component.getApplicationContext();
091        }
092    
093        public void start() throws Exception {
094            super.start();
095            context = new EndpointComponentContext(this);
096            channel = context.getDeliveryChannel();
097            Object pojo = getBean();
098            if (pojo != null) {
099                injectBean(pojo);
100                ReflectionUtils.callLifecycleMethod(pojo, PostConstruct.class);
101            }
102            beanType = pojo != null ? pojo.getClass() : createBean().getClass();
103            if (getMethodInvocationStrategy() == null) {
104                throw new IllegalArgumentException("No 'methodInvocationStrategy' property set");
105            }
106        }
107    
108    
109        public void stop() throws Exception {
110            super.stop();
111            Object pojo = getBean();
112            if (pojo != null) {
113                ReflectionUtils.callLifecycleMethod(pojo, PreDestroy.class);
114            }
115        }
116    
117    
118        public ApplicationContext getApplicationContext() {
119            return applicationContext;
120        }
121    
122        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
123            this.applicationContext = applicationContext;
124        }
125    
126        public String getBeanName() {
127            return beanName;
128        }
129    
130        public void setBeanName(String beanName) {
131            this.beanName = beanName;
132        }
133    
134        public Object getBean() {
135            return bean;
136        }
137    
138        public void setBean(Object bean) {
139            this.bean = bean;
140        }
141    
142        /**
143         * @return the beanType
144         */
145        public Class<?> getBeanType() {
146            return beanType;
147        }
148    
149        /**
150         * @param beanType the beanType to set
151         */
152        public void setBeanType(Class<?> beanType) {
153            this.beanType = beanType;
154        }
155    
156        /**
157         * @return the beanClassName
158         */
159        public String getBeanClassName() {
160            return beanClassName;
161        }
162    
163        /**
164         * @param beanClassName the beanClassName to set
165         */
166        public void setBeanClassName(String beanClassName) {
167            this.beanClassName = beanClassName;
168        }
169    
170        public BeanInfo getBeanInfo() {
171            if (beanInfo == null) {
172                beanInfo = new BeanInfo(beanType, getMethodInvocationStrategy());
173                beanInfo.introspect();
174            }
175            return beanInfo;
176        }
177    
178        public void setBeanInfo(BeanInfo beanInfo) {
179            this.beanInfo = beanInfo;
180        }
181    
182        public MethodInvocationStrategy getMethodInvocationStrategy() {
183            if (methodInvocationStrategy == null) {
184                methodInvocationStrategy = createMethodInvocationStrategy();
185            }
186            return methodInvocationStrategy;
187        }
188    
189        public void setMethodInvocationStrategy(MethodInvocationStrategy methodInvocationStrategy) {
190            this.methodInvocationStrategy = methodInvocationStrategy;
191        }
192    
193    
194        @Override
195        public void process(MessageExchange exchange) throws Exception {
196            if (exchange.getRole() == Role.CONSUMER) {
197                onConsumerExchange(exchange);
198            // Find or create the request for this provider exchange
199            } else if (exchange.getRole() == MessageExchange.Role.PROVIDER) {
200                onProviderExchange(exchange);
201            } else {
202                throw new IllegalStateException("Unknown role: " + exchange.getRole());
203            }
204        }
205        
206        protected void onProviderExchange(MessageExchange exchange) throws Exception {
207            Object corId = getCorrelation(exchange);
208            Request req = requests.get(corId);
209            if (req == null) {
210                Object pojo = getBean();
211                if (pojo == null) {
212                    pojo = createBean();
213                    injectBean(pojo);
214                    ReflectionUtils.callLifecycleMethod(pojo, PostConstruct.class);
215                }
216                req = new Request(pojo, exchange);
217                requests.put(corId, req);
218            }
219            currentRequest.set(req);
220            synchronized (req) {
221                // If the bean implements MessageExchangeListener,
222                // just call the method
223                if (req.getBean() instanceof MessageExchangeListener) {
224                    ((MessageExchangeListener) req.getBean()).onMessageExchange(exchange);
225                } else {
226                    // Exchange is finished
227                    if (exchange.getStatus() == ExchangeStatus.DONE) {
228                        return;
229                    // Exchange has been aborted with an exception
230                    } else if (exchange.getStatus() == ExchangeStatus.ERROR) {
231                        return;
232                    // Fault message
233                    } else if (exchange.getFault() != null) {
234                        // TODO: find a way to send it back to the bean before setting the DONE status
235                        done(exchange);
236                    } else {
237                        MethodInvocation invocation = getMethodInvocationStrategy().createInvocation(
238                                req.getBean(), getBeanInfo(), exchange, this);
239                        if (invocation == null) {
240                            throw new UnknownMessageExchangeTypeException(exchange, this);
241                        }
242                        try {
243                            invocation.proceed();
244                        } catch (Exception e) {
245                            throw e;
246                        } catch (Throwable throwable) {
247                            throw new MethodInvocationFailedException(req.getBean(), invocation, exchange, this, throwable);
248                        }
249                        if (exchange.getStatus() == ExchangeStatus.ERROR) {
250                            send(exchange);
251                        }
252                        if (exchange.getFault() == null && exchange.getMessage("out") == null)  {
253                            // TODO: handle MEP correctly (DONE should only be sent for InOnly)
254                            done(exchange);
255                        }
256                    }
257                }
258                checkEndOfRequest(req, corId);
259                currentRequest.set(null);
260            }
261        }
262        
263        protected void onConsumerExchange(MessageExchange exchange) throws Exception {
264            Object corId = exchange.getExchangeId();
265            Request req = requests.remove(corId);
266            if (req == null) {
267                throw new IllegalStateException("Receiving unknown consumer exchange: " + exchange);
268            }
269            currentRequest.set(req);
270            // If the bean implements MessageExchangeListener,
271            // just call the method
272            if (req.getBean() instanceof MessageExchangeListener) {
273                ((MessageExchangeListener) req.getBean()).onMessageExchange(exchange);
274            } else {
275                Holder me = exchanges.get(exchange.getExchangeId());
276                if (me == null) {
277                    throw new IllegalStateException("Consumer exchange not found");
278                }
279                me.set(exchange);
280                evaluateCallbacks(req);
281            }
282            checkEndOfRequest(req, corId);
283            currentRequest.set(null);
284        }
285        
286        protected Object getCorrelation(MessageExchange exchange) throws MessagingException {
287            return getCorrelationExpression().evaluate(exchange, exchange.getMessage("in"));
288        }
289    
290        protected Object createBean() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
291            if (beanName == null && beanType == null) {
292                throw new IllegalArgumentException("Property 'beanName' has not been set!");
293            }
294            if (beanType == null && beanClassName != null) {
295                beanType = Class.forName(beanClassName, true, getServiceUnit().getConfigurationClassLoader());
296            }
297            if (beanType != null) {
298                return beanType.newInstance();
299            } else if (beanName == null) {
300                throw new IllegalArgumentException("Property 'beanName', 'beanType' or 'beanClassName' must be set!");
301            } else if (applicationContext == null) {
302                throw new IllegalArgumentException("Property 'beanName' specified, but no BeanFactory set!");
303            } else {
304                Object answer = applicationContext.getBean(beanName);
305                if (answer == null) {
306                    throw new NoSuchBeanException(beanName, this);
307                }
308                return answer;
309            }
310        }
311    
312        protected MethodInvocationStrategy createMethodInvocationStrategy() {
313            DefaultMethodInvocationStrategy st = new DefaultMethodInvocationStrategy();
314            st.loadDefaultRegistry();
315            return st;
316        }
317    
318        /**
319         * A strategy method to allow implementations to perform some custom JBI based injection of the POJO
320         *
321         * @param target the bean to be injected
322         */
323        protected void injectBean(final Object target) {
324            // Inject fields
325            ReflectionUtils.doWithFields(target.getClass(), new ReflectionUtils.FieldCallback() {
326                public void doWith(Field f) throws IllegalArgumentException, IllegalAccessException {
327                    ExchangeTarget et = f.getAnnotation(ExchangeTarget.class);
328                    if (et != null) {
329                        ReflectionUtils.setField(f, target, new DestinationImpl(et.uri(), BeanEndpoint.this));
330                    }
331                    if (f.getAnnotation(Resource.class) != null) {
332                        if (ComponentContext.class.isAssignableFrom(f.getType())) {
333                            ReflectionUtils.setField(f, target, context);
334                        } else if (DeliveryChannel.class.isAssignableFrom(f.getType())) {
335                            ReflectionUtils.setField(f, target, channel);
336                        }
337                    }
338                }
339            });
340        }
341        
342        protected void evaluateCallbacks(final Request req) {
343            final Object obj = req.getBean();
344            ReflectionUtils.doWithMethods(obj.getClass(), new ReflectionUtils.MethodCallback() {
345                @SuppressWarnings("unchecked")
346                public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
347                    if (method.getAnnotation(Callback.class) != null) {
348                        try {
349                            Expression e = ExpressionFactory.createExpression(
350                                    method.getAnnotation(Callback.class).condition());
351                            JexlContext jc = JexlHelper.createContext();
352                            jc.getVars().put("this", obj);
353                            Object r = e.evaluate(jc);
354                            if (!(r instanceof Boolean)) {
355                                throw new RuntimeException("Expression did not returned a boolean value but: " + r);
356                            }
357                            Boolean oldVal = req.getCallbacks().get(method);
358                            Boolean newVal = (Boolean) r;
359                            if ((oldVal == null || !oldVal) && newVal) {
360                                req.getCallbacks().put(method, newVal);
361                                method.invoke(obj, new Object[0]);
362                                // TODO: handle return value and sent it as the answer
363                            }
364                        } catch (Exception e) {
365                            throw new RuntimeException("Unable to invoke callback", e);
366                        }
367                    }
368                }
369            });
370        }
371        
372        /**
373         * Used by POJOs acting as a consumer
374         * @param uri
375         * @param message
376         * @return
377         */
378        public Future<NormalizedMessage> send(String uri, NormalizedMessage message) {
379            try {
380                InOut me = getExchangeFactory().createInOutExchange();
381                URIResolver.configureExchange(me, getServiceUnit().getComponent().getComponentContext(), uri);
382                MessageUtil.transferTo(message, me, "in");
383                final Holder h = new Holder(); 
384                requests.put(me.getExchangeId(), currentRequest.get());
385                exchanges.put(me.getExchangeId(), h);
386                BeanEndpoint.this.send(me);
387                return h;
388            } catch (Exception e) {
389                throw new RuntimeException(e);
390            }
391        }
392        
393        protected void checkEndOfRequest(Request request, Object corId) {
394            if (request.getExchange().getStatus() != ExchangeStatus.ACTIVE) {
395                ReflectionUtils.callLifecycleMethod(request.getBean(), PreDestroy.class);
396                //request.setBean(null);
397                //request.setExchange(null);
398                requests.remove(corId);
399            }
400        }
401    
402        /**
403         * @return the correlationExpression
404         */
405        public org.apache.servicemix.expression.Expression getCorrelationExpression() {
406            if (correlationExpression == null) {
407                // Find correlation expression
408                Correlation cor = beanType.getAnnotation(Correlation.class);
409                if (cor != null) {
410                    if (cor.property() != null) {
411                        correlationExpression = new PropertyExpression(cor.property());
412                    } else if (cor.xpath() != null) {
413                        correlationExpression = new JAXPStringXPathExpression(cor.xpath());
414                    }
415                }
416                if (correlationExpression == null) {
417                    correlationExpression = new org.apache.servicemix.expression.Expression() {
418                        public Object evaluate(MessageExchange exchange, NormalizedMessage message) 
419                            throws MessagingException {
420                            return exchange.getExchangeId();
421                        }
422                    };
423                }
424            }
425            return correlationExpression;
426        }
427    
428        /**
429         * @param correlationExpression the correlationExpression to set
430         */
431        public void setCorrelationExpression(org.apache.servicemix.expression.Expression correlationExpression) {
432            this.correlationExpression = correlationExpression;
433        }
434    }