View Javadoc

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