You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@avalon.apache.org by cr...@apache.org on 2003/07/03 19:03:10 UTC

cvs commit: avalon-excalibur/sourceresolve/src/java/org/apache/excalibur/source/impl HTTPClientSource.java

crafterm    2003/07/03 10:03:10

  Modified:    sourceresolve/src/java/org/apache/excalibur/source/impl
                        HTTPClientSource.java
  Log:
  HTTPClientSource now implements ModifiableSource allowing
  output stream writes via HTTP PUT, and deletes via HTTP DELETE.
  
  Revision  Changes    Path
  1.4       +283 -3    avalon-excalibur/sourceresolve/src/java/org/apache/excalibur/source/impl/HTTPClientSource.java
  
  Index: HTTPClientSource.java
  ===================================================================
  RCS file: /home/cvs/avalon-excalibur/sourceresolve/src/java/org/apache/excalibur/source/impl/HTTPClientSource.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- HTTPClientSource.java	3 Jul 2003 14:40:48 -0000	1.3
  +++ HTTPClientSource.java	3 Jul 2003 17:03:09 -0000	1.4
  @@ -56,6 +56,9 @@
   
   import java.io.IOException;
   import java.io.InputStream;
  +import java.io.File;
  +import java.io.FileInputStream;
  +import java.io.FileOutputStream;
   import java.io.OutputStream;
   import java.util.Date;
   import java.util.Iterator;
  @@ -63,6 +66,7 @@
   
   import org.apache.avalon.framework.activity.Initializable;
   import org.apache.avalon.framework.logger.AbstractLogEnabled;
  +import org.apache.avalon.framework.logger.Logger;
   import org.apache.avalon.framework.parameters.ParameterException;
   import org.apache.avalon.framework.parameters.Parameterizable;
   import org.apache.avalon.framework.parameters.Parameters;
  @@ -73,10 +77,13 @@
   import org.apache.commons.httpclient.HttpStatus;
   import org.apache.commons.httpclient.NameValuePair;
   import org.apache.commons.httpclient.URIException;
  +import org.apache.commons.httpclient.methods.DeleteMethod;
   import org.apache.commons.httpclient.methods.GetMethod;
   import org.apache.commons.httpclient.methods.HeadMethod;
  +import org.apache.commons.httpclient.methods.PutMethod;
   import org.apache.commons.httpclient.methods.PostMethod;
  -import org.apache.excalibur.source.Source;
  +import org.apache.excalibur.source.ModifiableSource;
  +import org.apache.excalibur.source.SourceException;
   import org.apache.excalibur.source.SourceNotFoundException;
   import org.apache.excalibur.source.SourceParameters;
   import org.apache.excalibur.source.SourceResolver;
  @@ -93,7 +100,7 @@
    * @version CVS $Id$
    */
   public class HTTPClientSource extends AbstractLogEnabled 
  -    implements Source, Initializable, Parameterizable
  +    implements ModifiableSource, Initializable, Parameterizable
   {
       /**
        * Constant used for identifying POST requests.
  @@ -342,6 +349,37 @@
       }
   
       /**
  +     * Factory method to create a {@link PutMethod} object.
  +     *
  +     * @param uri URI to upload <code>uploadFile</code> to
  +     * @param uploadFile {@link File} to be uploaded
  +     * @return a {@link PutMethod} instance
  +     * @exception IOException if an error occurs
  +     */
  +    private PutMethod createPutMethod(
  +        final String uri, final File uploadFile
  +    )
  +        throws IOException
  +    {
  +        final PutMethod put = new PutMethod( uri );
  +        put.setRequestBody( 
  +            new FileInputStream( uploadFile.getAbsolutePath() ) 
  +        );
  +        return put;
  +    }
  +
  +    /**
  +     * Factory method to create a {@link DeleteMethod} object.
  +     *
  +     * @param uri URI to delete
  +     * @return {@link DeleteMethod} instance.
  +     */
  +    private DeleteMethod createDeleteMethod( final String uri )
  +    {
  +        return new DeleteMethod( uri );
  +    }
  +
  +    /**
        * Method to make response data available if possible without
        * actually making an actual request (ie. via HTTP HEAD).
        */
  @@ -618,5 +656,247 @@
       private void recycle()
       {
           m_dataValid = false;
  +    }
  +
  +    /////////////////////////// ModifiableSource methods
  +
  +    /**
  +     * Obtain an {@link OutputStream} to write to. The {@link OutputStream}
  +     * returned actually references a temporary local file, which will
  +     * be written to the server upon closing.
  +     *
  +     * @return an {@link OutputStream} instance
  +     * @exception IOException if an error occurs
  +     */
  +    public OutputStream getOutputStream() throws IOException
  +    {
  +        final File tempFile = File.createTempFile("httpclient", "tmp");
  +        return new WrappedFileOutputStream( tempFile, getLogger() );
  +    }
  +
  +    /**
  +     * Internal class which extends {@link FileOutputStream} to 
  +     * automatically upload the data written to it, upon a {@link #close}
  +     * operation.
  +     */
  +    private class WrappedFileOutputStream extends FileOutputStream
  +    {
  +        /**
  +         * Reference to the File being written itself.
  +         */
  +        private File m_file;
  +
  +        /**
  +         * Reference to a {@link Logger}.
  +         */
  +        private final Logger m_logger;
  +
  +        /**
  +         * Constructor, creates a new {@link WrappedFileOutputStream}
  +         * instance.
  +         *
  +         * @param file {@link File} to write to.
  +         * @param logger {@link Logger} reference.
  +         * @exception IOException if an error occurs
  +         */
  +        public WrappedFileOutputStream( final File file, final Logger logger )
  +            throws IOException
  +        {
  +            super( file );
  +            m_file = file;
  +            m_logger = logger;
  +        }
  +
  +        /**
  +         * Closes the stream, and uploads the file written to the
  +         * server.
  +         *
  +         * @exception IOException if an error occurs
  +         */
  +        public void close() throws IOException
  +        {
  +            super.close();
  +
  +            if ( m_file != null )
  +            {
  +                upload();
  +                m_file.delete();
  +                m_file = null;
  +            }
  +        }
  +
  +        /**
  +         * Method to test whether this stream can be closed.
  +         *
  +         * @return <code>true</code> if possible, false otherwise.
  +         */
  +        public boolean canCancel()
  +        {
  +            return m_file != null;
  +        }
  +
  +        /**
  +         * Cancels this stream.
  +         *
  +         * @exception IOException if stream is already closed
  +         */
  +        public void cancel() throws IOException
  +        {
  +            if ( m_file == null )
  +            {
  +                throw new IOException( "Stream already closed" );
  +            }
  +
  +            super.close();
  +            m_file.delete();
  +            m_file = null;
  +        }
  +
  +        /**
  +         * Helper method to attempt uploading of the local data file
  +         * to the remove server via a HTTP PUT.
  +         *
  +         * @exception IOException if an error occurs
  +         */
  +        private void upload()
  +            throws IOException
  +        {
  +            HttpMethod uploader = null;
  +
  +            if ( m_logger.isDebugEnabled() )
  +            {
  +                m_logger.debug( "Stream closed, writing data to " + m_uri );
  +            }
  +
  +            try
  +            {
  +                uploader = createPutMethod( m_uri, m_file );
  +                final int response = executeMethod( uploader );
  +
  +                if ( !successfulUpload( response ) )
  +                {
  +                    throw new SourceException( 
  +                        "Write to " + m_uri + " failed (" + response + ")"
  +                    );
  +                }
  +
  +                if ( m_logger.isDebugEnabled() )
  +                {
  +                    m_logger.debug( 
  +                        "Write to " + m_uri + " succeeded (" + response + ")"
  +                    );
  +                }
  +            }
  +            finally
  +            {
  +                if ( uploader != null )
  +                {
  +                    uploader.releaseConnection();
  +                }
  +            }
  +        }
  +
  +        /**
  +         * According to RFC2616 (HTTP 1.1) valid responses for a HTTP PUT
  +         * are 201 (Created), 200 (OK), and 204 (No Content).
  +         *
  +         * @param response response code from the HTTP PUT
  +         * @return true if upload was successful, false otherwise.
  +         */
  +        private boolean successfulUpload( final int response )
  +        {
  +            return response == HttpStatus.SC_OK
  +                || response == HttpStatus.SC_CREATED
  +                || response == HttpStatus.SC_NO_CONTENT;
  +        }
  +    }
  +
  +    /**
  +     * Deletes the referenced resource.
  +     *
  +     * @exception SourceException if an error occurs
  +     */
  +    public void delete() throws SourceException
  +    {
  +        try
  +        {
  +            final int response =
  +                executeMethod( createDeleteMethod( m_uri ) );
  +
  +            if ( !deleteSuccessful( response ) )
  +            {
  +                throw new SourceException(
  +                    "Failed to delete " + m_uri + " (" + response + ")"
  +                );
  +            }
  +
  +            if ( getLogger().isDebugEnabled() )
  +            {
  +                getLogger().debug( m_uri + " deleted (" + response + ")");
  +            }
  +        }
  +        catch ( final IOException e )
  +        {
  +            throw new SourceException(
  +                "IOException thrown during delete", e 
  +            );
  +        }
  +    }
  +
  +    /**
  +     * According to RFC2616 (HTTP 1.1) valid responses for a HTTP DELETE
  +     * are 200 (OK), 202 (Accepted) and 204 (No Content).
  +     *
  +     * @param response response code from the HTTP PUT
  +     * @return true if upload was successful, false otherwise.
  +     */
  +    private boolean deleteSuccessful( final int response )
  +    {
  +        return response == HttpStatus.SC_OK
  +            || response == HttpStatus.SC_ACCEPTED
  +            || response == HttpStatus.SC_NO_CONTENT;
  +    }
  +
  +    /**
  +     * Method to determine whether writing to the supplied OutputStream 
  +     * (which must be that returned from {@link #getOutputStream()}) can
  +     * be cancelled
  +     *
  +     * @return true if writing to the stream can be cancelled, 
  +     *         false otherwise
  +     */
  +    public boolean canCancel( final OutputStream stream )
  +    {
  +        // with help from FileSource, dankeschoen lads :)
  +
  +        if ( stream instanceof WrappedFileOutputStream )
  +        {
  +            return ((WrappedFileOutputStream) stream).canCancel();
  +        }
  +
  +        throw new IllegalArgumentException(
  +            "Output stream supplied was not created by this class"
  +        );
  +    }
  +
  +    /**
  +     * Cancels any data sent to the {@link OutputStream} returned by
  +     * {@link #getOutputStream()}.
  +     *
  +     * After calling this method, the supplied {@link OutputStream}
  +     * should no longer be used.
  +     */
  +    public void cancel( final OutputStream stream ) throws IOException
  +    {
  +        if ( stream instanceof WrappedFileOutputStream )
  +        {
  +            ((WrappedFileOutputStream) stream).cancel();
  +        }
  +        else
  +        {
  +            throw new IllegalArgumentException(
  +                "Output stream supplied was not created by this class"
  +            );
  +        }
       }
   }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: cvs-unsubscribe@avalon.apache.org
For additional commands, e-mail: cvs-help@avalon.apache.org