1    /*
2     * Copyright 2009 :torweg free software group
3     *
4     * This program is free software: you can redistribute it and/or modify
5     * it under the terms of the GNU General Public License as published by
6     * the Free Software Foundation, either version 3 of the License, or
7     * (at your option) any later version.
8     * 
9     * This program is distributed in the hope that it will be useful,
10    * but WITHOUT ANY WARRANTY; without even the implied warranty of
11    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    * GNU General Public License for more details.
13    * 
14    * You should have received a copy of the GNU General Public License
15    * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16    *
17    */
18   package org.torweg.pulse.service;
19   
20   import java.io.IOException;
21   import java.net.URI;
22   import java.net.URISyntaxException;
23   
24   import javax.servlet.FilterChain;
25   import javax.servlet.FilterConfig;
26   import javax.servlet.RequestDispatcher;
27   import javax.servlet.ServletException;
28   import javax.servlet.ServletRequest;
29   import javax.servlet.ServletResponse;
30   import javax.servlet.http.HttpServletRequest;
31   
32   import org.torweg.pulse.invocation.lifecycle.Lifecycle;
33   
34   /**
35    * filters the version prefix from the request URI to allow far future expires
36    * headers for static resources.
37    * <p>
38    * see also: <tt>xsl/globals.xsl ($version.number)</tt>.
39    * </p>
40    * <p>
41    * If an URI is not rewritten, the rest of the {@code FilterChain} is processed.
42    * Since {@code FilterChain}s are only constructed upon first request, filtering
43    * for rewritten URIs is aborted.
44    * </p>
45    * <p>
46    * If you need to post process rewritten URIs, consider overwriting
47    * {@link #dispatch(ServletRequest, ServletResponse, RequestRewriter)}.
48    * </p>
49    * 
50    * @author Thomas Weber
51    * @version $Revision: 1825 $
52    */
53   public class VersionRewriteFilter extends AbstractPulseFilter {
54   
55       /**
56        * @see javax.servlet.Filter#destroy()
57        */
58       public void destroy() {
59           return;
60       }
61   
62       /**
63        * does the URL rewriting.
64        * 
65        * @param servletRequest
66        *            the current servlet request
67        * @param servletResponse
68        *            the current servlet response
69        * @param chain
70        *            the filter chain
71        * @throws ServletException
72        *             on errors while filtering
73        * @throws IOException
74        *             on errors while filtering
75        * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
76        *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
77        */
78       public final void doFilter(final ServletRequest servletRequest,
79               final ServletResponse servletResponse, final FilterChain chain)
80               throws IOException, ServletException {
81           StringBuilder bs = new StringBuilder(
82                   ((HttpServletRequest) servletRequest).getContextPath())
83                   .append('/');
84           if (bs.charAt(0) != '/') {
85               bs.insert(0, '/');
86           }
87           RequestRewriter requestRewriter = new RequestRewriter(servletRequest,
88                   bs.toString());
89   
90           if (requestRewriter.isRewritten()) {
91               dispatch(servletRequest, servletResponse, requestRewriter);
92           } else {
93               // continue the filter chain...
94               chain.doFilter(servletRequest, servletResponse);
95           }
96       }
97   
98       /**
99        * actually takes care of the dispatching to the rewritten URI.
100       * <p>
101       * This method can be overwritten by subclassing {@code Filter}s.
102       * </p>
103       * <p>
104       * The original code of this method is no more than:<br/>
105       * {@code requestRewriter.getRequestDispatcher().forward(servletRequest,
106       * servletResponse);}
107       * </p>
108       * 
109       * @param servletRequest
110       *            the current servlet request
111       * @param servletResponse
112       *            the current servlet response
113       * @param requestRewriter
114       *            the current rewrite request
115       * @throws ServletException
116       *             on errors while dispatching
117       * @throws IOException
118       *             on errors while dispatching
119       */
120      protected void dispatch(final ServletRequest servletRequest,
121              final ServletResponse servletResponse,
122              final RequestRewriter requestRewriter) throws ServletException,
123              IOException {
124          // dispatch to rewritten URL
125          requestRewriter.getRequestDispatcher().forward(servletRequest,
126                  servletResponse);
127      }
128  
129      /**
130       * initialises the filter.
131       * 
132       * @param conf
133       *            the filter config (which is ignored)
134       */
135      public void init(final FilterConfig conf) {
136          return;
137      }
138  
139      /* ---------------- inner class ----------------------------- */
140  
141      /**
142       * Utility to rewrite a request.
143       */
144      public static final class RequestRewriter {
145  
146          /**
147           * the base URI.
148           */
149          private final String baseURI;
150  
151          /**
152           * the rewritten flag.
153           */
154          private final boolean rewritten;
155  
156          /**
157           * the dispatch URI.
158           */
159          private String dispatchURI = null;
160  
161          /**
162           * the URI of the {@code RequestRewriter}.
163           */
164          private URI uri;
165  
166          /**
167           * the original servlet request.
168           */
169          private final ServletRequest request;
170  
171          /**
172           * creates a new rewritten request.
173           * 
174           * @param req
175           *            the wrapped request
176           * @param base
177           *            the base URI
178           */
179          public RequestRewriter(final ServletRequest req, final String base) {
180              super();
181              this.baseURI = base;
182              this.request = req;
183              String originalRequestURI;
184              try {
185                  originalRequestURI = ((HttpServletRequest) req).getRequestURI();
186              } catch (Exception e) {
187                  originalRequestURI = (String) req
188                          .getAttribute("javax.servlet.include.request_uri");
189              }
190              try {
191                  URI requestURI = new URI(originalRequestURI);
192                  if (requestURI.getRawPath().startsWith(
193                          this.baseURI + Lifecycle.getVersioningPrefix())) {
194                      this.rewritten = true;
195                      rewrite(requestURI);
196                  } else {
197                      this.rewritten = false;
198                      this.uri = new URI(originalRequestURI);
199                  }
200              } catch (URISyntaxException e) {
201                  throw new PulseException(e);
202              }
203          }
204  
205          /**
206           * does the actual rewriting.
207           * 
208           * @param sourceURI
209           *            the source URI to be rewritten
210           */
211          private void rewrite(final URI sourceURI) {
212              StringBuilder rewrittenURI = new StringBuilder();
213              StringBuilder dispatchBuilder = new StringBuilder();
214              if (sourceURI.getScheme() != null) {
215                  rewrittenURI.append(sourceURI.getScheme()).append(':');
216              }
217              if (sourceURI.getRawAuthority() != null) {
218                  rewrittenURI.append("//").append(sourceURI.getRawAuthority());
219              }
220              if (sourceURI.getRawPath() != null) {
221                  String rawPath = sourceURI.getRawPath()
222                          .replaceFirst(
223                                  this.baseURI + Lifecycle.getVersioningPrefix()
224                                          + ".*?/", this.baseURI);
225                  rewrittenURI.append(rawPath);
226                  dispatchBuilder.append(rawPath
227                          .substring(this.baseURI.length() - 1));
228              }
229              if (sourceURI.getRawQuery() != null) {
230                  rewrittenURI.append('?').append(sourceURI.getRawQuery());
231                  dispatchBuilder.append('?').append(sourceURI.getRawQuery());
232              }
233              if (sourceURI.getRawFragment() != null) {
234                  rewrittenURI.append('#').append(sourceURI.getRawFragment());
235                  dispatchBuilder.append('#').append(sourceURI.getRawFragment());
236              }
237              this.dispatchURI = dispatchBuilder.toString();
238              try {
239                  this.uri = new URI(rewrittenURI.toString());
240              } catch (URISyntaxException e) {
241                  throw new PulseException(e);
242              }
243          }
244  
245          /**
246           * returns the request dispatcher for the rewritten URI.
247           * 
248           * @return the request dispatcher
249           */
250          public RequestDispatcher getRequestDispatcher() {
251              return this.request.getRequestDispatcher(this.dispatchURI);
252          }
253  
254          /**
255           * returns whether the URI of the request has actually been rewritten.
256           * 
257           * @return {@code true}, if and only if the URI of the request has been
258           *         rewritten. Otherwise {@code false}.
259           */
260          public boolean isRewritten() {
261              return this.rewritten;
262          }
263  
264          /**
265           * returns the rewritten URI.
266           * 
267           * @return the rewritten URI
268           */
269          public URI getRewrittenURI() {
270              return this.uri;
271          }
272  
273      }
274  
275  }
276