View Javadoc

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