cocoon-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From tcu...@apache.org
Subject cvs commit: xml-cocoon2/src/scratchpad/src/org/apache/cocoon/util ByteRange.java
Date Mon, 06 Jan 2003 06:10:25 GMT
tcurdt      2003/01/05 22:10:25

  Modified:    .        changes.xml
               src/documentation/xdocs/userdocs/readers image-reader.xml
                        resource-reader.xml
               src/java/org/apache/cocoon/reading ResourceReader.java
               src/scratchpad/src/org/apache/cocoon/reading
                        ImageReader.java
  Added:       src/java/org/apache/cocoon/util ByteRange.java
  Removed:     src/scratchpad/src/org/apache/cocoon/reading
                        ByteRangeResourceReader.java
               src/scratchpad/src/org/apache/cocoon/util ByteRange.java
  Log:
  moved the byte range support from the ByteRangeReader in scratchpad into the ResourceReader,
  ImageReader now extends the ResourceReader, because of that parameter "expire-time" becomes
"expires",
  no longer set byte range header for on-the-fly images (reader did not support it anyway),
  
  Revision  Changes    Path
  1.328     +15 -1     xml-cocoon2/changes.xml
  
  Index: changes.xml
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/changes.xml,v
  retrieving revision 1.327
  retrieving revision 1.328
  diff -u -r1.327 -r1.328
  --- changes.xml	31 Dec 2002 16:26:05 -0000	1.327
  +++ changes.xml	6 Jan 2003 06:10:24 -0000	1.328
  @@ -40,6 +40,20 @@
    </devs>
   
    <release version="@version@" date="@date@">
  +  <action dev="TC" type="update">    
  +    ImageReader extends now ResourceReader and therefor the
  +    "expire-time" parameter is now "expires". Also removed the
  +    setting of the byte range header for on-the-fly images.
  +    (did not support it anyway)
  +  </action>
  +  <action dev="TC" type="update">
  +    Moved the byte range support from the ByteRangeReader in scratchpad
  +    into the ResourceReader. Added the optional parameters "buffer-size"
  +    and "byte-range". Byte range support is enable by default.
  +  </action>
  +  <action dev="TC" type="update">
  +    Move the image inspection into a ImageUtils class
  +  </action>
     <action dev="TC" type="update">
       Major cleanup of the ImageDirectoryGenerator, removed the RuntimeExceptions,
       added support for the JPEG comment marker
  
  
  
  1.2       +8 -6      xml-cocoon2/src/documentation/xdocs/userdocs/readers/image-reader.xml
  
  Index: image-reader.xml
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/documentation/xdocs/userdocs/readers/image-reader.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- image-reader.xml	25 Dec 2002 12:56:07 -0000	1.1
  +++ image-reader.xml	6 Jan 2003 06:10:24 -0000	1.2
  @@ -26,7 +26,7 @@
             <td>TYPE</td><td>Reader, Sitemap Component</td>
           </tr>
           <tr>
  -          <td>BLOCK</td><td>Scratchpad</td>
  +          <td>BLOCK</td><td>Core</td>
           </tr>
           <tr>
             <td>CLASS</td><td>org.apache.cocoon.reading.ImageReader</td>
  @@ -80,7 +80,7 @@
   <map:readers default="resource">
   ...
     <map:reader name="image" 
  -    src="org.apache.cocoon.reading.ImageReader" 
  +    src="org.apache.cocoon.reading.ImageReader"
       logger="sitemap.reader.image" 
       pool-max="32" pool-min="1" pool-grow="4"/>
       <!-- optional reader configuration -->
  @@ -101,7 +101,7 @@
           </p>
           <table>
             <tr><th>Parametername</th><th>Type</th><th>Comment</th></tr>
  -          <tr><td>expire-time</td><td>Time in milliseconds</td>
  +          <tr><td>expires</td><td>Time in milliseconds</td>
               <td>
                 This parameter is optional. When specified it determines how long
                 in miliseconds the resources can be cached by any proxy or browser
  @@ -134,7 +134,7 @@
           <source><![CDATA[
   <map:match pattern="*.jpg">
     <map:reader type="image" 
  -    <map:parameter name="expire-time" value="86400000"/>
  +    <map:parameter name="expires" value="86400000"/>
       <map:parameter name="width" value="300"/>
     </map:reader>
   ...
  @@ -154,8 +154,8 @@
           Nevertheless it can serve any image data in a non transforming mode.
         </p>
         <p>
  -        The <code>ImageReader</code> does support HTTP ranges, thus
  -        it sets <code>Accept-Ranges</code> to <code>bytes</code>.
  +        The <code>ImageReader</code> does NOT support HTTP ranges, thus
  +        it sets <code>Accept-Ranges</code> to <code>none</code>.
         </p>
         <p>
           The java Bug Id 4502892 (which is found in *all* JVM implementations from
  @@ -167,6 +167,8 @@
       <s1 title="History">
         <p>
           12-25-02: Initial document creation by Bernhard Huber
  +        01-06-03: Renamed the expire-time -> expires parameter,
  +                  Fixed the statement about the byte range support, Torsten Curdt
         </p>
       </s1>
       <s1 title="Copyright">
  
  
  
  1.2       +13 -4     xml-cocoon2/src/documentation/xdocs/userdocs/readers/resource-reader.xml
  
  Index: resource-reader.xml
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/documentation/xdocs/userdocs/readers/resource-reader.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- resource-reader.xml	25 Dec 2002 12:56:07 -0000	1.1
  +++ resource-reader.xml	6 Jan 2003 06:10:24 -0000	1.2
  @@ -116,6 +116,18 @@
                 same source is used as last time.
               </td>
             </tr>
  +          <tr><td>byte-ranges</td><td>boolean</td>
  +            <td>
  +              This parameter is optional. This boolean parameter enables or disables
  +              support for the byte ranges.
  +            </td>
  +          </tr>
  +          <tr><td>buffer-size</td><td>integer</td>
  +            <td>
  +              This parameter is optional. It specifies the buffer/block size when
  +              reading from a resource.
  +            </td>
  +          </tr>
           </table>
           <p>
             The following <code>ResourceReader</code> declaration snippet
  @@ -142,14 +154,11 @@
         </s2>
       </s1>
       <s1 title="Bugs/Caveats">
  -      <p>
  -        The <code>ResourceReader</code> does not support HTTP ranges, thus
  -        it sets <code>Accept-Ranges</code> to <code>none</code>.
  -      </p>
       </s1>
       <s1 title="History">
         <p>
           12-25-02: Initial document creation by Bernhard Huber
  +        01-06-03: Added new parameters and byte range support, Torsten Curdt
         </p>
       </s1>
       <s1 title="Copyright">
  
  
  
  1.22      +146 -66   xml-cocoon2/src/java/org/apache/cocoon/reading/ResourceReader.java
  
  Index: ResourceReader.java
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/reading/ResourceReader.java,v
  retrieving revision 1.21
  retrieving revision 1.22
  diff -u -r1.21 -r1.22
  --- ResourceReader.java	18 Dec 2002 08:09:24 -0000	1.21
  +++ ResourceReader.java	6 Jan 2003 06:10:25 -0000	1.22
  @@ -52,6 +52,7 @@
   
   import org.apache.avalon.framework.parameters.Parameters;
   import org.apache.cocoon.ProcessingException;
  +import org.apache.cocoon.util.ByteRange;
   import org.apache.cocoon.caching.CacheableProcessingComponent;
   import org.apache.cocoon.components.source.SourceUtil;
   import org.apache.cocoon.environment.Context;
  @@ -59,6 +60,7 @@
   import org.apache.cocoon.environment.Request;
   import org.apache.cocoon.environment.Response;
   import org.apache.cocoon.environment.SourceResolver;
  +import org.apache.cocoon.environment.http.HttpResponse;
   import org.apache.excalibur.source.Source;
   import org.apache.excalibur.source.SourceException;
   import org.apache.excalibur.source.SourceValidity;
  @@ -88,38 +90,51 @@
    *       <dd>This parameter is optional. This boolean parameter controlls the
    *           last modified test. If set to true (default is false), only the
    *           last modified of the current source is tested, but not if the
  - *           same source is used as last time.
  + *           same source is used as last time. (see http://marc.theaimsgroup.com/?l=xml-cocoon-dev&m=102921894301915&w=2
)
    *       </dd>
    *   </dl>
    *
    * @author <a href="mailto:Giacomo.Pati@pwr.ch">Giacomo Pati</a>
  - * @version CVS $Id$
  + * @author <a href="mailto:tcurdt@apache.org">Torsten Curdt</a>
  +  * @version CVS $Id$
    */
  -public final class ResourceReader
  -  extends AbstractReader
  -  implements CacheableProcessingComponent {
  -
  -    /** The  source */
  -    private Source      inputSource;
  +public class ResourceReader extends AbstractReader implements CacheableProcessingComponent
{
   
       /** The list of generated documents */
       private static final Map documents = new HashMap();
  -    
  -    /** quick test */
  -    private boolean quickTest;
  -    
  +
  +    protected Source inputSource;
  +    protected InputStream inputStream;
  +
  +    protected boolean quickTest;
  +    protected boolean byteRanges;
  +
  +    protected Response response;
  +    protected Request request;
  +    protected long expires;
  +    protected int bufferSize;
  +
       /**
        * Setup the reader.
        * The resource is opened to get an <code>InputStream</code>,
        * the length and the last modification date
        */
  -    public void setup(SourceResolver resolver, Map objectModel, String src, Parameters
par)
  -    throws ProcessingException, SAXException, IOException {
  +    public void setup(SourceResolver resolver, Map objectModel, String src, Parameters
par) throws ProcessingException, SAXException, IOException {
           super.setup(resolver, objectModel, src, par);
  -        this.quickTest = par.getParameterAsBoolean("quick-modified-test", false);
  +
  +        request = ObjectModelHelper.getRequest(objectModel);
  +        response = ObjectModelHelper.getResponse(objectModel);
  +
  +        expires = par.getParameterAsInteger("expires", -1);
  +        bufferSize = par.getParameterAsInteger("buffer-size",8192);
  +
  +        byteRanges = par.getParameterAsBoolean("byte-ranges",true);
  +        quickTest = par.getParameterAsBoolean("quick-modified-test", false);
  +
           try {
  -            this.inputSource = resolver.resolveURI(src);
  -        } catch (SourceException se) {
  +            inputSource = resolver.resolveURI(src);
  +        }
  +        catch (SourceException se) {
               throw SourceUtil.handle("Error during resolving of '" + src + "'.", se);
           }
       }
  @@ -128,9 +143,9 @@
        * Recyclable
        */
       public void recycle() {
  -        if (this.inputSource != null) {
  -            super.resolver.release( this.inputSource );
  -            this.inputSource = null;
  +        if (inputSource != null) {
  +            super.resolver.release(inputSource);
  +            inputSource = null;
           }
           super.recycle();
       }
  @@ -142,7 +157,7 @@
        * @return The generated key hashes the src
        */
       public java.io.Serializable generateKey() {
  -        return this.inputSource.getSystemId();
  +        return inputSource.getSystemId();
       }
   
       /**
  @@ -152,7 +167,7 @@
        *         component is currently not cacheable.
        */
       public SourceValidity generateValidity() {
  -        return this.inputSource.getValidity();
  +        return inputSource.getValidity();
       }
   
       /**
  @@ -160,78 +175,143 @@
        *         possible to detect
        */
       public long getLastModified() {
  -    	if (this.quickTest) {
  -    		return this.inputSource.getLastModified();
  -    	}
  -        final Request request = ObjectModelHelper.getRequest(this.objectModel);
  -        final String systemId = (String)documents.get(request.getRequestURI());
  -        if (this.inputSource.getSystemId().equals(systemId)) {
  -            return this.inputSource.getLastModified();
  -        } else {
  -        	documents.remove(request.getRequestURI());
  -        	return 0;
  +        if (quickTest) {
  +            return inputSource.getLastModified();
  +        }
  +        final String systemId = (String) documents.get(request.getRequestURI());
  +        if (inputSource.getSystemId().equals(systemId)) {
  +            return inputSource.getLastModified();
  +        }
  +        else {
  +            documents.remove(request.getRequestURI());
  +            return 0;
           }
       }
   
  -    /**
  -     * Generates the requested resource.
  -     */
  -    public void generate()
  -    throws IOException, ProcessingException {
  -        final Response response = ObjectModelHelper.getResponse(this.objectModel);
  +    protected void processStream() throws IOException, ProcessingException {
  +        byte[] buffer = new byte[bufferSize];
  +        int length = -1;
  +
  +        String ranges = request.getHeader("Ranges");
  +
  +        ByteRange byteRange;
  +        if (ranges != null && byteRanges) {
  +            try {
  +                ranges = ranges.substring(ranges.indexOf('=') + 1);
  +                byteRange = new ByteRange(ranges);
  +            } catch (NumberFormatException e) {
  +                byteRange = null;
  +
  +                // TC: Hm.. why don't we have setStatus in the Response interface ?
  +                if (response instanceof HttpResponse) {
  +                    // Respond with status 416 (Request range not satisfiable)
  +                    ((HttpResponse)response).setStatus(416);
  +                    if (getLogger().isDebugEnabled()) {
  +                        getLogger().debug("malformed byte range header [" + String.valueOf(ranges)
+ "]");
  +                    }
  +                }
  +            }
  +        }
  +        else {
  +            byteRange = null;
  +        }
   
  -        try {
  -            final long expires = parameters.getParameterAsInteger("expires", -1);
  +        long contentLength = inputSource.getContentLength();
   
  -            if (expires > 0) {
  -                response.setDateHeader("Expires", System.currentTimeMillis() + expires);
  +        if (byteRange != null) {
  +            String entityLength;
  +            String entityRange;
  +            if (contentLength != -1) {
  +                entityLength = "" + contentLength;
  +                entityRange = byteRange.intersection(new ByteRange(0, contentLength)).toString();
  +            } else {
  +                entityLength = "*";
  +                entityRange = byteRange.toString();
  +            }
  +
  +            response.setHeader("Content-Range", entityRange + "/" + entityLength);
  +
  +            if (response instanceof HttpResponse) {
  +                // Response with status 206 (Partial content)
  +                ((HttpResponse)response).setStatus(206);
               }
   
  -            long contentLength = this.inputSource.getContentLength();
  +            response.setHeader("Accept-Ranges", "bytes");
  +
  +            int pos = 0;
  +            int posEnd;
  +            while ((length = inputStream.read(buffer)) > -1) {
  +                posEnd = pos + length - 1;
  +                ByteRange intersection = byteRange.intersection(new ByteRange(pos, posEnd));
  +                if (intersection != null) {
  +                    out.write(buffer, (int) intersection.getStart() - pos, (int) intersection.length());
  +                }
  +                pos += length;
  +            }
  +        }
  +        else {
               if (contentLength != -1) {
  -                // FIXME (VG): Environment has setContentLength, and
  -                // Response interface has not. Strange.
                   response.setHeader("Content-Length", Long.toString(contentLength));
               }
   
               // Bug #9539: This resource reader does not support ranges
               response.setHeader("Accept-Ranges", "none");
   
  -            byte[] buffer = new byte[8192];
  -            int length = -1;
  -
  -            InputStream inputStream = this.inputSource.getInputStream();
               while ((length = inputStream.read(buffer)) > -1) {
                   out.write(buffer, 0, length);
               }
  +        }
  +
  +        out.flush();
  +    }
  +
  +    /**
  +     * Generates the requested resource.
  +     */
  +    public void generate() throws IOException, ProcessingException {
  +        try {
  +            if (expires > 0) {
  +                response.setDateHeader("Expires", System.currentTimeMillis() + expires);
  +            }
  +
  +            try {
  +                inputStream = inputSource.getInputStream();
  +            }
  +            catch (SourceException se) {
  +                throw SourceUtil.handle("Error during resolving of the input stream", se);
  +            }
  +
  +            processStream();
  +
               inputStream.close();
  -            inputStream = null;
  -            out.flush();
  -            
  -            if (!this.quickTest) {
  -	            // if everything is ok, add this to the list of generated documents
  -	            final Request request = ObjectModelHelper.getRequest(this.objectModel);
  -	            documents.put(request.getRequestURI(), this.inputSource.getSystemId());
  +
  +            if (!quickTest) {
  +                // if everything is ok, add this to the list of generated documents
  +                // (see http://marc.theaimsgroup.com/?l=xml-cocoon-dev&m=102921894301915&w=2
)
  +                documents.put(request.getRequestURI(), inputSource.getSystemId());
               }
  -        } catch (SourceException se) {
  -            throw SourceUtil.handle("Exception during resolving of read source.", se);
  +        }
  +        catch (IOException e) {
  +            getLogger().debug("Received an IOException, assuming client severed connection
on purpose");
           }
       }
   
       /**
        * Returns the mime-type of the resource in process.
        */
  -    public String getMimeType () {
  -        Context ctx = ObjectModelHelper.getContext(this.objectModel);
  +    public String getMimeType() {
  +        Context ctx = ObjectModelHelper.getContext(objectModel);
   
           if (ctx != null) {
  -            if (ctx.getMimeType(this.source)!=null) {
  -                return ctx.getMimeType(this.source);
  -            } else {
  -                return this.inputSource.getMimeType();
  +            if (ctx.getMimeType(source) != null) {
  +                return ctx.getMimeType(source);
  +            }
  +            else {
  +                return inputSource.getMimeType();
               }
  -        } else {
  -           return this.inputSource.getMimeType();
  +        }
  +        else {
  +            return inputSource.getMimeType();
           }
       }
   
  
  
  
  1.1                  xml-cocoon2/src/java/org/apache/cocoon/util/ByteRange.java
  
  Index: ByteRange.java
  ===================================================================
  /*
  
   ============================================================================
                     The Apache Software License, Version 1.1
   ============================================================================
  
   Copyright (C) 1999-2002 The Apache Software Foundation. All rights reserved.
  
   Redistribution and use in source and binary forms, with or without modifica-
   tion, are permitted provided that the following conditions are met:
  
   1. Redistributions of  source code must  retain the above copyright  notice,
      this list of conditions and the following disclaimer.
  
   2. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
  
   3. The end-user documentation included with the redistribution, if any, must
      include  the following  acknowledgment:  "This product includes  software
      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
      Alternately, this  acknowledgment may  appear in the software itself,  if
      and wherever such third-party acknowledgments normally appear.
  
   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
      used to  endorse or promote  products derived from  this software without
      prior written permission. For written permission, please contact
      apache@apache.org.
  
   5. Products  derived from this software may not  be called "Apache", nor may
      "Apache" appear  in their name,  without prior written permission  of the
      Apache Software Foundation.
  
   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
   FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
   OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
   ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
   (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
   This software  consists of voluntary contributions made  by many individuals
   on  behalf of the Apache Software  Foundation and was  originally created by
   Stefano Mazzocchi  <stefano@apache.org>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.util;
  
  /**
   * @author <a href="mailto:stuart.roebuck@adolos.co.uk">Stuart Roebuck</a>
   * @version CVS $Id: ByteRange.java,v 1.1 2003/01/06 06:10:25 tcurdt Exp $
   */
  final public class ByteRange {
  
      
      private final long start;
      private final long end;
  
      
      public ByteRange(long start, long end) {
          this.start = start;
          this.end = end;
      }
  
      
      public ByteRange(String string) throws NumberFormatException {
          string = string.trim();
          int dashPos = string.indexOf('-');
          int length = string.length();
          if (string.indexOf(',') != -1) {
              throw new NumberFormatException("Simple ByteRange String contains a comma.");
          }
          if (dashPos > 0) {
              this.start = Integer.parseInt(string.substring(0, dashPos));
          } else {
              this.start = Long.MIN_VALUE;
          }
          if (dashPos < length - 1) {
              this.end = Integer.parseInt(string.substring(dashPos + 1, length));
          } else {
              this.end = Long.MAX_VALUE;
          }
          if (this.start > this.end) {
              throw new NumberFormatException("Start value is greater than end value.");
          }
      }
  
      
      public long getStart() {
          return this.start;
      }
  
      
      public long getEnd() {
          return this.end;
      }
  
      
      public long length() {
          return this.end - this.start + 1;
      }
  
      
      public ByteRange intersection(ByteRange range) {
          if (range.end < this.start || this.end < range.start) {
              return null;
          } else {
              long start = (this.start > range.start) ? this.start : range.start;
              long end = (this.end < range.end) ? this.end : range.end;
              return new ByteRange(start, end);
          }
      }
  
  
      public String toString() {
          return this.start + "-" + this.end;
      }
  
      
  }
  
  
  
  1.6       +67 -156   xml-cocoon2/src/scratchpad/src/org/apache/cocoon/reading/ImageReader.java
  
  Index: ImageReader.java
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/scratchpad/src/org/apache/cocoon/reading/ImageReader.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- ImageReader.java	2 Aug 2002 09:21:00 -0000	1.5
  +++ ImageReader.java	6 Jan 2003 06:10:25 -0000	1.6
  @@ -50,22 +50,10 @@
   */
   package org.apache.cocoon.reading;
   
  -import org.apache.avalon.framework.component.ComponentManager;
  -import org.apache.avalon.framework.component.Composable;
   import org.apache.avalon.framework.parameters.Parameters;
   
   import org.apache.cocoon.ProcessingException;
  -import org.apache.cocoon.caching.CacheableProcessingComponent;
  -import org.apache.cocoon.environment.Context;
  -import org.apache.cocoon.environment.ObjectModelHelper;
  -import org.apache.cocoon.environment.Request;
  -import org.apache.cocoon.environment.Response;
   import org.apache.cocoon.environment.SourceResolver;
  -import org.apache.cocoon.util.HashUtil;
  -
  -import org.apache.excalibur.source.Source;
  -import org.apache.excalibur.source.SourceException;
  -import org.apache.excalibur.source.SourceValidity;
   
   import com.sun.image.codec.jpeg.ImageFormatException;
   import com.sun.image.codec.jpeg.JPEGCodec;
  @@ -80,10 +68,6 @@
   import java.awt.image.WritableRaster;
   import java.io.ByteArrayOutputStream;
   import java.io.IOException;
  -import java.io.InputStream;
  -import java.io.OutputStream;
  -import java.io.Serializable;
  -import java.util.Date;
   import java.util.Map;
   
   /**
  @@ -106,110 +90,23 @@
    *
    * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
    * @author <a href="mailto:stephan@apache.org">Stephan Michels</a>
  + * @author <a href="mailto:tcurdt@apache.org">Torsten Curdt</a>
    * @version CVS $Revision$ $Date$
    */
    
  -public class ImageReader extends AbstractReader implements Composable, CacheableProcessingComponent
{
  -
  -    private ComponentManager manager;
  +final public class ImageReader extends ResourceReader {
   
  -    public void compose(ComponentManager manager) {
  -        this.manager = manager;
  -    }
  -
  -    private Source inputSource;
  -    private Response response;
  -    private Request request;
       private int width;
       private int height;
  -    private int expireTime;
  -    private String format;
   
  -    public void setup(SourceResolver resolver, Map objectModel, String src, Parameters
par)
  -        throws ProcessingException, SAXException, IOException {
  +    public void setup(SourceResolver resolver, Map objectModel, String src, Parameters
par) throws ProcessingException, SAXException, IOException {
           super.setup(resolver, objectModel, src, par);
  -        this.request = ObjectModelHelper.getRequest(objectModel);
  -        this.response = ObjectModelHelper.getResponse(objectModel);
  -        this.width = par.getParameterAsInteger("width", 0);
  -        this.height = par.getParameterAsInteger("height", 0);
  -        this.expireTime = par.getParameterAsInteger("expire-time", -1);
  -        getLogger().debug("Image data: [" + this.width + "x" + this.height + "] Expire
Time: " + expireTime);
  -
  -        try {
  -            this.inputSource = this.resolver.resolveURI(super.source);
  -        } catch (SourceException se) {
  -            throw new ProcessingException("Could not retrieve source '"+super.source+"'",
se);
  -        }
  -    }
  -
  -    /**
  -     * Generate the unique key.
  -     * This key must be unique inside the space of this component.
  -     * This method must be invoked before the generateValidity() method.
  -     *
  -     * @return The generated key or <code>null</code> if the component
  -     *              is currently not cacheable.
  -     */
  -    public Serializable generateKey() { 
  -       return this.inputSource.getSystemId() + ":" + width + height;
  -    }
   
  -    /**
  -     * Generate the validity object.
  -     * Before this method can be invoked the generateKey() method
  -     * must be invoked.
  -     *
  -     * @return The generated validity object or <code>null</code> if the
  -     *         component is currently not cacheable.
  -     */
  -    public SourceValidity generateValidity() {
  -        return this.inputSource.getValidity();
  -    }
  +        width = par.getParameterAsInteger("width", 0);
  +        height = par.getParameterAsInteger("height", 0);
   
  -    /**
  -     * @return the time the read source was last modified or 0 if it is not
  -     *         possible to detect
  -     */
  -    public long getLastModified() {
  -        return this.inputSource.getLastModified();
       }
   
  -    /**
  -     * Generates the requested resource.
  -     */
  -    public void generate() throws IOException, ProcessingException {
  -        if (expireTime > 0) {
  -            response.setDateHeader("Expires", new Date().getTime() + expireTime);
  -        }
  -    
  -        try {
  -            InputStream in = this.inputSource.getInputStream();
  -
  -            if ((width == 0) && (height == 0)) {
  -                read(in, out);
  -            } else {
  -                convert(in, width, height, out);
  -            }
  -        } catch (SourceException se) {
  -            throw new ProcessingException("Could not read source '"+super.source+"'", se);
  -        }
  -    }
  -    
  -    private void read(InputStream in, OutputStream out) {
  -        try {
  -            response.setHeader("Accept-Ranges", "bytes");
  -            byte[] buffer = new byte[8192];
  -            int length = -1;
  -            while ((length = in.read(buffer)) > -1) {
  -                out.write(buffer, 0, length);
  -            }
  -            in.close();
  -            out.flush();
  -        } catch (IOException ioe) {
  -            getLogger().debug("Received an IOException, assuming client severed connection
on purpose");
  -        }
  -    }
  -    
       /** 
        * Returns the affine transform that implements the scaling.
        * The behavior is the following: if both the new width and height values
  @@ -242,54 +139,68 @@
           return new AffineTransform(wm, 0.0d, 0.0d, hm, 0.0d, 0.0d);
       }
       
  -    /**
  -     * NOTE (SM): 
  -     * Due to Bug Id 4502892 (which is found in *all* JVM implementations from
  -     * 1.2.x and 1.3.x on all OS!), we must buffer the JPEG generation to avoid
  -     * that connection resetting by the peer (user pressing the stop button,
  -     * for example) crashes the entire JVM (yes, dude, the bug is *that* nasty
  -     * since it happens in JPEG routines which are native!)
  -     * I'm perfectly aware of the huge memory problems that this causes (almost
  -     * doubling memory consuption for each image and making the GC work twice
  -     * as hard) but it's *far* better than restarting the JVM every 2 minutes
  -     * (since this is the average experience for image-intensive web application
  -     * such as an image gallery).
  -     * Please, go to the <a href="http://developer.java.sun.com/developer/bugParade/bugs/4502892.html">Sun
Developers Connection</a>
  -     * and vote this BUG as the one you would like fixed sooner rather than 
  -     * later and all this hack will automagically go away.
  -     * Many deep thanks to Michael Hartle <mhartle@hartle-klug.com> for tracking
  -     * this down and suggesting the workaround.
  -     *
  -     * UPDATE (SM):
  -     * This appears to be fixed on JDK 1.4
  -     */
  -    private void convert(InputStream in, double w, double h, OutputStream out) throws IOException,
ProcessingException {
  -        try {
  -            JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(in);
  -            Raster original = decoder.decodeAsRaster();
  -            JPEGDecodeParam decodeParam = decoder.getJPEGDecodeParam();
  -            double ow = (double) decodeParam.getWidth();
  -            double oh = (double) decodeParam.getHeight();
  -            AffineTransformOp filter = new AffineTransformOp(getTransform(ow, oh, w, h),
AffineTransformOp.TYPE_BILINEAR);
  -            WritableRaster scaled = filter.createCompatibleDestRaster(original);
  -            filter.filter(original, scaled);
  -            // JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
  -            ByteArrayOutputStream bstream = new ByteArrayOutputStream();
  -            JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(bstream);
  -            encoder.encode(scaled);
  -            in.close();
  -            out.write(bstream.toByteArray());
  -            out.flush();        
  -        } catch (ImageFormatException e) {
  -            throw new ProcessingException("Error reading the image. Note that only JPEG
images are currently supported.");
  +    protected void processStream() throws IOException, ProcessingException {
  +        if (width > 0 || height > 0) {
  +            if (getLogger().isDebugEnabled()) {
  +                getLogger().debug("image " + ((width==0)?"?":Integer.toString(width)) +
"x" + ((height==0)?"?":Integer.toString(height)) +" expires: " + expires);
  +            }
  +
  +            // since we create the image on the fly
  +            response.setHeader("Accept-Ranges", "none");
  +
  +            /**
  +             * NOTE (SM):
  +             * Due to Bug Id 4502892 (which is found in *all* JVM implementations from
  +             * 1.2.x and 1.3.x on all OS!), we must buffer the JPEG generation to avoid
  +             * that connection resetting by the peer (user pressing the stop button,
  +             * for example) crashes the entire JVM (yes, dude, the bug is *that* nasty
  +             * since it happens in JPEG routines which are native!)
  +             * I'm perfectly aware of the huge memory problems that this causes (almost
  +             * doubling memory consuption for each image and making the GC work twice
  +             * as hard) but it's *far* better than restarting the JVM every 2 minutes
  +             * (since this is the average experience for image-intensive web application
  +             * such as an image gallery).
  +             * Please, go to the <a href="http://developer.java.sun.com/developer/bugParade/bugs/4502892.html">Sun
Developers Connection</a>
  +             * and vote this BUG as the one you would like fixed sooner rather than
  +             * later and all this hack will automagically go away.
  +             * Many deep thanks to Michael Hartle <mhartle@hartle-klug.com> for tracking
  +             * this down and suggesting the workaround.
  +             *
  +             * UPDATE (SM):
  +             * This appears to be fixed on JDK 1.4
  +             */
  +
  +            try {
  +                JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(inputStream);
  +                Raster original = decoder.decodeAsRaster();
  +                JPEGDecodeParam decodeParam = decoder.getJPEGDecodeParam();
  +                double ow = (double) decodeParam.getWidth();
  +                double oh = (double) decodeParam.getHeight();
  +                AffineTransformOp filter = new AffineTransformOp(getTransform(ow, oh, width,
height), AffineTransformOp.TYPE_BILINEAR);
  +                WritableRaster scaled = filter.createCompatibleDestRaster(original);
  +                filter.filter(original, scaled);
  +
  +                // JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
  +
  +                ByteArrayOutputStream bstream = new ByteArrayOutputStream();
  +                JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(bstream);
  +                encoder.encode(scaled);
  +                out.write(bstream.toByteArray());
  +
  +                out.flush();
  +            } catch (ImageFormatException e) {
  +                throw new ProcessingException("Error reading the image. Note that only
JPEG images are currently supported.");
  +            }
  +
  +            inputStream.close();
  +        }
  +        else {
  +            // only read the resource - no modifications requested
  +            if (getLogger().isDebugEnabled()) {
  +                getLogger().debug("passing original resource");
  +            }
  +            super.processStream();
           }
       }
   
  -    /**
  -     * Returns the mime-type of the resource in process.
  -     */
  -    public String getMimeType() {
  -        Context ctx = ObjectModelHelper.getContext(objectModel);
  -        return (ctx != null) ? ctx.getMimeType(this.source) : null;
  -    }
   }
  
  
  

----------------------------------------------------------------------
In case of troubles, e-mail:     webmaster@xml.apache.org
To unsubscribe, e-mail:          cocoon-cvs-unsubscribe@xml.apache.org
For additional commands, e-mail: cocoon-cvs-help@xml.apache.org


Mime
View raw message