/*
* ResponseObserver.java February 2007
*
* Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net>
*
* 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.simpleframework.http.core;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.simpleframework.http.core.ContainerEvent.ERROR;
import static org.simpleframework.http.core.ContainerEvent.RESPONSE_FINISHED;
import java.util.concurrent.atomic.AtomicBoolean;
import org.simpleframework.http.message.Entity;
import org.simpleframework.transport.Channel;
import org.simpleframework.transport.ByteWriter;
import org.simpleframework.transport.trace.Trace;
/**
* The <code>ResponseObserver</code> is used to observe the response
* streams. If there is an error or a close requested this will
* close the underlying transport. If however there is a successful
* response then this will flush the transport and hand the channel
* for the pipeline back to the server kernel. This ensures that
* the next HTTP request can be consumed from the transport.
*
* @author Niall Gallagher
*/
class ResponseObserver implements BodyObserver {
/**
* This is used to determine if the response has committed.
*/
private AtomicBoolean committed;
/**
* This flag determines whether the connection was closed.
*/
private AtomicBoolean closed;
/**
* This flag determines whether the was a response error.
*/
private AtomicBoolean error;
/**
* This is the controller used to initiate a new request.
*/
private Controller controller;
/**
* This is the channel associated with the client connection.
*/
private Channel channel;
/**
* This is the trace used to observe the state of the stream.
*/
private Trace trace;
/**
* This represents a time stamp that records the finish time.
*/
private Timer timer;
/**
* Constructor for the <code>ResponseObserver</code> object. This
* is used to create an observer using a HTTP request entity and an
* initiator which is used to reprocess a channel if there was a
* successful deliver of a response.
*
* @param controller the controller used to process channels
* @param entity this is the entity associated with the channel
*/
public ResponseObserver(Controller controller, Entity entity) {
this.timer = new Timer(MILLISECONDS);
this.committed = new AtomicBoolean();
this.closed = new AtomicBoolean();
this.error = new AtomicBoolean();
this.channel = entity.getChannel();
this.trace = channel.getTrace();
this.controller = controller;
}
/**
* This is used to close the underlying transport. A closure is
* typically done when the response is to a HTTP/1.0 client
* that does not require a keep alive connection. Also, if the
* container requests an explicit closure this is used when all
* of the content for the response has been sent.
*
* @param writer this is the writer used to send the response
*/
public void close(ByteWriter writer) {
try {
if(!isClosed()) {
closed.set(true);
timer.set();
trace.trace(RESPONSE_FINISHED);
writer.close();
}
} catch(Exception cause) {
trace.trace(ERROR, cause);
fail(writer);
}
}
/**
* This is used when there is an error sending the response. On
* error RFC 2616 suggests a connection closure is the best
* means to handle the condition, and the one clients should be
* expecting and support. All errors result in closure of the
* underlying transport and no more requests are processed.
*
* @param writer this is the writer used to send the response
*/
public void error(ByteWriter writer) {
try {
if(!isClosed()) {
error.set(true);
timer.set();
trace.trace(RESPONSE_FINISHED);
writer.close();
}
} catch(Exception cause) {
trace.trace(ERROR, cause);
fail(writer);
}
}
/**
* This is used when the response has been sent correctly and
* the connection supports persisted HTTP. When ready the channel
* is handed back in to the server kernel where the next request
* on the pipeline is read and used to compose the next entity.
*
* @param writer this is the writer used to send the response
*/
public void ready(ByteWriter writer) {
try {
if(!isClosed()) {
closed.set(true);
writer.flush();
timer.set();
trace.trace(RESPONSE_FINISHED);
controller.start(channel);
}
} catch(Exception cause) {
trace.trace(ERROR, cause);
fail(writer);
}
}
/**
* This is used to purge the writer so that it closes the socket
* ensuring there is no connection leak on shutdown. This is used
* when there is an exception signalling the state of the writer.
*
* @param writer this is the writer that is to be purged
*/
private void fail(ByteWriter writer) {
try {
writer.close();
} catch(Exception cause) {
trace.trace(ERROR, cause);
}
}
/**
* This is used to notify the observer that the HTTP response is
* committed and that the header can no longer be changed. It
* is also used to indicate whether the response can be reset.
*
* @param writer this is the writer used to send the response
*/
public void commit(ByteWriter writer) {
committed.set(true);
}
/**
* This can be used to determine whether the response has been
* committed. If the response is committed then the header can
* no longer be manipulated and the response has been partially
* send to the client.
*
* @return true if the response headers have been committed
*/
public boolean isCommitted() {
return committed.get();
}
/**
* This is used to determine if the response has completed or
* if there has been an error. This basically allows the writer
* of the response to take action on certain I/O events.
*
* @return this returns true if there was an error or close
*/
public boolean isClosed() {
return closed.get() || error.get();
}
/**
* This is used to determine if the response was in error. If
* the response was in error this allows the writer to throw an
* exception indicating that there was a problem responding.
*
* @return this returns true if there was a response error
*/
public boolean isError(){
return error.get();
}
/**
* This represents the time at which the response was either
* ready, closed or in error. Providing a time here is useful
* as it allows the time taken to generate a response to be
* determined even if the response is written asynchronously.
*
* @return the time when the response completed or failed
*/
public long getTime() {
return timer.get();
}
}