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 }