blob: a5c3e6cc9f0ccce102565a0496227796b62fdb5d [file] [log] [blame]
/*
Derby - Class org.apache.derby.impl.jdbc.EmbedBlob
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to you 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.apache.derby.impl.jdbc;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.jdbc.EngineLOB;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.types.DataValueDescriptor;
import org.apache.derby.iapi.types.RawToBinaryFormatStream;
import org.apache.derby.iapi.types.Resetable;
import org.apache.derby.iapi.services.io.InputStreamUtil;
import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
import org.apache.derby.iapi.util.InterruptStatus;
import java.sql.SQLException;
import java.sql.Blob;
import java.io.EOFException;
import java.io.InputStream;
import java.io.IOException;
/**
Implements java.sql.Blob (see the JDBC 2.0 spec).
A blob sits on top of a BINARY, VARBINARY or LONG VARBINARY column.
If its data is small (less than 1 page) it is a byte array taken from
the SQLBit class. If it is large (more than 1 page) it is a long column
in the database. The long column is accessed as a stream, and is implemented
in store as an OverflowInputStream. The Resetable interface allows sending
messages to that stream to initialize itself (reopen its container and
lock the corresponding row) and to reset itself to the beginning.
NOTE: In the case that the data is large, it is represented as a stream.
This stream is returned to the user in the getBinaryStream() method.
This means that we have limited control over the state of the stream,
since the user can read bytes from it at any time. Thus all methods
here reset the stream to the beginning before doing any work.
CAVEAT: The methods may not behave correctly if a user sets up
multiple threads and sucks data from the stream (returned from
getBinaryStream()) at the same time as calling the Blob methods.
<P><B>Supports</B>
<UL>
<LI> JSR169 - no subsetting for java.sql.Blob
<LI> JDBC 2.0
<LI> JDBC 3.0 - no new dependencies on new JDBC 3.0 or JDK 1.4 classes,
new update methods can safely be added into implementation.
</UL>
*/
final class EmbedBlob extends ConnectionChild implements Blob, EngineLOB
{
/**
* Tells whether the Blob has been materialized or not.
* <p>
* Materialization happens when the Blob is updated by the user. A
* materialized Blob is represented either in memory or in a temporary file
* on disk, depending on size.
* <p>
* A Blob that has not been materialized is represented by a stream into the
* Derby store, and is read-only.
*/
private boolean materialized;
/**
* The underlying positionable store stream, if any.
* <p>
* If {@link #materialized} is {@code true}, the stream is {@code null}.
*/
private PositionedStoreStream myStream;
/**
* Locator value for this Blob, used as a handle by the client driver to
* map operations to the correct Blob on the server side.
*
* @see #getLocator()
*/
private int locator = 0;
/**
* Length of the stream representing the Blob.
* <p>
* Set to -1 when the stream has been {@link #materialized} or
* the length of the stream is not currently known.
*/
private long streamLength = -1;
/**
* Position offset for the stream representing the Blob, if any.
* <p>
* This offset accounts for the bytes encoding the stream length at the
* head of the stream. Data byte {@code pos} is at
* {@code pos + streamPositionOffset} in the underlying stream.
* Set to {@code Integer.MIN_VALUE} if the Blob isn't represented by a
* store stream.
*/
private final int streamPositionOffset;
//This boolean variable indicates whether the Blob object has
//been invalidated by calling free() on it
private boolean isValid = true;
private LOBStreamControl control;
/**
* This constructor is used to create a empty Blob object. It is used by the
* Connection interface method createBlob().
*
* @param blobBytes A byte array containing the data to be stores in the
* Blob.
*
* @param con The EmbedConnection object associated with this Blob object.
*
*/
EmbedBlob(byte [] blobBytes,EmbedConnection con) throws SQLException {
super(con);
try {
control = new LOBStreamControl (con, blobBytes);
materialized = true;
streamPositionOffset = Integer.MIN_VALUE;
//add entry in connection so it can be cleared
//when transaction is not valid
con.addLOBReference (this);
}
catch (IOException e) {
throw Util.setStreamFailure (e);
}
catch (StandardException se) {
throw Util.generateCsSQLException (se);
}
}
/*
This constructor should only be called by EmbedResultSet.getBlob
*/
protected EmbedBlob(DataValueDescriptor dvd, EmbedConnection con)
throws StandardException, SQLException
{
super(con);
// if the underlying column is null, ResultSet.getBlob will return null,
// never should get this far
if (SanityManager.DEBUG)
SanityManager.ASSERT(!dvd.isNull(), "blob is created on top of a null column");
/*
We support three scenarios at this point:
a) The Blob value is already represented as bytes in memory.
This is the case for small Blobs (less than 32 KB).
b) The Blob value is represented as a resetable stream.
This is the case for Blobs coming from the store
(note the comment about SQLBit below).
c) The Blob value is represented as a wrapped user stream.
This stream cannot be reset, which means we have to drain the
stream and store it temporarily until it is either discarded or
inserted into the database.
*/
if (dvd.hasStream()) { // Cases b) and c)
streamPositionOffset = handleStreamValue(dvd.getStream(), con);
} else { // a) Blob already materialized in memory
materialized = true;
streamPositionOffset = Integer.MIN_VALUE;
// copy bytes into memory so that blob can live after result set
// is closed
byte[] dvdBytes = dvd.getBytes();
if (SanityManager.DEBUG)
SanityManager.ASSERT(dvdBytes != null,"blob has a null value underneath");
try {
control = new LOBStreamControl (
getEmbedConnection(), dvdBytes);
} catch (IOException e) {
throw Util.setStreamFailure(e);
}
}
//add entry in connection so it can be cleared
//when transaction is not valid
con.addLOBReference (this);
}
/**
* Constructs a Blob object on top of a stream.
*
* @param dvdStream the source stream
* @param con the connection owning the Blob
* @return The offset into the stream where the user data begins (used if
* resetting the stream).
* @throws StandardException if accessing the stream fails, or if writing
* data to temporary storage fails
*/
private int handleStreamValue(InputStream dvdStream, EmbedConnection con)
throws StandardException, SQLException {
int offset = 0;
// b) Resetable stream
// In this case the stream is coming from the Derby store.
if (dvdStream instanceof Resetable) {
materialized = false;
/*
We are expecting this stream to be a FormatIdInputStream with an
OverflowInputStream inside. FormatIdInputStream implements
Resetable. This should be the case when retrieving
data from a long column. However, SQLBit, which is the class
implementing the getStream() method for dvd.getStream(), does not
guarantee this for us
*/
if (SanityManager.DEBUG) {
SanityManager.ASSERT(dvdStream instanceof Resetable);
}
// Create a position aware stream on top of dvdStream so we can
// more easily move back and forth in the Blob.
try {
myStream = new PositionedStoreStream(dvdStream);
// The BinaryToRawStream will read the encoded length bytes.
BinaryToRawStream tmpStream =
new BinaryToRawStream(myStream, con);
offset = (int)myStream.getPosition();
// Check up front if the stream length is specified.
streamLength = tmpStream.getLength();
tmpStream.close();
} catch (StandardException se) {
if (se.getMessageId().equals(SQLState.DATA_CONTAINER_CLOSED)) {
throw StandardException
.newException(SQLState.BLOB_ACCESSED_AFTER_COMMIT);
} else {
throw se;
}
} catch (IOException ioe) {
throw StandardException.newException(
SQLState.LANG_STREAMING_COLUMN_I_O_EXCEPTION, ioe, "BLOB");
}
// c) Non-resetable stream
// This is most likely a stream coming in from the user, and we
// don't have any guarantees on how it behaves.
} else {
// The code below will only work for RawToBinaryFormatStream.
if (SanityManager.DEBUG) {
SanityManager.ASSERT(
dvdStream instanceof RawToBinaryFormatStream,
"Invalid stream type: " + dvdStream.getClass());
}
// The source stream isn't resetable, so we have to write it to a
// temporary location to be able to support the Blob operations.
materialized = true;
offset = Integer.MIN_VALUE;
try {
control = new LOBStreamControl(getEmbedConnection());
BinaryToRawStream tmpStream =
new BinaryToRawStream(dvdStream, con);
// Transfer the data.
byte[] bytes = new byte[4096]; // 4 KB buffer
long pos = 0;
while (true) {
int read = tmpStream.read(bytes, 0, bytes.length);
if (read < 1) {
// Reached EOF, or stream is behaving badly.
break;
}
// If the stream is larger than the maximum allowed by
// Derby, the call below will thrown an exception.
pos = control.write(bytes, 0, read, pos);
}
tmpStream.close();
} catch (IOException ioe) {
throw Util.setStreamFailure(ioe);
}
}
return offset;
}
/**
* Sets the position of the Blob to {@code logicalPos}, where position 0 is
* the beginning of the Blob content.
* <p>
* The position is only guaranteed to be valid from the time this method is
* invoked until the synchronization monitor is released, or until the next
* invokation of this method.
* <p>
* The position is logical in the sense that it specifies the requested
* position in the Blob content. This position might be at a different
* position in the underlying representation, for instance the Derby store
* stream prepends the Blob content with a length field.
*
* @param logicalPos requested Blob position, 0-based
* @return The new position, which will be equal to the requested position.
* @throws IOException if reading/accessing the Blob fails
* @throws StandardException throws BLOB_POSITION_TOO_LARGE if the requested
* position is larger than the Blob length, throws other SQL states if
* resetting the stream fails
*/
//@GuardedBy(getConnectionSynchronization())
private long setBlobPosition(long logicalPos)
throws StandardException, IOException
{
if (SanityManager.DEBUG)
SanityManager.ASSERT(logicalPos >= 0);
if (materialized) {
// Nothing to do here, except checking if the position is valid.
if (logicalPos >= control.getLength()) {
throw StandardException.newException(
SQLState.BLOB_POSITION_TOO_LARGE, logicalPos);
}
} else {
// Reposition the store stream, account for the length field offset.
try {
this.myStream.reposition(
logicalPos + this.streamPositionOffset);
} catch (EOFException eofe) {
throw StandardException.newException(
SQLState.BLOB_POSITION_TOO_LARGE, eofe,
logicalPos);
}
}
return logicalPos;
}
/**
* Reads one byte from the Blob at the specified position.
* <p>
* Depending on the representation, this might result in a read from a byte
* array, a temporary file on disk or from a Derby store stream.
*
* @return the byte at the current position, or -1 if end of file has been
* reached
* @throws IOException if reading from the underlying data representation
* fails
*/
private int read(long pos)
throws IOException, StandardException {
int c;
if (materialized) {
if (pos >= control.getLength())
return -1;
else
c = control.read (pos);
} else {
// Make sure we're at the right position.
this.myStream.reposition(pos + this.streamPositionOffset);
// Read one byte from the stream.
c = this.myStream.read();
}
return c;
}
/**
* Returns the number of bytes in the <code>BLOB</code> value
* designated by this <code>Blob</code> object.
* @return length of the <code>BLOB</code> in bytes
* @exception SQLException if there is an error accessing the
* length of the <code>BLOB</code>
*/
// PT stream part may get pushed to store
public long length()
throws SQLException
{
//call checkValidity to exit by throwing a SQLException if
//the Blob object has been freed by calling free() on it
checkValidity();
try {
if (materialized)
return control.getLength ();
}
catch (IOException e) {
throw Util.setStreamFailure (e);
}
if (streamLength != -1)
return streamLength;
boolean pushStack = false;
try
{
// we have a stream
synchronized (getConnectionSynchronization())
{
EmbedConnection ec = getEmbedConnection();
pushStack = !ec.isClosed();
if (pushStack)
setupContextStack();
// We have to read the entire stream!
myStream.resetStream();
BinaryToRawStream tmpStream =
new BinaryToRawStream(myStream, this);
if (SanityManager.DEBUG) {
SanityManager.ASSERT(tmpStream.getLength() == -1);
}
streamLength = InputStreamUtil.skipUntilEOF(tmpStream);
tmpStream.close();
// Save for future uses.
restoreIntrFlagIfSeen(pushStack, ec);
return streamLength;
}
}
catch (Throwable t)
{
throw handleMyExceptions(t);
}
finally
{
if (pushStack)
restoreContextStack();
}
}
/**
* Returns as an array of bytes part or all of the <code>BLOB</code>
* value that this <code>Blob</code> object designates. The byte
* array contains up to <code>length</code> consecutive bytes
* starting at position <code>startPos</code>.
* The starting position must be between 1 and the length
* of the BLOB plus 1. This allows for zero-length BLOB values, from
* which only zero-length byte arrays can be returned.
* If a larger length is requested than there are bytes available,
* characters from the start position to the end of the BLOB are returned.
* @param startPos the ordinal position of the first byte in the
* <code>BLOB</code> value to be extracted; the first byte is at
* position 1
* @param length is the number of consecutive bytes to be copied
* @return a byte array containing up to <code>length</code>
* consecutive bytes from the <code>BLOB</code> value designated
* by this <code>Blob</code> object, starting with the
* byte at position <code>startPos</code>.
* @exception SQLException if there is an error accessing the
* <code>BLOB</code>
* NOTE: If the starting position is the length of the BLOB plus 1,
* zero bytess are returned regardless of the length requested.
*/
public byte[] getBytes(long startPos, int length)
throws SQLException
{
//call checkValidity to exit by throwing a SQLException if
//the Blob object has been freed by calling free() on it
checkValidity();
boolean pushStack = false;
try
{
if (startPos < 1)
throw StandardException.newException(
SQLState.BLOB_BAD_POSITION, startPos);
if (length < 0)
throw StandardException.newException(
SQLState.BLOB_NONPOSITIVE_LENGTH, length);
byte[] result;
// if the blob is materialized
if (materialized) {
result = new byte [length];
int sz = control.read (result, 0, result.length, startPos - 1);
if (sz == -1) {
InterruptStatus.restoreIntrFlagIfSeen();
return new byte [0];
}
if (sz < length) {
byte [] tmparray = new byte [sz];
System.arraycopy (result, 0, tmparray, 0, sz);
result = tmparray;
}
InterruptStatus.restoreIntrFlagIfSeen();
}
else // we have a stream
{
synchronized (getConnectionSynchronization())
{
EmbedConnection ec = getEmbedConnection();
pushStack = !ec.isClosed();
if (pushStack)
setupContextStack();
setBlobPosition(startPos-1);
// read length bytes into a string
result = new byte[length];
int n = InputStreamUtil.readLoop(myStream,result,0,length);
/*
According to the spec, if there are only n < length bytes
to return, we should just return these bytes. Rather than
return them in an array of size length, where the trailing
bytes are not initialized, and the user cannot tell how
many bytes were actually returned, we should return an
array of n bytes.
*/
if (n < length)
{
byte[] result2 = new byte[n];
System.arraycopy(result,0,result2,0,n);
restoreIntrFlagIfSeen(pushStack, ec);
return result2;
}
restoreIntrFlagIfSeen(pushStack,ec);
}
}
return result;
}
catch (StandardException e)
{ // if this is a setPosition exception then we ran out of Blob
if (e.getMessageId().equals(SQLState.BLOB_LENGTH_TOO_LONG))
e = StandardException.newException(
SQLState.BLOB_POSITION_TOO_LARGE, startPos);
throw handleMyExceptions(e);
}
catch (Throwable t)
{
throw handleMyExceptions(t);
}
finally
{
if (pushStack)
restoreContextStack();
}
}
/**
* Retrieves the <code>BLOB</code> designated by this
* <code>Blob</code> instance as a stream.
* @return a stream containing the <code>BLOB</code> data
* @exception SQLException if there is an error accessing the
* <code>BLOB</code>
*/
public java.io.InputStream getBinaryStream()
throws SQLException
{
//call checkValidity to exit by throwing a SQLException if
//the Blob object has been freed by calling free() on it
checkValidity();
boolean pushStack = false;
try
{
// if we have byte array, not a stream
if (materialized)
{
java.io.InputStream result = control.getInputStream(0);
return result;
}
else
{
// have a stream
synchronized (getConnectionSynchronization())
{
EmbedConnection ec = getEmbedConnection();
pushStack = !ec.isClosed();
if (pushStack)
setupContextStack();
// Reset stream, because AutoPositionigStream wants to read
// the encoded length bytes.
myStream.resetStream();
UpdatableBlobStream result = new UpdatableBlobStream(
this,
new AutoPositioningStream (this, myStream, this));
restoreIntrFlagIfSeen(pushStack, ec);
return result;
}
}
}
catch (Throwable t)
{
throw handleMyExceptions(t);
}
finally
{
if (pushStack)
restoreContextStack();
}
}
/**
* Determines the byte position at which the specified byte
* <code>pattern</code> begins within the <code>BLOB</code>
* value that this <code>Blob</code> object represents. The
* search for <code>pattern</code>. begins at position
* <code>start</code>
* @param pattern the byte array for which to search
* @param start the position at which to begin searching; the
* first position is 1
* @return the position at which the pattern appears, else -1.
* @exception SQLException if there is an error accessing the
* <code>BLOB</code>
*/
public long position(byte[] pattern, long start)
throws SQLException
{
//call checkValidity to exit by throwing a SQLException if
//the Blob object has been freed by calling free() on it
checkValidity();
boolean pushStack = false;
try
{
if (start < 1)
throw StandardException.newException(
SQLState.BLOB_BAD_POSITION, start);
if (pattern == null)
throw StandardException.newException(SQLState.BLOB_NULL_PATTERN_OR_SEARCH_STR);
if (pattern.length == 0)
return start; // match DB2's SQL LOCATE function
synchronized (getConnectionSynchronization())
{
EmbedConnection ec = getEmbedConnection();
pushStack = !ec.isClosed();
if (pushStack)
setupContextStack();
long pos = setBlobPosition(start -1);
// look for first character
int lookFor = pattern[0];
long curPos;
int c;
while (true)
{
c = read(pos++); // Note the position increment.
if (c == -1) { // run out of stream
restoreIntrFlagIfSeen(pushStack, ec);
return -1;
}
if (c == lookFor)
{
curPos = pos;
if (checkMatch(pattern, pos)) {
restoreIntrFlagIfSeen(pushStack, ec);
return curPos;
} else
pos = setBlobPosition(curPos);
}
}
}
}
catch (StandardException e) {
throw handleMyExceptions(e);
}
catch (Throwable t)
{
throw handleMyExceptions(t);
}
finally
{
if (pushStack)
restoreContextStack();
}
}
/**
* Checks if the pattern (starting from the second byte) appears inside
* the Blob content.
* <p>
* At this point, the first byte of the pattern must already have been
* matched, and {@code pos} must be pointing at the second byte to compare.
*
* @param pattern the byte array to search for, passed in by the user
* @param pos the position in the Blob content to start searching from
* @return {@code true} if a match is found, {@code false} if not.
*/
private boolean checkMatch(byte[] pattern, long pos)
throws IOException, StandardException {
// check whether rest matches
// might improve performance by reading more
for (int i = 1; i < pattern.length; i++)
{
int b = read(pos++);
if ((b < 0) || (b != pattern[i])) // mismatch or stream runs out
return false;
}
return true;
}
/**
* Determines the byte position in the <code>BLOB</code> value
* designated by this <code>Blob</code> object at which
* <code>pattern</code> begins. The search begins at position
* <code>start</code>.
* @param pattern the <code>Blob</code> object designating
* the <code>BLOB</code> value for which to search
* @param start the position in the <code>BLOB</code> value
* at which to begin searching; the first position is 1
* @return the position at which the pattern begins, else -1
* @exception SQLException if there is an error accessing the
* <code>BLOB</code>
*/
public long position(Blob pattern, long start)
throws SQLException
{
//call checkValidity to exit by throwing a SQLException if
//the Blob object has been freed by calling free() on it
checkValidity();
boolean pushStack = false;
try
{
if (start < 1)
throw StandardException.newException(
SQLState.BLOB_BAD_POSITION, start);
if (pattern == null)
throw StandardException.newException(SQLState.BLOB_NULL_PATTERN_OR_SEARCH_STR);
synchronized (getConnectionSynchronization())
{
EmbedConnection ec = getEmbedConnection();
pushStack = !ec.isClosed();
if (pushStack)
setupContextStack();
long pos = setBlobPosition(start-1);
// look for first character
byte[] b;
try
{ // pattern is not necessarily a Derby Blob
b = pattern.getBytes(1,1);
}
catch (SQLException e)
{
throw StandardException.newException(SQLState.BLOB_UNABLE_TO_READ_PATTERN);
}
if (b == null || b.length < 1) { // the 'empty' blob
restoreIntrFlagIfSeen(pushStack, ec);
return start; // match DB2's SQL LOCATE function
}
int lookFor = b[0];
int c;
long curPos;
while (true)
{
c = read(pos++); // Note the position increment.
if (c == -1) { // run out of stream
restoreIntrFlagIfSeen(pushStack, ec);
return -1;
}
if (c == lookFor)
{
curPos = pos;
if (checkMatch(pattern, pos)) {
restoreIntrFlagIfSeen(pushStack, ec);
return curPos;
} else
pos = setBlobPosition(curPos);
}
}
}
}
catch (StandardException e) {
throw handleMyExceptions(e);
}
catch (Throwable t)
{
throw handleMyExceptions(t);
}
finally
{
if (pushStack)
restoreContextStack();
}
}
/**
* Checks if the pattern (starting from the second byte) appears inside
* the Blob content.
*
* @param pattern the Blob to search for, passed in by the user
* @param pos the position in the Blob (this) content to start searching
* @return {@code true} if a match is found, {@code false} if not.
*/
private boolean checkMatch(Blob pattern, long pos)
throws IOException, StandardException {
// check whether rest matches
// might improve performance by reading buffer at a time
InputStream pStream;
try
{
pStream = pattern.getBinaryStream();
}
catch (SQLException e)
{
return false;
}
if (pStream == null)
return false;
// throw away first character since we already read it in the calling
// method
int b1 = pStream.read();
if (b1 < 0)
return false;
while (true)
{
b1 = pStream.read();
if (b1 < 0) // search blob runs out
return true;
int b2 = read(pos++);
if ((b1 != b2) || (b2 < 0)) // mismatch or stream runs out
return false;
}
}
/*
Convert exceptions where needed before calling handleException to convert
them to SQLExceptions.
*/
private SQLException handleMyExceptions(Throwable t)
throws SQLException
{
if (t instanceof StandardException)
{
// container closed means the blob or clob was accessed after commit
if (((StandardException) t).getMessageId().equals(SQLState.DATA_CONTAINER_CLOSED))
{
t = StandardException.newException(SQLState.BLOB_ACCESSED_AFTER_COMMIT);
}
}
return handleException(t);
}
/*
If we have a stream, release the resources associated with it.
*/
protected void finalize()
{
if (!materialized)
myStream.closeStream();
}
/**
Following methods are for the new JDBC 3.0 methods in java.sql.Blob
(see the JDBC 3.0 spec). We have the JDBC 3.0 methods in Local20
package, so we don't have to have a new class in Local30.
The new JDBC 3.0 methods don't make use of any new JDBC3.0 classes and
so this will work fine in jdbc2.0 configuration.
*/
/////////////////////////////////////////////////////////////////////////
//
// JDBC 3.0 - New public methods
//
/////////////////////////////////////////////////////////////////////////
/**
* Writes the given array of bytes to the BLOB value that this Blob object
* represents, starting at position pos, and returns the number of bytes
* written.
*
* @param pos the position in the BLOB object at which to start writing
* @param bytes the array of bytes to be written to the BLOB value that this
* Blob object represents
* @return The number of bytes written to the BLOB.
* @throws SQLException if writing the bytes to the BLOB fails
* @since 1.4
*/
public int setBytes(long pos, byte[] bytes) throws SQLException {
return setBytes(pos, bytes, 0, bytes.length);
}
/**
* Writes all or part of the given array of byte array to the BLOB value
* that this Blob object represents and returns the number of bytes written.
* Writing starts at position pos in the BLOB value; len bytes from the
* given byte array are written.
*
* @param pos the position in the BLOB object at which to start writing
* @param bytes the array of bytes to be written to the BLOB value that this
* Blob object represents
* @param offset the offset into the byte array at which to start reading
* the bytes to be written
* @param len the number of bytes to be written to the BLOB value from the
* array of bytes bytes
* @return The number of bytes written to the BLOB.
* @throws SQLException if writing the bytes to the BLOB fails
* @throws IndexOutOfBoundsException if {@code len} is larger than
* {@code bytes.length - offset}
* @since 1.4
*/
public int setBytes(long pos,
byte[] bytes,
int offset,
int len) throws SQLException {
checkValidity();
if (pos - 1 > length())
throw Util.generateCsSQLException(SQLState.BLOB_POSITION_TOO_LARGE,
pos);
if (pos < 1)
throw Util.generateCsSQLException(SQLState.BLOB_BAD_POSITION,
pos);
if ((offset < 0) || offset > bytes.length) {
throw Util.generateCsSQLException(SQLState.BLOB_INVALID_OFFSET,
offset);
}
if (len < 0) {
throw Util.generateCsSQLException(SQLState.BLOB_NONPOSITIVE_LENGTH,
len);
}
if (len == 0) {
return 0;
}
if (len > bytes.length - offset) {
throw Util.generateCsSQLException(SQLState.BLOB_LENGTH_TOO_LONG,
len);
}
try {
if (materialized) {
control.write(bytes, offset, len, pos - 1);
} else {
control = new LOBStreamControl(getEmbedConnection());
control.copyData(myStream, length());
control.write(bytes, offset, len, pos - 1);
myStream.close();
streamLength = -1;
materialized = true;
}
return len;
} catch (IOException e) {
throw Util.setStreamFailure(e);
} catch (StandardException se) {
throw Util.generateCsSQLException(se);
}
}
/**
* JDBC 3.0
*
* Retrieves a stream that can be used to write to the BLOB value that this
* Blob object represents. The stream begins at position pos.
*
* @param pos - the position in the BLOB object at which to start writing
* @return a java.io.OutputStream object to which data can be written
* @exception SQLException Feature not implemented for now.
*/
public java.io.OutputStream setBinaryStream (long pos)
throws SQLException {
checkValidity ();
if (pos - 1 > length())
throw Util.generateCsSQLException(
SQLState.BLOB_POSITION_TOO_LARGE, pos);
if (pos < 1)
throw Util.generateCsSQLException(
SQLState.BLOB_BAD_POSITION, pos);
try {
if (materialized) {
return control.getOutputStream (pos - 1);
}
else {
control = new LOBStreamControl (
getEmbedConnection());
control.copyData (myStream, pos - 1);
myStream.close ();
streamLength = -1;
materialized = true;
return control.getOutputStream(pos - 1);
}
}
catch (IOException e) {
throw Util.setStreamFailure (e);
}
catch (StandardException se) {
throw Util.generateCsSQLException (se);
}
}
/**
* JDBC 3.0
*
* Truncates the BLOB value that this Blob object represents to be len bytes
* in length.
*
* @param len - the length, in bytes, to which the BLOB value that this Blob
* object represents should be truncated
* @exception SQLException Feature not implemented for now.
*/
public void truncate(long len)
throws SQLException
{
if (len > length())
throw Util.generateCsSQLException(
SQLState.BLOB_LENGTH_TOO_LONG, len);
try {
if (materialized) {
control.truncate (len);
}
else {
setBlobPosition(0); // copy from the beginning
control = new LOBStreamControl (getEmbedConnection());
control.copyData (myStream, len);
myStream.close();
streamLength = -1;
materialized = true;
}
}
catch (IOException e) {
throw Util.setStreamFailure (e);
}
catch (StandardException se) {
throw Util.generateCsSQLException (se);
}
}
/////////////////////////////////////////////////////////////////////////
//
// JDBC 4.0 - New public methods
//
/////////////////////////////////////////////////////////////////////////
/**
* This method frees the <code>Blob</code> object and releases the resources that
* it holds. The object is invalid once the <code>free</code>
* method is called. If <code>free</code> is called multiple times, the subsequent
* calls to <code>free</code> are treated as a no-op.
*
* @throws SQLException if an error occurs releasing
* the Blob's resources
*/
public void free()
throws SQLException {
//calling free() on a already freed object is treated as a no-op
if (!isValid) return;
//now that free has been called the Blob object is no longer
//valid
isValid = false;
// Remove entry from connection if a locator has been created.
if (this.locator != 0) {
localConn.removeLOBMapping(locator);
}
//initialialize length to default value -1
streamLength = -1;
//if it is a stream then close it.
//if a array of bytes then initialize it to null
//to free up space
if (!materialized) {
myStream.closeStream();
myStream = null;
} else {
try {
control.free ();
control = null;
}
catch (IOException e) {
throw Util.setStreamFailure (e);
}
}
}
/**
* Returns an <code>InputStream</code> object that contains a partial
* <code>Blob</code> value, starting with the byte specified by pos,
* which is length bytes in length.
*
* @param pos the offset to the first byte of the partial value to be
* retrieved. The first byte in the <code>Blob</code> is at
* position 1
* @param length the length in bytes of the partial value to be retrieved
* @return through which the partial <code>Blob</code> value can be read.
* @throws SQLException if pos is less than 1 or if pos is greater than
* the number of bytes in the {@code Blob} or if {@code pos + length}
* is greater than {@code Blob.length() +1}
*/
public InputStream getBinaryStream(long pos, long length)
throws SQLException {
//call checkValidity to exit by throwing a SQLException if
//the Blob object has been freed by calling free() on it
checkValidity();
if (pos <= 0) {
throw Util.generateCsSQLException(
SQLState.BLOB_BAD_POSITION,
pos);
}
if (length < 0) {
throw Util.generateCsSQLException(
SQLState.BLOB_NONPOSITIVE_LENGTH,
length);
}
if (length > (this.length() - (pos -1))) {
throw Util.generateCsSQLException(
SQLState.POS_AND_LENGTH_GREATER_THAN_LOB,
pos, length);
}
try {
return new UpdatableBlobStream(this,
getBinaryStream(),
pos-1,
length);
} catch (IOException ioe) {
throw Util.setStreamFailure(ioe);
}
}
/*
* Checks is isValid is true. If it is not true throws
* a SQLException stating that a method has been called on
* an invalid LOB object
*
* throws SQLException if isvalid is not true.
*/
private void checkValidity() throws SQLException{
//check for connection to maintain sqlcode for closed
//connection
getEmbedConnection().checkIfClosed();
if(!isValid)
throw newSQLException(SQLState.LOB_OBJECT_INVALID);
}
/**
* Returns if blob data is stored locally (using LOBStreamControl).
* @return true if materialized else false
*/
boolean isMaterialized () {
return materialized;
}
/**
* Return locator for this lob.
*
* @return The locator identifying this lob.
*/
public int getLocator() {
if (locator == 0) {
locator = localConn.addLOBMapping(this);
}
return locator;
}
}