1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.resource.servlet

File AbstractServletResourceReferenceHandler.java

 

Coverage histogram

../../../../img/srcFileCovDistChart9.png
38% of files have more coverage

Code metrics

16
44
8
1
250
130
22
0.5
5.5
8
2.75

Classes

Class Line # Actions
AbstractServletResourceReferenceHandler 58 44 0% 22 8
0.8823529588.2%
 

Contributing tests

This file is covered by 5 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.resource.servlet;
21   
22    import java.io.BufferedInputStream;
23    import java.io.IOException;
24    import java.io.InputStream;
25    import java.util.Date;
26   
27    import javax.inject.Inject;
28    import javax.servlet.http.HttpServletResponse;
29   
30    import org.apache.commons.io.IOUtils;
31    import org.apache.http.HttpHeaders;
32    import org.apache.http.HttpStatus;
33    import org.apache.tika.Tika;
34    import org.slf4j.Logger;
35    import org.xwiki.container.Container;
36    import org.xwiki.container.Request;
37    import org.xwiki.container.Response;
38    import org.xwiki.container.servlet.ServletRequest;
39    import org.xwiki.container.servlet.ServletResponse;
40    import org.xwiki.resource.AbstractResourceReferenceHandler;
41    import org.xwiki.resource.ResourceReference;
42    import org.xwiki.resource.ResourceReferenceHandler;
43    import org.xwiki.resource.ResourceReferenceHandlerChain;
44    import org.xwiki.resource.ResourceReferenceHandlerException;
45    import org.xwiki.resource.ResourceType;
46    import org.xwiki.stability.Unstable;
47   
48    /**
49    * Base class for {@link ResourceReferenceHandler}s that can handle servlet resource requests.
50    *
51    * @param <R> the resource type
52    * @version $Id: b838d4755648c3c4642774dbd5798b03c5fe7e43 $
53    * @since 7.4.6
54    * @since 8.2.2
55    * @since 8.3
56    */
57    @Unstable
 
58    public abstract class AbstractServletResourceReferenceHandler<R extends ResourceReference>
59    extends AbstractResourceReferenceHandler<ResourceType>
60    {
61    /**
62    * One year duration can be considered as permanent caching.
63    */
64    private static final long CACHE_DURATION = 365 * 24 * 3600 * 1000L;
65   
66    @Inject
67    private Logger logger;
68   
69    @Inject
70    private Container container;
71   
72    /**
73    * Used to determine the Content Type of the requested resource files.
74    */
75    private Tika tika = new Tika();
76   
 
77  1720 toggle @Override
78    public void handle(ResourceReference resourceReference, ResourceReferenceHandlerChain chain)
79    throws ResourceReferenceHandlerException
80    {
81  1720 @SuppressWarnings("unchecked")
82    R typedResourceReference = (R) resourceReference;
83   
84  1707 if (!isResourceAccessible(typedResourceReference)) {
85  0 sendError(HttpStatus.SC_FORBIDDEN, "You are not allowed to view [%s].",
86    getResourceName(typedResourceReference));
87  1714 } else if (!shouldBrowserUseCachedContent(typedResourceReference)) {
88    // If we get here then either the resource is not cached by the browser or the resource is dynamic.
89  1660 InputStream resourceStream = getResourceStream(typedResourceReference);
90  1667 if (resourceStream != null) {
91  1666 try {
92  1666 serveResource(typedResourceReference, filterResource(typedResourceReference, resourceStream));
93    } catch (ResourceReferenceHandlerException e) {
94  1 this.logger.error(e.getMessage(), e);
95  1 sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage());
96    }
97    } else {
98  1 sendError(HttpStatus.SC_NOT_FOUND, "Resource not found [%s].", getResourceName(typedResourceReference));
99    }
100    }
101   
102    // Be a good citizen, continue the chain, in case some lower-priority handler has something to do for this
103    // resource reference.
104  1720 chain.handleNext(resourceReference);
105    }
106   
107    /**
108    * @param resourceReference the reference of the requested resource
109    * @return {@code true} if the specified resource is accessible, {@code false} otherwise
110    */
 
111  1720 toggle protected boolean isResourceAccessible(R resourceReference)
112    {
113  1720 return true;
114    }
115   
116    /**
117    * @param resourceReference the reference of the requested resource
118    * @return {@code true} if the requested resource is static and is cached by the browser, {@code false} if the
119    * browser should discard the cached version and use the new version from this response
120    */
 
