View Javadoc

1   /**
2    * Copyright (C) 2005-2009 Alfresco Software Limited.
3    *
4    * This file is part of the Spring Surf Extension project.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.springframework.extensions.webscripts.servlet.mvc;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.net.URL;
25  import java.net.URLConnection;
26  import java.util.HashMap;
27  import java.util.Map;
28  
29  import javax.servlet.RequestDispatcher;
30  import javax.servlet.ServletException;
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.springframework.core.io.Resource;
37  import org.springframework.util.ClassUtils;
38  import org.springframework.web.context.support.ServletContextResource;
39  import org.springframework.web.servlet.HandlerMapping;
40  import org.springframework.web.servlet.ModelAndView;
41  import org.springframework.web.servlet.mvc.AbstractController;
42  
43  /**
44   * Default Spring controller for retrieving and serving resources.
45   * 
46   * This controller retrieves content by interrogating resource providers
47   * in the following order:
48   * 
49   * 1) Web application path
50   * 2) Web application context resources (classpath)
51   * 3) Delegation to a default url handler
52   * 
53   * The following URL format is supported:
54   * 
55   *    /resource/<path>
56   *
57   * @author muzquiano
58   */
59  public class ResourceController extends AbstractController
60  {
61      private static Log logger = LogFactory.getLog(ResourceController.class);
62      
63      private static final String HTTP_CONTENT_LENGTH_HEADER = "Content-Length";
64      private static final String HTTP_LAST_MODIFIED_HEADER = "Last-Modified";
65      private static final String HTTP_EXPIRES_HEADER = "Expires";
66      private static final String HTTP_CACHE_CONTROL_HEADER = "Cache-Control";
67      
68      private static int BUFFER_SIZE = 1024;
69          
70      private Map<String, String> defaultMimeTypes = new HashMap<String, String>();
71      {
72          defaultMimeTypes.put(".css", "text/css");
73          defaultMimeTypes.put(".gif", "image/gif");
74          defaultMimeTypes.put(".ico", "image/vnd.microsoft.icon");
75          defaultMimeTypes.put(".jpeg", "image/jpeg");
76          defaultMimeTypes.put(".jpg", "image/jpeg");
77          defaultMimeTypes.put(".js", "text/javascript");
78          defaultMimeTypes.put(".png", "image/png");
79      }
80      
81      // allow for a default redirection url to be provided
82      private String defaultUrl;
83          
84      /**
85       * Sets the default url.
86       * 
87       * @param defaultUrl the new default url
88       */
89      public void setDefaultUrl(String defaultUrl)
90      {
91          this.defaultUrl = defaultUrl;
92      }
93      
94      /**
95       * Gets the default url.
96       * 
97       * @return the default url
98       */
99      public String getDefaultUrl()
100     {
101         return this.defaultUrl;
102     }
103 
104     /* (non-Javadoc)
105      * @see org.alfresco.web.framework.mvc.AbstractWebFrameworkController#getLogger()
106      */
107     public Log getLogger()
108     {
109         return logger;        
110     }
111     
112     /* (non-Javadoc)
113      * @see org.alfresco.web.framework.mvc.AbstractController#createModelAndView(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
114      */
115     public ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
116         throws Exception
117     {
118         // get the path to the resource (resolved by Spring for us)
119         String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
120         
121         dispatchResource(path, request, response);
122         
123         return null;
124     }
125     
126     /**
127      * Dispatches to the resource with the given path
128      * 
129      * @param path the path
130      * @param request the request
131      * @param response the response
132      * 
133      * @return true, if the dispatch succeeded
134      */
135     public boolean dispatchResource(String path, HttpServletRequest request, HttpServletResponse response)
136         throws ServletException, IOException
137     {
138         boolean resolved = false;
139         
140         // serve back the resource from the web application (if it exists)
141         ServletContextResource resource = new ServletContextResource(getServletContext(), "/" + path);
142         if (resource.exists())
143         {
144             // dispatch to resource
145             RequestDispatcher rd = this.getServletContext().getRequestDispatcher("/" + path);
146             rd.include(request, response);
147             
148             resolved = true;
149         }
150         
151         if (!resolved)
152         {
153             // look up the resource in the resource provider
154             // (application context resources / classpath)
155             
156             // check the class path
157             Resource r = getApplicationContext().getResource("classpath*:" + path);
158             if (r != null && r.exists())
159             {
160                 URL resourceUrl = r.getURL();
161                 
162                 // attempt to stream this back
163                 try
164                 {
165                     commitResponse(resourceUrl, response);
166                     resolved = true;
167                 }
168                 catch (IOException ioe)
169                 {
170                     ioe.printStackTrace();
171                 }
172             }
173         }
174 
175         if (!resolved)
176         {
177             // check JAR files
178             URL resourceUrl = ClassUtils.getDefaultClassLoader().getResource("META-INF/" + path);
179             if (resourceUrl != null)
180             {
181                 // attempt to stream this back
182                 try
183                 {
184                     commitResponse(resourceUrl, response);
185                     resolved = true;
186                 }
187                 catch (IOException ioe)
188                 {
189                     ioe.printStackTrace();
190                     // TODO: silent fail
191                 }
192             }
193         }            
194         
195         if (!resolved && defaultUrl != null)
196         {
197             // try to hand off to a default servlet context (compatibility with Spring JS resource servlet)
198             // use a forward here because the new servlet context may be different than the current servlet context 
199             RequestDispatcher rd = this.getServletContext().getRequestDispatcher(defaultUrl + "/" + path);
200             rd.forward(request, response);
201             
202             resolved = true;            
203         }
204         
205         return resolved;
206     }
207     
208     public void commitResponse(URL resourceUrl, HttpServletResponse response)
209         throws IOException
210     {
211         long lastModified = -1;
212         
213         // determine properties of the resource being served back
214         URLConnection resourceConn = resourceUrl.openConnection();
215         if (resourceConn.getLastModified() > lastModified)
216         {
217             lastModified = resourceConn.getLastModified();
218         }
219         String mimetype = getServletContext().getMimeType(resourceUrl.getPath());
220         if (mimetype == null) 
221         {
222             String extension = resourceUrl.getPath().substring(resourceUrl.getPath().lastIndexOf('.'));
223             mimetype = (String) defaultMimeTypes.get(extension);
224         }
225         int contentLength = resourceConn.getContentLength();
226     
227         // set response headers
228         response.setContentType(mimetype);
229         response.setHeader(HTTP_CONTENT_LENGTH_HEADER, Long.toString(contentLength));
230         response.setDateHeader(HTTP_LAST_MODIFIED_HEADER, lastModified);
231 
232         // stream back to response
233         copyStream(resourceUrl.openStream(), response.getOutputStream(), true);
234     }
235     
236     /**
237      * Copy stream.
238      * 
239      * @param in
240      *            the in
241      * @param out
242      *            the out
243      * @param closeStreams
244      *            the close streams
245      * 
246      * @return the int
247      * 
248      * @throws IOException
249      *             Signals that an I/O exception has occurred.
250      */
251     public static int copyStream(final InputStream in, final OutputStream out, final boolean closeStreams)
252         throws IOException
253     {
254         try
255         {
256             int byteCount = 0;
257             final byte[] buffer = new byte[BUFFER_SIZE];
258             int bytesRead = -1;
259             while ((bytesRead = in.read(buffer)) != -1)
260             {
261                 out.write(buffer, 0, bytesRead);
262                 byteCount += bytesRead;
263             }
264             out.flush();
265             return byteCount;
266         }
267         finally
268         {
269             if (closeStreams)
270             {
271                 try
272                 {
273                     in.close();
274                 }
275                 catch (IOException ex)
276                 {
277                 }
278                 try
279                 {
280                     out.close();
281                 }
282                 catch (IOException ex)
283                 {
284                 }
285             }
286         }
287     }
288     
289 }