KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > reading > ResourceReader


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.reading;
17
18 import org.apache.avalon.framework.configuration.Configurable;
19 import org.apache.avalon.framework.configuration.Configuration;
20 import org.apache.avalon.framework.configuration.ConfigurationException;
21 import org.apache.avalon.framework.parameters.ParameterException;
22 import org.apache.avalon.framework.parameters.Parameters;
23
24 import org.apache.cocoon.ProcessingException;
25 import org.apache.cocoon.caching.CacheableProcessingComponent;
26 import org.apache.cocoon.components.source.SourceUtil;
27 import org.apache.cocoon.environment.Context;
28 import org.apache.cocoon.environment.ObjectModelHelper;
29 import org.apache.cocoon.environment.Request;
30 import org.apache.cocoon.environment.Response;
31 import org.apache.cocoon.environment.SourceResolver;
32 import org.apache.cocoon.environment.http.HttpResponse;
33 import org.apache.cocoon.util.ByteRange;
34
35 import org.apache.excalibur.source.Source;
36 import org.apache.excalibur.source.SourceException;
37 import org.apache.excalibur.source.SourceValidity;
38 import org.xml.sax.SAXException JavaDoc;
39
40 import java.io.IOException JavaDoc;
41 import java.io.InputStream JavaDoc;
42 import java.io.Serializable JavaDoc;
43 import java.util.HashMap JavaDoc;
44 import java.util.Map JavaDoc;
45
46 /**
47  * The <code>ResourceReader</code> component is used to serve binary data
48  * in a sitemap pipeline. It makes use of HTTP Headers to determine if
49  * the requested resource should be written to the <code>OutputStream</code>
50  * or if it can signal that it hasn't changed.
51  *
52  * <p>Configuration:
53  * <dl>
54  * <dt>&lt;expires&gt;</dt>
55  * <dd>This parameter is optional. When specified it determines how long
56  * in miliseconds the resources can be cached by any proxy or browser
57  * between Cocoon and the requesting visitor. Defaults to -1.
58  * </dd>
59  * <dt>&lt;quick-modified-test&gt;</dt>
60  * <dd>This parameter is optional. This boolean parameter controls the
61  * last modified test. If set to true (default is false), only the
62  * last modified of the current source is tested, but not if the
63  * same source is used as last time
64  * (see http://marc.theaimsgroup.com/?l=xml-cocoon-dev&m=102921894301915 )
65  * </dd>
66  * <dt>&lt;byte-ranges&gt;</dt>
67  * <dd>This parameter is optional. This boolean parameter controls whether
68  * Cocoon should support byterange requests (to allow clients to resume
69  * broken/interrupted downloads).
70  * Defaults to true.
71  * </dl>
72  *
73  * <p>Default configuration:
74  * <pre>
75  * &lt;expires&gt;-1&lt;/expires&gt;
76  * &lt;quick-modified-test&gt;false&lt;/quick-modified-test&gt;
77  * &lt;byte-ranges&gt;true&lt;/byte-ranges&gt;
78  * </pre>
79  *
80  * <p>In addition to reader configuration, above parameters can be passed
81  * to the reader at the time when it is used.
82  *
83  * @author <a HREF="mailto:Giacomo.Pati@pwr.ch">Giacomo Pati</a>
84  * @author <a HREF="mailto:tcurdt@apache.org">Torsten Curdt</a>
85  * @author <a HREF="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
86  * @version CVS $Id: ResourceReader.java 155087 2005-02-23 22:17:31Z vgritsenko $
87  */