121  1698 toggle private boolean shouldBrowserUseCachedContent(R resourceReference)
122    {
123    // If the request contains an "If-Modified-Since" header and the requested resource has not been modified then
124    // return a 304 Not Modified to tell the browser to use its cached version.
125  1706 Request request = this.container.getRequest();
126  1712 if (request instanceof ServletRequest
127    && ((ServletRequest) request).getHttpServletRequest().getHeader("If-Modified-Since") != null
128    && isResourceCacheable(resourceReference)) {
129    // The user probably used F5 to reload the page and the browser checks if there are changes.
130  53 Response response = this.container.getResponse();
131  53 if (response instanceof ServletResponse) {
132    // Return the 304 Not Modified.
133  53 ((ServletResponse) response).getHttpServletResponse().setStatus(HttpServletResponse.SC_NOT_MODIFIED);
134  53 return true;
135    }
136    }
137  1657 return false;
138    }
139   
140    /**
141    * @param resourceReference a resource reference
142    * @return {@code true} if the specified resource can be cached, {@code false} otherwise
143    */
 
144  0 toggle protected boolean isResourceCacheable(R resourceReference)
145    {
146  0 return true;
147    }
148   
149    /**
150    * @param resourceReference the reference of the requested resource
151    * @return the stream that can be used to read the resource
152    */
153    protected abstract InputStream getResourceStream(R resourceReference);
154   
155    /**
156    * @param resourceReference the reference of the requested resource
157    * @return the name of the specified resource
158    */
159    protected abstract String getResourceName(R resourceReference);
160   
161    /**
162    * Sends back the specified resource.
163    *
164    * @param resourceReference the reference of the requested resource
165    * @param rawResourceStream the resource stream used to read the resource
166    * @throws ResourceReferenceHandlerException if it fails to read the resource
167    */
 
168  1665 toggle private void serveResource(R resourceReference, InputStream rawResourceStream)
169    throws ResourceReferenceHandlerException
170    {
171  1665 InputStream resourceStream = rawResourceStream;
172  1665 String resourceName = getResourceName(resourceReference);
173   
174    // Make sure the resource stream supports mark & reset which is needed in order be able to detect the
175    // content type without affecting the stream (Tika may need to read a few bytes from the start of the
176    // stream, in which case it will mark & reset the stream).
177  1665 if (!resourceStream.markSupported()) {
178  459 resourceStream = new BufferedInputStream(resourceStream);
179    }
180   
181  1665 try {
182  1665 Response response = this.container.getResponse();
183  1665 setResponseHeaders(response, resourceReference);
184  1665 response.setContentType(this.tika.detect(resourceStream, resourceName));
185  1665 IOUtils.copy(resourceStream, response.getOutputStream());
186    } catch (Exception e) {
187  0 throw new ResourceReferenceHandlerException(String.format("Failed to read resource [%s]", resourceName), e);
188    } finally {
189  1665 IOUtils.closeQuietly(resourceStream);
190    }
191    }
192   
193    /**
194    * Filter the resource before sending it to the client.
195    *
196    * @param resourceReference the resource to filter
197    * @param resourceStream the resource content
198    * @return the filtered resource content
199    */
 
200  460 toggle protected InputStream filterResource(R resourceReference, InputStream resourceStream)
201    throws ResourceReferenceHandlerException
202    {
203  460 return resourceStream;
204    }
205   
206    /**
207    * Sets the response headers needed to cache the resource permanently, if the resource can be cached.
208    *
209    * @param response the response
210    * @param resourceReference the resource that is being served
211    */
 
212  1665 toggle private void setResponseHeaders(Response response, R resourceReference)
213    {
214    // Cache the resource if possible.
215  1665 if (response instanceof ServletResponse && isResourceCacheable(resourceReference)) {
216  460 HttpServletResponse httpResponse = ((ServletResponse) response).getHttpServletResponse();
217  460 httpResponse.setHeader(HttpHeaders.CACHE_CONTROL, "public");
218  460 httpResponse.setDateHeader(HttpHeaders.EXPIRES, new Date().getTime() + CACHE_DURATION);
219    // Even if the resource is cached permanently, most browsers are still sending a request if the user reloads
220    // the page using F5. We send back the "Last-Modified" header in the response so that the browser will send
221    // us an "If-Modified-Since" request for any subsequent call for this static resource. When this happens we
222    // return a 304 to tell the browser to use its cached version.
223  460 httpResponse.setDateHeader(HttpHeaders.LAST_MODIFIED, new Date().getTime());
224    }
225    }
226   
227    /**
228    * Sends back the specified status code with the given message in order for the browser to know the resource
229    * couldn't be served. This is especially important as we don't want to cache an empty response.
230    *
231    * @param statusCode the response status code to send
232    * @param message the error message
233    * @param parameters the message parameters
234    * @throws ResourceReferenceHandlerException if setting the response status code fails
235    */
 
236  2 toggle private void sendError(int statusCode, String message, Object... parameters)
237    throws ResourceReferenceHandlerException
238    {
239  2 Response response = this.container.getResponse();
240  2 if (response instanceof ServletResponse) {
241  2 HttpServletResponse httpResponse = ((ServletResponse) response).getHttpServletResponse();
242  2 try {
243  2 httpResponse.sendError(statusCode, String.format(message, parameters));
244    } catch (IOException e) {
245  0 throw new ResourceReferenceHandlerException(
246    String.format("Failed to return status code [%s].", statusCode), e);
247    }
248    }
249    }
250    }