View Javadoc

1   package com.atlassian.plugins.rest.common.interceptor.impl;
2   
3   import java.lang.reflect.InvocationTargetException;
4   import java.util.ArrayList;
5   import java.util.Collection;
6   import java.util.List;
7   import java.util.concurrent.atomic.AtomicReference;
8   
9   import javax.ws.rs.QueryParam;
10  import javax.ws.rs.core.GenericEntity;
11  import javax.ws.rs.core.Response;
12  
13  import com.atlassian.plugins.rest.common.interceptor.MethodInvocation;
14  import com.atlassian.plugins.rest.common.interceptor.ResourceInterceptor;
15  import com.sun.jersey.api.core.HttpContext;
16  import com.sun.jersey.api.model.AbstractResourceMethod;
17  import com.sun.jersey.api.model.Parameter;
18  import com.sun.jersey.core.spi.factory.ResponseBuilderImpl;
19  import com.sun.jersey.server.impl.inject.InjectableValuesProvider;
20  import com.sun.jersey.server.impl.model.method.dispatch.ResourceJavaMethodDispatcher;
21  import com.sun.jersey.spi.container.JavaMethodInvokerFactory;
22  import com.sun.jersey.spi.dispatch.RequestDispatcher;
23  
24  
25  /**
26   * Helps invoke the appropriate method, wrapping the execution in an interceptor chain.
27   *
28   * This is a private class and used by the  {@link EntityParamDispatchProviderWrapper}
29   * and {@link com.atlassian.plugins.rest.common.multipart.jersey.MultipartFormDispatchProvider}
30   * which both will use this helper class to wrap calls to rest methods with interceptors.
31   *
32   * @since 2.0
33   */
34  public class DispatchProviderHelper
35  {
36      // REST-206 / JRADEV-11989 - Map @QueryParam injections of empty collections back to null
37      // for compatibility with the Jersey 1.0.3 behaviour.  To be removed once JIRA 6.0 ships.
38      static final AtomicReference<Boolean> JERSEY_291_SHIM = new AtomicReference<Boolean>();
39  
40      private final InterceptorChainBuilder interceptorChainBuilder;
41  
42      public DispatchProviderHelper(InterceptorChainBuilder interceptorChainBuilder)
43      {
44          this.interceptorChainBuilder = interceptorChainBuilder;
45      }
46  
47      public RequestDispatcher create(AbstractResourceMethod abstractResourceMethod, InjectableValuesProvider pp)
48      {
49          if (pp == null)
50          {
51              return null;
52          }
53  
54          final List<ResourceInterceptor> interceptors = interceptorChainBuilder.getResourceInterceptorsForMethod(abstractResourceMethod.getMethod());
55  
56          // TODO
57          // Strictly speaking a GET request can contain an entity in the
58          // message body, but this is likely to be not implemented by many
59          // servers and clients, but should we support it?
60          boolean requireReturnOfRepresentation =
61                  "GET".equals(abstractResourceMethod.getHttpMethod());
62  
63          Class<?> returnType = abstractResourceMethod.getMethod().getReturnType();
64          if (Response.class.isAssignableFrom(returnType))
65          {
66              return new ResponseOutInvoker(abstractResourceMethod, pp, interceptors);
67          }
68          else if (returnType != void.class)
69          {
70              if (returnType == Object.class || GenericEntity.class.isAssignableFrom(returnType))
71              {
72                  return new ObjectOutInvoker(abstractResourceMethod, pp, interceptors);
73              }
74              else
75              {
76                  return new TypeOutInvoker(abstractResourceMethod, pp, interceptors);
77              }
78          }
79          else if (requireReturnOfRepresentation)
80          {
81              return null;
82          }
83          else
84          {
85              return new VoidOutInvoker(abstractResourceMethod, pp, interceptors);
86          }
87      }
88  
89      static void invokeMethodWithInterceptors(List<ResourceInterceptor> originalInterceptors,
90                                                       AbstractResourceMethod method,
91                                                       Object resource,
92                                                       HttpContext httpContext,
93                                                       Object[] params,
94                                                       final MethodInvoker methodInvocation) throws IllegalAccessException, InvocationTargetException
95      {
96          ResourceInterceptor lastInterceptor = new ResourceInterceptor()
97          {
98              public void intercept(MethodInvocation invocation) throws IllegalAccessException, InvocationTargetException
99              {
100                 methodInvocation.invoke();
101             }
102         };
103 
104         List<ResourceInterceptor> interceptors = new ArrayList<ResourceInterceptor>(originalInterceptors);
105         interceptors.add(lastInterceptor);
106 
107         // REST-206 / JRADEV-11989 - Map @QueryParam injections of empty collections back to null
108         // for compatibility with the Jersey 1.0.3 behaviour.  To be removed once JIRA 6.0 ships.
109         Boolean shim = JERSEY_291_SHIM.get();
110         if (shim == null)
111         {
112             shim = Boolean.getBoolean("com.atlassian.plugins.rest.shim.JERSEY-291");
113             JERSEY_291_SHIM.set(shim);
114         }
115         if (shim)
116         {
117             final List<Parameter> parameterList = method.getParameters();
118             for (int i=0; i<params.length; ++i)
119             {
120                 if (parameterList.get(i).isAnnotationPresent(QueryParam.class))
121                 {
122                     final Object param = params[i];
123                     if (param instanceof Collection<?> && ((Collection<?>)param).isEmpty())
124                     {
125                         params[i] = null;
126                     }
127                 }
128             }
129         }
130 
131         MethodInvocation inv = new DefaultMethodInvocation(resource, method, httpContext, interceptors, params);
132         inv.invoke();
133     }
134 
135     private static interface MethodInvoker
136     {
137         void invoke() throws IllegalAccessException, InvocationTargetException;
138     }
139 
140 
141     private static abstract class EntityParamInInvoker extends ResourceJavaMethodDispatcher
142     {
143         private final InjectableValuesProvider pp;
144         final AbstractResourceMethod abstractResourceMethod;
145         final List<ResourceInterceptor> interceptors;
146 
147         EntityParamInInvoker(AbstractResourceMethod abstractResourceMethod,
148                              InjectableValuesProvider pp,
149                              List<ResourceInterceptor> interceptors)
150         {
151             super(abstractResourceMethod, JavaMethodInvokerFactory.getDefault());
152             this.pp = pp;
153             this.abstractResourceMethod = abstractResourceMethod;
154             this.interceptors = interceptors;
155         }
156 
157         final Object[] getParams(HttpContext context)
158         {
159             return pp.getInjectableValues(context);
160         }
161 
162     }
163 
164     private static final class VoidOutInvoker extends EntityParamInInvoker
165     {
166         VoidOutInvoker(AbstractResourceMethod abstractResourceMethod,
167                        InjectableValuesProvider pp,
168                        List<ResourceInterceptor> interceptors)
169         {
170             super(abstractResourceMethod, pp, interceptors);
171         }
172 
173         public void _dispatch(final Object resource, HttpContext context)
174                 throws IllegalAccessException, InvocationTargetException
175         {
176             final Object[] params = getParams(context);
177             invokeMethodWithInterceptors(interceptors, abstractResourceMethod, resource, context, params, new MethodInvoker()
178             {
179                 public void invoke() throws IllegalAccessException, InvocationTargetException
180                 {
181                     method.invoke(resource, params);
182                 }
183             });
184         }
185     }
186 
187     private static final class TypeOutInvoker extends EntityParamInInvoker
188     {
189         TypeOutInvoker(AbstractResourceMethod abstractResourceMethod,
190                        InjectableValuesProvider pp,
191                        List<ResourceInterceptor> interceptors)
192         {
193             super(abstractResourceMethod, pp, interceptors);
194         }
195 
196         public void _dispatch(final Object resource, final HttpContext context)
197                 throws IllegalAccessException, InvocationTargetException
198         {
199             final Object[] params = getParams(context);
200 
201             invokeMethodWithInterceptors(interceptors, abstractResourceMethod, resource, context, params, new MethodInvoker()
202             {
203                 public void invoke() throws IllegalAccessException, InvocationTargetException
204                 {
205                     Object o = method.invoke(resource, params);
206                     if (o != null)
207                     {
208                         Response r = new ResponseBuilderImpl().entity(o).status(200).build();
209                         context.getResponse().setResponse(r);
210                     }
211                 }
212             });
213         }
214     }
215 
216     private static final class ResponseOutInvoker extends EntityParamInInvoker
217     {
218         ResponseOutInvoker(AbstractResourceMethod abstractResourceMethod,
219                            InjectableValuesProvider pp,
220                            List<ResourceInterceptor> interceptors)
221         {
222             super(abstractResourceMethod, pp, interceptors);
223         }
224 
225         public void _dispatch(final Object resource, final HttpContext context)
226                 throws IllegalAccessException, InvocationTargetException
227         {
228             final Object[] params = getParams(context);
229 
230             invokeMethodWithInterceptors(interceptors, abstractResourceMethod, resource, context, params, new MethodInvoker()
231             {
232                 public void invoke() throws IllegalAccessException, InvocationTargetException
233                 {
234                     Response r = (Response) method.invoke(resource, params);
235                     if (r != null)
236                     {
237                         context.getResponse().setResponse(r);
238                     }
239                 }
240             });
241 
242         }
243     }
244 
245     private static final class ObjectOutInvoker extends EntityParamInInvoker
246     {
247         ObjectOutInvoker(AbstractResourceMethod abstractResourceMethod,
248                          InjectableValuesProvider pp,
249                          List<ResourceInterceptor> interceptors)
250         {
251             super(abstractResourceMethod, pp, interceptors);
252         }
253 
254         public void _dispatch(final Object resource, final HttpContext context)
255                 throws IllegalAccessException, InvocationTargetException
256         {
257             final Object[] params = getParams(context);
258 
259             invokeMethodWithInterceptors(interceptors, abstractResourceMethod, resource, context, params, new MethodInvoker()
260             {
261                 public void invoke() throws IllegalAccessException, InvocationTargetException
262                 {
263                     Object o = method.invoke(resource, params);
264 
265                     if (o instanceof Response)
266                     {
267                         Response r = (Response) o;
268                         context.getResponse().setResponse(r);
269                     }
270                     else if (o != null)
271                     {
272                         Response r = new ResponseBuilderImpl().status(200).entity(o).build();
273                         context.getResponse().setResponse(r);
274                     }
275                 }
276             });
277         }
278     }
279 }