blob: 921dfb042f096c9774701fd4b52cf5b92b68772a [file] [log] [blame]
/*
*************************************************************************
* Copyright (c) 2006, 2007 Actuate Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* Actuate Corporation - initial API and implementation
*
*************************************************************************
*/
package org.eclipse.datatools.connectivity.oda.consumer.helper;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import org.eclipse.datatools.connectivity.oda.IBlob;
import org.eclipse.datatools.connectivity.oda.OdaException;
/**
* Internal implementation class responsible for reading large binary object data from
* an input stream.
* This provides default implementation for those ODA drivers that do not implement
* the optional methods of ODA IBlob interface.
*/
class BlobReader extends OdaObject
{
private static final int DEFAULT_BUFFER_SIZE = 2048;
private static final String COMMA_SEPARATOR = ", "; //$NON-NLS-1$
private static String sm_className = null; // lazy initialization
private IBlob m_odaBlob;
private int m_defaultBufferSize;
protected BlobReader( IBlob odaBlob )
{
init( odaBlob, DEFAULT_BUFFER_SIZE );
}
protected BlobReader( IBlob odaBlob, int defaultBufferSize )
{
init( odaBlob, defaultBufferSize );
}
private void init( IBlob odaBlob, int defaultBufferSize )
{
assert( odaBlob != null );
m_odaBlob = odaBlob;
m_defaultBufferSize = ( defaultBufferSize >= 0 ) ?
defaultBufferSize : DEFAULT_BUFFER_SIZE;
// for logging context
if( sm_className == null )
sm_className = getClassName();
}
/**
* Returns the default buffer size to use for incremental read
* when unable to determine the total number of bytes to read
* till end of stream is reached.
* @return default size of each byte array buffer
*/
private int getDefaultBufferSize()
{
return m_defaultBufferSize;
}
/**
* Provides default implementation to retrieve all or part of
* the BLOB data from the wrapped Blob.
* Returns null if not able to retrieve from stream.
* @param startPos the 1-based ordinal position of the first byte
* in the specified input stream to be extracted
* @param length the number of consecutive bytes to be copied
* @return a byte array containing up to <code>length</code>
* consecutive bytes from the specified input stream,
* starting with the byte at <code>startPos</code>;
* or null if not able to retrieve from stream as specified
* @throws IOException if error in reading from the input stream
* @throws OdaException if data source error occurs
*/
byte[] getBytes( long startPos, int length )
throws IOException, OdaException
{
final String context = sm_className + ".getBytes( " + //$NON-NLS-1$
startPos + COMMA_SEPARATOR + length + " )\t"; //$NON-NLS-1$
logMethodCalled( context );
// first get the underlying driver's stream
InputStream driverStream = null;
try
{
driverStream = m_odaBlob.getBinaryStream();
}
catch( OdaException e1 )
{
log( context, e1.toString() );
}
if( driverStream == null )
{
logMethodExitWithReturn( context, null );
return null;
}
// use java.io utility to handle reading specified length of bytes
// in a single call
BufferedInputStream bufferedStream = new BufferedInputStream( driverStream );
// if the first byte to retrieve is after the first position in BLOB,
// first skip all the bytes before startPos
long numToSkip = 0;
if( startPos > 1 )
{
numToSkip = startPos - 1;
long numSkipped = bufferedStream.skip( numToSkip );
if( numSkipped != numToSkip )
{
bufferedStream.close();
logMethodExitWithReturn( context, null );
return null; // not able to skip to given position
}
}
int numToRead = length;
// check if the specified length is within the remaining bytes in stream
long streamLen = getStreamLength();
if( streamLen >= 0 ) // driver is able to determine the stream length
{
int numAvailable = ( streamLen > numToSkip ) ?
( new Long( streamLen - numToSkip )).intValue() :
0; // nothing available to read beyond starting position
// caller specifies to read till end of stream, or more than what's available
if( length < 0 || length > numAvailable )
numToRead = numAvailable; // read remaining bytes till end of stream
}
// next, retrieve the exact numToRead bytes from stream
if( numToRead >= 0 )
{
byte[] bytesData = readBytesFromStream( bufferedStream, numToRead );
bufferedStream.close();
int arraySize = ( bytesData != null ) ? bytesData.length : -1;
logMethodExitWithReturn( context, arraySize );
return bytesData;
}
// do not know exact bytes length till end of stream,
// do incremental read till end of stream
byte[] remainingBytes = null;
try
{
remainingBytes = readRemainingBytes( bufferedStream );
}
catch( OutOfMemoryError err ) // attempts to catch error
{
err.printStackTrace();
}
bufferedStream.close();
int arraySize = ( remainingBytes != null ) ? remainingBytes.length : -1;
logMethodExitWithReturn( context, arraySize );
return remainingBytes;
}
/**
* Reads the specified inputStream from its current position up to the specified
* maximum number of bytes.
* @param stream
* @param maxBufSize
* @return
* @throws IOException
*/
private byte[] readBytesFromStream( InputStream stream, int maxBufSize )
throws IOException
{
final String context = sm_className + ".readBytesFromStream( " + //$NON-NLS-1$
stream + COMMA_SEPARATOR + maxBufSize + " )\t"; //$NON-NLS-1$
assert( maxBufSize >= 0 );
byte[] outBuffer = new byte[ maxBufSize ];
if( maxBufSize == 0 )
{
log( context, "Returns an empty byte array." ); //$NON-NLS-1$
return outBuffer;
}
int bytesRead = stream.read( outBuffer, 0, outBuffer.length );
if( bytesRead == maxBufSize )
{
log( context, "Returns a byte array of requested size: " + outBuffer.length ); //$NON-NLS-1$
return outBuffer;
}
if( bytesRead < 0 ) // end of stream
{
log( context, "Returns a null byte array for end of stream." ); //$NON-NLS-1$
return null;
}
// in case end of stream is reached before full buffer length is read
byte[] resizedBuf = new byte[ bytesRead ];
System.arraycopy( outBuffer, 0, resizedBuf, 0, bytesRead );
log( context, "Returns a byte array of size: " + resizedBuf.length ); //$NON-NLS-1$
return resizedBuf;
}
/**
* Reads the specified inputStream from its current position up to the
* end of stream.
* This approach is inefficient and takes up extra memory, and is the last
* resort used when the underlying input stream provides no stream length info.
* @param stream
* @return read data in a single byte array; array may be empty if no data
* was read in a normal condition
* @throws IOException
*/
private byte[] readRemainingBytes( InputStream stream )
throws IOException
{
final String context = sm_className + ".readRemainingBytes( " + stream + " )\t"; //$NON-NLS-1$ //$NON-NLS-2$
// read one chunk of bytes at a time using default buffer size,
// and keep the chunk buffers in a temp collection
ArrayList bufferList = new ArrayList();
int bufferSize = getDefaultBufferSize(); // may be zero for empty array
log( context, "Default buffer size: " + bufferSize ); //$NON-NLS-1$
boolean endOfStream = false;
byte[] aChunk = null;
while( ! endOfStream )
{
aChunk = readBytesFromStream( stream, bufferSize );
if( aChunk != null )
bufferList.add( aChunk );
endOfStream = ( aChunk == null || aChunk.length < bufferSize ||
bufferSize == 0 );
}
if( bufferList.isEmpty() ) // no data was read
{
log( context, "Returns an empty byte array." ); //$NON-NLS-1$
return new byte[0];
}
// first get the count of total bytes read in all buffers
int totalBytes = 0;
Iterator bufferIter = bufferList.iterator();
while( bufferIter.hasNext() )
{
aChunk = (byte[]) bufferIter.next();
assert( aChunk != null );
totalBytes += aChunk.length;
}
if( totalBytes <= 0 ) // no data was read
{
log( context, "Returns an empty byte array." ); //$NON-NLS-1$
return new byte[0];
}
// concatenate all the buffers into a single byte array to return
byte[] remainingBytes = new byte[ totalBytes ];
bufferIter = bufferList.iterator();
int bytesCopied = 0;
while( bufferIter.hasNext() )
{
aChunk = (byte[]) bufferIter.next();
System.arraycopy( aChunk, 0, remainingBytes, bytesCopied, aChunk.length );
bytesCopied += aChunk.length;
assert( bytesCopied <= totalBytes );
}
log( context, "Returns a byte array of size: " + remainingBytes.length ); //$NON-NLS-1$
return remainingBytes;
}
private long getStreamLength()
{
long len = -1; // default for unknown length
try
{
len = m_odaBlob.length();
}
catch( OdaException odaException )
{
// ignore
}
catch( UnsupportedOperationException uoException )
{
// ignore
}
catch( RuntimeException rtException )
{
// ignore
}
return len;
}
}