88 public class ResourceReader extends AbstractReader
89                             implements CacheableProcessingComponent, Configurable {
90
91     /**
92      * The list of generated documents
93      */

94     private static final Map JavaDoc documents = new HashMap JavaDoc();
95
96     protected long configuredExpires;
97     protected boolean configuredQuickTest;
98     protected int configuredBufferSize;
99     protected boolean configuredByteRanges;
100
101     protected long expires;
102     protected boolean quickTest;
103     protected int bufferSize;
104     protected boolean byteRanges;
105
106     protected Response response;
107     protected Request request;
108     protected Source inputSource;
109
110     /**
111      * Read reader configuration
112      */

113     public void configure(Configuration configuration) throws ConfigurationException {
114         // VG Parameters are deprecated as of 2.2.0-Dev/2.1.6-Dev
115
final Parameters parameters = Parameters.fromConfiguration(configuration);
116         this.configuredExpires = parameters.getParameterAsLong("expires", -1);
117         this.configuredQuickTest = parameters.getParameterAsBoolean("quick-modified-test", false);
118         this.configuredBufferSize = parameters.getParameterAsInteger("buffer-size", 8192);
119         this.configuredByteRanges = parameters.getParameterAsBoolean("byte-ranges", true);
120
121         // Configuration has precedence over parameters.
122
this.configuredExpires = configuration.getChild("expires").getValueAsLong(configuredExpires);
123         this.configuredQuickTest = configuration.getChild("quick-modified-test").getValueAsBoolean(configuredQuickTest);
124         this.configuredBufferSize = configuration.getChild("buffer-size").getValueAsInteger(configuredBufferSize);
125         this.configuredByteRanges = configuration.getChild("byte-ranges").getValueAsBoolean(configuredByteRanges);
126     }
127
128     /* (non-Javadoc)
129      * @see org.apache.avalon.framework.parameters.Parameterizable#parameterize(Parameters)
130      */

131     public void parameterize(Parameters parameters) throws ParameterException {
132     }
133
134     /**
135      * Setup the reader.
136      * The resource is opened to get an <code>InputStream</code>,
137      * the length and the last modification date
138      */

139     public void setup(SourceResolver resolver, Map JavaDoc objectModel, String JavaDoc src, Parameters par)
140     throws ProcessingException, SAXException JavaDoc, IOException JavaDoc {
141         super.setup(resolver, objectModel, src, par);
142
143         this.request = ObjectModelHelper.getRequest(objectModel);
144         this.response = ObjectModelHelper.getResponse(objectModel);
145
146         this.expires = par.getParameterAsLong("expires", this.configuredExpires);
147         this.quickTest = par.getParameterAsBoolean("quick-modified-test", this.configuredQuickTest);
148         this.bufferSize = par.getParameterAsInteger("buffer-size", this.configuredBufferSize);
149         this.byteRanges = par.getParameterAsBoolean("byte-ranges", this.configuredByteRanges);
150
151         try {
152             this.inputSource = resolver.resolveURI(src);
153         } catch (SourceException e) {
154             throw SourceUtil.handle("Error during resolving of '" + src + "'.", e);
155         }
156
157         setupHeaders();
158     }
159
160     /**
161      * Setup the response headers: Accept-Ranges, Expires.
162      */

163     protected void setupHeaders() {
164         // Tell the client whether we support byte range requests or not
165
if (byteRanges) {
166             response.setHeader("Accept-Ranges", "bytes");
167         } else {
168             response.setHeader("Accept-Ranges", "none");
169         }
170
171         if (expires > 0) {
172             response.setDateHeader("Expires", System.currentTimeMillis() + expires);
173         } else if (expires == 0) {
174             // See Bug #14048
175
response.addHeader("Vary", "Host");
176         }
177     }
178
179     /**
180      * Recyclable
181      */

182     public void recycle() {
183         this.request = null;
184         this.response = null;
185         if (this.inputSource != null) {
186             super.resolver.release(this.inputSource);
187             this.inputSource = null;
188         }
189         super.recycle();
190     }
191
192     /**
193      * @return True if byte ranges support is enabled and request has range header.
194      */

195     protected boolean hasRanges() {
196         return this.byteRanges && this.request.getHeader("Range") != null;
197     }
198
199     /**
200      * Generate the unique key.
201      * This key must be unique inside the space of this component.
202      *
203      * @return The generated key hashes the src
204      */

205     public Serializable JavaDoc getKey() {
206         return inputSource.getURI();
207     }
208
209     /**
210      * Generate the validity object.
211      *
212      * @return The generated validity object or <code>null</code> if the
213      * component is currently not cacheable.
214      */

215     public SourceValidity getValidity() {
216         if (hasRanges()) {
217             // This is a byte range request so we can't use the cache, return null.
218
return null;
219         } else {
220             return inputSource.getValidity();
221         }
222     }
223
224     /**
225      * @return the time the read source was last modified or 0 if it is not
226      * possible to detect
227      */

228     public long getLastModified() {
229         if (hasRanges()) {
230             // This is a byte range request so we can't use the cache, return null.
231
return 0;
232         }
233
234         if (quickTest) {
235             return inputSource.getLastModified();
236         }
237
238         final String JavaDoc systemId = (String JavaDoc) documents.get(request.getRequestURI());
239         if (systemId == null || inputSource.getURI().equals(systemId)) {
240             return inputSource.getLastModified();
241         }
242
243         documents.remove(request.getRequestURI());
244         return 0;
245     }
246
247     protected void processStream(InputStream JavaDoc inputStream)
248     throws IOException JavaDoc, ProcessingException {
249         byte[] buffer = new byte[bufferSize];
250         int length = -1;
251
252         String JavaDoc ranges = request.getHeader("Range");
253
254         ByteRange byteRange;
255         if (byteRanges && ranges != null) {
256             try {
257                 ranges = ranges.substring(ranges.indexOf('=') + 1);
258                 byteRange = new ByteRange(ranges);
259             } catch (NumberFormatException JavaDoc e) {
260                 byteRange = null;
261
262                 // TC: Hm.. why don't we have setStatus in the Response interface ?
263
if (response instanceof HttpResponse) {
264                     // Respond with status 416 (Request range not satisfiable)
265
((HttpResponse)response).setStatus(416);
266                     if (getLogger().isDebugEnabled()) {
267                         getLogger().debug("malformed byte range header [" + String.valueOf(ranges) + "]");
268                     }
269                 }
270             }
271         } else {
272             byteRange = null;
273         }
274
275         long contentLength = inputSource.getContentLength();
276
277         if (byteRange != null) {
278             String JavaDoc entityLength;
279             String JavaDoc entityRange;
280             if (contentLength != -1) {
281                 entityLength = "" + contentLength;
282                 entityRange = byteRange.intersection(new ByteRange(0, contentLength)).toString();
283             } else {
284                 entityLength = "*";
285                 entityRange = byteRange.toString();
286             }
287
288             response.setHeader("Content-Range", entityRange + "/" + entityLength);
289             if (response instanceof HttpResponse) {
290                 // Response with status 206 (Partial content)
291
((HttpResponse)response).setStatus(206);
292             }
293
294             int pos = 0;
295             int posEnd;
296             while ((length = inputStream.read(buffer)) > -1) {
297                 posEnd = pos + length - 1;
298                 ByteRange intersection = byteRange.intersection(new ByteRange(pos, posEnd));
299                 if (intersection != null) {
300                     out.write(buffer, (int) intersection.getStart() - pos, (int) intersection.length());
301                 }
302                 pos += length;
303             }
304         } else {
305             if (contentLength != -1) {
306                 response.setHeader("Content-Length", Long.toString(contentLength));
307             }
308
309             while ((length = inputStream.read(buffer)) > -1) {
310                 out.write(buffer, 0, length);
311             }
312         }
313
314         out.flush();
315     }
316
317     /**
318      * Generates the requested resource.
319      */

320     public void generate()
321     throws IOException JavaDoc, ProcessingException {
322         try {
323             InputStream JavaDoc inputStream;
324             try {
325                 inputStream = inputSource.getInputStream();
326             } catch (SourceException e) {
327                 throw SourceUtil.handle("Error during resolving of the input stream", e);
328             }
329
330             // Bugzilla Bug #25069: Close inputStream in finally block.
331
try {
332                 processStream(inputStream);
333             } finally {
334                 if (inputStream != null) {
335                     inputStream.close();
336                 }
337             }
338
339             if (!quickTest) {
340                 // if everything is ok, add this to the list of generated documents
341
// (see http://marc.theaimsgroup.com/?l=xml-cocoon-dev&m=102921894301915 )
342
documents.put(request.getRequestURI(), inputSource.getURI());
343             }
344         } catch (IOException JavaDoc e) {
345             getLogger().debug("Received an IOException, assuming client severed connection on purpose");
346         }
347     }
348
349     /**
350      * Returns the mime-type of the resource in process.
351      */

352     public String JavaDoc getMimeType() {
353         Context ctx = ObjectModelHelper.getContext(objectModel);
354         if (ctx != null) {
355             final String JavaDoc mimeType = ctx.getMimeType(source);
356             if (mimeType != null) {
357                 return mimeType;
358             }
359         }
360
361         return inputSource.getMimeType();
362     }
363 }
364
Popular Tags