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 */
027package org.apache.http.nio.protocol;
028
029import java.io.IOException;
030import java.util.concurrent.atomic.AtomicBoolean;
031
032import org.apache.http.HttpEntity;
033import org.apache.http.HttpException;
034import org.apache.http.HttpResponse;
035import org.apache.http.entity.ContentType;
036import org.apache.http.nio.ContentDecoder;
037import org.apache.http.nio.IOControl;
038import org.apache.http.protocol.HttpContext;
039
040/**
041 * Abstract {@link HttpAsyncResponseConsumer} implementation that relieves its
042 * subclasses from having to manage internal state and provides a number of protected
043 * event methods that they need to implement.
044 *
045 * @since 4.2
046 */
047public abstract class AbstractAsyncResponseConsumer<T> implements HttpAsyncResponseConsumer<T> {
048
049    private final AtomicBoolean completed;
050
051    private volatile T result;
052    private volatile Exception ex;
053
054    public AbstractAsyncResponseConsumer() {
055        super();
056        this.completed = new AtomicBoolean(false);
057    }
058
059    /**
060     * Invoked when a HTTP response message is received. Please note
061     * that the {@link #onContentReceived(ContentDecoder, IOControl)} method
062     * will be invoked only if the response messages has a content entity
063     * enclosed.
064     *
065     * @param response HTTP response message.
066     * @throws HttpException in case of HTTP protocol violation
067     * @throws IOException in case of an I/O error
068     */
069    protected abstract void onResponseReceived(
070            HttpResponse response) throws HttpException, IOException;
071
072    /**
073     * Invoked to process a chunk of content from the {@link ContentDecoder}.
074     * The {@link IOControl} interface can be used to suspend input events
075     * if the consumer is temporarily unable to consume more content.
076     * <p>
077     * The consumer can use the {@link ContentDecoder#isCompleted()} method
078     * to find out whether or not the message content has been fully consumed.
079     *
080     * @param decoder content decoder.
081     * @param ioctrl I/O control of the underlying connection.
082     * @throws IOException in case of an I/O error
083     */
084    protected abstract void onContentReceived(
085            ContentDecoder decoder, IOControl ioctrl) throws IOException;
086
087    /**
088     * Invoked if the response message encloses a content entity.
089     *
090     * @param entity HTTP entity
091     * @param contentType expected content type.
092     * @throws IOException in case of an I/O error
093     */
094    protected abstract void onEntityEnclosed(
095            HttpEntity entity, ContentType contentType) throws IOException;
096
097    /**
098     * Invoked to generate a result object from the received HTTP response
099     * message.
100     *
101     * @param context HTTP context.
102     * @return result of the response processing.
103     * @throws Exception in case of an abnormal termination.
104     */
105    protected abstract T buildResult(HttpContext context) throws Exception;
106
107    /**
108     * Invoked to release all system resources currently allocated.
109     */
110    protected abstract void releaseResources();
111
112    /**
113     * Invoked when the consumer is being closed.
114     * @throws IOException may be thrown by subclassses
115     *
116     * @since 4.3
117     */
118    protected void onClose() throws IOException {
119    }
120
121    /**
122     * @since 4.4
123     */
124    protected ContentType getContentType(final HttpEntity entity) {
125        return entity != null ? ContentType.getOrDefault(entity) : null;
126    }
127
128    /**
129     * Use {@link #onResponseReceived(HttpResponse)} instead.
130     */
131    @Override
132    public final void responseReceived(
133            final HttpResponse response) throws IOException, HttpException {
134        onResponseReceived(response);
135        final HttpEntity entity = response.getEntity();
136        if (entity != null) {
137            onEntityEnclosed(entity, getContentType(entity));
138        }
139    }
140
141    /**
142     * Use {@link #onContentReceived(ContentDecoder, IOControl)} instead.
143     */
144    @Override
145    public final void consumeContent(
146            final ContentDecoder decoder, final IOControl ioctrl) throws IOException {
147        onContentReceived(decoder, ioctrl);
148    }
149
150    /**
151     * Use {@link #buildResult(HttpContext)} instead.
152     */
153    @Override
154    public final void responseCompleted(final HttpContext context) {
155        if (this.completed.compareAndSet(false, true)) {
156            try {
157                this.result = buildResult(context);
158            } catch (final Exception ex) {
159                this.ex = ex;
160            } finally {
161                releaseResources();
162            }
163        }
164    }
165
166    @Override
167    public final boolean cancel() {
168        if (this.completed.compareAndSet(false, true)) {
169            releaseResources();
170            return true;
171        }
172        return false;
173    }
174
175    @Override
176    public final void failed(final Exception ex) {
177        if (this.completed.compareAndSet(false, true)) {
178            this.ex = ex;
179            releaseResources();
180        }
181    }
182
183    @Override
184    public final void close() throws IOException {
185        if (this.completed.compareAndSet(false, true)) {
186            releaseResources();
187            onClose();
188        }
189    }
190
191    @Override
192    public Exception getException() {
193        return this.ex;
194    }
195
196    @Override
197    public T getResult() {
198        return this.result;
199    }
200
201    @Override
202    public boolean isDone() {
203        return this.completed.get();
204    }
205
206}