001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.impl.execchain;
029
030import java.io.IOException;
031import java.io.InputStream;
032import java.io.OutputStream;
033import java.net.SocketException;
034
035import org.apache.http.HttpEntity;
036import org.apache.http.HttpResponse;
037import org.apache.http.conn.EofSensorInputStream;
038import org.apache.http.conn.EofSensorWatcher;
039import org.apache.http.entity.HttpEntityWrapper;
040
041/**
042 * A wrapper class for {@link HttpEntity} enclosed in a response message.
043 *
044 * @since 4.3
045 */
046class ResponseEntityProxy extends HttpEntityWrapper implements EofSensorWatcher {
047
048    private final ConnectionHolder connHolder;
049
050    public static void enchance(final HttpResponse response, final ConnectionHolder connHolder) {
051        final HttpEntity entity = response.getEntity();
052        if (entity != null && entity.isStreaming() && connHolder != null) {
053            response.setEntity(new ResponseEntityProxy(entity, connHolder));
054        }
055    }
056
057    ResponseEntityProxy(final HttpEntity entity, final ConnectionHolder connHolder) {
058        super(entity);
059        this.connHolder = connHolder;
060    }
061
062    private void cleanup() throws IOException {
063        if (this.connHolder != null) {
064            this.connHolder.close();
065        }
066    }
067
068    private void abortConnection() throws IOException {
069        if (this.connHolder != null) {
070            this.connHolder.abortConnection();
071        }
072    }
073
074    public void releaseConnection() throws IOException {
075        if (this.connHolder != null) {
076            this.connHolder.releaseConnection();
077        }
078    }
079
080    @Override
081    public boolean isRepeatable() {
082        return false;
083    }
084
085    @Override
086    public InputStream getContent() throws IOException {
087        return new EofSensorInputStream(this.wrappedEntity.getContent(), this);
088    }
089
090    @Deprecated
091    @Override
092    public void consumeContent() throws IOException {
093        releaseConnection();
094    }
095
096    @Override
097    public void writeTo(final OutputStream outstream) throws IOException {
098        try {
099            if (outstream != null) {
100                this.wrappedEntity.writeTo(outstream);
101            }
102            releaseConnection();
103        } catch (final IOException ex) {
104            abortConnection();
105            throw ex;
106        } catch (final RuntimeException ex) {
107            abortConnection();
108            throw ex;
109        } finally {
110            cleanup();
111        }
112    }
113
114    @Override
115    public boolean eofDetected(final InputStream wrapped) throws IOException {
116        try {
117            // there may be some cleanup required, such as
118            // reading trailers after the response body:
119            if (wrapped != null) {
120                wrapped.close();
121            }
122            releaseConnection();
123        } catch (final IOException ex) {
124            abortConnection();
125            throw ex;
126        } catch (final RuntimeException ex) {
127            abortConnection();
128            throw ex;
129        } finally {
130            cleanup();
131        }
132        return false;
133    }
134
135    @Override
136    public boolean streamClosed(final InputStream wrapped) throws IOException {
137        try {
138            final boolean open = connHolder != null && !connHolder.isReleased();
139            // this assumes that closing the stream will
140            // consume the remainder of the response body:
141            try {
142                if (wrapped != null) {
143                    wrapped.close();
144                }
145                releaseConnection();
146            } catch (final SocketException ex) {
147                if (open) {
148                    throw ex;
149                }
150            }
151        } catch (final IOException ex) {
152            abortConnection();
153            throw ex;
154        } catch (final RuntimeException ex) {
155            abortConnection();
156            throw ex;
157        } finally {
158            cleanup();
159        }
160        return false;
161    }
162
163    @Override
164    public boolean streamAbort(final InputStream wrapped) throws IOException {
165        cleanup();
166        return false;
167    }
168
169    @Override
170    public String toString() {
171        final StringBuilder sb = new StringBuilder("ResponseEntityProxy{");
172        sb.append(wrappedEntity);
173        sb.append('}');
174        return sb.toString();
175    }
176
177}