All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xsocket.connection.http.NonBlockingBodyDataSource Maven / Gradle / Ivy

There is a newer version: 2.0-beta-1
Show newest version
/*
 *  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