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