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      // REST-206 / JRADEV-11989 - Map @QueryParam injections of empty collections back to null
36      // for compatibility with the Jersey 1.0.3 behaviour.  To be removed once JIRA 6.0 ships.
37      static final AtomicReference<Boolean> JERSEY_291_SHIM = new AtomicReference<Boolean>();
38  
39      private final InterceptorChainBuilder interceptorChainBuilder;
40  
41      public DispatchProviderHelper(InterceptorChainBuilder interceptorChainBuilder) {
42          this.interceptorChainBuilder = interceptorChainBuilder;
43      }
44  
45      public RequestDispatcher create(AbstractResourceMethod abstractResourceMethod, InjectableValuesProvider pp) {
46          if (pp == null) {
47              return null;
48          }
49  
50          final List<ResourceInterceptor> interceptors = interceptorChainBuilder.getResourceInterceptorsForMethod(abstractResourceMethod.getMethod());
51  
52          // TODO
53          // Strictly speaking a GET request can contain an entity in the
54          // message body, but this is likely to be not implemented by many
55          // servers and clients, but should we support it?
56          boolean requireReturnOfRepresentation =
57                  "GET".equals(abstractResourceMethod.getHttpMethod());
58  
59          Class<?> returnType = abstractResourceMethod.getMethod().getReturnType();
60          if (Response.class.isAssignableFrom(returnType)) {
61              return new ResponseOutInvoker(abstractResourceMethod, pp, interceptors);
62          } else if (returnType != void.class) {
63              if (returnType == Object.class || GenericEntity.class.isAssignableFrom(returnType)) {
64                  return new ObjectOutInvoker(abstractResourceMethod, pp, interceptors);
65              } else {
66                  return new TypeOutInvoker(abstractResourceMethod, pp, interceptors);
67              }
68          } else if (requireReturnOfRepresentation) {
69              return null;
70          } else {
71              return new VoidOutInvoker(abstractResourceMethod, pp, interceptors);
72          }
73      }
74  
75      static void invokeMethodWithInterceptors(List<ResourceInterceptor> originalInterceptors,
76                                               AbstractResourceMethod method,
77                                               Object resource,
78                                               HttpContext httpContext,
79                                               Object[] params,
80                                               final MethodInvoker methodInvocation) throws IllegalAccessException, InvocationTargetException {
81          ResourceInterceptor lastInterceptor = new ResourceInterceptor() {
82              public void intercept(MethodInvocation invocation) throws IllegalAccessException, InvocationTargetException {
83                  methodInvocation.invoke();
84              }
85          };
86  
87          List<ResourceInterceptor> interceptors = new ArrayList<ResourceInterceptor>(originalInterceptors);
88          interceptors.add(lastInterceptor);
89  
90          // REST-206 / JRADEV-11989 - Map @QueryParam injections of empty collections back to null
91          // for compatibility with the Jersey 1.0.3 behaviour.  To be removed once JIRA 6.0 ships.
92          Boolean shim = JERSEY_291_SHIM.get();
93          if (shim == null) {
94              shim = Boolean.getBoolean("com.atlassian.plugins.rest.shim.JERSEY-291");
95              JERSEY_291_SHIM.set(shim);
96          }
97          if (shim) {
98              final List<Parameter> parameterList = method.getParameters();
99              for (int i = 0; i < params.length; ++i) {
100                 if (parameterList.get(i).isAnnotationPresent(QueryParam.class)) {
101                     final Object param = params[i];
102                     if (param instanceof Collection<?> && ((Collection<?>) param).isEmpty()) {
103                         params[i] = null;
104                     }
105                 }
106             }
107         }
108 
109         MethodInvocation inv = new DefaultMethodInvocation(resource, method, httpContext, interceptors, params);
110         inv.invoke();
111     }
112 
113     private static interface MethodInvoker {
114         void invoke() throws IllegalAccessException, InvocationTargetException;
115     }
116 
117 
118     private static abstract class EntityParamInInvoker extends ResourceJavaMethodDispatcher {
119         private final InjectableValuesProvider pp;
120         final AbstractResourceMethod abstractResourceMethod;
121         final List<ResourceInterceptor> interceptors;
122 
123         EntityParamInInvoker(AbstractResourceMethod abstractResourceMethod,
124                              InjectableValuesProvider pp,
125                              List<ResourceInterceptor> interceptors) {
126             super(abstractResourceMethod, JavaMethodInvokerFactory.getDefault());
127             this.pp = pp;
128             this.abstractResourceMethod = abstractResourceMethod;
129             this.interceptors = interceptors;
130         }
131 
132         final Object[] getParams(HttpContext context) {
133             return pp.getInjectableValues(context);
134         }
135 
136     }
137 
138     private static final class VoidOutInvoker extends EntityParamInInvoker {
139         VoidOutInvoker(AbstractResourceMethod abstractResourceMethod,
140                        InjectableValuesProvider pp,
141                        List<ResourceInterceptor> interceptors) {
142             super(abstractResourceMethod, pp, interceptors);
143         }
144 
145         public void _dispatch(final Object resource, HttpContext context)
146                 throws IllegalAccessException, InvocationTargetException {
147             final Object[] params = getParams(context);
148             invokeMethodWithInterceptors(interceptors, abstractResourceMethod, resource, context, params, new MethodInvoker() {
149                 public void invoke() throws IllegalAccessException, InvocationTargetException {
150                     method.invoke(resource, params);
151                 }
152             });
153         }
154     }
155 
156     private static final class TypeOutInvoker extends EntityParamInInvoker {
157         TypeOutInvoker(AbstractResourceMethod abstractResourceMethod,
158                        InjectableValuesProvider pp,
159                        List<ResourceInterceptor> interceptors) {
160             super(abstractResourceMethod, pp, interceptors);
161         }
162 
163         public void _dispatch(final Object resource, final HttpContext context)
164                 throws IllegalAccessException, InvocationTargetException {
165             final Object[] params = getParams(context);
166 
167             invokeMethodWithInterceptors(interceptors, abstractResourceMethod, resource, context, params, new MethodInvoker() {
168                 public void invoke() throws IllegalAccessException, InvocationTargetException {
169                     Object o = method.invoke(resource, params);
170                     if (o != null) {
171                         Response r = new ResponseBuilderImpl().entity(o).status(200).build();
172                         context.getResponse().setResponse(r);
173                     }
174                 }
175             });
176         }
177     }
178 
179     private static final class ResponseOutInvoker extends EntityParamInInvoker {
180         ResponseOutInvoker(AbstractResourceMethod abstractResourceMethod,
181                            InjectableValuesProvider pp,
182                            List<ResourceInterceptor> interceptors) {
183             super(abstractResourceMethod, pp, interceptors);
184         }
185 
186         public void _dispatch(final Object resource, final HttpContext context)
187                 throws IllegalAccessException, InvocationTargetException {
188             final Object[] params = getParams(context);
189 
190             invokeMethodWithInterceptors(interceptors, abstractResourceMethod, resource, context, params, new MethodInvoker() {
191                 public void invoke() throws IllegalAccessException, InvocationTargetException {
192                     Response r = (Response) method.invoke(resource, params);
193                     if (r != null) {
194                         context.getResponse().setResponse(r);
195                     }
196                 }
197             });
198 
199         }
200     }
201 
202     private static final class ObjectOutInvoker extends EntityParamInInvoker {
203         ObjectOutInvoker(AbstractResourceMethod abstractResourceMethod,
204                          InjectableValuesProvider pp,
205                          List<ResourceInterceptor> interceptors) {
206             super(abstractResourceMethod, pp, interceptors);
207         }
208 
209         public void _dispatch(final Object resource, final HttpContext context)
210                 throws IllegalAccessException, InvocationTargetException {
211             final Object[] params = getParams(context);
212 
213             invokeMethodWithInterceptors(interceptors, abstractResourceMethod, resource, context, params, new MethodInvoker() {
214                 public void invoke() throws IllegalAccessException, InvocationTargetException {
215                     Object o = method.invoke(resource, params);
216 
217                     if (o instanceof Response) {
218                         Response r = (Response) o;
219                         context.getResponse().setResponse(r);
220                     } else if (o != null) {
221                         Response r = new ResponseBuilderImpl().status(200).entity(o).build();
222                         context.getResponse().setResponse(r);
223                     }
224                 }
225             });
226         }
227     }
228 }