View Javadoc
1   package com.atlassian.plugin.servlet;
2   
3   import com.atlassian.plugin.servlet.descriptors.BaseServletModuleDescriptor;
4   import org.apache.commons.lang3.StringUtils;
5   
6   import javax.servlet.AsyncContext;
7   import javax.servlet.ServletRequest;
8   import javax.servlet.ServletResponse;
9   import javax.servlet.http.HttpServletRequest;
10  import javax.servlet.http.HttpServletRequestWrapper;
11  import javax.servlet.http.HttpSession;
12  
13  /**
14   * A request wrapper for requests bound for servlets declared in plugins. Does the necessary path
15   * munging for requests so that they look like they are
16   * <p>
17   * Also wraps the HttpSession in order to work around the Weblogic Session Attribute serialization problem (see PLUG-515)
18   */
19  public class PluginHttpRequestWrapper extends HttpServletRequestWrapper {
20      private final String basePath;
21      private HttpServletRequest delegate;
22      private boolean asyncSupported;
23  
24      public PluginHttpRequestWrapper(HttpServletRequest request, BaseServletModuleDescriptor<?> descriptor) {
25          super(request);
26          this.delegate = request;
27          this.basePath = findBasePath(descriptor);
28          this.asyncSupported = request.isAsyncSupported() && descriptor.isAsyncSupported();
29      }
30  
31      public String getServletPath() {
32          String servletPath = super.getServletPath();
33          if (basePath != null) {
34              servletPath += basePath;
35          }
36          return servletPath;
37      }
38  
39      public String getPathInfo() {
40          String pathInfo = super.getPathInfo();
41          if (pathInfo != null && basePath != null) {
42              if (basePath.equals(pathInfo)) {
43                  return null;
44              } else if (pathInfo.startsWith(basePath)) {
45                  return pathInfo.substring(basePath.length());
46              }
47          }
48          return pathInfo;
49      }
50  
51      /**
52       * A <a href="http://bluxte.net/blog/2006-03/29-40-33.html">commenter</a> based on the
53       * <a href="http://java.sun.com/products/servlet/2.1/html/introduction.fm.html#1499">servlet mapping spec</a>
54       * defined the mapping processing as:
55       *
56       * <ol>
57       * <li>A string beginning with a '/' character and ending with a '/*' postfix is used for path mapping.</li>
58       * <li>A string beginning with a'*.' prefix is used as an extension mapping.</li>
59       * <li>A string containing only the '/' character indicates the "default" servlet of the application. In this
60       * case the servlet path is the request URI minus the context path and the path info is null.</li>
61       * <li>All other strings are used for exact matches only.</li>
62       * </ol>
63       *
64       * To find the base path we're really only interested in the first and last case. Everything else will just get a
65       * null base path. So we'll iterate through the list of paths specified and for the ones that match (1) above,
66       * check if the path info returned by the super class matches. If it does, we return that base path, otherwise we
67       * move onto the next one.
68       */
69      private String findBasePath(BaseServletModuleDescriptor<?> descriptor) {
70          String pathInfo = super.getPathInfo();
71  
72          if (pathInfo != null) {
73              // Exact match
74              for (String basePath : descriptor.getPaths()) {
75                  if (basePath.equals(pathInfo)) {
76                      return basePath;
77                  }
78              }
79  
80              // Prefix match
81              final String[] pathInfoComponents = StringUtils.split(pathInfo, '/');
82              for (String basePath : descriptor.getPaths()) {
83                  if (isPathMapping(basePath)) {
84                      final String mappingRootPath = getMappingRootPath(basePath);
85                      final String[] mappingRootPathComponents = StringUtils.split(mappingRootPath, '/');
86  
87                      if (arrayStartsWith(pathInfoComponents, mappingRootPathComponents)) {
88                          return mappingRootPath;
89                      }
90                  }
91              }
92          }
93          return null;
94      }
95  
96      private static boolean arrayStartsWith(String[] array, String[] prefixArray) {
97          // prefix array cannot be longer than the array.
98          if (prefixArray.length > array.length) {
99              return false;
100         }
101 
102         // Assume the last bit less likely to match.
103         for (int i = prefixArray.length - 1; i >= 0; i--) {
104             if (!prefixArray[i].equals(array[i])) {
105                 return false;
106             }
107         }
108 
109         return true;
110     }
111 
112     private boolean isPathMapping(String path) {
113         return path.startsWith("/") && path.endsWith("/*");
114     }
115 
116     private String getMappingRootPath(String pathMapping) {
117         return pathMapping.substring(0, pathMapping.length() - "/*".length());
118     }
119 
120     @Override
121     public HttpSession getSession() {
122         return this.getSession(true);
123     }
124 
125     @Override
126     public HttpSession getSession(final boolean create) {
127         HttpSession session = delegate.getSession(create);
128         if (session == null) {
129             // The delegate returned a null session - so do we.
130             return null;
131         } else {
132             // Wrap this non-null HttpSession
133             return session instanceof PluginHttpSessionWrapper ? session : new PluginHttpSessionWrapper(session);
134         }
135     }
136 
137     /**
138      * @since 4.6.0
139      */
140     @Override
141     public boolean isAsyncSupported() {
142         return asyncSupported;
143     }
144 
145     /**
146      * @since 4.6.0
147      */
148     @Override
149     public AsyncContext startAsync() throws IllegalStateException {
150         if (!isAsyncSupported()) {
151             throw new IllegalStateException("One of the plugins in the filter chain does not support async");
152         }
153         return super.startAsync();
154     }
155 
156     /**
157      * @since 4.6.0
158      */
159     @Override
160     public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
161         if (!isAsyncSupported()) {
162             throw new IllegalStateException("One of the plugins in the filter chain does not support async");
163         }
164         return super.startAsync(servletRequest, servletResponse);
165     }
166 }