Java tutorial
/* * JetS3t : Java S3 Toolkit * Project hosted at http://bitbucket.org/jmurty/jets3t/ * * Copyright 2006-2010 James Murty * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jets3t.service.impl.rest.httpclient; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpResponse; import org.apache.http.util.EntityUtils; import org.jets3t.service.io.InputStreamWrapper; import org.jets3t.service.io.InterruptableInputStream; /** * Utility class to wrap InputStreams obtained from an HttpClient library's HttpMethod object, and * ensure the stream and HTTP connection is cleaned up properly. * <p> * This input stream wrapper is used to ensure that input streams obtained through HttpClient * connections are cleaned up correctly once the caller has read all the contents of the * connection's input stream, or closed that input stream. * </p> * <p> * <b>Important!</b> This input stream must be completely consumed or closed to ensure the necessary * cleanup operations can be performed. * </p> * * @author James Murty * */ public class HttpMethodReleaseInputStream extends InputStream implements InputStreamWrapper { private static final Log log = LogFactory.getLog(HttpMethodReleaseInputStream.class); private InputStream inputStream = null; private HttpResponse httpResponse = null; private boolean alreadyReleased = false; private boolean underlyingStreamConsumed = false; /** * Constructs an input stream based on an {@link HttpResponse} object representing an HTTP connection. * If a connection input stream is available, this constructor wraps the underlying input stream * in an {@link InterruptableInputStream} and makes that stream available. If no underlying connection * is available, an empty {@link ByteArrayInputStream} is made available. * * @param httpMethod */ public HttpMethodReleaseInputStream(HttpResponse httpMethod) { this.httpResponse = httpMethod; try { this.inputStream = new InterruptableInputStream(httpMethod.getEntity().getContent()); } catch (IOException e) { if (log.isWarnEnabled()) { log.warn("Unable to obtain HttpMethod's response data stream", e); } try { EntityUtils.consume(httpMethod.getEntity()); } catch (Exception ee) { // ignore } this.inputStream = new ByteArrayInputStream(new byte[] {}); // Empty input stream; } } /** * Returns the underlying HttpResponse object. * * @return * the HttpResponse object that provides the data input stream. */ public HttpResponse getHttpResponse() { return httpResponse; } /** * Forces the release of an HttpMethod's connection in a way that will perform all the necessary * cleanup through the correct use of HttpClient methods. * * @throws IOException */ protected void releaseConnection() throws IOException { if (!alreadyReleased) { if (!underlyingStreamConsumed) { // Underlying input stream has not been consumed, // trigger connection close and clean-up. EntityUtils.consume(httpResponse.getEntity()); } alreadyReleased = true; } } /** * Standard input stream read method, except it calls {@link #releaseConnection} when the underlying * input stream is consumed. */ @Override public int read() throws IOException { try { int read = inputStream.read(); if (read == -1) { underlyingStreamConsumed = true; if (!alreadyReleased) { releaseConnection(); if (log.isDebugEnabled()) { log.debug("Released HttpMethod as its response data stream is fully consumed"); } } } return read; } catch (IOException e) { try { releaseConnection(); } catch (IOException ignored) { // } if (log.isDebugEnabled()) { log.debug("Released HttpMethod as its response data stream threw an exception", e); } throw e; } } /** * Standard input stream read method, except it calls {@link #releaseConnection} when the underlying * input stream is consumed. */ @Override public int read(byte[] b, int off, int len) throws IOException { try { int read = inputStream.read(b, off, len); if (read == -1) { underlyingStreamConsumed = true; if (!alreadyReleased) { releaseConnection(); if (log.isDebugEnabled()) { log.debug("Released HttpMethod as its response data stream is fully consumed"); } } } return read; } catch (IOException e) { try { releaseConnection(); } catch (IOException ignored) { // } if (log.isDebugEnabled()) { log.debug("Released HttpMethod as its response data stream threw an exception", e); } throw e; } } @Override public int available() throws IOException { try { return inputStream.available(); } catch (IOException e) { try { releaseConnection(); } catch (IOException ignored) { // } if (log.isDebugEnabled()) { log.debug("Released HttpMethod as its response data stream threw an exception", e); } throw e; } } /** * Standard input stream close method, except it ensures that {@link #releaseConnection()} is called * before the input stream is closed. */ @Override public void close() throws IOException { if (!alreadyReleased) { releaseConnection(); if (log.isDebugEnabled()) { log.debug("Released HttpMethod as its response data stream is closed"); } } inputStream.close(); } /** * Tries to ensure a connection is always cleaned-up correctly by calling {@link #releaseConnection()} * on class destruction if the cleanup hasn't already been done. * <p> * This desperate cleanup act will only be necessary if the user of this class does not completely * consume or close this input stream prior to object destruction. This method will log Warning * messages if a forced cleanup is required, hopefully reminding the user to close their streams * properly. */ @Override protected void finalize() throws Throwable { if (!alreadyReleased) { if (log.isWarnEnabled()) { log.warn( "Attempting to release HttpMethod in finalize() as its response data stream has gone out of scope. " + "This attempt will not always succeed and cannot be relied upon! Please ensure response data streams are " + "always fully consumed or closed to avoid HTTP connection starvation."); } releaseConnection(); if (log.isWarnEnabled()) { log.warn("Successfully released HttpMethod in finalize(). You were lucky this time... " + "Please ensure response data streams are always fully consumed or closed."); } } super.finalize(); } /** * @return * the underlying input stream wrapped by this class. */ public InputStream getWrappedInputStream() { return inputStream; } }