--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.jdbc.StoreStreamClob\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one\r
+ or more contributor license agreements. See the NOTICE file\r
+ distributed with this work for additional information\r
+ regarding copyright ownership. The ASF licenses this file\r
+ to you under the Apache License, Version 2.0 (the\r
+ "License"); you may not use this file except in compliance\r
+ with 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,\r
+ software distributed under the License is distributed on an\r
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r
+ KIND, either express or implied. See the License for the\r
+ specific language governing permissions and limitations\r
+ under the License.\r
+\r
+ */\r
+package org.apache.derby.impl.jdbc;\r
+\r
+import java.io.BufferedInputStream;\r
+import java.io.EOFException;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.Reader;\r
+import java.io.Writer;\r
+\r
+import java.sql.SQLException;\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.types.Resetable;\r
+import org.apache.derby.iapi.types.TypeId;\r
+import org.apache.derby.iapi.util.UTF8Util;\r
+\r
+/**\r
+ * A read-only Clob representation operating on streams out of the Derby store\r
+ * module.\r
+ * <p>\r
+ * Note that the streams from the store are expected to have the following\r
+ * properties:\r
+ * <ol> <li>The first two bytes are used for length encoding. Note that due to\r
+ * the inadequate max number of this format, it is always ignored. This\r
+ * is also true if there actually is a length encoded there. The two\r
+ * bytes are excluded from the length of the stream.\r
+ * <li>A Derby-specific end-of-stream marker at the end of the stream can\r
+ * be present. The marker is expected to be <code>0xe0 0x00 0x00</code>\r
+ * </ol>\r
+ */\r
+final class StoreStreamClob\r
+ implements InternalClob {\r
+\r
+ /** Maximum value used when requesting bytes/chars to be skipped. */\r
+ private static final long SKIP_BUFFER_SIZE = 8*1024; // 8 KB\r
+\r
+ /** Tells whether this Clob has been released or not. */\r
+ private volatile boolean released = false;\r
+\r
+ /**\r
+ * The stream from store, used to read bytes from the database.\r
+ * <p>\r
+ * To be able to support the requirements, the stream must implement\r
+ * {@link Resetable}.\r
+ */\r
+ //@GuardedBy("synchronizationObject")\r
+ private final PositionedStoreStream positionedStoreStream;\r
+ /** The connection (child) this Clob belongs to. */\r
+ private final ConnectionChild conChild;\r
+ /** Object used for synchronizing access to the store stream. */\r
+ private final Object synchronizationObject;\r
+\r
+\r
+ /**\r
+ * Creates a new Clob based on a stream from store.\r
+ * <p>\r
+ * Note that the stream passed in have to fulfill certain requirements,\r
+ * which are not currently totally enforced by Java (the language).\r
+ *\r
+ * @param stream the stream containing the Clob value. This stream is\r
+ * expected to implement {@link Resetable} and to be a\r
+ * {@link org.apache.derby.iapi.services.io.FormatIdInputStream} with\r
+ * an ${link org.apache.derby.impl.store.raw.data.OverflowInputStream}\r
+ * inside. However, the available interfaces does not guarantee this.\r
+ * See the class JavaDoc for more information about this stream.\r
+ * @param conChild the connection (child) this Clob belongs to\r
+ * @throws StandardException if initializing the store stream fails\r
+ * @throws NullPointerException if <code>stream</code> or\r
+ * <code>conChild</code> is null\r
+ * @throws ClassCastException if <code>stream</code> is not an instance\r
+ * of <code>Resetable</code>\r
+ * @see org.apache.derby.iapi.services.io.FormatIdInputStream\r
+ * @see org.apache.derby.impl.store.raw.data.OverflowInputStream\r
+ */\r
+ public StoreStreamClob(InputStream stream, ConnectionChild conChild)\r
+ throws StandardException {\r
+ this.positionedStoreStream = new PositionedStoreStream(stream);\r
+ this.conChild = conChild;\r
+ this.synchronizationObject = conChild.getConnectionSynchronization();\r
+ this.positionedStoreStream.initStream();\r
+ }\r
+\r
+ /**\r
+ * Releases resources associated with this Clob.\r
+ */\r
+ public void release() {\r
+ if (!released) {\r
+ this.positionedStoreStream.closeStream();\r
+ this.released = true;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Returns the number of bytes in the Clob.\r
+ *\r
+ * @return The number of bytes in the Clob.\r
+ * @throws IOException if accessing the I/O resources fail\r
+ * @throws SQLException if accessing the store resources fail\r
+ */\r
+ public long getByteLength()\r
+ throws IOException, SQLException {\r
+ checkIfValid();\r
+ // Read through the whole stream to get the length.\r
+ long byteLength = 0;\r
+ try {\r
+ this.conChild.setupContextStack();\r
+ this.positionedStoreStream.reposition(0L);\r
+ // See if length is encoded in the stream.\r
+ int us1 = this.positionedStoreStream.read();\r
+ int us2 = this.positionedStoreStream.read();\r
+ byteLength = (us1 << 8) + (us2 << 0);\r
+ if (byteLength == 0) {\r
+ while (true) {\r
+ long skipped =\r
+ this.positionedStoreStream.skip(SKIP_BUFFER_SIZE);\r
+ if (skipped <= 0) {\r
+ break;\r
+ }\r
+ byteLength += skipped;\r
+ }\r
+ // Subtract 3 bytes for the end-of-stream marker.\r
+ byteLength -= 3;\r
+ }\r
+ return byteLength;\r
+ } catch (StandardException se) {\r
+ throw Util.generateCsSQLException(se);\r
+ } finally {\r
+ this.conChild.restoreContextStack();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Returns the number of characters in the Clob.\r
+ *\r
+ * @return Number of characters in the Clob.\r
+ * @throws SQLException if any kind of error is encountered, be it related\r
+ * to I/O or something else\r
+ */\r
+ public long getCharLength()\r
+ throws SQLException {\r
+ checkIfValid();\r
+ synchronized (this.synchronizationObject) {\r
+ this.conChild.setupContextStack();\r
+ try {\r
+ return UTF8Util.skipUntilEOF(\r
+ new BufferedInputStream(getRawByteStream()));\r
+ } catch (Throwable t) {\r
+ throw noStateChangeLOB(t);\r
+ } finally {\r
+ this.conChild.restoreContextStack();\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Returns a stream serving the raw bytes of this Clob.\r
+ * <p>\r
+ * Note that the stream returned is an internal stream, and it should not be\r
+ * pulished to end users.\r
+ *\r
+ * @return A stream serving the bytes of this Clob, initialized at byte 0 of\r
+ * the data. The buffer must be assumed to be unbuffered, but no such\r
+ * guarantee is made.\r
+ * @throws IOException if accessing the I/O resources fail\r
+ * @throws SQLException if accessing the store resources fail\r
+ */\r
+ public InputStream getRawByteStream()\r
+ throws IOException, SQLException {\r
+ checkIfValid();\r
+ try {\r
+ // Skip the encoded length.\r
+ this.positionedStoreStream.reposition(2L);\r
+ } catch (StandardException se) {\r
+ throw Util.generateCsSQLException(se);\r
+ }\r
+ return this.positionedStoreStream;\r
+ }\r
+\r
+ /**\r
+ * Returns a reader for the Clob, initialized at the specified character\r
+ * position.\r
+ *\r
+ * @param pos character position. The first character is at position 1.\r
+ * @return A reader initialized at the specified position.\r
+ * @throws EOFException if the positions is larger than the Clob\r
+ * @throws IOException if accessing the I/O resources fail\r
+ * @throws SQLException if accessing the store resources fail\r
+ */\r
+ public Reader getReader(long pos)\r
+ throws IOException, SQLException {\r
+ checkIfValid();\r
+ try {\r
+ this.positionedStoreStream.reposition(0L);\r
+ } catch (StandardException se) {\r
+ throw Util.generateCsSQLException(se);\r
+ }\r
+ Reader reader = new UTF8Reader(this.positionedStoreStream,\r
+ TypeId.CLOB_MAXWIDTH, this.conChild,\r
+ this.synchronizationObject);\r
+ long leftToSkip = pos -1;\r
+ long skipped;\r
+ while (leftToSkip > 0) {\r
+ skipped = reader.skip(leftToSkip);\r
+ // Since Reader.skip block until some characters are available,\r
+ // a return value of 0 must mean EOF.\r
+ if (skipped <= 0) {\r
+ throw new EOFException("Reached end-of-stream prematurely");\r
+ }\r
+ leftToSkip -= skipped;\r
+ }\r
+ return reader;\r
+ }\r
+\r
+ /**\r
+ * Returns the byte position for the specified character position.\r
+ *\r
+ * @param charPos character position. First character is at position 1.\r
+ * @return Corresponding byte position. First byte is at position 0.\r
+ * @throws EOFException if the position is bigger then the Clob\r
+ * @throws IOException if accessing the underlying I/O resources fail\r
+ * @throws SQLException if accessing the underlying store resources fail\r
+ */\r
+ public long getBytePosition(long charPos)\r
+ throws IOException, SQLException {\r
+ return UTF8Util.skipFully(getRawByteStream(), charPos -1);\r
+ }\r
+\r
+ /**\r
+ * Not supported.\r
+ *\r
+ * @see InternalClob#getWriter\r
+ */\r
+ public Writer getWriter(long pos) {\r
+ throw new UnsupportedOperationException(\r
+ "A StoreStreamClob object is not updatable");\r
+ }\r
+\r
+ /**\r
+ * Not supported.\r
+ *\r
+ * @see InternalClob#insertString\r
+ */\r
+ public long insertString(String str, long pos) {\r
+ throw new UnsupportedOperationException(\r
+ "A StoreStreamClob object is not updatable");\r
+ }\r
+\r
+ /**\r
+ * Tells if this Clob can be modified.\r
+ *\r
+ * @return <code>false</code>, this Clob is read-only.\r
+ */\r
+ public boolean isWritable() {\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Not supported.\r
+ *\r
+ * @see InternalClob#truncate\r
+ */\r
+ public void truncate(long newLength) {\r
+ throw new UnsupportedOperationException(\r
+ "A StoreStreamClob object is not updatable");\r
+ }\r
+\r
+ /**\r
+ * Wrap real exception in a {@link SQLException} to avoid changing the state\r
+ * of the connection child by cleaning it up.\r
+ *\r
+ * @param t real cause of error that we want to "ignore" with respect to\r
+ * transaction context cleanup\r
+ * @return A {@link SQLException} wrapped around the real cause of the error\r
+ */\r
+ private static SQLException noStateChangeLOB(Throwable t) {\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 org.apache.derby.impl.jdbc.EmbedResultSet.noStateChangeException(t);\r
+ }\r
+\r
+ /**\r
+ * Makes sure the Clob has not been released.\r
+ * <p>\r
+ * All operations are invalid on a released Clob.\r
+ *\r
+ * @throws IllegalStateException if the Clob has been released\r
+ */\r
+ private void checkIfValid() {\r
+ if (this.released) {\r
+ throw new IllegalStateException(\r
+ "The Clob has been released and is not valid");\r
+ }\r
+ }\r
+} // End class StoreStreamClob\r