1   package com.atlassian.plugins.rest.common.filter;
2   
3   import com.sun.jersey.spi.container.ContainerRequest;
4   import com.sun.jersey.spi.container.ContainerRequestFilter;
5   import org.apache.commons.lang.StringUtils;
6   import org.apache.commons.lang.Validate;
7   
8   import javax.ws.rs.core.HttpHeaders;
9   import static javax.ws.rs.core.MediaType.*;
10  import javax.ws.rs.core.UriBuilder;
11  import javax.ws.rs.ext.Provider;
12  import java.net.URI;
13  import java.util.Collection;
14  import java.util.HashMap;
15  import java.util.LinkedList;
16  import java.util.List;
17  import java.util.Map;
18  import java.util.regex.Pattern;
19  
20  /**
21   * <p>A filter to handle URI with extenstions. It will set the correct accept header for each extension and remove the extension
22   * from the URI to allow for normal processing of the request.</p>
23   * <p>Currently supported extension and their matching headers are defined in {@link #EXTENSION_TO_ACCEPT_HEADER the extension to header mapping}.</p>
24   * <p>One can exclude URIs from being processed by this filter. Simply create the filter with a collection of {@link Pattern patterns} to be excluded.</p>
25   * <p><strong>Example:</strong> URI <code>http://localhost:8080/app/rest/my/resource.json</code> would be automatically mapped to URI <code>http://localhost:8080/app/rest/my/resource</code>
26   * with its <code>accept</code> header set to <code>application/json</code></p>
27   */
28  @Provider
29  public class ExtensionJerseyFilter implements ContainerRequestFilter
30  {
31      private static final String DOT = ".";
32  
33      private final Collection<Pattern> pathExcludePatterns;
34  
35      public ExtensionJerseyFilter()
36      {
37          pathExcludePatterns = new LinkedList<Pattern>();
38      }
39  
40      public ExtensionJerseyFilter(Collection<String> pathExcludePatterns)
41      {
42          Validate.notNull(pathExcludePatterns);
43          this.pathExcludePatterns = compilePatterns(pathExcludePatterns);
44      }
45  
46      final static Map<String, String> EXTENSION_TO_ACCEPT_HEADER = new HashMap<String, String>()
47      {{
48              put("txt", TEXT_PLAIN);
49              put("htm", TEXT_HTML);
50              put("html", TEXT_HTML);
51              put("json", APPLICATION_JSON);
52              put("xml", APPLICATION_XML);
53              put("atom", APPLICATION_ATOM_XML);
54          }};
55  
56      public ContainerRequest filter(ContainerRequest request)
57      {
58          // the path to the request without query params
59          final String absolutePath = request.getAbsolutePath().toString();
60  
61          final String extension = StringUtils.substringAfterLast(absolutePath, DOT);
62          if (shouldFilter("/" + StringUtils.difference(request.getBaseUri().toString(), absolutePath), extension))
63          {
64              request.getRequestHeaders().putSingle(HttpHeaders.ACCEPT, EXTENSION_TO_ACCEPT_HEADER.get(extension));
65              final String absolutePathWithoutExtension = StringUtils.substringBeforeLast(absolutePath, DOT);
66              request.setUris(request.getBaseUri(), getRequestUri(absolutePathWithoutExtension, request.getQueryParameters()));
67          }
68          return request;
69      }
70  
71      private boolean shouldFilter(String restPath, String extension)
72      {
73          for (Pattern pattern : pathExcludePatterns)
74          {
75              if (pattern.matcher(restPath).matches())
76              {
77                  return false;
78              }
79          }
80          return EXTENSION_TO_ACCEPT_HEADER.containsKey(extension);
81      }
82  
83      private URI getRequestUri(String absolutePathWithoutExtension, Map<String, List<String>> queryParams)
84      {
85          final UriBuilder requestUriBuilder = UriBuilder.fromPath(absolutePathWithoutExtension);
86          for (Map.Entry<String, List<String>> queryParamEntry : queryParams.entrySet())
87          {
88              for (String value : queryParamEntry.getValue())
89              {
90                  requestUriBuilder.queryParam(queryParamEntry.getKey(), value);
91              }
92          }
93          return requestUriBuilder.build();
94      }
95  
96      private Collection<Pattern> compilePatterns(Collection<String> pathExcludePatterns)
97      {
98          final Collection<Pattern> patterns = new LinkedList<Pattern>();
99          for (String pattern : pathExcludePatterns)
100         {
101             patterns.add(Pattern.compile(pattern));
102         }
103         return patterns;
104     }
105 }