View Javadoc

1   package org.jmage.mapper;
2   
3   import org.apache.log4j.Logger;
4   import org.apache.log4j.Priority;
5   import org.jmage.ApplicationContext;
6   import org.jmage.ImageRequest;
7   import org.jmage.util.ExceptionUtil;
8   
9   import javax.servlet.ServletException;
10  import javax.servlet.http.HttpServlet;
11  import javax.servlet.http.HttpServletRequest;
12  import javax.servlet.http.HttpServletResponse;
13  import java.io.IOException;
14  import java.net.URI;
15  import java.net.URISyntaxException;
16  import java.util.Enumeration;
17  import java.util.Properties;
18  
19  /***
20   * Maps ImageRequests based on queryString params.
21   */
22  public class ServletMapper extends HttpServlet {
23      public static final String CHAINPARAM_DELIMITER = "^.*__.*$";
24      public static final String CHAIN_DELIMITER = "^.*--.*$";
25  
26      protected static Logger log = Logger.getLogger(ServletMapper.class.getName());
27      protected static ApplicationContext context;
28  
29      private static final String CHAIN_REGEX = "[Cc][Hh][Aa][Ii][Nn]";
30      private static final String IMAGE_REGEX = "[Ii][Mm][Aa][Gg][Ee]";
31      private static final String IMAGE_URI_REGEX = "[Ii][Mm][Aa][Gg][Ee]_[Uu][Rr][Ii]";
32      private static final String SRC_REGEX = "[Ss][Rr][Cc]";
33      private static final String URI_REGEX = "^.*[Uu][Rr][Ii]$";
34      private static final String CHAINURISCHEME = "chain:";
35      private static final String ENCODE = "encode";
36      private static final String SERVLET_CONTEXT = "SERVLET_CONTEXT";
37      private static final String SLASH = "/";
38      private static final String DOT = ".";
39      private static final String COLON = ":";
40  
41      private static final String CONTENT_DISPOSITION = "Content-disposition";
42      private static final String FILENAME = "filename=image_";
43      private static final String MAP_ERROR = "unable to map image request, cause: ";
44      private static final String SOCKET_RESET_ERROR = "discarding request, connection reset by peer, cause: ";
45      private static final String FILE = "file";
46  
47      protected static final String TOMCAT_CLIENTABORT = "ClientAbortException";
48  
49      protected ThreadLocalServletImageRequestMap requestMap;
50      private static final String EMPTY_STRING = "";
51  
52      protected String chainParamDelimiter;
53      protected String chainDelimiter;
54  
55      public ServletMapper() {
56          super();
57          requestMap = new ThreadLocalServletImageRequestMap();
58      }
59  
60      public void init() throws ServletException {
61          if (context == null) {
62              context = ApplicationContext.getContext();
63          }
64          if (context.get(SERVLET_CONTEXT) == null) {
65              context.put(SERVLET_CONTEXT, this.getServletContext());
66          }
67          chainParamDelimiter = context.getProperty("chainParamDelimiter", CHAINPARAM_DELIMITER);
68          chainDelimiter = context.getProperty("chainDelimiter", CHAIN_DELIMITER);
69      }
70  
71      protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
72          try {
73              //synchronize servlet request, response and imageRequestMapper with imageRequest hanging off it, into
74              //a threadlocal
75              synchronized (this) {
76                  requestMap.setRequest(request);
77                  requestMap.setResponse(response);
78  
79                  ImageRequestMapper imageRequestMapper = this.getImageRequestMapper();
80                  ImageRequest imageRequest = this.populateImageRequestFrom(request);
81                  imageRequestMapper.setImageRequest(imageRequest);
82                  requestMap.setMapper(imageRequestMapper);
83              }
84  
85              requestMap.getResponse().setContentType(this.getServletContext().getMimeType(DOT +
86                      requestMap.getThreadLocalImageRequest().getEncodingFormat()));
87              requestMap.getResponse().setHeader(CONTENT_DISPOSITION, FILENAME +
88                      requestMap.getThreadLocalImageRequest().hashCode() +
89                      DOT + requestMap.getThreadLocalImageRequest().getEncodingFormat());
90              requestMap.getResponse().getOutputStream().write(requestMap.getMapper().processRequest());
91              requestMap.getResponse().getOutputStream().flush();
92          } catch (Exception e) {
93              String message = MAP_ERROR + e.getMessage();
94              //TODO bit ugly. tomcat throws internal exception with this message when client aborts, but it shouldn't be logged as error
95              if (new ExceptionUtil().hasMessage(e, TOMCAT_CLIENTABORT)) {
96                  if (log.isEnabledFor(Priority.DEBUG)) log.debug(SOCKET_RESET_ERROR + message);
97              } else {
98                  if (log.isEnabledFor(Priority.ERROR)) log.error(message);
99              }
100         } finally {
101             //clean out
102             synchronized (this) {
103                 requestMap.removeRequest();
104                 requestMap.removeResponse();
105                 requestMap.removeMapper();
106             }
107         }
108     }
109 
110     public ImageRequestMapper getImageRequestMapper() {
111         return new ImageRequestMapper();
112     }
113 
114     protected ImageRequest populateImageRequestFrom(HttpServletRequest request) throws URISyntaxException {
115         ImageRequest imageRequest = new ImageRequest();
116         Properties filterChainProperties = new Properties();
117 
118         Enumeration paramNames = request.getParameterNames();
119         while (paramNames.hasMoreElements()) {
120             String key = ((String) paramNames.nextElement());
121             String value = (String) request.getParameter(key);
122 
123             //complete relative URIs in value part of params
124             if(key.matches(URI_REGEX) || key.matches(IMAGE_REGEX) || key.matches(IMAGE_URI_REGEX) || key.matches(SRC_REGEX)) {
125                 String scheme = URI.create(value).getScheme();
126                 if (scheme==null || scheme.length()==0) {
127                     value = this.completeUri(value).toString();
128                 }
129             }
130 
131             //filter chain param
132             if (key.matches(CHAIN_REGEX)) {
133                 this.fillChainParam(key, value, imageRequest);
134                 continue;
135             }
136 
137             //image, src param
138             if (key.matches(IMAGE_REGEX) || key.matches(IMAGE_URI_REGEX) || key.matches(SRC_REGEX)) {
139                 this.fillImageParam(key, value, imageRequest);
140                 continue;
141             }
142 
143             //filterchain property params
144             if (key.matches(chainParamDelimiter)) {
145                 this.fillFilterChainProperties(key, value, filterChainProperties);
146             } else {
147                 filterChainProperties.setProperty(key.toUpperCase(), value);
148             }
149         }
150 
151         //check for specific encoding format and override image source format
152         String encode = request.getParameter(ENCODE);
153         if (encode != null) {
154             imageRequest.setEncodingFormat(encode);
155         }
156 
157         imageRequest.setFilterChainProperties(filterChainProperties);
158         return imageRequest;
159     }
160 
161     /***
162      * Extract per filterchain only properties. put them into their own properties and store them in global hashmap with
163      * filterchain name as lookup key.
164      *
165      * @param key the per filterchain property key
166      * @param value the per filterchain property value
167      * @param filterChainProperties per image request properties
168      */
169     protected void fillFilterChainProperties(String key, String value, Properties filterChainProperties) {
170         String[] perFilterChainProperty = key.split("__");
171         Object perFilterChainProps = filterChainProperties.get(perFilterChainProperty[0] != null ? perFilterChainProperty[0] : EMPTY_STRING);
172         if (perFilterChainProps == null) {
173             perFilterChainProps = new Properties();
174         }
175         ((Properties) perFilterChainProps).setProperty(
176                 perFilterChainProperty[1] != null ? perFilterChainProperty[1].toUpperCase() : EMPTY_STRING, value);
177         filterChainProperties.put(perFilterChainProperty[0] != null ? perFilterChainProperty[0] : EMPTY_STRING, perFilterChainProps);
178     }
179 
180     /***
181      * Set the image param on the ImageRequest
182      *
183      * @param key
184      * @param imageRequest
185      * @throws URISyntaxException
186      */
187     protected void fillImageParam(String key, String image, ImageRequest imageRequest) throws URISyntaxException {
188         imageRequest.setImageURI(new URI(image));
189         imageRequest.setEncodingFormat(determineContentType(image));
190     }
191 
192     /***
193      * Set the chain param on the ImageRequest
194      *
195      * @param key
196      * @param value
197      * @param imageRequest
198      * @throws URISyntaxException
199      */
200     protected void fillChainParam(String key, String value, ImageRequest imageRequest) throws URISyntaxException {
201         String[] chains = value.split("--");
202         URI[] filterChainURIs = new URI[chains.length];
203         for (int i = 0; i < chains.length; i++) {
204             String chain = chains[i];
205             if (!chain.startsWith(CHAINURISCHEME)) {
206                 chain = CHAINURISCHEME + chain;
207             }
208             filterChainURIs[i] = new URI(chain);
209         }
210         imageRequest.setFilterChainURI(filterChainURIs);
211     }
212 
213     /***
214      * Fix partial URIs
215      *
216      * @param resource
217      * @return
218      * @throws URISyntaxException
219      */
220     protected URI completeUri(String resource) throws URISyntaxException {
221         URI uri;
222         StringBuffer buffer = new StringBuffer();
223         buffer.append(FILE);
224         buffer.append(COLON);
225         buffer.append(SLASH);
226         buffer.append(SLASH);
227         buffer.append(resource.startsWith(SLASH) ? resource : SLASH + resource);
228         uri = new URI(buffer.toString());
229         return uri;
230     }
231 
232     /***
233      * What content type are we looking for?
234      *
235      * @param imagePath
236      * @return file extension
237      */
238     protected String determineContentType(String imagePath) {
239         return imagePath.substring(imagePath.lastIndexOf(DOT) + 1).toLowerCase();
240     }
241 }