--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.jdbc.EmbedBlob\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+\r
+package org.apache.derby.impl.jdbc;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.jdbc.EngineLOB;\r
+import org.apache.derby.iapi.services.monitor.Monitor;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+import org.apache.derby.iapi.types.Resetable;\r
+import org.apache.derby.iapi.services.io.NewByteArrayInputStream;\r
+import org.apache.derby.iapi.services.io.InputStreamUtil;\r
+import org.apache.derby.iapi.services.io.ArrayInputStream;\r
+\r
+import java.sql.SQLException;\r
+import java.sql.Blob;\r
+import java.io.InputStream;\r
+import java.io.EOFException;\r
+import java.io.IOException;\r
+\r
+/**\r
+ Implements java.sql.Blob (see the JDBC 2.0 spec).\r
+ A blob sits on top of a BINARY, VARBINARY or LONG VARBINARY column.\r
+ If its data is small (less than 1 page) it is a byte array taken from\r
+ the SQLBit class. If it is large (more than 1 page) it is a long column\r
+ in the database. The long column is accessed as a stream, and is implemented\r
+ in store as an OverflowInputStream. The Resetable interface allows sending\r
+ messages to that stream to initialize itself (reopen its container and\r
+ lock the corresponding row) and to reset itself to the beginning. \r
+\r
+ NOTE: In the case that the data is large, it is represented as a stream.\r
+ This stream is returned to the user in the getBinaryStream() method.\r
+ This means that we have limited control over the state of the stream,\r
+ since the user can read bytes from it at any time. Thus all methods\r
+ here reset the stream to the beginning before doing any work.\r
+ CAVEAT: The methods may not behave correctly if a user sets up\r
+ multiple threads and sucks data from the stream (returned from\r
+ getBinaryStream()) at the same time as calling the Blob methods.\r
+\r
+ <P><B>Supports</B>\r
+ <UL>\r
+ <LI> JSR169 - no subsetting for java.sql.Blob\r
+ <LI> JDBC 2.0\r
+ <LI> JDBC 3.0 - no new dependencies on new JDBC 3.0 or JDK 1.4 classes,\r
+ new update methods can safely be added into implementation.\r
+ </UL>\r
+\r
+ */\r
+\r
+final class EmbedBlob extends ConnectionChild implements Blob, EngineLOB\r
+{\r
+ // blob is either materialized or still in stream\r
+ private boolean materialized;\r
+ private InputStream myStream;\r
+ \r
+ // locator key for lob. used by Network Server.\r
+ private int locator;\r
+ \r
+ /*\r
+ * Length of the BLOB if known. Set to -1 if\r
+ * the current length of the BLOB is not known.\r
+ */\r
+ private long myLength = -1;\r
+ \r
+ // note: cannot control position of the stream since user can do a getBinaryStream\r
+ private long pos;\r
+ // this stream sits on top of myStream\r
+ private BinaryToRawStream biStream;\r
+\r
+ // buffer for reading in blobs from a stream (long column)\r
+ // and trashing them (to set the position of the stream etc.)\r
+ private static int BLOB_BUF_SIZE = 4096;\r
+ private byte buf[];\r
+ \r
+ //This boolean variable indicates whether the Blob object has\r
+ //been invalidated by calling free() on it\r
+ private boolean isValid = true;\r
+\r
+ private LOBStreamControl control;\r
+\r
+ /**\r
+ * This constructor is used to create a empty Blob object. It is used by the\r
+ * Connection interface method createBlob().\r
+ * \r
+ * @param blobBytes A byte array containing the data to be stores in the \r
+ * Blob.\r
+ *\r
+ * @param con The EmbedConnection object associated with this Blob object.\r
+ *\r
+ */\r
+ \r
+ EmbedBlob(byte [] blobBytes,EmbedConnection con) throws SQLException {\r
+ super(con);\r
+ try {\r
+ control = new LOBStreamControl (con.getDBName(), blobBytes);\r
+ materialized = true;\r
+ //add entry in connection so it can be cleared \r
+ //when transaction is not valid\r
+ con.addLOBReference (this);\r
+ }\r
+ catch (IOException e) {\r
+ throw Util.setStreamFailure (e);\r
+ }\r
+ catch (StandardException se) {\r
+ throw Util.generateCsSQLException (se);\r
+ }\r
+ }\r
+ \r
+ /*\r
+ This constructor should only be called by EmbedResultSet.getBlob\r
+ */\r
+ protected EmbedBlob(DataValueDescriptor dvd, EmbedConnection con)\r
+ throws StandardException\r
+ {\r
+ super(con);\r
+ // if the underlying column is null, ResultSet.getBlob will return null,\r
+ // never should get this far\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(!dvd.isNull(), "blob is created on top of a null column");\r
+\r
+ myStream = dvd.getStream();\r
+ if (myStream == null)\r
+ {\r
+ materialized = true;\r
+ // copy bytes into memory so that blob can live after result set\r
+ // is closed\r
+ byte[] dvdBytes = dvd.getBytes();\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(dvdBytes != null,"blob has a null value underneath");\r
+ try {\r
+ control = new LOBStreamControl (\r
+ getEmbedConnection().getDBName(), dvdBytes);\r
+ }\r
+ catch (SQLException e) {\r
+ throw StandardException.newException (e.getSQLState());\r
+ }\r
+ catch (IOException e) {\r
+ throw StandardException.newException (\r
+ SQLState.SET_STREAM_FAILURE, e);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ materialized = false;\r
+\r
+ /*\r
+ We are expecting this stream to be a FormatIdInputStream with an\r
+ OverflowInputStream inside. FormatIdInputStream implements\r
+ Resetable. This should be the case when retrieving\r
+ data from a long column. However, SQLBit, which is the class\r
+ implementing the getStream() method for dvd.getStream(), does not\r
+ guarantee this for us\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(myStream instanceof Resetable);\r
+ //make myStream a position aware stream\r
+ myStream = new PositionedStoreStream (myStream);\r
+ try {\r
+ ((Resetable) myStream).initStream();\r
+ } catch (StandardException se) {\r
+ if (se.getMessageId().equals(SQLState.DATA_CONTAINER_CLOSED)) {\r
+ throw StandardException\r
+ .newException(SQLState.BLOB_ACCESSED_AFTER_COMMIT);\r
+ }\r
+ }\r
+ // set up the buffer for trashing the bytes to set the position of\r
+ // the\r
+ // stream, only need a buffer when we have a long column\r
+ buf = new byte[BLOB_BUF_SIZE];\r
+ }\r
+ pos = 0;\r
+ //add entry in connection so it can be cleared \r
+ //when transaction is not valid\r
+ con.addLOBReference (this);\r
+ }\r
+\r
+\r
+ /*\r
+ Sets the position of the stream to position newPos, where position 0 is\r
+ the beginning of the stream.\r
+\r
+ @param newPos the position to set to\r
+ @exception StandardException (BLOB_SETPOSITION_FAILED) throws this if\r
+ the stream runs out before we get to newPos\r
+ */\r
+ private void setPosition(long newPos)\r
+ throws StandardException, IOException\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(newPos >= 0);\r
+ if (materialized)\r
+ pos = newPos;\r
+ else {\r
+ // Always resets the stream to the beginning first, because user can\r
+ // influence the state of the stream without letting us know.\r
+ ((Resetable)myStream).resetStream();\r
+ // PT could try to save creating a new object each time\r
+ biStream = new BinaryToRawStream(myStream, this);\r
+ pos = 0;\r
+ while (pos < newPos)\r
+ {\r
+ int size = biStream.read(\r
+ buf,0,(int) Math.min((newPos-pos), (long) BLOB_BUF_SIZE));\r
+ if (size <= 0) // ran out of stream\r
+ throw StandardException.newException(SQLState.BLOB_LENGTH_TOO_LONG);\r
+ pos += size;\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ /*\r
+ Reads one byte, either from the byte array or else from the stream.\r
+ */\r
+ private int read() throws IOException, SQLException {\r
+ int c;\r
+ if (materialized)\r
+ {\r
+ try {\r
+ if (pos >= control.getLength())\r
+ return -1;\r
+ else\r
+ c = control.read (pos);\r
+ }\r
+ catch (StandardException se) {\r
+ throw Util.generateCsSQLException (se);\r
+ }\r
+ }\r
+ else\r
+ c = biStream.read();\r
+ pos++;\r
+ return c;\r
+ }\r
+\r
+ /**\r
+ * Returns the number of bytes in the <code>BLOB</code> value\r
+ * designated by this <code>Blob</code> object.\r
+ * @return length of the <code>BLOB</code> in bytes\r
+ * @exception SQLException if there is an error accessing the\r
+ * length of the <code>BLOB</code>\r
+ */\r
+ // PT stream part may get pushed to store\r
+ public long length()\r
+ throws SQLException\r
+ {\r
+ //call checkValidity to exit by throwing a SQLException if\r
+ //the Blob object has been freed by calling free() on it\r
+ checkValidity();\r
+ try {\r
+ if (materialized)\r
+ return control.getLength ();\r
+ }\r
+ catch (IOException e) {\r
+ throw Util.setStreamFailure (e);\r
+ }\r
+ if (myLength != -1)\r
+ return myLength;\r
+ \r
+ boolean pushStack = false;\r
+ try\r
+ {\r
+ // we have a stream\r
+ synchronized (getConnectionSynchronization())\r
+ {\r
+ pushStack = !getEmbedConnection().isClosed();\r
+ if (pushStack)\r
+ setupContextStack();\r
+\r
+ setPosition(0);\r
+ // If possible get the length from the encoded\r
+ // length at the front of the raw stream.\r
+ if ((myLength = biStream.getLength()) != -1) {\r
+ biStream.close();\r
+ return myLength;\r
+ }\r
+ \r
+ // Otherwise have to read the entire stream!\r
+ for (;;)\r
+ {\r
+ int size = biStream.read(buf);\r
+ if (size == -1)\r
+ break;\r
+ pos += size;\r
+ }\r
+ // Save for future uses.\r
+ myLength = pos;\r
+ biStream.close();\r
+ return pos;\r
+ }\r
+ }\r
+ catch (Throwable t)\r
+ {\r
+ throw handleMyExceptions(t);\r
+ }\r
+ finally\r
+ {\r
+ if (pushStack)\r
+ restoreContextStack();\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Returns as an array of bytes part or all of the <code>BLOB</code>\r
+ * value that this <code>Blob</code> object designates. The byte\r
+ * array contains up to <code>length</code> consecutive bytes\r
+ * starting at position <code>startPos</code>.\r
+ * The starting position must be between 1 and the length\r
+ * of the BLOB plus 1. This allows for zero-length BLOB values, from\r
+ * which only zero-length byte arrays can be returned. \r
+ * If a larger length is requested than there are bytes available,\r
+ * characters from the start position to the end of the BLOB are returned.\r
+ * @param startPos the ordinal position of the first byte in the\r
+ * <code>BLOB</code> value to be extracted; the first byte is at\r
+ * position 1\r
+ * @param length is the number of consecutive bytes to be copied\r
+ * @return a byte array containing up to <code>length</code>\r
+ * consecutive bytes from the <code>BLOB</code> value designated\r
+ * by this <code>Blob</code> object, starting with the\r
+ * byte at position <code>startPos</code>.\r
+ * @exception SQLException if there is an error accessing the\r
+ * <code>BLOB</code>\r
+ * NOTE: If the starting position is the length of the BLOB plus 1,\r
+ * zero bytess are returned regardless of the length requested.\r
+ */\r
+ public byte[] getBytes(long startPos, int length)\r
+ throws SQLException\r
+ {\r
+ //call checkValidity to exit by throwing a SQLException if\r
+ //the Blob object has been freed by calling free() on it\r
+ checkValidity();\r
+ \r
+ boolean pushStack = false;\r
+ try\r
+ {\r
+ if (startPos < 1)\r
+ throw StandardException.newException(\r
+ SQLState.BLOB_BAD_POSITION, new Long(startPos));\r
+ if (length < 0)\r
+ throw StandardException.newException(\r
+ SQLState.BLOB_NONPOSITIVE_LENGTH, new Integer(length));\r
+\r
+ byte[] result;\r
+ // if the blob is materialized\r
+ if (materialized) {\r
+ result = new byte [length];\r
+ int sz = control.read (result, 0, result.length, startPos - 1);\r
+ if (sz == -1)\r
+ return new byte [0];\r
+ if (sz < length) {\r
+ byte [] tmparray = new byte [sz];\r
+ System.arraycopy (result, 0, tmparray, 0, sz);\r
+ result = tmparray;\r
+ }\r
+ }\r
+ else // we have a stream\r
+ {\r
+ synchronized (getConnectionSynchronization())\r
+ {\r
+ pushStack = !getEmbedConnection().isClosed();\r
+ if (pushStack)\r
+ setupContextStack();\r
+\r
+ setPosition(startPos-1);\r
+ // read length bytes into a string\r
+ result = new byte[length];\r
+ int n = InputStreamUtil.readLoop(biStream,result,0,length);\r
+ pos += n;\r
+ /*\r
+ According to the spec, if there are only n < length bytes\r
+ to return, we should just return these bytes. Rather than\r
+ return them in an array of size length, where the trailing\r
+ bytes are not initialized, and the user cannot tell how\r
+ many bytes were actually returned, we should return an\r
+ array of n bytes.\r
+ */\r
+ if (n < length)\r
+ {\r
+ byte[] result2 = new byte[n];\r
+ System.arraycopy(result,0,result2,0,n);\r
+ return result2;\r
+ }\r
+ }\r
+ }\r
+ return result;\r
+ }\r
+ catch (StandardException e)\r
+ { // if this is a setPosition exception then we ran out of Blob\r
+ if (e.getMessageId().equals(SQLState.BLOB_LENGTH_TOO_LONG))\r
+ e = StandardException.newException(\r
+ SQLState.BLOB_POSITION_TOO_LARGE, new Long(startPos));\r
+ throw handleMyExceptions(e);\r
+ }\r
+ catch (Throwable t)\r
+ {\r
+ throw handleMyExceptions(t);\r
+ }\r
+ finally\r
+ {\r
+ if (pushStack)\r
+ restoreContextStack();\r
+ }\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Retrieves the <code>BLOB</code> designated by this\r
+ * <code>Blob</code> instance as a stream.\r
+ * @return a stream containing the <code>BLOB</code> data\r
+ * @exception SQLException if there is an error accessing the\r
+ * <code>BLOB</code>\r
+ */\r
+ public java.io.InputStream getBinaryStream()\r
+ throws SQLException\r
+ {\r
+ //call checkValidity to exit by throwing a SQLException if\r
+ //the Blob object has been freed by calling free() on it\r
+ checkValidity();\r
+ \r
+ boolean pushStack = false;\r
+ try\r
+ {\r
+ // if we have byte array, not a stream\r
+ if (materialized)\r
+ {\r
+ return control.getInputStream(0);\r
+ }\r
+ else\r
+ { \r
+ // have a stream\r
+\r
+ synchronized (getConnectionSynchronization())\r
+ {\r
+ pushStack = !getEmbedConnection().isClosed();\r
+ if (pushStack)\r
+ setupContextStack();\r
+\r
+ ((Resetable)myStream).resetStream();\r
+ return new UpdatableBlobStream (this, \r
+ new AutoPositioningStream (this, myStream, this));\r
+ }\r
+ }\r
+ }\r
+ catch (Throwable t)\r
+ {\r
+ throw handleMyExceptions(t);\r
+ }\r
+ finally\r
+ {\r
+ if (pushStack)\r
+ restoreContextStack();\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Determines the byte position at which the specified byte\r
+ * <code>pattern</code> begins within the <code>BLOB</code>\r
+ * value that this <code>Blob</code> object represents. The\r
+ * search for <code>pattern</code. begins at position\r
+ * <code>start</code>\r
+ * @param pattern the byte array for which to search\r
+ * @param start the position at which to begin searching; the\r
+ * first position is 1\r
+ * @return the position at which the pattern appears, else -1.\r
+ * @exception SQLException if there is an error accessing the\r
+ * <code>BLOB</code>\r
+ */\r
+ public long position(byte[] pattern, long start)\r
+ throws SQLException\r
+ {\r
+ //call checkValidity to exit by throwing a SQLException if\r
+ //the Blob object has been freed by calling free() on it\r
+ checkValidity();\r
+ \r
+ boolean pushStack = false;\r
+ try\r
+ {\r
+ if (start < 1)\r
+ throw StandardException.newException(\r
+ SQLState.BLOB_BAD_POSITION, new Long(start));\r
+ if (pattern == null)\r
+ throw StandardException.newException(SQLState.BLOB_NULL_PATTERN_OR_SEARCH_STR);\r
+ if (pattern.length == 0)\r
+ return start; // match DB2's SQL LOCATE function\r
+\r
+ synchronized (getConnectionSynchronization())\r
+ {\r
+ pushStack = !getEmbedConnection().isClosed();\r
+ if (pushStack)\r
+ setupContextStack();\r
+\r
+ setPosition(start-1);\r
+ // look for first character\r
+ int lookFor = pattern[0];\r
+ long curPos;\r
+ int c;\r
+ while (true)\r
+ {\r
+ c = read();\r
+ if (c == -1) // run out of stream\r
+ return -1;\r
+ if (c == lookFor)\r
+ {\r
+ curPos = pos;\r
+ if (checkMatch(pattern))\r
+ return curPos;\r
+ else\r
+ setPosition(curPos);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ catch (StandardException e)\r
+ { // if this is a setPosition exception then not found\r
+ if (e.getMessageId().equals(SQLState.BLOB_LENGTH_TOO_LONG))\r
+ return -1;\r
+ else\r
+ throw handleMyExceptions(e);\r
+ }\r
+ catch (Throwable t)\r
+ {\r
+ throw handleMyExceptions(t);\r
+ }\r
+ finally\r
+ {\r
+ if (pushStack)\r
+ restoreContextStack();\r
+ }\r
+\r
+ }\r
+\r
+\r
+ /*\r
+ check whether pattern (starting from the second byte) appears inside\r
+ posStream (at the current position)\r
+ @param posStream the stream to search inside\r
+ @param pattern the byte array passed in by the user to search with\r
+ @return true if match, false otherwise\r
+ */\r
+ private boolean checkMatch(byte[] pattern)\r
+ throws IOException, SQLException {\r
+ // check whether rest matches\r
+ // might improve performance by reading more\r
+ for (int i = 1; i < pattern.length; i++)\r
+ {\r
+ int b = read();\r
+ if ((b < 0) || (b != pattern[i])) // mismatch or stream runs out\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Determines the byte position in the <code>BLOB</code> value\r
+ * designated by this <code>Blob</code> object at which\r
+ * <code>pattern</code> begins. The search begins at position\r
+ * <code>start</code>.\r
+ * @param pattern the <code>Blob</code> object designating\r
+ * the <code>BLOB</code> value for which to search\r
+ * @param start the position in the <code>BLOB</code> value\r
+ * at which to begin searching; the first position is 1\r
+ * @return the position at which the pattern begins, else -1\r
+ * @exception SQLException if there is an error accessing the\r
+ * <code>BLOB</code>\r
+ */\r
+ public long position(Blob pattern, long start)\r
+ throws SQLException\r
+ {\r
+ //call checkValidity to exit by throwing a SQLException if\r
+ //the Blob object has been freed by calling free() on it\r
+ checkValidity();\r
+ \r
+ boolean pushStack = false;\r
+ try\r
+ {\r
+ if (start < 1)\r
+ throw StandardException.newException(\r
+ SQLState.BLOB_BAD_POSITION, new Long(start));\r
+ if (pattern == null)\r
+ throw StandardException.newException(SQLState.BLOB_NULL_PATTERN_OR_SEARCH_STR);\r
+ synchronized (getConnectionSynchronization())\r
+ {\r
+ pushStack = !getEmbedConnection().isClosed();\r
+ if (pushStack)\r
+ setupContextStack();\r
+\r
+ setPosition(start-1);\r
+ // look for first character\r
+ byte[] b;\r
+ try\r
+ { // pattern is not necessarily a Derby Blob\r
+ b = pattern.getBytes(1,1);\r
+ }\r
+ catch (SQLException e)\r
+ {\r
+ throw StandardException.newException(SQLState.BLOB_UNABLE_TO_READ_PATTERN);\r
+ }\r
+ if (b == null || b.length < 1) // the 'empty' blob\r
+ return start; // match DB2's SQL LOCATE function\r
+ int lookFor = b[0];\r
+ int c;\r
+ long curPos;\r
+ while (true)\r
+ {\r
+ c = read();\r
+ if (c == -1) // run out of stream\r
+ return -1;\r
+ if (c == lookFor)\r
+ {\r
+ curPos = pos;\r
+ if (checkMatch(pattern))\r
+ return curPos;\r
+ else\r
+ setPosition(curPos);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ catch (StandardException e)\r
+ { // if this is a setPosition exception then not found\r
+ if (e.getMessageId().equals(SQLState.BLOB_LENGTH_TOO_LONG))\r
+ return -1;\r
+ else\r
+ throw handleMyExceptions(e);\r
+ }\r
+ catch (Throwable t)\r
+ {\r
+ throw handleMyExceptions(t);\r
+ }\r
+ finally\r
+ {\r
+ if (pushStack)\r
+ restoreContextStack();\r
+ }\r
+\r
+ }\r
+\r
+\r
+ /*\r
+ check whether pattern (starting from the second byte) appears inside\r
+ posStream (at the current position)\r
+ @param posStream the stream to search inside\r
+ @param pattern the blob passed in by the user to search with\r
+ @return true if match, false otherwise\r
+ */\r
+ private boolean checkMatch(Blob pattern)\r
+ throws IOException, SQLException\r
+ {\r
+ // check whether rest matches\r
+ // might improve performance by reading buffer at a time\r
+ InputStream pStream;\r
+ try\r
+ {\r
+ pStream = pattern.getBinaryStream();\r
+ }\r
+ catch (SQLException e)\r
+ {\r
+ return false;\r
+ }\r
+ if (pStream == null)\r
+ return false;\r
+ // throw away first character since we already read it in the calling\r
+ // method\r
+ int b1 = pStream.read();\r
+ if (b1 < 0)\r
+ return false;\r
+ while (true)\r
+ {\r
+ b1 = pStream.read();\r
+ if (b1 < 0) // search blob runs out\r
+ return true;\r
+ int b2 = read();\r
+ if ((b1 != b2) || (b2 < 0)) // mismatch or stream runs out\r
+ return false;\r
+ }\r
+ }\r
+\r
+ /*\r
+ Convert exceptions where needed before calling handleException to convert\r
+ them to SQLExceptions.\r
+ */\r
+ private SQLException handleMyExceptions(Throwable t)\r
+ throws SQLException\r
+ {\r
+ if (t instanceof StandardException)\r
+ {\r
+ // container closed means the blob or clob was accessed after commit\r
+ if (((StandardException) t).getMessageId().equals(SQLState.DATA_CONTAINER_CLOSED))\r
+ {\r
+ t = StandardException.newException(SQLState.BLOB_ACCESSED_AFTER_COMMIT);\r
+ }\r
+ }\r
+ return handleException(t);\r
+ }\r
+\r
+\r
+ /*\r
+ If we have a stream, release the resources associated with it.\r
+ */\r
+ protected void finalize()\r
+ {\r
+ if (!materialized)\r
+ ((Resetable)myStream).closeStream();\r
+ }\r
+\r
+ /**\r
+ Following methods are for the new JDBC 3.0 methods in java.sql.Blob\r
+ (see the JDBC 3.0 spec). We have the JDBC 3.0 methods in Local20\r
+ package, so we don't have to have a new class in Local30.\r
+ The new JDBC 3.0 methods don't make use of any new JDBC3.0 classes and\r
+ so this will work fine in jdbc2.0 configuration.\r
+ */\r
+\r
+ /////////////////////////////////////////////////////////////////////////\r
+ //\r
+ // JDBC 3.0 - New public methods\r
+ //\r
+ /////////////////////////////////////////////////////////////////////////\r
+\r
+ /**\r
+ * JDBC 3.0\r
+ *\r
+ * Writes the given array of bytes to the BLOB value that this Blob object\r
+ * represents, starting at position pos, and returns the number of bytes written.\r
+ *\r
+ * @param pos - the position in the BLOB object at which to start writing\r
+ * @param bytes - the array of bytes to be written to the BLOB value that this\r
+ * Blob object represents\r
+ * @return the number of bytes written\r
+ * @exception SQLException Feature not implemented for now.\r
+ */\r
+ public int setBytes(long pos, byte[] bytes) throws SQLException {\r
+ return setBytes(pos, bytes, 0, bytes.length);\r
+ }\r
+\r
+ /**\r
+ * JDBC 3.0\r
+ *\r
+ * Writes all or part of the given array of byte array to the BLOB value that\r
+ * this Blob object represents and returns the number of bytes written.\r
+ * Writing starts at position pos in the BLOB value; len bytes from the given\r
+ * byte array are written.\r
+ *\r
+ * @param pos - the position in the BLOB object at which to start writing\r
+ * @param bytes - the array of bytes to be written to the BLOB value that this\r
+ * Blob object represents\r
+ * @param offset - the offset into the array bytes at which to start reading\r
+ * the bytes to be set\r
+ * @param len - the number of bytes to be written to the BLOB value from the\r
+ * array of bytes bytes\r
+ * @return the number of bytes written\r
+ * @exception SQLException Feature not implemented for now.\r
+ */\r
+ public int setBytes(long pos,\r
+ byte[] bytes,\r
+ int offset,\r
+ int len) throws SQLException {\r
+ checkValidity();\r
+ try {\r
+ if (materialized) {\r
+ if (pos - 1 > length())\r
+ throw Util.generateCsSQLException(\r
+ SQLState.BLOB_POSITION_TOO_LARGE, new Long(pos));\r
+ if (pos < 1)\r
+ throw Util.generateCsSQLException(\r
+ SQLState.BLOB_BAD_POSITION, new Long(pos));\r
+ len = (int) control.write (bytes, offset, len, pos - 1);\r
+ }\r
+ else {\r
+ control = new LOBStreamControl (getEmbedConnection().getDBName());\r
+ control.copyData (myStream, length());\r
+ len = (int) control.write(bytes, offset, len, pos - 1);\r
+ myStream.close();\r
+ materialized = true;\r
+ }\r
+ return len;\r
+ }\r
+ catch (IOException e) {\r
+ throw Util.setStreamFailure (e);\r
+ }\r
+ catch (StandardException se) {\r
+ throw Util.generateCsSQLException (se);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * JDBC 3.0\r
+ *\r
+ * Retrieves a stream that can be used to write to the BLOB value that this\r
+ * Blob object represents. The stream begins at position pos. \r
+ *\r
+ * @param pos - the position in the BLOB object at which to start writing\r
+ * @return a java.io.OutputStream object to which data can be written \r
+ * @exception SQLException Feature not implemented for now.\r
+ */\r
+ public java.io.OutputStream setBinaryStream (long pos)\r
+ throws SQLException {\r
+ checkValidity ();\r
+ if (pos - 1 > length())\r
+ throw Util.generateCsSQLException(\r
+ SQLState.BLOB_POSITION_TOO_LARGE, new Long(pos));\r
+ if (pos < 1)\r
+ throw Util.generateCsSQLException(\r
+ SQLState.BLOB_BAD_POSITION, new Long(pos));\r
+ try {\r
+ if (materialized) {\r
+ return control.getOutputStream (pos - 1);\r
+ }\r
+ else {\r
+ control = new LOBStreamControl (\r
+ getEmbedConnection().getDBName());\r
+ control.copyData (myStream, pos - 1);\r
+ myStream.close ();\r
+ materialized = true;\r
+ return control.getOutputStream(pos - 1);\r
+\r
+ }\r
+ }\r
+ catch (IOException e) {\r
+ throw Util.setStreamFailure (e);\r
+ }\r
+ catch (StandardException se) {\r
+ throw Util.generateCsSQLException (se);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * JDBC 3.0\r
+ *\r
+ * Truncates the BLOB value that this Blob object represents to be len bytes\r
+ * in length.\r
+ *\r
+ * @param len - the length, in bytes, to which the BLOB value that this Blob\r
+ * object represents should be truncated\r
+ * @exception SQLException Feature not implemented for now.\r
+ */\r
+ public void truncate(long len)\r
+ throws SQLException\r
+ {\r
+ if (len > length())\r
+ throw Util.generateCsSQLException(\r
+ SQLState.BLOB_LENGTH_TOO_LONG, new Long(pos));\r
+ try {\r
+ if (materialized) {\r
+ control.truncate (len);\r
+ }\r
+ else {\r
+ control = new LOBStreamControl (getEmbedConnection().getDBName());\r
+ control.copyData (myStream, len);\r
+ myStream.close();\r
+ materialized = true;\r
+ }\r
+ }\r
+ catch (IOException e) {\r
+ throw Util.setStreamFailure (e);\r
+ }\r
+ catch (StandardException se) {\r
+ throw Util.generateCsSQLException (se);\r
+ }\r
+ }\r
+\r
+ /////////////////////////////////////////////////////////////////////////\r
+ //\r
+ // JDBC 4.0 - New public methods\r
+ //\r
+ /////////////////////////////////////////////////////////////////////////\r
+ /**\r
+ * This method frees the <code>Blob</code> object and releases the resources that \r
+ * it holds. The object is invalid once the <code>free</code>\r
+ * method is called. If <code>free</code> is called multiple times, the subsequent\r
+ * calls to <code>free</code> are treated as a no-op.\r
+ * \r
+ * @throws SQLException if an error occurs releasing\r
+ * the Blob's resources\r
+ */\r
+ public void free()\r
+ throws SQLException {\r
+ //calling free() on a already freed object is treated as a no-op\r
+ if (!isValid) return;\r
+ \r
+ //now that free has been called the Blob object is no longer\r
+ //valid\r
+ isValid = false;\r
+ \r
+ //remove entry from connection\r
+ localConn.removeLOBMapping(locator);\r
+ //initialialize length to default value -1\r
+ myLength = -1;\r
+ \r
+ //if it is a stream then close it.\r
+ //if a array of bytes then initialize it to null\r
+ //to free up space\r
+ if (!materialized)\r
+ ((Resetable)myStream).closeStream();\r
+ else {\r
+ try {\r
+ control.free ();\r
+ control = null;\r
+ }\r
+ catch (IOException e) {\r
+ throw Util.setStreamFailure (e);\r
+ }\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Returns an <code>InputStream</code> object that contains a partial \r
+ * <code>Blob</code> value, starting with the byte specified by pos, \r
+ * which is length bytes in length.\r
+ *\r
+ * @param pos the offset to the first byte of the partial value to be \r
+ * retrieved. The first byte in the <code>Blob</code> is at \r
+ * position 1\r
+ * @param length the length in bytes of the partial value to be retrieved\r
+ * @return through which the partial <code>Blob</code> value can be read. \r
+ * @throws SQLException if pos is less than 1 or if pos is greater than \r
+ * the number of bytes in the <code>Blob</code> or if pos + length is\r
+ * greater than the number of bytes in the <code>Blob</code>\r
+ */\r
+ public InputStream getBinaryStream(long pos, long length)\r
+ throws SQLException {\r
+ //call checkValidity to exit by throwing a SQLException if\r
+ //the Blob object has been freed by calling free() on it\r
+ checkValidity();\r
+ \r
+ if (pos <= 0) {\r
+ throw Util.generateCsSQLException(\r
+ SQLState.BLOB_BAD_POSITION,\r
+ new Long(pos));\r
+ }\r
+ if (length < 0) {\r
+ throw Util.generateCsSQLException(\r
+ SQLState.BLOB_NONPOSITIVE_LENGTH,\r
+ new Long(length));\r
+ }\r
+ if (length > (this.length() - pos)) {\r
+ throw Util.generateCsSQLException(\r
+ SQLState.POS_AND_LENGTH_GREATER_THAN_LOB,\r
+ new Long(pos), new Long(length));\r
+ }\r
+ \r
+ try {\r
+ return new UpdatableBlobStream(this,\r
+ getBinaryStream(),\r
+ pos-1,\r
+ length);\r
+ } catch (IOException ioe) {\r
+ throw Util.setStreamFailure(ioe);\r
+ }\r
+ }\r
+ \r
+ /*\r
+ * Checks is isValid is true. If it is not true throws \r
+ * a SQLException stating that a method has been called on\r
+ * an invalid LOB object\r
+ *\r
+ * throws SQLException if isvalid is not true.\r
+ */\r
+ private void checkValidity() throws SQLException{\r
+ //check for connection to maintain sqlcode for closed\r
+ //connection\r
+ getEmbedConnection().checkIfClosed();\r
+ if(!isValid)\r
+ throw newSQLException(SQLState.LOB_OBJECT_INVALID);\r
+ }\r
+ \r
+ /**\r
+ * Returns if blob data is stored locally (using LOBStreamControl).\r
+ * @return true if materialized else false\r
+ */\r
+ boolean isMaterialized () {\r
+ return materialized;\r
+ }\r
+\r
+ /**\r
+ * Return locator for this lob.\r
+ * \r
+ * @return The locator identifying this blob\r
+ */\r
+ public int getLocator() {\r
+ if (locator == 0) {\r
+ locator = localConn.addLOBMapping(this);\r
+ }\r
+ return locator;\r
+ }\r
+}\r