001: /*
002:
003: Derby - Class org.apache.derby.client.am.Blob
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to You under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.client.am;
023:
024: import java.io.IOException;
025: import java.io.InputStream;
026: import java.sql.SQLException;
027: import java.util.ArrayList;
028:
029: import org.apache.derby.shared.common.reference.SQLState;
030:
031: public class Blob extends Lob implements java.sql.Blob {
032:
033: //This boolean variable indicates whether the Blob object has
034: //been invalidated by calling free() on it
035: private boolean isValid = true;
036:
037: //-----------------------------state------------------------------------------
038:
039: byte[] binaryString_ = null;
040:
041: // Only used for input purposes. For output, each getBinaryStream call
042: // must generate an independent stream.
043: java.io.InputStream binaryStream_ = null;
044: int dataOffset_;
045:
046: //---------------------constructors/finalizer---------------------------------
047:
048: public Blob(byte[] binaryString, Agent agent, int dataOffset) {
049: super (agent);
050: binaryString_ = binaryString;
051: dataType_ |= BINARY_STRING;
052: sqlLength_ = binaryString.length - dataOffset;
053: lengthObtained_ = true;
054: dataOffset_ = dataOffset;
055: }
056:
057: // CTOR for input:
058: public Blob(Agent agent, java.io.InputStream binaryStream,
059: int length) {
060: super (agent);
061: binaryStream_ = binaryStream;
062: dataType_ |= BINARY_STREAM;
063: sqlLength_ = length;
064: lengthObtained_ = true;
065: }
066:
067: /**
068: * Create a new <code>Blob</code> from a stream with unknown length.
069: * <em>Important:</em> This constructor is a temporary solution for
070: * implementing lengthless overloads in the JDBC4 API. Before a proper
071: * solution can be implemented, we need to enable streaming without having
072: * to know the stream length in the DRDA protocol. See Jira DERBY-1471 and
073: * DERBY-1417 for more details.
074: *
075: * <em>Shortcomings:</em> This constructor will cause the <em>whole stream
076: * to be materialized</em> to determine its length. If the stream is big
077: * enough, the client will fail with an OutOfMemoryError. Since this is a
078: * temporary solution, state checking is not added to all methods as it
079: * would clutter up the class severely. After using the constructor, the
080: * <code>length</code>-method must be called, which will materialize the
081: * stream and determine the length. <em>Do not pass a Blob object created
082: * with this constructor to the user!</em>
083: *
084: * @param agent
085: * @param binaryStream the stream to get data from
086: */
087: public Blob(Agent agent, java.io.InputStream binaryStream) {
088: super (agent);
089: binaryStream_ = binaryStream;
090: dataType_ |= BINARY_STREAM;
091: sqlLength_ = -1;
092: lengthObtained_ = false;
093: }
094:
095: // ---------------------------jdbc 2------------------------------------------
096:
097: public long length() throws SQLException {
098: //call checkValidity to exit by throwing a SQLException if
099: //the Blob object has been freed by calling free() on it
100: checkValidity();
101: try {
102: synchronized (agent_.connection_) {
103: if (agent_.loggingEnabled()) {
104: agent_.logWriter_.traceEntry(this , "length");
105: }
106: // Code to handle the lengthless constructor.
107: if (!lengthObtained_) {
108: binaryStream_ = super .materializeStream(
109: binaryStream_, "java.sql.Blob");
110: }
111: long retVal = super .sqlLength();
112: if (agent_.loggingEnabled()) {
113: agent_.logWriter_.traceExit(this , "length", retVal);
114: }
115: return retVal;
116: }
117: } catch (SqlException se) {
118: throw se.getSQLException();
119: }
120: }
121:
122: /**
123: * Returns as an array of bytes part or all of the <code>BLOB</code>
124: * value that this <code>Blob</code> object designates. The byte
125: * array contains up to <code>length</code> consecutive bytes
126: * starting at position <code>pos</code>.
127: * The starting position must be between 1 and the length
128: * of the BLOB plus 1. This allows for zero-length BLOB values, from
129: * which only zero-length byte arrays can be returned.
130: * If a larger length is requested than there are bytes available,
131: * characters from the start position to the end of the BLOB are returned.
132: * @param pos the ordinal position of the first byte in the
133: * <code>BLOB</code> value to be extracted; the first byte is at
134: * position 1
135: * @param length is the number of consecutive bytes to be copied
136: * @return a byte array containing up to <code>length</code>
137: * consecutive bytes from the <code>BLOB</code> value designated
138: * by this <code>Blob</code> object, starting with the
139: * byte at position <code>startPos</code>.
140: * @exception SQLException if there is an error accessing the
141: * <code>BLOB</code>
142: * NOTE: If the starting position is the length of the BLOB plus 1,
143: * zero bytess are returned regardless of the length requested.
144: */
145: public byte[] getBytes(long pos, int length) throws SQLException {
146: //call checkValidity to exit by throwing a SQLException if
147: //the Blob object has been freed by calling free() on it
148: checkValidity();
149: try {
150: synchronized (agent_.connection_) {
151: if (agent_.loggingEnabled()) {
152: agent_.logWriter_.traceEntry(this , "getBytes",
153: (int) pos, length);
154: }
155: if (pos <= 0) {
156: throw new SqlException(agent_.logWriter_,
157: new ClientMessageId(
158: SQLState.BLOB_BAD_POSITION),
159: new Long(pos));
160: }
161: if (pos > this .length() + 1) {
162: throw new SqlException(agent_.logWriter_,
163: new ClientMessageId(
164: SQLState.BLOB_POSITION_TOO_LARGE),
165: new Long(pos));
166: }
167: if (length < 0) {
168: throw new SqlException(agent_.logWriter_,
169: new ClientMessageId(
170: SQLState.BLOB_NONPOSITIVE_LENGTH),
171: new Integer(length));
172: }
173: byte[] retVal = getBytesX(pos, length);
174: if (agent_.loggingEnabled()) {
175: agent_.logWriter_.traceExit(this , "getBytes",
176: retVal);
177: }
178: return retVal;
179: }
180: } catch (SqlException se) {
181: throw se.getSQLException();
182: }
183: }
184:
185: private byte[] getBytesX(long pos, int length) throws SqlException {
186: checkForClosedConnection();
187:
188: long actualLength;
189: try {
190: // actual length is the lesser of the number of bytes requested
191: // and the number of bytes available from pos to the end
192: actualLength = Math.min(this .length() - pos + 1,
193: (long) length);
194: } catch (SQLException se) {
195: throw new SqlException(se);
196: }
197: byte[] retVal = new byte[(int) actualLength];
198: System.arraycopy(binaryString_, (int) pos + dataOffset_ - 1,
199: retVal, 0, (int) actualLength);
200: return retVal;
201: }
202:
203: public java.io.InputStream getBinaryStream() throws SQLException {
204: //call checkValidity to exit by throwing a SQLException if
205: //the Blob object has been freed by calling free() on it
206: checkValidity();
207: try {
208: synchronized (agent_.connection_) {
209: if (agent_.loggingEnabled()) {
210: agent_.logWriter_.traceEntry(this ,
211: "getBinaryStream");
212: }
213: java.io.InputStream retVal = getBinaryStreamX();
214: if (agent_.loggingEnabled()) {
215: agent_.logWriter_.traceExit(this ,
216: "getBinaryStream", retVal);
217: }
218: return retVal;
219: }
220: } catch (SqlException se) {
221: throw se.getSQLException();
222: }
223: }
224:
225: private java.io.InputStream getBinaryStreamX() throws SqlException {
226: checkForClosedConnection();
227:
228: if (isBinaryStream()) // this Lob is used for input
229: {
230: return binaryStream_;
231: }
232:
233: return new java.io.ByteArrayInputStream(binaryString_,
234: dataOffset_, binaryString_.length - dataOffset_);
235: }
236:
237: public long position(byte[] pattern, long start)
238: throws SQLException {
239: //call checkValidity to exit by throwing a SQLException if
240: //the Blob object has been freed by calling free() on it
241: checkValidity();
242: try {
243: synchronized (agent_.connection_) {
244: if (agent_.loggingEnabled()) {
245: agent_.logWriter_.traceEntry(this ,
246: "position(byte[], long)", pattern, start);
247: }
248: if (pattern == null) {
249: throw new SqlException(
250: agent_.logWriter_,
251: new ClientMessageId(
252: SQLState.BLOB_NULL_PATTERN_OR_SEARCH_STR));
253: }
254: if (start < 1) {
255: throw new SqlException(agent_.logWriter_,
256: new ClientMessageId(
257: SQLState.BLOB_BAD_POSITION),
258: new Long(start));
259: }
260: long pos = positionX(pattern, start);
261: if (agent_.loggingEnabled()) {
262: agent_.logWriter_.traceExit(this ,
263: "position(byte[], long)", pos);
264: }
265: return pos;
266: }
267: } catch (SqlException se) {
268: throw se.getSQLException();
269: }
270: }
271:
272: private long positionX(byte[] pattern, long start)
273: throws SqlException {
274: checkForClosedConnection();
275:
276: return binaryStringPosition(pattern, start);
277: }
278:
279: public long position(java.sql.Blob pattern, long start)
280: throws SQLException {
281: //call checkValidity to exit by throwing a SQLException if
282: //the Blob object has been freed by calling free() on it
283: checkValidity();
284: try {
285: synchronized (agent_.connection_) {
286: if (agent_.loggingEnabled()) {
287: agent_.logWriter_.traceEntry(this ,
288: "position(Blob, long)", pattern, start);
289: }
290: if (pattern == null) {
291: throw new SqlException(
292: agent_.logWriter_,
293: new ClientMessageId(
294: SQLState.BLOB_NULL_PATTERN_OR_SEARCH_STR));
295: }
296: if (start < 1) {
297: throw new SqlException(agent_.logWriter_,
298: new ClientMessageId(
299: SQLState.BLOB_BAD_POSITION),
300: new Long(start));
301: }
302: long pos = positionX(pattern, start);
303: if (agent_.loggingEnabled()) {
304: agent_.logWriter_.traceExit(this ,
305: "position(Blob, long)", pos);
306: }
307: return pos;
308: }
309: } catch (SqlException se) {
310: throw se.getSQLException();
311: }
312: }
313:
314: private long positionX(java.sql.Blob pattern, long start)
315: throws SqlException {
316: checkForClosedConnection();
317:
318: try {
319: return binaryStringPosition(pattern.getBytes(1L,
320: (int) pattern.length()), start);
321: } catch (java.sql.SQLException e) {
322: throw new SqlException(e);
323: }
324: }
325:
326: // -------------------------- JDBC 3.0 -----------------------------------
327:
328: public int setBytes(long pos, byte[] bytes) throws SQLException {
329: //call checkValidity to exit by throwing a SQLException if
330: //the Blob object has been freed by calling free() on it
331: checkValidity();
332: try {
333: synchronized (agent_.connection_) {
334: if (agent_.loggingEnabled()) {
335: agent_.logWriter_.traceEntry(this , "setBytes",
336: (int) pos, bytes);
337: }
338: int length = setBytesX(pos, bytes, 0, bytes.length);
339: if (agent_.loggingEnabled()) {
340: agent_.logWriter_.traceExit(this , "setBytes",
341: length);
342: }
343: return length;
344: }
345: } catch (SqlException se) {
346: throw se.getSQLException();
347: }
348: }
349:
350: public int setBytes(long pos, byte[] bytes, int offset, int len)
351: throws SQLException {
352: //call checkValidity to exit by throwing a SQLException if
353: //the Blob object has been freed by calling free() on it
354: checkValidity();
355: try {
356: synchronized (agent_.connection_) {
357: if (agent_.loggingEnabled()) {
358: agent_.logWriter_.traceEntry(this , "setBytes",
359: (int) pos, bytes, offset, len);
360: }
361: int length = setBytesX(pos, bytes, offset, len);
362: if (agent_.loggingEnabled()) {
363: agent_.logWriter_.traceExit(this , "setBytes",
364: length);
365: }
366: return length;
367: }
368: } catch (SqlException se) {
369: throw se.getSQLException();
370: }
371: }
372:
373: public int setBytesX(long pos, byte[] bytes, int offset, int len)
374: throws SqlException {
375: int length = 0;
376:
377: /*
378: Check if position is less than 0 and if true
379: raise an exception
380: */
381:
382: if (pos <= 0L) {
383: throw new SqlException(agent_.logWriter_,
384: new ClientMessageId(SQLState.BLOB_BAD_POSITION),
385: new Long(pos));
386: }
387:
388: /*
389: Currently only 2G-1 bytes can be inserted in a
390: single Blob column hence check corresponding position
391: value
392: */
393:
394: if (pos >= Integer.MAX_VALUE) {
395: throw new SqlException(agent_.logWriter_,
396: new ClientMessageId(
397: SQLState.BLOB_POSITION_TOO_LARGE),
398: new Long(pos));
399: }
400:
401: if (pos - 1 > binaryString_.length - dataOffset_) {
402: throw new SqlException(agent_.logWriter_,
403: new ClientMessageId(
404: SQLState.BLOB_POSITION_TOO_LARGE),
405: new Long(pos));
406: }
407:
408: if ((offset < 0) || offset > bytes.length) {
409: throw new SqlException(agent_.logWriter_,
410: new ClientMessageId(SQLState.BLOB_INVALID_OFFSET),
411: new Integer(offset));
412: }
413: if (len < 0) {
414: throw new SqlException(agent_.logWriter_,
415: new ClientMessageId(
416: SQLState.BLOB_NONPOSITIVE_LENGTH),
417: new Integer(length));
418: }
419: if (len == 0) {
420: return 0;
421: }
422: length = Math.min((bytes.length - offset), len);
423: if ((binaryString_.length - dataOffset_ - (int) pos + 1) < length) {
424: byte newbuf[] = new byte[(int) pos + length + dataOffset_
425: - 1];
426: System.arraycopy(binaryString_, 0, newbuf, 0,
427: binaryString_.length);
428: binaryString_ = newbuf;
429: }
430:
431: System.arraycopy(bytes, offset, binaryString_, (int) pos
432: + dataOffset_ - 1, length);
433: binaryStream_ = new java.io.ByteArrayInputStream(binaryString_);
434: sqlLength_ = binaryString_.length - dataOffset_;
435: return length;
436: }
437:
438: public java.io.OutputStream setBinaryStream(long pos)
439: throws SQLException {
440: //call checkValidity to exit by throwing a SQLException if
441: //the Blob object has been freed by calling free() on it
442: checkValidity();
443: synchronized (agent_.connection_) {
444: if (agent_.loggingEnabled()) {
445: agent_.logWriter_.traceEntry(this , "setBinaryStream",
446: (int) pos);
447: }
448: BlobOutputStream outStream = new BlobOutputStream(this , pos);
449:
450: if (agent_.loggingEnabled()) {
451: agent_.logWriter_.traceExit(this , "setBinaryStream",
452: outStream);
453: }
454: return outStream;
455: }
456: }
457:
458: public void truncate(long len) throws SQLException {
459: //call checkValidity to exit by throwing a SQLException if
460: //the Blob object has been freed by calling free() on it
461: checkValidity();
462: try {
463: synchronized (agent_.connection_) {
464: if (agent_.loggingEnabled()) {
465: agent_.logWriter_.traceEntry(this , " truncate",
466: (int) len);
467: }
468: if (len < 0 || len > this .length()) {
469: throw new SqlException(agent_.logWriter_,
470: new ClientMessageId(
471: SQLState.INVALID_API_PARAMETER),
472: new Long(len), "len", "Blob.truncate()");
473: }
474: if (len == this .length()) {
475: return;
476: }
477: long newLength = (int) len + dataOffset_;
478: byte newbuf[] = new byte[(int) len + dataOffset_];
479: System.arraycopy(binaryString_, 0, newbuf, 0,
480: (int) newLength);
481: binaryString_ = newbuf;
482: binaryStream_ = new java.io.ByteArrayInputStream(
483: binaryString_);
484: sqlLength_ = binaryString_.length - dataOffset_;
485: }
486: } catch (SqlException se) {
487: throw se.getSQLException();
488: }
489: }
490:
491: // -------------------------- JDBC 4.0 -----------------------------------
492:
493: /**
494: * This method frees the <code>Blob</code> object and releases the resources that
495: * it holds. The object is invalid once the <code>free</code>
496: * method is called. If <code>free</code> is called multiple times, the subsequent
497: * calls to <code>free</code> are treated as a no-op.
498: *
499: * @throws SQLException if an error occurs releasing
500: * the Blob's resources
501: */
502: public void free() throws SQLException {
503:
504: //calling free() on a already freed object is treated as a no-op
505: if (!isValid)
506: return;
507:
508: //now that free has been called the Blob object is no longer
509: //valid
510: isValid = false;
511:
512: if (isBinaryStream()) {
513: try {
514: binaryStream_.close();
515: } catch (IOException ioe) {
516: throw new SqlException(null, new ClientMessageId(
517: SQLState.IO_ERROR_UPON_LOB_FREE))
518: .getSQLException();
519: }
520: } else {
521: binaryString_ = null;
522: }
523: }
524:
525: public InputStream getBinaryStream(long pos, long length)
526: throws SQLException {
527: throw SQLExceptionFactory
528: .notImplemented("getBinaryStream(long,long)");
529: }
530:
531: //------------------ Material layer event callback methods -------------------
532:
533: //---------------------------- helper methods --------------------------------
534: public boolean isBinaryString() {
535: return ((dataType_ & BINARY_STRING) == BINARY_STRING);
536: }
537:
538: public boolean isBinaryStream() {
539: return ((dataType_ & BINARY_STREAM) == BINARY_STREAM);
540: }
541:
542: public byte[] getBinaryString() {
543: return binaryString_;
544: }
545:
546: protected long binaryStringPosition(byte[] pattern, long start) {
547: // perform a local byte string search, starting at start
548: // check that the range of comparison is valid
549: int index = (int) start + dataOffset_ - 1; // api start begins at 1
550:
551: while (index + pattern.length <= binaryString_.length) {
552: if (isSubString(pattern, index)) {
553: return (long) (index - dataOffset_ + 1); // readjust for api indexing
554: }
555: index++;
556: }
557: return -1L; // not found
558: }
559:
560: // precondition: binaryString_ is long enough for the comparison
561: protected boolean isSubString(byte[] pattern, int index) {
562: for (int i = 0; i < pattern.length; i++, index++) {
563: if (pattern[i] != binaryString_[index]) {
564: return false;
565: }
566: }
567:
568: return true;
569: }
570:
571: /*
572: * Checks is isValid is true. If it is not true throws
573: * a SQLException stating that a method has been called on
574: * an invalid LOB object
575: *
576: * throws SQLException if isvalid is not true.
577: */
578: private void checkValidity() throws SQLException {
579: if (!isValid)
580: throw new SqlException(null, new ClientMessageId(
581: SQLState.LOB_OBJECT_INVALID)).getSQLException();
582: }
583: }
|