View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.impl.model.resolvers;
8   
9   import org.mule.api.MuleEventContext;
10  import org.mule.api.annotations.meta.Evaluator;
11  import org.mule.api.config.MuleProperties;
12  import org.mule.api.lifecycle.InitialisationException;
13  import org.mule.api.model.InvocationResult;
14  import org.mule.api.transformer.Transformer;
15  import org.mule.api.transformer.TransformerException;
16  import org.mule.api.transport.PropertyScope;
17  import org.mule.config.expression.ExpressionAnnotationsHelper;
18  import org.mule.config.i18n.CoreMessages;
19  import org.mule.expression.transformers.ExpressionTransformer;
20  import org.mule.model.resolvers.AbstractEntryPointResolver;
21  import org.mule.transport.NullPayload;
22  import org.mule.util.ClassUtils;
23  import org.mule.util.annotation.AnnotationUtils;
24  
25  import java.lang.reflect.Method;
26  import java.util.Map;
27  
28  import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
29  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
30  
31  import net.sf.cglib.proxy.Enhancer;
32  
33  /**
34   * A Mule {@link org.mule.api.model.EntryPointResolver} implementation that will resolve methods on a service class
35   * that have Mule expression annotations such as {@link org.mule.api.annotations.param.Payload}, {@link org.mule.api.annotations.param.InboundHeaders}
36   * It will transform the received message to match the annotated method arguments. For example -
37   * <code>
38   * public Object doSomething(
39   *         &#64;XPath ("/foo/bar") String bar,
40   *         &#64;Payload Document doc,
41   *         &#64;InboundHeaders("name") String name)
42   *  {
43   *      //do stuff
44   *  }
45   * </code>
46   *
47   * The component method above will be invoked by running the XPath expression on the payload, adding a second parameter as
48   * the payload itself and passing in the header 'name' as the third parameter.
49   *
50   * There are some rules for how components with annotations will be processed -
51   * <ul>
52   * <li>For components with more than one method annotated with Mule expression annotations the method name to call needs
53   * to be set on the incoming message or endpoint using the {@link org.mule.api.config.MuleProperties#MULE_METHOD_PROPERTY} key.</li>
54   * <li>If the component has only one method annotated with Mule expression annotations there is no need to set the method name to invoke</li>
55   * <li>Every parameter on the method needs to be annotated</li>
56   * </ul>
57   *
58   * @see org.mule.api.annotations.param.Payload
59   * @see org.mule.api.annotations.param.InboundHeaders
60   * @see org.mule.api.annotations.param.InboundAttachments
61   * @see org.mule.api.annotations.param.OutboundHeaders
62   * @see org.mule.api.annotations.param.OutboundAttachments
63   * @see org.mule.api.annotations.expressions.Mule
64   *
65   * @since 3.0
66   *
67   */
68  public class AnnotatedEntryPointResolver extends AbstractEntryPointResolver
69  {
70  
71      private AtomicBoolean cacheBuilt = new AtomicBoolean(false);
72  
73  
74      @SuppressWarnings("unchecked")
75      private Map<Method, Transformer> transformerCache = new ConcurrentHashMap();
76  
77      public InvocationResult invoke(Object component, MuleEventContext context) throws Exception
78      {
79          try
80          {
81              initCache(component, context);
82          }
83          catch (Exception e)
84          {
85              InvocationResult result = new InvocationResult(this, InvocationResult.State.NOT_SUPPORTED);
86              result.setErrorMessage(e.toString());
87              return result;
88          }
89  
90          ConcurrentHashMap methodCache = getMethodCache(component);
91          if(methodCache.size()==0)
92          {
93              InvocationResult result = new InvocationResult(this, InvocationResult.State.NOT_SUPPORTED);
94              result.setErrorMessage("Component: " + component + " doesn't have any annotated methods, skipping.");
95              return result;
96          }
97  
98          Object[] payload;
99          Method method = null;
100         //We remove the property here as a workaround to MULE-4769
101         Object tempMethod = context.getMessage().removeProperty(MuleProperties.MULE_METHOD_PROPERTY, PropertyScope.INVOCATION);
102         String methodName = null;
103         if (tempMethod != null && tempMethod instanceof Method)
104         {
105             method = (Method) tempMethod;
106         }
107         else
108         {
109             methodName = (String)tempMethod;
110         }
111 
112         //If a method param is set use that over anything else. This is used by the @Reply Callbacks and where annotations are set
113         //on the method
114         if (methodName != null)
115         {
116             method = getMethodByName(component, methodName, context);
117             if (method == null)
118             {
119                 InvocationResult result = new InvocationResult(this, InvocationResult.State.NOT_SUPPORTED);
120                 result.setErrorMessage("Method not found: " + methodName + " on object: " + component.getClass() + ". If the component is a proxy there needs to be an interface on the proxy that defines this method");
121                 return result;
122                 //TODO i18n
123             }
124             payload = getPayloadForMethod(method, component, context);
125         }
126         else if (method != null)
127         {
128             payload = getPayloadForMethod(method, component, context);
129         }
130         else if (methodCache.size() == 1)
131         {
132             method = (Method) methodCache.values().iterator().next();
133             payload = getPayloadForMethod(method, component, context);
134         }
135         else
136         {
137             InvocationResult result = new InvocationResult(this, InvocationResult.State.FAILED);
138             result.setErrorMessage("Component: " + component + " has more than one method annotated, which means the target method name needs to be set on the event");
139             return result;
140         }
141         return invokeMethod(component, method,
142                 (method.getParameterTypes().length == 0 ? ClassUtils.NO_ARGS : payload));
143     }
144 
145     protected Object[] getPayloadForMethod(Method method, Object component, MuleEventContext context) throws TransformerException, InitialisationException
146     {
147         Object[] payload = null;
148         Method m = method;
149         //If we are using cglib enhanced service objects, we need to read annotations from the real component class
150         if (Enhancer.isEnhanced(component.getClass()))
151         {
152             try
153             {
154                 m = component.getClass().getSuperclass().getMethod(method.getName(), method.getParameterTypes());
155             }
156             catch (NoSuchMethodException e)
157             {
158                 throw new TransformerException(CoreMessages.createStaticMessage(e.getMessage()), e);
159             }
160         }
161 
162         if (AnnotationUtils.getParamAnnotationsWithMeta(m, Evaluator.class).size() > 0)
163         {
164             payload = getPayloadFromMessageWithAnnotations(m, context);
165         }
166         return payload;
167     }
168 
169     protected Object[] getPayloadFromMessageWithAnnotations(Method method, MuleEventContext context) throws TransformerException, InitialisationException
170     {
171         ExpressionTransformer trans = (ExpressionTransformer) transformerCache.get(method);
172         if (trans == null)
173         {
174             trans = ExpressionAnnotationsHelper.getTransformerForMethodWithAnnotations(method, context.getMuleContext());
175             transformerCache.put(method, trans);
176         }
177 
178         Object o = trans.transform(context.getMessage());
179         if (o instanceof NullPayload)
180         {
181             return new Object[]{null};
182         }
183         else if (o.getClass().isArray())
184         {
185             return (Object[]) o;
186         }
187         else
188         {
189             return new Object[]{o};
190         }
191     }
192 
193     @Override
194     public String toString()
195     {
196         final StringBuffer sb = new StringBuffer();
197         sb.append("AnnotatedEntryPointResolver");
198         sb.append(", acceptVoidMethods=").append(isAcceptVoidMethods());
199         sb.append('}');
200         return sb.toString();
201     }
202 
203 
204     protected void initCache(Object component, MuleEventContext context)
205     {
206         if (!cacheBuilt.get())
207         {
208             synchronized (this)
209             {
210                 try
211                 {
212                     if (!cacheBuilt.get())
213                     {
214                         for (int i = 0; i < component.getClass().getMethods().length; i++)
215                         {
216                             Method method = component.getClass().getMethods()[i];
217                             if (AnnotationUtils.getParamAnnotationsWithMeta(method, Evaluator.class).size() > 0)
218                             {
219                                 this.addMethodByName(component, method, context);
220                             }
221                         }
222                     }
223                 }
224                 finally
225                 {
226                     cacheBuilt.set(true);
227                 }
228             }
229         }
230     }
231 }