View Javadoc

1   package com.atlassian.plugins.rest.module;
2   
3   import com.atlassian.plugin.osgi.factory.OsgiPlugin;
4   import com.google.common.base.Preconditions;
5   import com.sun.jersey.api.core.ResourceConfig;
6   import com.sun.jersey.spi.container.WebApplication;
7   import com.sun.jersey.spi.container.servlet.ServletContainer;
8   import com.sun.jersey.spi.container.servlet.WebConfig;
9   import org.apache.commons.lang.StringUtils;
10  
11  import javax.servlet.Filter;
12  import javax.servlet.FilterChain;
13  import javax.servlet.FilterConfig;
14  import javax.servlet.ServletException;
15  import javax.servlet.ServletRequest;
16  import javax.servlet.ServletResponse;
17  import javax.servlet.http.HttpServletRequest;
18  import javax.servlet.http.HttpServletResponse;
19  import javax.ws.rs.core.UriBuilder;
20  import java.io.IOException;
21  import java.net.URI;
22  import java.util.Map;
23  
24  /**
25   * A delegating servlet that swaps the context class loader with a {@link ChainingClassLoader} that delegates to both
26   * the class loader of the {@link RestModuleDescriptor} and the current context class loader.
27   */
28  class RestDelegatingServletFilter implements Filter
29  {
30      /**
31       * Helper object for installing and un-installing the SLF4J bridge.
32       */
33      private static final Slf4jBridge.Helper SLF4J_BRIDGE = Slf4jBridge.createHelper();
34  
35      /**
36       * Overriden servlet container to use our own {@link OsgiResourceConfig}.
37       */
38      private final ServletContainer servletContainer;
39      private final ResourceConfigManager resourceConfigManager;
40  
41      /**
42       * This class loader is set as the thread context class loader whenever we are calling into Jersey. We need to reuse
43       * the same class loader every time because when this filter is initialised it sets up the JUL->SLF4J bridge (the
44       * same one that allows ).
45       *
46       * The thread context class loader (TCCL) is set to this class loader when calling into Jersey. We keep a reference
47       * to it as opposed to creating a new one in every invocation because Jersey uses java.util.logging, which on
48       * Tomcat is implemented by JULI.
49       */
50      private volatile ClassLoader chainingClassLoader;
51  
52      RestDelegatingServletFilter(OsgiPlugin plugin, RestApiContext restContextPath)
53      {
54          resourceConfigManager = new ResourceConfigManager(plugin, plugin.getBundle());
55          this.servletContainer = new JerseyOsgiServletContainer(plugin, restContextPath, resourceConfigManager);
56      }
57  
58      public void init(FilterConfig config) throws ServletException
59      {
60          initChainingClassLoader();
61          initServletContainer(config);
62      }
63  
64      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
65      {
66          ClassLoader currentThreadClassLoader = Thread.currentThread().getContextClassLoader();
67          Thread.currentThread().setContextClassLoader(chainingClassLoader);
68          try
69          {
70              servletContainer.doFilter(request, response, chain);
71          }
72          finally
73          {
74              Thread.currentThread().setContextClassLoader(currentThreadClassLoader);
75          }
76      }
77  
78      public void destroy()
79      {
80          destroyServletContainer();
81          resourceConfigManager.destroy();
82      }
83  
84      private void initChainingClassLoader()
85      {
86          chainingClassLoader = new ChainingClassLoader(RestModuleDescriptor.class.getClassLoader(), Thread.currentThread().getContextClassLoader());
87      }
88  
89      private void initServletContainer(FilterConfig config) throws ServletException
90      {
91          ClassLoader currentThreadClassLoader = Thread.currentThread().getContextClassLoader();
92          Thread.currentThread().setContextClassLoader(chainingClassLoader);
93          try
94          {
95              SLF4J_BRIDGE.install();
96              servletContainer.init(config);
97          }
98          finally
99          {
100             Thread.currentThread().setContextClassLoader(currentThreadClassLoader);
101         }
102     }
103 
104     private void destroyServletContainer()
105     {
106         ClassLoader currentThreadClassLoader = Thread.currentThread().getContextClassLoader();
107         Thread.currentThread().setContextClassLoader(chainingClassLoader);
108         try
109     {
110         servletContainer.destroy();
111             SLF4J_BRIDGE.uninstall();
112         }
113         finally
114         {
115             Thread.currentThread().setContextClassLoader(currentThreadClassLoader);
116         }
117     }
118 
119     /**
120      *
121      */
122     private static class JerseyOsgiServletContainer extends ServletContainer
123     {
124         private final OsgiPlugin plugin;
125         private final RestApiContext restApiContext;
126         private final ResourceConfigManager resourceConfigManager;
127 
128         private static final String PARAM_EXTENSION_FILTER_EXCLUDES = "extension.filter.excludes";
129 
130         public JerseyOsgiServletContainer(OsgiPlugin plugin, RestApiContext restApiContext, final ResourceConfigManager resourceConfigManager)
131         {
132             this.resourceConfigManager = Preconditions.checkNotNull(resourceConfigManager);
133             this.plugin = Preconditions.checkNotNull(plugin);
134             this.restApiContext = Preconditions.checkNotNull(restApiContext);
135         }
136 
137         @Override
138         protected ResourceConfig getDefaultResourceConfig(Map<String, Object> props, WebConfig webConfig) throws ServletException
139         {
140             // get the excludes parameter: support the old param name for Atlassian Gadgets
141             final String deprecatedName = "com.atlassian.plugins.rest.module.filter.ExtensionJerseyFilter" + "#excludes";
142             final String excludeParam = webConfig.getInitParameter(deprecatedName) != null ?
143                     webConfig.getInitParameter(deprecatedName) : webConfig.getInitParameter(PARAM_EXTENSION_FILTER_EXCLUDES);
144 
145             final String[] excludes = StringUtils.split(excludeParam, " ,;");
146 
147             return resourceConfigManager.createResourceConfig(props, excludes, restApiContext.getPackages());
148         }
149 
150         @Override
151         public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
152                 throws IOException, ServletException
153         {
154             // This is overridden so that we can change the base URI, and keep the context path the same
155             String baseUriPath;
156             if (request.getRequestURI().contains(restApiContext.getPathToLatest()))
157             {
158                  baseUriPath = request.getContextPath() + restApiContext.getPathToLatest();
159             }
160             else
161             {
162                 baseUriPath = request.getContextPath() + restApiContext.getPathToVersion();
163             }
164             final UriBuilder absoluteUriBuilder = UriBuilder.fromUri(request.getRequestURL().toString());
165 
166             final URI baseUri = absoluteUriBuilder.replacePath(baseUriPath).path("/").build();
167 
168             final URI requestUri = absoluteUriBuilder.replacePath(request.getRequestURI()).replaceQuery(
169                     request.getQueryString()).build();
170 
171             service(baseUri, requestUri, request, response);
172         }
173 
174         @Override
175         protected void initiate(ResourceConfig resourceConfig, WebApplication webApplication)
176         {
177             webApplication.initiate(resourceConfig, new OsgiComponentProviderFactory(resourceConfig, plugin));
178         }
179     }
180 }