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