Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/apache/derby/impl/jdbc/EmbedBlob.java


1   /*
2   
3      Derby - Class org.apache.derby.impl.jdbc.EmbedBlob
4   
5      Copyright 2000, 2004 The Apache Software Foundation or its licensors, as applicable.
6   
7      Licensed under the Apache License, Version 2.0 (the "License");
8      you may not use this file except in compliance with the License.
9      You may obtain a copy of the License at
10  
11        http://www.apache.org/licenses/LICENSE-2.0
12  
13     Unless required by applicable law or agreed to in writing, software
14     distributed under the License is distributed on an "AS IS" BASIS,
15     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16     See the License for the specific language governing permissions and
17     limitations under the License.
18  
19   */
20  
21  
22  package org.apache.derby.impl.jdbc;
23  
24  import org.apache.derby.iapi.reference.SQLState;
25  import org.apache.derby.iapi.error.StandardException;
26  import org.apache.derby.iapi.services.sanity.SanityManager;
27  import org.apache.derby.iapi.types.DataValueDescriptor;
28  import org.apache.derby.iapi.types.Resetable;
29  import org.apache.derby.impl.jdbc.ConnectionChild;
30  import org.apache.derby.impl.jdbc.EmbedConnection;
31  import org.apache.derby.impl.jdbc.Util;
32  import org.apache.derby.iapi.services.io.NewByteArrayInputStream;
33  import org.apache.derby.iapi.services.io.InputStreamUtil;
34  import org.apache.derby.iapi.services.io.ArrayInputStream;
35  
36  import java.sql.SQLException;
37  import java.sql.Blob;
38  import java.io.InputStream;
39  import java.io.EOFException;
40  import java.io.IOException;
41  
42  /**
43      Implements java.sql.Blob (see the JDBC 2.0 spec).
44      A blob sits on top of a BINARY, VARBINARY or LONG VARBINARY column.
45      If its data is small (less than 1 page) it is a byte array taken from
46      the SQLBit class. If it is large (more than 1 page) it is a long column
47      in the database. The long column is accessed as a stream, and is implemented
48      in store as an OverflowInputStream.  The Resetable interface allows sending
49      messages to that stream to initialize itself (reopen its container and
50      lock the corresponding row) and to reset itself to the beginning. 
51  
52      NOTE: In the case that the data is large, it is represented as a stream.
53      This stream is returned to the user in the getBinaryStream() method.
54      This means that we have limited control over the state of the stream,
55      since the user can read bytes from it at any time.  Thus all methods
56      here reset the stream to the beginning before doing any work.
57      CAVEAT: The methods may not behave correctly if a user sets up
58      multiple threads and sucks data from the stream (returned from
59      getBinaryStream()) at the same time as calling the Blob methods.
60  
61    <P><B>Supports</B>
62     <UL>
63     <LI> JSR169 - no subsetting for java.sql.Blob
64     <LI> JDBC 2.0
65     <LI> JDBC 3.0 - no new dependencies on new JDBC 3.0 or JDK 1.4 classes,
66          new update methods can safely be added into implementation.
67     </UL>
68  
69   */
70  
71  final class EmbedBlob extends ConnectionChild implements Blob
72  {
73      // clob is either bytes or stream
74      private boolean         isBytes;
75      private InputStream     myStream;
76      private byte[]          myBytes;
77      // note: cannot control position of the stream since user can do a getBinaryStream
78      private long            pos;
79      // this stream sits on top of myStream
80      private BinaryToRawStream biStream;
81  
82      // buffer for reading in blobs from a stream (long column)
83      // and trashing them (to set the position of the stream etc.)
84      private static int BLOB_BUF_SIZE = 4096;
85      private byte buf[];
86  
87      /*
88        This constructor should only be called by EmbedResultSet.getBlob
89      */
90      protected EmbedBlob(DataValueDescriptor dvd, EmbedConnection con)
91          throws StandardException
92      {
93          super(con);
94          // if the underlying column is null, ResultSet.getBlob will return null,
95          // never should get this far
96          if (SanityManager.DEBUG)
97              SanityManager.ASSERT(!dvd.isNull(), "blob is created on top of a null column");
98  
99          myStream = dvd.getStream();
100         if (myStream == null)
101         {
102             isBytes = true;
103             // copy bytes into memory so that blob can live after result set
104             // is closed
105             byte[] dvdBytes = dvd.getBytes();
106 
107             if (SanityManager.DEBUG)
108                 SanityManager.ASSERT(dvdBytes != null,"blob has a null value underneath");
109 
110             myBytes = new byte[dvdBytes.length];
111             System.arraycopy(dvdBytes, 0, myBytes, 0, dvdBytes.length);
112         }
113         else
114         {
115             isBytes = false;
116 
117             /*
118              We are expecting this stream to be a FormatIdInputStream with an
119              OverflowInputStream inside. FormatIdInputStream implements
120              Resetable. This should be the case when retrieving
121              data from a long column. However, SQLBit, which is the class
122              implementing the getStream() method for dvd.getStream(), does not
123              guarantee this for us
124              */
125             if (SanityManager.DEBUG)
126                 SanityManager.ASSERT(myStream instanceof Resetable);
127 
128             try {
129                 ((Resetable) myStream).initStream();
130             } catch (StandardException se) {
131                 if (se.getMessageId().equals(SQLState.DATA_CONTAINER_CLOSED)) {
132                     throw StandardException
133                             .newException(SQLState.BLOB_ACCESSED_AFTER_COMMIT);
134                 }
135             }
136             // set up the buffer for trashing the bytes to set the position of
137             // the
138             // stream, only need a buffer when we have a long column
139             buf = new byte[BLOB_BUF_SIZE];
140         }
141         pos = 0;
142     }
143 
144 
145     /*
146         Sets the position of the stream to position newPos, where position 0 is
147         the beginning of the stream.
148 
149         @param newPos the position to set to
150         @exception StandardException (BLOB_SETPOSITION_FAILED) throws this if
151         the stream runs out before we get to newPos
152     */
153     private void setPosition(long newPos)
154         throws StandardException, IOException
155     {
156         if (SanityManager.DEBUG)
157             SanityManager.ASSERT(newPos >= 0);
158         if (isBytes)
159             pos = newPos;
160         else
161         {
162             // Always resets the stream to the beginning first, because user can
163             // influence the state of the stream without letting us know.
164             ((Resetable)myStream).resetStream();
165             // PT could try to save creating a new object each time
166             biStream = new BinaryToRawStream(myStream, this);
167             pos = 0;
168             while (pos < newPos)
169             {
170                 int size = biStream.read(
171                     buf,0,(int) Math.min((newPos-pos), (long) BLOB_BUF_SIZE));
172                 if (size <= 0)   // ran out of stream
173                     throw StandardException.newException(SQLState.BLOB_SETPOSITION_FAILED);
174                 pos += size;
175             }
176         }
177     }
178 
179 
180     /*
181         Reads one byte, either from the byte array or else from the stream.
182     */
183     private int read()
184         throws IOException
185     {
186         int c;
187         if (isBytes)
188         {
189             if (pos >= myBytes.length)
190                 return -1;
191             else
192                 c = myBytes[(int) pos];
193         }
194         else
195             c = biStream.read();
196         pos++;
197         return c;
198     }
199 
200   /**
201    * Returns the number of bytes in the <code>BLOB</code> value
202    * designated by this <code>Blob</code> object.
203    * @return length of the <code>BLOB</code> in bytes
204    * @exception SQLException if there is an error accessing the
205    * length of the <code>BLOB</code>
206    */
207     // PT stream part may get pushed to store
208     public long length()
209         throws SQLException
210     {
211         boolean pushStack = false;
212         try
213         {
214             if (isBytes)
215                 return myBytes.length;
216             // we have a stream
217             synchronized (getConnectionSynchronization())
218             {
219                 pushStack = !getEmbedConnection().isClosed();
220                 if (pushStack)
221                     setupContextStack();
222 
223                 setPosition(0);
224                 for (;;)
225                 {
226                     int size = biStream.read(buf);
227                     if (size == -1)
228                         break;
229                     pos += size;
230                 }
231                 return pos;
232             }
233         }
234         catch (Throwable t)
235         {
236       throw handleMyExceptions(t);
237         }
238         finally
239         {
240             if (pushStack)
241                 restoreContextStack();
242         }
243     }
244 
245 
246   /**
247    * Returns as an array of bytes part or all of the <code>BLOB</code>
248    * value that this <code>Blob</code> object designates.  The byte
249    * array contains up to <code>length</code> consecutive bytes
250    * starting at position <code>pos</code>.
251    * @param pos the ordinal position of the first byte in the
252    * <code>BLOB</code> value to be extracted; the first byte is at
253    * position 1
254    * @param length is the number of consecutive bytes to be copied
255    * @return a byte array containing up to <code>length</code>
256    * consecutive bytes from the <code>BLOB</code> value designated
257    * by this <code>Blob</code> object, starting with the
258    * byte at position <code>pos</code>.
259    * @exception SQLException if there is an error accessing the
260    * <code>BLOB</code>
261    NOTE: return new byte[0] if startPos is too large
262    */
263    // PT stream part may get pushed to store
264 
265     public byte[] getBytes(long startPos, int length)
266         throws SQLException
267     {
268         boolean pushStack = false;
269         try
270         {
271             if (startPos < 1)
272                 throw StandardException.newException(
273                     SQLState.BLOB_BAD_POSITION, new Long(startPos));
274             if (length <= 0)
275                 throw StandardException.newException(
276                     SQLState.BLOB_NONPOSITIVE_LENGTH, new Integer(length));
277 
278             byte[] result;
279             // if we have a byte array, not a stream
280             if (isBytes)
281             {
282                 // if blob length is less than pos bytes, raise an exception
283                 if (myBytes.length < startPos)
284                     throw StandardException.newException(
285                         SQLState.BLOB_POSITION_TOO_LARGE, new Long(startPos));
286                 // cannot go over length of array
287                 int lengthFromPos = myBytes.length - (int) startPos + 1;
288                 int actualLength = length > lengthFromPos ? lengthFromPos : length;
289                 result = new byte[actualLength];
290                 System.arraycopy(myBytes, ((int) startPos) - 1, result, 0, actualLength);
291             }
292             else // we have a stream
293             {
294                 synchronized (getConnectionSynchronization())
295                 {
296                     pushStack = !getEmbedConnection().isClosed();
297                     if (pushStack)
298                         setupContextStack();
299 
300                     setPosition(startPos-1);
301                     // read length bytes into a string
302                     result = new byte[length];
303                     int n = InputStreamUtil.readLoop(biStream,result,0,length);
304                     pos += n;
305                     /*
306                      According to the spec, if there are only n < length bytes
307                      to return, we should just return these bytes. Rather than
308                      return them in an array of size length, where the trailing
309                      bytes are not initialized, and the user cannot tell how
310                      many bytes were actually returned, we should return an
311                      array of n bytes.
312                      */
313                     if (n < length)
314                     {
315                         byte[] result2 = new byte[n];
316                         System.arraycopy(result,0,result2,0,n);
317                         return result2;
318                     }
319                 }
320             }
321             return result;
322         }
323         catch (StandardException e)
324         {  // if this is a setPosition exception then we ran out of Blob
325             if (e.getMessageId().equals(SQLState.BLOB_SETPOSITION_FAILED))
326                 e = StandardException.newException(
327                     SQLState.BLOB_POSITION_TOO_LARGE, new Long(startPos));
328             throw handleMyExceptions(e);
329         }
330         catch (Throwable t)
331         {
332       throw handleMyExceptions(t);
333         }
334         finally
335         {
336             if (pushStack)
337                 restoreContextStack();
338         }
339 
340     }
341 
342 
343   /**
344    * Retrieves the <code>BLOB</code> designated by this
345    * <code>Blob</code> instance as a stream.
346    * @return a stream containing the <code>BLOB</code> data
347    * @exception SQLException if there is an error accessing the
348    * <code>BLOB</code>
349    */
350     public java.io.InputStream getBinaryStream()
351         throws SQLException
352     {
353         boolean pushStack = false;
354         try
355         {
356             // if we have byte array, not a stream
357             if (isBytes)
358             {
359                 return new NewByteArrayInputStream(myBytes);
360             }
361             else
362             { 
363                 // have a stream
364 
365                 synchronized (getConnectionSynchronization())
366                 {
367                     pushStack = !getEmbedConnection().isClosed();
368                     if (pushStack)
369                         setupContextStack();
370 
371                     setPosition(0);
372                     return biStream;
373                 }
374             }
375         }
376         catch (Throwable t)
377         {
378       throw handleMyExceptions(t);
379         }
380         finally
381         {
382             if (pushStack)
383                 restoreContextStack();
384         }
385     }
386 
387 
388   /**
389    * Determines the byte position at which the specified byte
390    * <code>pattern</code> begins within the <code>BLOB</code>
391    * value that this <code>Blob</code> object represents.  The
392    * search for <code>pattern</code. begins at position
393    * <code>start</code>
394    * @param pattern the byte array for which to search
395    * @param start the position at which to begin searching; the
396    *        first position is 1
397    * @return the position at which the pattern appears, else -1.
398    * @exception SQLException if there is an error accessing the
399    * <code>BLOB</code>
400    */
401     public long position(byte[] pattern, long start)
402         throws SQLException
403     {
404         boolean pushStack = false;
405         try
406         {
407             if (start < 1)
408                 throw StandardException.newException(
409                     SQLState.BLOB_BAD_POSITION, new Long(start));
410             if (pattern == null)
411                 throw StandardException.newException(SQLState.BLOB_NULL_PATTERN);
412             if (pattern.length == 0)
413                 return start; // match DB2's SQL LOCATE function
414 
415             synchronized (getConnectionSynchronization())
416             {
417                 pushStack = !getEmbedConnection().isClosed();
418                 if (pushStack)
419                     setupContextStack();
420 
421                 setPosition(start-1);
422                 // look for first character
423                 int lookFor = pattern[0];
424                 long curPos;
425                 int c;
426                 while (true)
427                 {
428                     c = read();
429                     if (c == -1)  // run out of stream
430                         return -1;
431                     if (c == lookFor)
432                     {
433                         curPos = pos;
434                         if (checkMatch(pattern))
435                             return curPos;
436                         else
437                             setPosition(curPos);
438                     }
439                 }
440             }
441         }
442         catch (StandardException e)
443         {  // if this is a setPosition exception then not found
444             if (e.getMessageId().equals(SQLState.BLOB_SETPOSITION_FAILED))
445                 return -1;
446             else
447                 throw handleMyExceptions(e);
448         }
449         catch (Throwable t)
450         {
451       throw handleMyExceptions(t);
452         }
453         finally
454         {
455             if (pushStack)
456                 restoreContextStack();
457         }
458 
459     }
460 
461 
462     /*
463      check whether pattern (starting from the second byte) appears inside
464      posStream (at the current position)
465      @param posStream the stream to search inside
466      @param pattern the byte array passed in by the user to search with
467      @return true if match, false otherwise
468      */
469     private boolean checkMatch(byte[] pattern)
470         throws IOException
471     {
472        // check whether rest matches
473        // might improve performance by reading more
474         for (int i = 1; i < pattern.length; i++)
475         {
476             int b = read();
477             if ((b < 0) || (b != pattern[i]))  // mismatch or stream runs out
478                 return false;
479         }
480         return true;
481     }
482 
483   /**
484    * Determines the byte position in the <code>BLOB</code> value
485    * designated by this <code>Blob</code> object at which
486    * <code>pattern</code> begins.  The search begins at position
487    * <code>start</code>.
488    * @param pattern the <code>Blob</code> object designating
489    * the <code>BLOB</code> value for which to search
490    * @param start the position in the <code>BLOB</code> value
491    *        at which to begin searching; the first position is 1
492    * @return the position at which the pattern begins, else -1
493    * @exception SQLException if there is an error accessing the
494    * <code>BLOB</code>
495    */
496     public long position(Blob pattern, long start)
497         throws SQLException
498     {
499         boolean pushStack = false;
500         try
501         {
502             if (start < 1)
503                 throw StandardException.newException(
504                     SQLState.BLOB_BAD_POSITION, new Long(start));
505             if (pattern == null)
506                 throw StandardException.newException(SQLState.BLOB_NULL_PATTERN);
507             synchronized (getConnectionSynchronization())
508             {
509                 pushStack = !getEmbedConnection().isClosed();
510                 if (pushStack)
511                     setupContextStack();
512 
513                 setPosition(start-1);
514                 // look for first character
515                 byte[] b;
516                 try
517                 { // pattern is not necessarily a cloudscape Blob
518                     b = pattern.getBytes(1,1);
519                 }
520                 catch (SQLException e)
521                 {
522                     throw StandardException.newException(SQLState.BLOB_UNABLE_TO_READ_PATTERN);
523                 }
524                 if (b == null || b.length < 1)  // the 'empty' blob
525                     return start; // match DB2's SQL LOCATE function
526                 int lookFor = b[0];
527                 int c;
528                 long curPos;
529                 while (true)
530                 {
531                     c = read();
532                     if (c == -1)  // run out of stream
533                         return -1;
534                     if (c == lookFor)
535                     {
536                         curPos = pos;
537                         if (checkMatch(pattern))
538                             return curPos;
539                         else
540                             setPosition(curPos);
541                     }
542                 }
543             }
544         }
545         catch (StandardException e)
546         {  // if this is a setPosition exception then not found
547             if (e.getMessageId().equals(SQLState.BLOB_SETPOSITION_FAILED))
548                 return -1;
549             else
550                 throw handleMyExceptions(e);
551         }
552         catch (Throwable t)
553         {
554       throw handleMyExceptions(t);
555         }
556         finally
557         {
558             if (pushStack)
559                 restoreContextStack();
560         }
561 
562     }
563 
564 
565     /*
566      check whether pattern (starting from the second byte) appears inside
567      posStream (at the current position)
568      @param posStream the stream to search inside
569      @param pattern the blob passed in by the user to search with
570      @return true if match, false otherwise
571      */
572     private boolean checkMatch(Blob pattern)
573         throws IOException
574     {
575         // check whether rest matches
576         // might improve performance by reading buffer at a time
577         InputStream pStream;
578         try
579         {
580             pStream = pattern.getBinaryStream();
581         }
582         catch (SQLException e)
583         {
584             return false;
585         }
586         if (pStream == null)
587             return false;
588         // throw away first character since we already read it in the calling
589         // method
590         int b1 = pStream.read();
591         if (b1 < 0)
592             return false;
593         while (true)
594         {
595             b1 = pStream.read();
596             if (b1 < 0)  // search blob runs out
597                 return true;
598             int b2 = read();
599             if ((b1 != b2) || (b2 < 0))  // mismatch or stream runs out
600                 return false;
601         }
602     }
603 
604     /*
605       Convert exceptions where needed before calling handleException to convert
606       them to SQLExceptions.
607     */
608   private SQLException handleMyExceptions(Throwable t)
609         throws SQLException
610     {
611         if (t instanceof StandardException)
612         {
613             // container closed means the blob or clob was accessed after commit
614             if (((StandardException) t).getMessageId().equals(SQLState.DATA_CONTAINER_CLOSED))
615             {
616                 t = StandardException.newException(SQLState.BLOB_ACCESSED_AFTER_COMMIT);
617             }
618         }
619         return handleException(t);
620   }
621 
622 
623    /*
624     If we have a stream, release the resources associated with it.
625     */
626     protected void finalize()
627     {
628         if (!isBytes)
629             ((Resetable)myStream).closeStream();
630     }
631 
632   /**
633     Following methods are for the new JDBC 3.0 methods in java.sql.Blob
634     (see the JDBC 3.0 spec). We have the JDBC 3.0 methods in Local20
635     package, so we don't have to have a new class in Local30.
636     The new JDBC 3.0 methods don't make use of any new JDBC3.0 classes and
637     so this will work fine in jdbc2.0 configuration.
638   */
639 
640   /////////////////////////////////////////////////////////////////////////
641   //
642   //  JDBC 3.0  -  New public methods
643   //
644   /////////////////////////////////////////////////////////////////////////
645 
646   /**
647     * JDBC 3.0
648     *
649     * Writes the given array of bytes to the BLOB value that this Blob object
650     * represents, starting at position pos, and returns the number of bytes written.
651     *
652     * @param pos - the position in the BLOB object at which to start writing
653     * @param bytes - the array of bytes to be written to the BLOB value that this
654     * Blob object represents
655     * @return the number of bytes written
656     * @exception SQLException Feature not implemented for now.
657   */
658   public int setBytes(long pos,
659           byte[] bytes)
660     throws SQLException
661   {
662     throw Util.notImplemented();
663   }
664 
665   /**
666     * JDBC 3.0
667     *
668     * Writes all or part of the given array of byte array to the BLOB value that
669     * this Blob object represents and returns the number of bytes written.
670     * Writing starts at position pos in the BLOB value; len bytes from the given
671     * byte array are written.
672     *
673     * @param pos - the position in the BLOB object at which to start writing
674     * @param bytes - the array of bytes to be written to the BLOB value that this
675     * Blob object represents
676     * @param offset - the offset into the array bytes at which to start reading
677     * the bytes to be set
678     * @param len - the number of bytes to be written to the BLOB value from the
679     * array of bytes bytes
680     * @return the number of bytes written
681     * @exception SQLException Feature not implemented for now.
682   */
683   public int setBytes(long pos,
684           byte[] bytes, int offset,
685           int len)
686     throws SQLException
687   {
688     throw Util.notImplemented();
689   }
690 
691   /**
692     * JDBC 3.0
693     *
694     * Retrieves a stream that can be used to write to the BLOB value that this
695     * Blob object represents. The stream begins at position pos. 
696     *
697     * @param pos - the position in the BLOB object at which to start writing
698     * @return a java.io.OutputStream object to which data can be written 
699     * @exception SQLException Feature not implemented for now.
700   */
701   public java.io.OutputStream setBinaryStream(long pos)
702     throws SQLException
703   {
704     throw Util.notImplemented();
705   }
706 
707   /**
708     * JDBC 3.0
709     *
710     * Truncates the BLOB value that this Blob object represents to be len bytes
711     * in length.
712     *
713     * @param len - the length, in bytes, to which the BLOB value that this Blob
714     * object represents should be truncated
715     * @exception SQLException Feature not implemented for now.
716   */
717   public void truncate(long len)
718     throws SQLException
719   {
720     throw Util.notImplemented();
721   }
722 
723 }
724 
725 
726