1 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 ; 39 40 import java.io.IOException ; 41 import java.io.InputStream ; 42 import java.io.Serializable ; 43 import java.util.HashMap ; 44 import java.util.Map ; 45 46 88 public class ResourceReader extends AbstractReader 89 implements CacheableProcessingComponent, Configurable { 90 91 94 private static final Map documents = new HashMap (); 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 113 public void configure(Configuration configuration) throws ConfigurationException { 114 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 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 131 public void parameterize(Parameters parameters) throws ParameterException { 132 } 133 134 139 public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) 140 throws ProcessingException, SAXException , IOException { 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 163 protected void setupHeaders() { 164 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 response.addHeader("Vary", "Host"); 176 } 177 } 178 179 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 195 protected boolean hasRanges() { 196 return this.byteRanges && this.request.getHeader("Range") != null; 197 } 198 199 205 public Serializable getKey() { 206 return inputSource.getURI(); 207 } 208 209 215 public SourceValidity getValidity() { 216 if (hasRanges()) { 217 return null; 219 } else { 220 return inputSource.getValidity(); 221 } 222 } 223 224 228 public long getLastModified() { 229 if (hasRanges()) { 230 return 0; 232 } 233 234 if (quickTest) { 235 return inputSource.getLastModified(); 236 } 237 238 final String systemId = (String ) 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 inputStream) 248 throws IOException , ProcessingException { 249 byte[] buffer = new byte[bufferSize]; 250 int length = -1; 251 252 String 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 e) { 260 byteRange = null; 261 262 if (response instanceof HttpResponse) { 264 ((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 entityLength; 279 String 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 ((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 320 public void generate() 321 throws IOException , ProcessingException { 322 try { 323 InputStream 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 try { 332 processStream(inputStream); 333 } finally { 334 if (inputStream != null) { 335 inputStream.close(); 336 } 337 } 338 339 if (!quickTest) { 340 documents.put(request.getRequestURI(), inputSource.getURI()); 343 } 344 } catch (IOException e) { 345 getLogger().debug("Received an IOException, assuming client severed connection on purpose"); 346 } 347 } 348 349 352 public String getMimeType() { 353 Context ctx = ObjectModelHelper.getContext(objectModel); 354 if (ctx != null) { 355 final String mimeType = ctx.getMimeType(source); 356 if (mimeType != null) { 357 return mimeType; 358 } 359 } 360 361 return inputSource.getMimeType(); 362 } 363 } 364 | Popular Tags |