org.xsocket.connection.http.NonBlockingBodyDataSource Maven / Gradle / Ivy
/*
* Copyright (c) xsocket.org, 2006 - 2008. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
* The latest copy of this software may be found on http://www.xsocket.org/
*/
package org.xsocket.connection.http;
import java.io.Closeable;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xsocket.DataConverter;
import org.xsocket.IDataSource;
import org.xsocket.MaxReadSizeExceededException;
import org.xsocket.connection.AbstractNonBlockingStream;
import org.xsocket.connection.IConnection;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.http.AbstractHttpMessage.BodyType;
/**
*
* I/O resource capable of providing body data in a non blocking way.
* Read operations returns immediately
*
* @author grro
*
*/
public class NonBlockingBodyDataSource implements IDataSource, ReadableByteChannel, Closeable, Cloneable {
private static final Logger LOG = Logger.getLogger(NonBlockingBodyDataSource.class.getName());
// delegee classes
private final NonBlockingStream nonBlockingStream = new NonBlockingStream();
private final HandlerCaller handlerCaller = new HandlerCaller();
// close flag
private final AtomicBoolean isOpen = new AtomicBoolean(true);
private final AtomicBoolean isUnderlyingConnectionOpen = new AtomicBoolean(true);
// receive timeout handling
private static final long MIN_WATCHDOG_PERIOD_MILLIS = 10 * 1000;
public static final Integer DEFAULT_RECEIVE_TIMEOUT_SEC = Integer.MAX_VALUE;
private int receiveTimeoutSec = DEFAULT_RECEIVE_TIMEOUT_SEC;
private long creationTimeMillis = 0;
private long lastTimeDataReceivedMillis = System.currentTimeMillis();
private TimeoutWatchDogTask watchDogTask = null;
// the underlying connection
private AbstractHttpConnection httpConnection = null;
// listener management
private List completeListeners = new ArrayList();
private final AtomicBoolean isComplete = new AtomicBoolean(false);
// handler management
private IBodyDataHandler handler = null;
private boolean isMultithreaded = true;
private boolean isSystem = false;
private boolean suspendHandling = false;
// exception support
private final AtomicReference exceptionHolder = new AtomicReference();
/**
* constructor
*
* @param encoding the encoding
*/
NonBlockingBodyDataSource(String encoding) {
nonBlockingStream.setEncoding(encoding);
}
/**
* constructor
*
* @param encoding the encoding
* @param httpConnection the underlying httpConnection
*/
NonBlockingBodyDataSource(String encoding, AbstractHttpConnection httpConnection) {
nonBlockingStream.setEncoding(encoding);
this.httpConnection = httpConnection;
}
/**
* constructor
*
* @param body the body
* @param encoding the encoding
*/
NonBlockingBodyDataSource(String body, String encoding) {
nonBlockingStream.setEncoding(encoding);
nonBlockingStream.append(DataConverter.toByteBuffer(body, encoding));
isComplete.set(true);
}
/**
* constructor
*
* @param body the body
* @param encoding the encoding
*/
NonBlockingBodyDataSource(byte[] body, String encoding) {
nonBlockingStream.setEncoding(encoding);
nonBlockingStream.append(DataConverter.toByteBuffer(body));
isComplete.set(true);
}
/**
* constructor
*
* @param body the body
* @param encoding the encoding
*/
NonBlockingBodyDataSource(ByteBuffer[] body, String encoding) {
if (encoding != null) {
nonBlockingStream.setEncoding(encoding);
}
nonBlockingStream.append(body);
isComplete.set(true);
}
/**
* constructor
*
* @param bodyDatasource the body data source
* @param encoding the encoding
*/
NonBlockingBodyDataSource(ReadableByteChannel bodyDatasource, String encoding) throws IOException {
this(bodyDatasource, 8192, encoding);
}
/**
* constructor
*
* @param bodyDatasource the body data source
* @param encoding the encoding
*/
NonBlockingBodyDataSource(FileChannel bodyDatasource, String encoding) throws IOException {
this(bodyDatasource, (int) bodyDatasource.size(), encoding);
}
public final void destroy() {
handler = null;
if (httpConnection != null) {
httpConnection.destroy();
}
}
final void onUnderlyingHttpConnectionClosed() {
isUnderlyingConnectionOpen.set(false);
// call body handler
callBodyHandler(true);
}
public boolean onDisconnect(INonBlockingConnection connection) throws IOException {
if (!isComplete.get()) {
setIOException(new IOException("connection has been closed (by peer?)"));
}
return true;
}
AbstractHttpConnection getHttpConnection() {
return httpConnection;
}
final void removeBodyParser() {
if (httpConnection != null) {
httpConnection.removeBodyParser();
}
}
BodyType getBodyType() {
return null;
}
/**
* set the receive time out by performing the call or send method
*
* @param receiveTimeout the receive timeout
*/
public void setReceiveTimeoutSec(int receiveTimeoutSec) {
if (receiveTimeoutSec <= 0) {
setIOException(new ReceiveTimeoutException(receiveTimeoutSec));
return;
}
creationTimeMillis = System.currentTimeMillis();
if (this.receiveTimeoutSec != receiveTimeoutSec) {
this.receiveTimeoutSec = receiveTimeoutSec;
long watchdogPeriod = receiveTimeoutSec * 100;
if (watchdogPeriod > MIN_WATCHDOG_PERIOD_MILLIS) {
watchdogPeriod = MIN_WATCHDOG_PERIOD_MILLIS;
}
updateWatchDog(watchdogPeriod);
}
}
private synchronized void updateWatchDog(long watchDogPeriod) {
terminateWatchDog();
watchDogTask = new TimeoutWatchDogTask(this);
AbstractHttpConnection.schedule(watchDogTask, watchDogPeriod, watchDogPeriod);
}
private synchronized void terminateWatchDog() {
if (watchDogTask != null) {
watchDogTask.cancel();
watchDogTask = null;
}
}
private void checkTimeouts() {
if (isComplete.get()) {
terminateWatchDog();
}
long currentTimeMillis = System.currentTimeMillis();
if (currentTimeMillis > (lastTimeDataReceivedMillis + (((long) receiveTimeoutSec) * 1000)) &&
currentTimeMillis > (creationTimeMillis + (((long) receiveTimeoutSec) * 1000))) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout reached. set exception");
}
setIOException(new ReceiveTimeoutException());
httpConnection.destroy();
}
}
/**
* returns the receive timeout
*
* @return the receive timeout
*/
public int getReceiveTimeoutSec() {
return receiveTimeoutSec;
}
final void setIOException(IOException ioe) {
if (exceptionHolder.get() == null) {
exceptionHolder.set(ioe);
}
closeSilence();
// terminate watch dog
terminateWatchDog();
// call body handler
callBodyHandler(true);
}
private void throwExceptionIfExist() throws IOException {
if (exceptionHolder.get() != null) {
IOException ex = exceptionHolder.get();
exceptionHolder.set(null);
throw ex;
}
}
/**
* constructor
*
* @param bodyDatasource the body data source
* @param encoding the encoding
*/
private NonBlockingBodyDataSource(ReadableByteChannel bodyDatasource, int chunkSize, String encoding) throws IOException {
nonBlockingStream.setEncoding(encoding);
List buffers = new ArrayList();
int read = 0;
do {
ByteBuffer transferBuffer = ByteBuffer.allocate(chunkSize);
read = bodyDatasource.read(transferBuffer);
if (read > 0) {
if (transferBuffer.remaining() == 0) {
transferBuffer.flip();
buffers.add(transferBuffer);
} else {
transferBuffer.flip();
buffers.add(transferBuffer.slice());
}
}
} while (read > 0);
nonBlockingStream.append(buffers.toArray(new ByteBuffer[buffers.size()]));
isComplete.set(true);
}
String getEncoding() {
return nonBlockingStream.getEncoding();
}
public boolean isOpen() {
return isOpen.get();
}
public void close() throws IOException {
terminateWatchDog();
nonBlockingStream.close();
}
private int getSize() throws IOException {
return nonBlockingStream.available();
}
/**
* returns the available bytes
*
* @return the number of available bytes, possibly zero, or -1 if the channel has reached end-of-stream
*/
public int available() throws IOException {
if ((exceptionHolder.get() != null) && (exceptionHolder.get().getClass() != ClosedChannelException.class)) {
IOException ex = exceptionHolder.get();
exceptionHolder.set(null);
throw ex;
}
int available = nonBlockingStream.available();
if ((available <= 0) && isComplete.get()) {
return -1;
} else {
return available;
}
}
final int size() throws IOException {
int available = nonBlockingStream.available();
if ((available <= 0) && isComplete.get()) {
return -1;
} else {
return available;
}
}
private int getVersion() throws IOException {
return nonBlockingStream.getReadBufferVersion();
}
public int getReadBufferVersion() throws IOException {
throwExceptionIfExist();
return nonBlockingStream.getReadBufferVersion();
}
/**
* Marks the read position in the connection. Subsequent calls to resetToReadMark() will attempt
* to reposition the connection to this point.
*
*/
public final void markReadPosition() {
nonBlockingStream.markReadPosition();
}
/**
* Resets to the marked read position. If the connection has been marked,
* then attempt to reposition it at the mark.
*
* @return true, if reset was successful
*/
public final boolean resetToReadMark() {
return nonBlockingStream.resetToReadMark();
}
/**
* remove the read mark
*/
public final void removeReadMark() {
nonBlockingStream.removeReadMark();
}
public int indexOf(String str) throws IOException {
throwExceptionIfExist();
return nonBlockingStream.indexOf(str);
}
public int indexOf(String str, String encoding) throws IOException, MaxReadSizeExceededException {
throwExceptionIfExist();
return nonBlockingStream.indexOf(str, encoding);
}
public byte readByte() throws IOException, BufferUnderflowException {
throwExceptionIfExist();
return nonBlockingStream.readByte();
}
public short readShort() throws IOException, BufferUnderflowException {
throwExceptionIfExist();
return nonBlockingStream.readShort();
}
public int readInt() throws IOException, BufferUnderflowException {
throwExceptionIfExist();
return nonBlockingStream.readInt();
}
public long readLong() throws IOException, BufferUnderflowException {
throwExceptionIfExist();
return nonBlockingStream.readLong();
}
public double readDouble() throws IOException, BufferUnderflowException {
throwExceptionIfExist();
return nonBlockingStream.readDouble();
}
public int read(ByteBuffer buffer) throws IOException {
throwExceptionIfExist();
return nonBlockingStream.read(buffer);
};
public ByteBuffer[] readByteBufferByDelimiter(String delimiter) throws IOException, BufferUnderflowException {
throwExceptionIfExist();
return nonBlockingStream.readByteBufferByDelimiter(delimiter);
}
public ByteBuffer[] readByteBufferByDelimiter(String delimiter, int maxLength) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
throwExceptionIfExist();
return nonBlockingStream.readByteBufferByDelimiter(delimiter, maxLength);
}
public ByteBuffer[] readByteBufferByLength(int length) throws IOException, BufferUnderflowException {
throwExceptionIfExist();
return nonBlockingStream.readByteBufferByLength(length);
}
public byte[] readBytesByDelimiter(String delimiter) throws IOException, BufferUnderflowException {
throwExceptionIfExist();
return nonBlockingStream.readBytesByDelimiter(delimiter);
}
public byte[] readBytesByDelimiter(String delimiter, int maxLength) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
throwExceptionIfExist();
return nonBlockingStream.readBytesByDelimiter(delimiter, maxLength);
}
public byte[] readBytesByLength(int length) throws IOException, BufferUnderflowException {
throwExceptionIfExist();
return nonBlockingStream.readBytesByLength(length);
}
public String readStringByDelimiter(String delimiter) throws IOException, BufferUnderflowException, UnsupportedEncodingException {
throwExceptionIfExist();
return nonBlockingStream.readStringByDelimiter(delimiter);
}
public String readStringByDelimiter(String delimiter, int maxLength) throws IOException, BufferUnderflowException, UnsupportedEncodingException, MaxReadSizeExceededException {
throwExceptionIfExist();
return nonBlockingStream.readStringByDelimiter(delimiter, maxLength);
}
public String readStringByLength(int length) throws IOException, BufferUnderflowException, UnsupportedEncodingException {
throwExceptionIfExist();
return nonBlockingStream.readStringByLength(length);
}
public long transferTo(WritableByteChannel target, int length) throws IOException, ClosedChannelException,BufferUnderflowException {
throwExceptionIfExist();
return nonBlockingStream.transferTo(target, length);
}
/**
*
* adds a complete listener
*
* @param listener the complete listener
*/
public void addCompleteListener(IBodyCompleteListener listener) {
completeListeners.add(listener);
if (isComplete.get()) {
callCompleteListener(listener);
}
}
/**
* set complete flag
*
* @param isComplete the complete flag
*/
void setComplete(boolean isComplete) {
// terminate watch dog
terminateWatchDog();
// set complete flag
this.isComplete.set(isComplete);
// call body handler
callBodyHandler(true);
// notify listeners
if (!completeListeners.isEmpty()) {
for (IBodyCompleteListener listener : completeListeners) {
callCompleteListener(listener);
}
}
}
private void callCompleteListener(final IBodyCompleteListener listener) {
if (LOG.isLoggable(Level.FINER)) {
LOG.finer("[" + httpConnection.getId() + "] call complete listener " + listener);
}
Runnable task = new Runnable() {
public void run() {
try {
listener.onComplete();
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Error occured by calling complete listener " + listener + " " + ioe.toString());
}
}
}
};
if (httpConnection != null) {
if (HttpUtils.isMutlithreaded(listener)) {
httpConnection.processMultiThreaded(task);
} else {
httpConnection.processNonThreaded(task);
}
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("httpconnection not set. perform body listner call back method non threaded");
}
task.run();
}
}
/**
* return true, if all body data has been received
*
* @return true, if all body data has been received
* @throws IOException if an exception occurs
*/
final boolean isComplete() throws IOException {
throwExceptionIfExist();
return isComplete.get();
}
/**
* set the body handler
*
* @param bodyHandler the body handler
* @throws IOException if an exception occurs
*/
public void setDataHandler(IBodyDataHandler bodyHandler) throws IOException {
setDataHandler(bodyHandler, HttpUtils.isMutlithreaded(bodyHandler));
}
void setSystemDataHandler(IBodyDataHandler bodyHandler) throws IOException {
isSystem = true;
setDataHandler(bodyHandler, HttpUtils.isMutlithreaded(bodyHandler));
}
public IBodyDataHandler getDataHandler() {
return handler;
}
private void setDataHandler(IBodyDataHandler bodyHandler, boolean isMultithreaded) {
this.handler = bodyHandler;
this.isMultithreaded = isMultithreaded;
callBodyHandler(false);
}
/**
* appends data
*
* @param data the data to append
*/
void append(ByteBuffer data) {
lastTimeDataReceivedMillis = System.currentTimeMillis();
nonBlockingStream.append(data);
callBodyHandler(false);
}
/**
* appends data
*
* @param data the data to append
*/
void append(ByteBuffer[] data) {
lastTimeDataReceivedMillis = System.currentTimeMillis();
nonBlockingStream.append(data);
callBodyHandler(false);
}
private void callBodyHandler(boolean forceCall) {
if (handler != null) {
handlerCaller.setForceCall(forceCall);
if ((httpConnection == null) || isSystem) {
handler.onData(this);
} else {
if (isMultithreaded) {
if (LOG.isLoggable(Level.FINER)) {
LOG.finer("calling body handler " + handler + " multithreaded");
}
httpConnection.processMultiThreaded(handlerCaller);
} else {
if (LOG.isLoggable(Level.FINER)) {
LOG.finer("calling body handler " + handler + " nonthreaded");
}
httpConnection.processNonThreaded(handlerCaller);
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected Object clone() throws CloneNotSupportedException {
NonBlockingBodyDataSource copy = (NonBlockingBodyDataSource) super.clone();
copy.handler = null;
copy.completeListeners = new ArrayList();
return copy;
}
/**
* duplicates the body data source
* @return the copy
*/
public NonBlockingBodyDataSource duplicate() {
try {
return (NonBlockingBodyDataSource) clone();
} catch (CloneNotSupportedException cnse) {
throw new RuntimeException("could not clone " + this + " " + cnse.toString());
}
}
final int getWriteTransferChunkeSize() {
return nonBlockingStream.getWriteTransferChunkeSize();
}
private void closeSilence() {
try {
close();
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Error occured by closing data source " + this + " " + ioe.toString());
}
}
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return nonBlockingStream.toString();
}
/**
* the handler caller
*
*/
final class HandlerCaller implements Runnable {
private boolean forceCall = false;
private void setForceCall(boolean forceCall) {
this.forceCall = forceCall;
}
/**
* {@inheritDoc}
*/
public void run() {
if (!suspendHandling) {
try {
while ((getSize() != 0) || forceCall) {
int version = getVersion();
boolean success = call();
if (!success) {
return;
}
int newVersion = getVersion();
if (newVersion != version) {
version = newVersion;
} else {
if (size() == -1) {
suspendHandling = true;
}
return;
}
}
} catch (BufferUnderflowException bue) {
// swallow it
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("error occured by performing handler call " + ioe.toString());
}
}
}
}
private boolean call() {
try {
return handler.onData(NonBlockingBodyDataSource.this);
} catch (RuntimeException re) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("closing data source because an error has been occured by handling data by bodyHandler. " + handler + " Reason: " + re.toString());
}
closeSilence();
}
return false;
}
}
private final class NonBlockingStream extends AbstractNonBlockingStream {
private void append(ByteBuffer buffer) {
appendDataToReadBuffer(new ByteBuffer[] { buffer });
}
private void append(ByteBuffer[] buffers) {
appendDataToReadBuffer(buffers);
}
@Override
public void close() throws IOException {
super.close();
if (isOpen.get()) {
terminateWatchDog();
}
isOpen.set(false);
}
@Override
protected boolean isDataWriteable() {
return isOpen.get();
}
@Override
protected boolean isMoreInputDataExpected() {
if (isComplete.get()) {
return false;
}
if (!isUnderlyingConnectionOpen.get()) {
return false;
}
return true;
}
public boolean isOpen() {
return NonBlockingBodyDataSource.this.isOpen();
}
@Override
protected int getWriteTransferChunkeSize() {
if (httpConnection != null) {
try {
return (Integer) httpConnection.getOption(IConnection.SO_SNDBUF);
} catch (IOException ignore) { }
}
return super.getWriteTransferChunkeSize();
}
@Override
protected void onPostRead() throws IOException {
if ((NonBlockingBodyDataSource.this.available() == 0) && (NonBlockingBodyDataSource.this.isComplete.get())) {
try {
NonBlockingBodyDataSource.this.close();
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("error occured by closing body data source " + ioe.toString());
}
}
}
}
@Override
public String toString() {
return printReadBuffer(NonBlockingBodyDataSource.this.getEncoding());
}
}
private static final class TimeoutWatchDogTask extends TimerTask {
private WeakReference dataSourceRef = null;
public TimeoutWatchDogTask(NonBlockingBodyDataSource dataSource) {
dataSourceRef = new WeakReference(dataSource);
}
@Override
public void run() {
try {
NonBlockingBodyDataSource dataSource = dataSourceRef.get();
if (dataSource == null) {
this.cancel();
} else {
dataSource.checkTimeouts();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy