/*
* Copyright (c) xlightweb.org, 2008 - 2010. 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.xlightweb.org/
*/
package org.xlightweb;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Closeable;
import java.net.SocketTimeoutException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xsocket.DataConverter;
import org.xsocket.IDataSource;
import org.xsocket.MaxReadSizeExceededException;
/**
*
* I/O resource capable of providing body data in a blocking way. Read operations will be suspended,
* if not enough data is available.
*
* The BlockingBodyDataSource wraps a {@link NonBlockingBodyDataSource}
*
* @author grro@xlightweb.org
*
*/
public final class BlockingBodyDataSource implements IDataSource, ReadableByteChannel, Closeable {
private static final Logger LOG = Logger.getLogger(BlockingBodyDataSource.class.getName());
public static final int DEFAULT_RECEIVE_TIMEOUT = Integer.MAX_VALUE;
private final ReadNotificationHandler handler = new ReadNotificationHandler();
private final Object readGuard = new Object();
private final NonBlockingBodyDataSource delegate;
private int receiveTimeoutSec = DEFAULT_RECEIVE_TIMEOUT;
// part support
private PartHandler partHandler = null;
/**
* constructor
*
* @param delegate the underlying non NonBlockingBodyDataSource
*/
BlockingBodyDataSource(NonBlockingBodyDataSource delegate) throws IOException {
this.delegate = delegate;
delegate.setDataHandler(handler);
}
IHeader getHeader() {
return delegate.getHeader();
}
String getEncoding() {
return delegate.getEncoding();
}
/**
* returns the underlying tcp connection
* @return the underlying tcp connection
*/
NonBlockingBodyDataSource getUnderliyingBodyDataSource() {
return delegate;
}
/**
* sets the receive time out by reading data
*
* @param timeout the receive timeout
*/
public void setReceiveTimeoutSec(int timeout) {
this.receiveTimeoutSec = timeout;
}
/**
* gets receive time out by reading data
* @return the receive timeout
*/
public int getReceiveTimeoutSec() {
return receiveTimeoutSec;
}
/**
* returns, if the connection is open.
*
* @return true if the connection is open
*/
public boolean isOpen() {
return delegate.isOpen();
}
/**
* return true if the body is a mulipart
* @return true, if the body is a mulipart
*/
public final boolean isMultipart() {
return delegate.isMultipart();
}
/**
* {@inheritDoc}
*/
public void close() throws IOException {
delegate.close();
}
/**
* get the body size
*
* @return the body size
* @throws IOException if an exception occurs
*/
public int size() throws IOException {
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
synchronized (readGuard) {
if (delegate.isCompleteReceived()) {
return delegate.available();
} else {
waitUntilBodyIsComplete(remainingTime);
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
/**
* Marks the read position in the connection. Subsequent calls to resetToReadMark() will attempt
* to reposition the connection to this point.
*
*/
public void markReadPosition() {
delegate.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 boolean resetToReadMark() {
return delegate.resetToReadMark();
}
/**
* remove the read mark
*/
public void removeReadMark() {
delegate.removeReadMark();
}
/**
* read the next part of the mulipart body. {@link BlockingBodyDataSource#isMultipart()} can
* be used to verify if the body is a multipart one
*
* <pre>
* // ...
*
* BlockingBodyDataSource body = response.getBlockingBody();
* if (body.isMultipart()) {
* IPart part = body.readPart();
* // ...
* } else {
* // ...
* }
* </pre>
*
* @return the next part
* @throws IOException if an exception occurs
* @throws NoMultipartTypeException if the body type is not a multipart type
*/
public IPart readPart() throws NoMultipartTypeException, IOException {
initPartReader();
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
synchronized (readGuard) {
try {
IPart part = delegate.readPart();
return part;
} catch (BufferUnderflowException bue) {
waitUntilBodyIsComplete(remainingTime);
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
/**
* return all parts of the multipart body. {@link BlockingBodyDataSource#isMultipart()} can
* be used to verify if the body is a multipart one
*
* <pre>
* // ...
*
* BlockingBodyDataSource body = response.getBlockingBody();
* if (body.isMultipart()) {
* List<IPart> parts = body.readParts();
* // ...
* } else {
* // ...
* }
* </pre>
*
* @return the list of all parts
* @throws IOException if an exception occurs
* @throws NoMultipartTypeException if the surrounding body type is not a multipart type
*/
public List<IPart> readParts() throws NoMultipartTypeException, IOException {
List<IPart> parts = new ArrayList<IPart>();
while (true) {
try {
parts.add(readPart());
} catch (ClosedChannelException cce) {
return parts;
}
}
}
private synchronized void initPartReader() throws IOException {
if (partHandler == null) {
partHandler = new PartHandler();
delegate.setBodyPartHandler(partHandler);
}
}
private final class PartHandler implements IPartHandler, IUnsynchronized {
public PartHandler() {
}
public void onPart(NonBlockingBodyDataSource dataSource) throws IOException, BadMessageException {
onReadDataInserted();
}
}
/**
* read the body
*
* @return the body as byte buffer
*
* @throws IOException if an exception occurs
*/
public ByteBuffer[] readByteBuffer() throws IOException {
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
synchronized (readGuard) {
if (delegate.isCompleteReceived()) {
return readByteBufferByLength(delegate.available());
} else {
waitUntilBodyIsComplete(remainingTime);
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
/**
* read the body
*
* @return the body as bytes
*
* @throws IOException if an exception occurs
*/
public byte[] readBytes() throws IOException {
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
synchronized (readGuard) {
if (delegate.isCompleteReceived()) {
return readBytesByLength(delegate.available());
} else {
waitUntilBodyIsComplete(remainingTime);
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
/**
* read the body
*
* @return the body as string
*
* @throws IOException if an exception occurs
*/
public String readString() throws IOException {
return readString(delegate.getEncoding());
}
/**
* read the body
*
* @param encoding the encoding
* @return the body as string
*
* @throws IOException if an exception occurs
*/
public String readString(String encoding) throws IOException {
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
synchronized (readGuard) {
if (delegate.isCompleteReceived()) {
delegate.removeLeadingBOM();
return readStringByLength(delegate.available(), encoding);
} else {
waitUntilBodyIsComplete(remainingTime);
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
private void waitUntilBodyIsComplete(long remainingTime) throws IOException {
if (delegate.isMoreInputDataExpected()) { // is not complete AND not terminated
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("waiting until body is complete received (guard: " + readGuard + ")");
}
waitingForData(readGuard, remainingTime);
} else {
// if not complete throw exeption
if (!delegate.isCompleteReceived()) {
DetailedClosedChannelException cce = new DetailedClosedChannelException("insufficient data received (data received: " + delegate.getSizeDataReceived() + ")");
if (LOG.isLoggable(Level.FINE)) {
LOG.fine(cce.getMessage());
}
throw cce;
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("complete body received (guard: " + readGuard + ")");
}
}
}
}
/**
* {@inheritDoc}.
*/
public int read(ByteBuffer buffer) throws IOException {
int size = buffer.remaining();
if (size < 1) {
return 0;
}
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
synchronized (readGuard) {
do {
int availableSize = delegate.available();
// if at least one byte is available -> read and return
if (availableSize > 0) {
int read = delegate.read(buffer);
if (read > 0) {
return read;
}
}
if (availableSize == -1) {
// check if channel is closed by reading with length 0
// is closed a ClosedChannelException will be thrown
delegate.read(ByteBuffer.allocate(0));
}
// no data available
if (isOpen()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("waiting for at least 1 byte (guard: " + readGuard + ")");
}
waitingForData(readGuard, remainingTime);
} else {
return -1;
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
/**
* {@inheritDoc}
*/
public byte readByte() throws IOException, BufferUnderflowException, SocketTimeoutException {
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
synchronized (readGuard) {
try {
return delegate.readByte();
} catch (BufferUnderflowException bue) {
if (!delegate.isCompleteReceived()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("waiting for 1 byte (guard: " + readGuard + ")");
}
waitingForData(readGuard, remainingTime);
} else {
throw new ClosedChannelException();
}
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
/**
* {@inheritDoc}
*/
public short readShort() throws IOException, BufferUnderflowException, SocketTimeoutException {
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
synchronized (readGuard) {
try {
return delegate.readShort();
} catch (BufferUnderflowException bue) {
if (!delegate.isCompleteReceived()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("waiting for " + (2 - delegate.size()) + "bytes (guard: " + readGuard + ")");
}
waitingForData(readGuard, remainingTime);
} else {
throw new ClosedChannelException();
}
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
/**
* {@inheritDoc}
*/
public int readInt() throws IOException, BufferUnderflowException, SocketTimeoutException {
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
synchronized (readGuard) {
try {
return delegate.readInt();
} catch (BufferUnderflowException bue) {
if (!delegate.isCompleteReceived()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("waiting for " + (4 - delegate.size()) + "bytes (guard: " + readGuard + ")");
}
waitingForData(readGuard, remainingTime);
} else {
throw new ClosedChannelException();
}
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
/**
* {@inheritDoc}
*/
public long readLong() throws IOException, BufferUnderflowException, SocketTimeoutException {
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
synchronized (readGuard) {
try {
return delegate.readLong();
} catch (BufferUnderflowException bue) {
if (!delegate.isCompleteReceived()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("waiting for " + (8 - delegate.size()) + "bytes (guard: " + readGuard + ")");
}
waitingForData(readGuard, remainingTime);
} else {
throw new ClosedChannelException();
}
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
/**
* {@inheritDoc}
*/
public double readDouble() throws IOException, BufferUnderflowException, SocketTimeoutException {
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
synchronized (readGuard) {
try {
return delegate.readDouble();
} catch (BufferUnderflowException bue) {
if (!delegate.isCompleteReceived()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("waiting for " + (8 - delegate.size()) + "bytes (guard: " + readGuard + ")");
}
waitingForData(readGuard, remainingTime);
} else {
throw new ClosedChannelException();
}
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
/**
* {@inheritDoc}
*/
public ByteBuffer[] readByteBufferByDelimiter(String delimiter) throws IOException, BufferUnderflowException, SocketTimeoutException {
return readByteBufferByDelimiter(delimiter, Integer.MAX_VALUE);
}
/**
* {@inheritDoc}
*/
public ByteBuffer[] readByteBufferByDelimiter(String delimiter, int maxLength) throws IOException, BufferUnderflowException, MaxReadSizeExceededException, SocketTimeoutException {
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
synchronized (readGuard) {
try {
return delegate.readByteBufferByDelimiter(delimiter, maxLength);
} catch (MaxReadSizeExceededException mre) {
throw mre;
} catch (BufferUnderflowException bue) {
if (!delegate.isCompleteReceived()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("waiting for more reveived data (guard: " + readGuard + ")");
}
waitingForData(readGuard, remainingTime);
} else {
throw new ClosedChannelException();
}
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
/**
* {@inheritDoc}
*/
public ByteBuffer[] readByteBufferByLength(int length) throws IOException, BufferUnderflowException, SocketTimeoutException {
if (length <= 0) {
return new ByteBuffer[0];
}
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
synchronized (readGuard) {
try {
return delegate.readByteBufferByLength(length);
} catch (BufferUnderflowException bue) {
if (!delegate.isCompleteReceived()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("waiting for " + (length - delegate.size()) + "bytes (guard: " + readGuard + ")");
}
waitingForData(readGuard, remainingTime);
} else {
throw new ClosedChannelException();
}
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
private long computeRemainingTime(long start, int receiveTimeoutSec) {
return (start + ((long) receiveTimeoutSec * 1000)) - System.currentTimeMillis();
}
/**
* {@inheritDoc}
*/
public byte[] readBytesByDelimiter(String delimiter) throws IOException, BufferUnderflowException, SocketTimeoutException {
return readBytesByDelimiter(delimiter, Integer.MAX_VALUE);
}
/**
* {@inheritDoc}
*/
public byte[] readBytesByDelimiter(String delimiter, int maxLength) throws IOException, BufferUnderflowException, MaxReadSizeExceededException, SocketTimeoutException {
return DataConverter.toBytes(readByteBufferByDelimiter(delimiter, maxLength));
}
/**
* {@inheritDoc}
*/
public byte[] readBytesByLength(int length) throws IOException, BufferUnderflowException, SocketTimeoutException {
return DataConverter.toBytes(readByteBufferByLength(length));
}
/**
* {@inheritDoc}
*/
public String readStringByDelimiter(String delimiter) throws IOException, BufferUnderflowException, UnsupportedEncodingException, SocketTimeoutException {
return readStringByDelimiter(delimiter, delegate.getEncoding());
}
/**
* read a string by using a delimiter
*
* @param delimiter the delimiter
* @param encoding encoding
* @return the string
* @throws IOException If some other I/O error occurs
* @throws UnsupportedEncodingException if the default encoding is not supported
* @throws BufferUnderflowException if not enough data is available
* @throws SocketTimeoutException if a timeout occurs
*/
public String readStringByDelimiter(String delimiter, String encoding) throws IOException, BufferUnderflowException, UnsupportedEncodingException, SocketTimeoutException {
delegate.removeLeadingBOM();
return DataConverter.toString(readByteBufferByDelimiter(delimiter), encoding);
}
/**
* {@inheritDoc}
*/
public String readStringByDelimiter(String delimiter, int maxLength) throws IOException, BufferUnderflowException, UnsupportedEncodingException, MaxReadSizeExceededException, SocketTimeoutException {
return readStringByDelimiter(delimiter, maxLength, delegate.getEncoding());
}
/**
* read a string by using a delimiter
*
* @param delimiter the delimiter
* @param maxLength the max length of bytes that should be read. If the limit is exceeded a MaxReadSizeExceededException will been thrown
* @param encoding the encoding
* @return the string
* @throws MaxReadSizeExceededException If the max read length has been exceeded and the delimiter hasn�t been found
* @throws IOException If some other I/O error occurs
* @throws UnsupportedEncodingException If the given encoding is not supported
* @throws BufferUnderflowException if not enough data is available
* @throws SocketTimeoutException if the timout is reached
*/
public String readStringByDelimiter(String delimiter, int maxLength, String encoding) throws IOException, BufferUnderflowException, UnsupportedEncodingException, MaxReadSizeExceededException, SocketTimeoutException {
delegate.removeLeadingBOM();
return DataConverter.toString(readByteBufferByDelimiter(delimiter, maxLength), encoding);
}
/**
* {@inheritDoc}
*/
public String readStringByLength(int length) throws IOException, BufferUnderflowException, UnsupportedEncodingException, SocketTimeoutException {
return readStringByLength(length, delegate.getEncoding());
}
/**
* read a string by using a length definition
*
* @param length the amount of bytes to read
* @param encoding the encoding
* @return the string
* @throws IOException If some other I/O error occurs
* @throws SocketTimeoutException if the timeout is reached
* @throws BufferUnderflowException if not enough data is available
*/
public String readStringByLength(int length, String encoding) throws IOException, BufferUnderflowException, UnsupportedEncodingException, SocketTimeoutException {
delegate.removeLeadingBOM();
return DataConverter.toString(readByteBufferByLength(length), encoding);
}
/**
* {@inheritDoc}
*/
public long transferTo(WritableByteChannel target, int length) throws IOException, BufferUnderflowException, SocketTimeoutException {
long written = 0;
ByteBuffer[] buffers = readByteBufferByLength(length);
for (ByteBuffer buffer : buffers) {
written += target.write(buffer);
}
return written;
}
/**
* transfers the content to the given channel
*
* @param target the target channel
* @return the number of transfered bytes
* @return the number of transfered bytes
* @throws ClosedChannelException If either this channel or the target channel is closed
* @throws IOException If some other I/O error occurs
*/
public long transferTo(WritableByteChannel target) throws IOException, BufferUnderflowException, SocketTimeoutException {
long written = 0;
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
int available = delegate.available();
if (available > 0) {
written += transferTo(target, available);
} else if (available == -1) {
return written;
}
synchronized (readGuard) {
if (delegate.available() == 0) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("waiting for al least 1 bytes (guard: " + readGuard + ")");
}
waitingForData(readGuard, remainingTime);
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
/**
* transfer the available data of the this source channel to the given data sink
*
* @param dataSink the data sink
*
* @return the number of transfered bytes
* @throws ClosedChannelException If either this channel or the target channel is closed
* @throws IOException If some other I/O error occurs
* @throws BufferUnderflowException if not enough data is available
*/
public long transferTo(BodyDataSink dataSink) throws ProtocolException, IOException, ClosedChannelException,BufferUnderflowException {
return transferTo(dataSink, size());
}
/**
* transfer the available data of the this source channel to the given data sink
*
* @param dataSink the data sink
*
* @return the number of transfered bytes
* @throws ClosedChannelException If either this channel or the target channel is closed
* @throws IOException If some other I/O error occurs
* @throws BufferUnderflowException if not enough data is available
*/
public long transferTo(OutputStream dataSink) throws ProtocolException, IOException, ClosedChannelException,BufferUnderflowException {
return transferTo(Channels.newChannel(dataSink));
}
/**
* transfer the data of the this source channel to the given data sink
*
* @param dataSink the data sink
* @param length the size to transfer
*
* @return the number of transfered bytes
* @throws ClosedChannelException If either this channel or the target channel is closed
* @throws IOException If some other I/O error occurs
* @throws BufferUnderflowException if not enough data is available
*/
public long transferTo(BodyDataSink dataSink, final int length) throws ProtocolException, IOException, ClosedChannelException,BufferUnderflowException {
long written = 0;
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
int available = delegate.available();
if (available >= length) {
written += delegate.transferTo(dataSink, length);
} else if (available == -1) {
return written;
}
synchronized (readGuard) {
if (delegate.available() < length) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("waiting for " + (length - delegate.size()) + " bytes (guard: " + readGuard + ")");
}
waitingForData(readGuard, remainingTime);
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
/**
* returns this {@link ReadableByteChannel} as {@link InputStream}
* @return the input stream
*/
public InputStream toInputStream() {
return Channels.newInputStream(this);
}
/**
* returns this {@link ReadableByteChannel} as {@link Reader}
* @return the input stream
*/
public Reader toReader() {
return Channels.newReader(this, getEncoding());
}
private void waitingForData(Object readGuard, long maxWaittime) {
try {
readGuard.wait(maxWaittime);
} catch (InterruptedException ie) {
// Restore the interrupted status
Thread.currentThread().interrupt();
}
}
private void onReadDataInserted() {
if (LOG.isLoggable(Level.FINE)) {
try {
LOG.fine("notifying waiting threads isCompleteReceived=" + delegate.isCompleteReceived() +
" available=" + delegate.size() +
" moreDataExpected=" + delegate.isMoreInputDataExpected() +
" (guard: " + readGuard + ")");
} catch (IOException ioe) {
LOG.fine("logging error occured " + ioe.toString());
}
}
synchronized (readGuard) {
readGuard.notifyAll();
}
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
try {
do {
synchronized (readGuard) {
if (delegate.isCompleteReceived()) {
return delegate.toString();
} else {
if (!delegate.isCompleteReceived()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("waiting for all bytes (guard: " + readGuard + ")");
}
waitingForData(readGuard, remainingTime);
}
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
return "timeout error occured within toString method";
} catch (IOException ioe) {
return "error occured by performing toString: " + ioe.toString();
}
}
private final class ReadNotificationHandler implements IBodyDataHandler, IUnsynchronized {
public boolean onData(NonBlockingBodyDataSource bodyDataSource) {
onReadDataInserted();
return true;
}
}
}