View Javadoc

1   /*
2    * $Id: InvokerMessageProcessor.java 22566 2011-07-27 22:52:53Z julien.eluard $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.processor;
12  
13  import org.mule.DefaultMuleEvent;
14  import org.mule.DefaultMuleMessage;
15  import org.mule.api.AnnotatedObject;
16  import org.mule.api.MessagingException;
17  import org.mule.api.MuleContext;
18  import org.mule.api.MuleEvent;
19  import org.mule.api.MuleException;
20  import org.mule.api.MuleMessage;
21  import org.mule.api.context.MuleContextAware;
22  import org.mule.api.expression.ExpressionManager;
23  import org.mule.api.lifecycle.Initialisable;
24  import org.mule.api.lifecycle.InitialisationException;
25  import org.mule.api.processor.MessageProcessor;
26  import org.mule.api.registry.RegistrationException;
27  import org.mule.api.transformer.DataType;
28  import org.mule.api.transformer.Transformer;
29  import org.mule.api.transformer.TransformerException;
30  import org.mule.config.i18n.CoreMessages;
31  import org.mule.transformer.TransformerTemplate;
32  import org.mule.transformer.types.DataTypeFactory;
33  import org.mule.transport.NullPayload;
34  import org.mule.util.ClassUtils;
35  import org.mule.util.TemplateParser;
36  import org.mule.util.TemplateParser.PatternInfo;
37  
38  import java.lang.reflect.Method;
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.Collection;
42  import java.util.Collections;
43  import java.util.HashMap;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.Map.Entry;
47  import java.util.concurrent.ConcurrentHashMap;
48  
49  import javax.xml.namespace.QName;
50  
51  import org.apache.commons.logging.Log;
52  import org.apache.commons.logging.LogFactory;
53  
54  /**
55   * <code>InvokerMessageProcessor</code> invokes a specified method of an object. An
56   * array of argument expressions can be provided to map the message to the method
57   * arguments. The method used is determined by the method name along with the number
58   * of argument expressions provided. The results of the expression evaluations will
59   * automatically be transformed where possible to the method argument type. Multiple
60   * methods with the same name and same number of arguments are not supported
61   * currently.
62   */
63  public class InvokerMessageProcessor implements MessageProcessor, Initialisable, MuleContextAware, AnnotatedObject
64  {
65      protected final transient Log logger = LogFactory.getLog(getClass());
66  
67      protected Object object;
68      protected Class<?> objectType;
69      protected String methodName;
70      protected List<?> arguments = new ArrayList<Object>();
71      protected Class<?>[] argumentTypes;
72      protected String name;
73      protected PatternInfo patternInfo = TemplateParser.createMuleStyleParser().getStyle();
74  
75      protected Method method;
76      protected ExpressionManager expressionManager;
77      protected MuleContext muleContext;
78      private final Map<QName, Object> annotations = new ConcurrentHashMap<QName, Object>();
79  
80      @Override
81      public void initialise() throws InitialisationException
82      {
83          if (object == null)
84          {
85              lookupObjectInstance();
86          }
87  
88          resolveMethodToInvoke();
89  
90          expressionManager = muleContext.getExpressionManager();
91      }
92  
93      protected void resolveMethodToInvoke() throws InitialisationException
94      {
95          if (argumentTypes != null)
96          {
97              method = ClassUtils.getMethod(object.getClass(), methodName, argumentTypes);
98              if (method == null)
99              {
100                 throw new InitialisationException(CoreMessages.methodWithParamsNotFoundOnObject(methodName,
101                     argumentTypes, object.getClass()), this);
102             }
103         }
104         else
105         {
106             List<Method> matchingMethods = new ArrayList<Method>();
107             int argSize = arguments != null ? arguments.size() : 0;
108             for (Method methodCandidate : object.getClass().getMethods())
109             {
110                 if (methodCandidate.getName().equals(methodName)
111                     && methodCandidate.getParameterTypes().length == argSize)
112                     matchingMethods.add(methodCandidate);
113             }
114             if (matchingMethods.size() == 1)
115             {
116                 method = matchingMethods.get(0);
117                 argumentTypes = method.getParameterTypes();
118             }
119             else
120             {
121                 throw new InitialisationException(CoreMessages.methodWithNumParamsNotFoundOnObject(
122                     methodName, arguments.size(), object), this);
123             }
124         }
125 
126         if (logger.isDebugEnabled())
127         {
128             logger.debug(String.format("Initialised %s to use method: '%s'", this, method));
129         }
130     }
131 
132     protected void lookupObjectInstance() throws InitialisationException
133     {
134         if (logger.isDebugEnabled())
135         {
136             logger.debug(String.format(
137                 "No object instance speciedied.  Looking up single instance of type %s in mule registry",
138                 objectType));
139         }
140 
141         try
142         {
143             object = muleContext.getRegistry().lookupObject(objectType);
144         }
145         catch (RegistrationException e)
146         {
147             throw new InitialisationException(
148                 CoreMessages.initialisationFailure(String.format(
149                     "Muliple instances of '%s' were found in the registry so you need to configure a specific instance",
150                     objectType)), this);
151         }
152         if (object == null)
153         {
154             throw new InitialisationException(CoreMessages.initialisationFailure(String.format(
155                 "No instance of '%s' was found in the registry", objectType)), this);
156 
157         }
158     }
159 
160     @Override
161     public MuleEvent process(MuleEvent event) throws MuleException
162     {
163         MuleEvent resultEvent = event;
164         Object[] args = evaluateArguments(event, arguments);
165 
166         if (logger.isDebugEnabled())
167         {
168             logger.debug(String.format("Invoking  '%s' of '%s' with arguments: '%s'", method.getName(),
169                 object, args));
170         }
171 
172         try
173         {
174             Object result = method.invoke(object, args);
175             if (!method.getReturnType().equals(void.class))
176             {
177                 resultEvent = createResultEvent(event, result);
178             }
179         }
180         catch (Exception e)
181         {
182             throw new MessagingException(CoreMessages.failedToInvoke(object.toString()), event, e);
183         }
184         return resultEvent;
185     }
186 
187     protected Object[] evaluateArguments(MuleEvent event, List<?> argumentTemplates)
188         throws MessagingException
189     {
190         int argSize = argumentTemplates != null ? argumentTemplates.size() : 0;
191         Object[] args = new Object[argSize];
192         MuleMessage message = event.getMessage();
193         try
194         {
195             for (int i = 0; i < args.length; i++)
196             {
197                 Object argumentTemplate = argumentTemplates.get(i);
198                 if (argumentTemplate != null)
199                 {
200                     args[i] = transformArgument(evaluateExpressionCandidate(argumentTemplate, message),
201                         argumentTypes[i]);
202                 }
203             }
204             return args;
205         }
206         catch (TransformerException e)
207         {
208             throw new MessagingException(event, e);
209         }
210     }
211 
212     @SuppressWarnings("unchecked")
213     protected Object evaluateExpressionCandidate(Object expressionCandidate, MuleMessage message)
214         throws TransformerException
215     {
216         if (expressionCandidate instanceof Collection<?>)
217         {
218             Collection<Object> collectionTemplate = (Collection<Object>) expressionCandidate;
219             Collection<Object> newCollection = new ArrayList<Object>();
220             for (Object object : collectionTemplate)
221             {
222                 newCollection.add(evaluateExpressionCandidate(object, message));
223             }
224             return newCollection;
225         }
226         else if (expressionCandidate instanceof Map<?, ?>)
227         {
228             Map<Object, Object> mapTemplate = (Map<Object, Object>) expressionCandidate;
229             Map<Object, Object> newMap = new HashMap<Object, Object>();
230             for (Entry<Object, Object> entry : mapTemplate.entrySet())
231             {
232                 newMap.put(evaluateExpressionCandidate(entry.getKey(), message), evaluateExpressionCandidate(
233                     entry.getValue(), message));
234             }
235             return newMap;
236         }
237         else if (expressionCandidate instanceof String[])
238         {
239             String[] stringArrayTemplate = (String[]) expressionCandidate;
240             Object[] newArray = new String[stringArrayTemplate.length];
241             for (int j = 0; j < stringArrayTemplate.length; j++)
242             {
243                 newArray[j] = evaluateExpressionCandidate(stringArrayTemplate[j], message);
244             }
245             return newArray;
246         }
247         if (expressionCandidate instanceof String)
248         {
249             Object arg;
250             String expression = (String) expressionCandidate;
251             // If string contains is a single expression then evaluate otherwise
252             // parse. We can't use parse() always because that will convert
253             // everything to a string
254             if (expression.startsWith(patternInfo.getPrefix())
255                 && expression.endsWith(patternInfo.getSuffix()))
256             {
257                 arg = expressionManager.evaluate(expression, message);
258             }
259             else
260             {
261                 arg = expressionManager.parse(expression, message);
262             }
263 
264             // If expression evaluates to a MuleMessage then use it's payload
265             if (arg instanceof MuleMessage)
266             {
267                 arg = ((MuleMessage) arg).getPayload();
268             }
269             return arg;
270         }
271         else
272         {
273             // Not an expression so use object itself
274             return expressionCandidate;
275         }
276     }
277 
278     private Object transformArgument(Object arg, Class<?> type) throws TransformerException
279     {
280         if (!(type.isAssignableFrom(arg.getClass())))
281         {
282             DataType<?> source = DataTypeFactory.create(arg.getClass());
283             DataType<?> target = DataTypeFactory.create(type);
284             // Throws TransformerException if no suitable transformer is found
285             Transformer t = muleContext.getRegistry().lookupTransformer(source, target);
286             arg = t.transform(arg);
287         }
288         return arg;
289     }
290 
291     public void setObject(Object object)
292     {
293         this.object = object;
294     }
295 
296     public void setMethodName(String methodName)
297     {
298         this.methodName = methodName;
299     }
300 
301     public void setArgumentExpressionsString(String arguments)
302     {
303         this.arguments = Arrays.asList(arguments.split("\\s*,\\s*"));
304     }
305 
306     public void setArguments(List<?> arguments)
307     {
308         this.arguments = arguments;
309     }
310 
311     protected MuleEvent createResultEvent(MuleEvent event, Object result) throws MuleException
312     {
313         if (result instanceof MuleMessage)
314         {
315             return new DefaultMuleEvent((MuleMessage) result, event);
316         }
317         else if (result != null)
318         {
319             event.getMessage().applyTransformers(
320                 event,
321                 Collections.<Transformer> singletonList(new TransformerTemplate(
322                     new TransformerTemplate.OverwitePayloadCallback(result))));
323             return event;
324         }
325         else
326         {
327             return new DefaultMuleEvent(new DefaultMuleMessage(NullPayload.getInstance(),
328                 event.getMuleContext()), event);
329         }
330     }
331 
332     public String getName()
333     {
334         return name;
335     }
336 
337     public void setName(String name)
338     {
339         this.name = name;
340     }
341 
342     public void setArgumentTypes(Class<?>[] argumentTypes)
343     {
344         this.argumentTypes = argumentTypes;
345     }
346 
347     @Override
348     public String toString()
349     {
350         return String.format(
351             "InvokerMessageProcessor [name=%s, object=%s, methodName=%s, argExpressions=%s, argTypes=%s]",
352             name, object, methodName, arguments, argumentTypes);
353     }
354 
355     @Override
356     public void setMuleContext(MuleContext context)
357     {
358         this.muleContext = context;
359     }
360 
361     public void setObjectType(Class<?> objectType)
362     {
363         this.objectType = objectType;
364     }
365 
366     public final Object getAnnotation(QName name)
367     {
368         return annotations.get(name);
369     }
370 
371     public final Map<QName, Object> getAnnotations()
372     {
373         return Collections.unmodifiableMap(annotations);
374     }
375 
376     public synchronized final void setAnnotations(Map<QName, Object> newAnnotations)
377     {
378         annotations.clear();
379         annotations.putAll(newAnnotations);
380     }
381 }