1   package com.atlassian.plugins.rest.module;
2   
3   import com.atlassian.plugin.osgi.factory.OsgiPlugin;
4   import com.atlassian.plugins.rest.common.expand.jersey.ExpandResponseFilter;
5   import com.atlassian.plugins.rest.common.expand.resolver.ChainingEntityExpanderResolver;
6   import com.atlassian.plugins.rest.common.expand.resolver.CollectionEntityExpanderResolver;
7   import com.atlassian.plugins.rest.common.expand.resolver.EntityExpanderResolver;
8   import com.atlassian.plugins.rest.common.expand.resolver.ExpandConstraintEntityExpanderResolver;
9   import com.atlassian.plugins.rest.common.expand.resolver.ListWrapperEntityExpanderResolver;
10  import com.atlassian.plugins.rest.common.expand.resolver.IdentityEntityExpanderResolver;
11  import com.atlassian.plugins.rest.module.expand.resolver.OsgiPluginEntityExpanderResolver;
12  import com.atlassian.plugins.rest.module.filter.ExtensionJerseyFilter;
13  import com.atlassian.plugins.rest.module.filter.AcceptHeaderJerseyMvcFilter;
14  import com.atlassian.plugins.rest.module.json.JsonWithPaddingResponseFilter;
15  import com.google.common.base.Preconditions;
16  import com.google.common.collect.Lists;
17  import com.sun.jersey.api.core.ResourceConfig;
18  import com.sun.jersey.spi.container.ResourceFilterFactory;
19  import com.sun.jersey.spi.container.WebApplication;
20  import com.sun.jersey.spi.container.servlet.ServletContainer;
21  import com.sun.jersey.spi.container.servlet.WebConfig;
22  import com.sun.jersey.spi.inject.InjectableProvider;
23  import com.sun.jersey.spi.template.TemplateProcessor;
24  import org.apache.commons.lang.StringUtils;
25  import org.osgi.framework.BundleContext;
26  import org.osgi.framework.ServiceReference;
27  
28  import javax.servlet.Filter;
29  import javax.servlet.FilterChain;
30  import javax.servlet.FilterConfig;
31  import javax.servlet.ServletException;
32  import javax.servlet.ServletRequest;
33  import javax.servlet.ServletResponse;
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpServletResponse;
36  import javax.ws.rs.core.UriBuilder;
37  import java.io.IOException;
38  import java.net.URI;
39  import java.util.Arrays;
40  import java.util.Collection;
41  import java.util.Collections;
42  import java.util.Map;
43  
44  /**
45   * A delegating servlet that swaps the context class loader with a {@link ChainingClassLoader} that delegates to both
46   * the class loader of the {@link RestModuleDescriptor} and the current context class loader.
47   */
48  class RestDelegatingServletFilter implements Filter
49  {
50      /**
51       * Overriden servlet container to use our own {@link OsgiResourceConfig}.
52       */
53      private final ServletContainer servletContainer;
54  
55      private OsgiServiceAccessor<ResourceFilterFactory> resourceFilterFactories;
56      private OsgiServiceAccessor<InjectableProvider> injectableProviders;
57      private OsgiServiceAccessor<TemplateProcessor> templateProcessors;
58  
59      RestDelegatingServletFilter(OsgiPlugin plugin, RestApiContext restContextPath)
60      {
61          final BundleContext bundleContext = Preconditions.checkNotNull(plugin).getBundle().getBundleContext();
62  
63          // looking up resource filters
64          this.resourceFilterFactories = new OsgiServiceAccessor<ResourceFilterFactory>(ResourceFilterFactory.class, bundleContext, new OsgiFactory<ResourceFilterFactory>()
65          {
66              public ResourceFilterFactory getInstance(BundleContext bundleContext, ServiceReference serviceReference)
67              {
68                  return new OsgiServiceReferenceResourceFilterFactory(bundleContext, serviceReference);
69              }
70          });
71  
72          // looking up injectable providers
73          this.injectableProviders = new OsgiServiceAccessor<InjectableProvider>(InjectableProvider.class, bundleContext, new OsgiFactory<InjectableProvider>()
74          {
75              public InjectableProvider getInstance(BundleContext bundleContext, ServiceReference serviceReference)
76              {
77                  return (InjectableProvider) bundleContext.getService(serviceReference);
78              }
79          });
80  
81          this.templateProcessors = new OsgiServiceAccessor<TemplateProcessor>(TemplateProcessor.class, bundleContext, new OsgiFactory<TemplateProcessor>()
82          {
83              public TemplateProcessor getInstance(BundleContext bundleContext, ServiceReference serviceReference)
84              {
85                  return (TemplateProcessor) bundleContext.getService(serviceReference);
86              }
87          });
88  
89          this.servletContainer = new JerseyOsgiServletContainer(plugin, restContextPath, resourceFilterFactories, injectableProviders, templateProcessors);
90      }
91  
92      public void init(FilterConfig config) throws ServletException
93      {
94          final ClassLoader currentThreadClassLoader = Thread.currentThread().getContextClassLoader();
95          try
96          {
97              Thread.currentThread().setContextClassLoader(new ChainingClassLoader(RestModuleDescriptor.class.getClassLoader(), currentThreadClassLoader));
98              servletContainer.init(config);
99          }
100         finally
101         {
102             Thread.currentThread().setContextClassLoader(currentThreadClassLoader);
103         }
104     }
105 
106     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
107     {
108         final ClassLoader currentThreadClassLoader = Thread.currentThread().getContextClassLoader();
109         try
110         {
111             Thread.currentThread().setContextClassLoader(new ChainingClassLoader(RestModuleDescriptor.class.getClassLoader(), currentThreadClassLoader));
112             servletContainer.doFilter(request, response, chain);
113         }
114         finally
115         {
116             Thread.currentThread().setContextClassLoader(currentThreadClassLoader);
117         }
118     }
119 
120     public void destroy()
121     {
122         servletContainer.destroy();
123         resourceFilterFactories.release();
124         injectableProviders.release();
125         templateProcessors.release();
126     }
127 
128     /**
129      *
130      */
131     private static class JerseyOsgiServletContainer extends ServletContainer
132     {
133         private final OsgiPlugin plugin;
134         private final RestApiContext restApiContext;
135         private final OsgiServiceAccessor<ResourceFilterFactory> resourceFilterFactories;
136         private final OsgiServiceAccessor<InjectableProvider> injectableProviders;
137         private final OsgiServiceAccessor<TemplateProcessor> templateProcessors;
138 
139         public JerseyOsgiServletContainer(OsgiPlugin plugin, RestApiContext restApiContext,
140                                           OsgiServiceAccessor<ResourceFilterFactory> resourceFilterFactories,
141                                           OsgiServiceAccessor<InjectableProvider> injectableProviders,
142                                           OsgiServiceAccessor<TemplateProcessor> templateProcessors)
143         {
144             this.plugin = Preconditions.checkNotNull(plugin);
145             this.restApiContext = Preconditions.checkNotNull(restApiContext);
146             this.resourceFilterFactories = Preconditions.checkNotNull(resourceFilterFactories);
147             this.injectableProviders = Preconditions.checkNotNull(injectableProviders);
148             this.templateProcessors = Preconditions.checkNotNull(templateProcessors);
149         }
150 
151         @Override
152         protected ResourceConfig getDefaultResourceConfig(Map<String, Object> props, WebConfig webConfig) throws ServletException
153         {
154             // get the excludes parameter
155             final String[] excludes = StringUtils.split(webConfig.getInitParameter(ExtensionJerseyFilter.class.getName() + "#excludes"), " ,;");
156             final Collection<String> excludesCollection = excludes != null ? Arrays.asList(excludes) : Collections.<String>emptyList();
157 
158             final EntityExpanderResolver expanderResolver = new ChainingEntityExpanderResolver(Lists.<EntityExpanderResolver>newArrayList(
159                     new OsgiPluginEntityExpanderResolver(plugin),
160                     new CollectionEntityExpanderResolver(),
161                     new ListWrapperEntityExpanderResolver(),
162                     new ExpandConstraintEntityExpanderResolver(),
163                     new IdentityEntityExpanderResolver()
164             ));
165 
166             final Collection<Object> providers = Lists.newLinkedList();
167             providers.addAll(injectableProviders.get());
168             providers.addAll(templateProcessors.get());
169 
170             return new OsgiResourceConfig(plugin.getBundle(),
171                     Lists.newArrayList(new ExtensionJerseyFilter(excludesCollection), new AcceptHeaderJerseyMvcFilter()), // request filters
172                     Lists.newArrayList(new JsonWithPaddingResponseFilter(), new ExpandResponseFilter(expanderResolver)), // response filters
173                     resourceFilterFactories.get(), // resource filters
174                     providers); // provider instances
175         }
176 
177         @Override
178         public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
179             throws IOException, ServletException
180         {
181             // This is overridden so that we can change the base URI, and keep the context path the same
182             String baseUriPath;
183             if (request.getServletPath().startsWith(restApiContext.getPathToLatest()))
184             {
185                 baseUriPath = request.getContextPath() + restApiContext.getPathToLatest();
186             }
187             else
188             {
189                 baseUriPath = request.getContextPath() + restApiContext.getPathToVersion();
190             }
191             final UriBuilder absoluteUriBuilder = UriBuilder.fromUri(request.getRequestURL().toString());
192 
193             final URI baseUri = absoluteUriBuilder.replacePath(baseUriPath).path("/").build();
194 
195             final URI requestUri = absoluteUriBuilder.replacePath(request.getRequestURI()).replaceQuery(
196                 request.getQueryString()).build();
197 
198             service(baseUri, requestUri, request, response);
199         }
200 
201         @Override
202         protected void initiate(ResourceConfig resourceConfig, WebApplication webApplication)
203         {
204             webApplication.initiate(resourceConfig, new OsgiComponentProviderFactory(resourceConfig, plugin));
205         }
206     }
207 }