1 /*
2 Copyright 2002-2007 MySQL AB, 2008 Sun Microsystems
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of version 2 of the GNU General Public License as
6 published by the Free Software Foundation.
7
8 There are special exceptions to the terms and conditions of the GPL
9 as it is applied to this software. View the full text of the
10 exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
11 software distribution.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
22
23
24 */
25 package com.mysql.jdbc;
26
27 import java.io.ByteArrayInputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.OutputStream;
31 import java.sql.SQLException;
32
33 /**
34 * The representation (mapping) in the JavaTM programming language of an SQL
35 * BLOB value. An SQL BLOB is a built-in type that stores a Binary Large Object
36 * as a column value in a row of a database table. The driver implements Blob
37 * using an SQL locator(BLOB), which means that a Blob object contains a logical
38 * pointer to the SQL BLOB data rather than the data itself. A Blob object is
39 * valid for the duration of the transaction in which is was created. Methods in
40 * the interfaces ResultSet, CallableStatement, and PreparedStatement, such as
41 * getBlob and setBlob allow a programmer to access an SQL BLOB value. The Blob
42 * interface provides methods for getting the length of an SQL BLOB (Binary
43 * Large Object) value, for materializing a BLOB value on the client, and for
44 * determining the position of a pattern of bytes within a BLOB value. This
45 * class is new in the JDBC 2.0 API.
46 *
47 * @author Mark Matthews
48 * @version $Id$
49 */
50 public class Blob implements java.sql.Blob, OutputStreamWatcher {
51
52 //
53 // This is a real brain-dead implementation of BLOB. Once I add
54 // streamability to the I/O for MySQL this will be more efficiently
55 // implemented (except for the position() method, ugh).
56 //
57
58 /** The binary data that makes up this BLOB */
59 private byte[] binaryData = null;
60 private boolean isClosed = false;
61 private ExceptionInterceptor exceptionInterceptor;
62
63 /**
64 * Creates a Blob without data
65 */
66 Blob(ExceptionInterceptor exceptionInterceptor) {
67 setBinaryData(Constants.EMPTY_BYTE_ARRAY);
68 this.exceptionInterceptor = exceptionInterceptor;
69 }
70
71 /**
72 * Creates a BLOB encapsulating the given binary data
73 *
74 * @param data
75 * DOCUMENT ME!
76 */
77 Blob(byte[] data, ExceptionInterceptor exceptionInterceptor) {
78 setBinaryData(data);
79 this.exceptionInterceptor = exceptionInterceptor;
80 }
81
82 /**
83 * Creates an updatable BLOB that can update in-place (not implemented yet).
84 *
85 * @param data
86 * DOCUMENT ME!
87 * @param creatorResultSetToSet
88 * DOCUMENT ME!
89 * @param columnIndexToSet
90 * DOCUMENT ME!
91 */
92 Blob(byte[] data, ResultSetInternalMethods creatorResultSetToSet, int columnIndexToSet) {
93 setBinaryData(data);
94 }
95
96 private synchronized byte[] getBinaryData() {
97 return this.binaryData;
98 }
99
100 /**
101 * Retrieves the BLOB designated by this Blob instance as a stream.
102 *
103 * @return this BLOB represented as a binary stream of bytes.
104 *
105 * @throws SQLException
106 * if a database error occurs
107 */
108 public synchronized java.io.InputStream getBinaryStream() throws SQLException {
109 checkClosed();
110
111 return new ByteArrayInputStream(getBinaryData());
112 }
113
114 /**
115 * Returns as an array of bytes, part or all of the BLOB value that this
116 * Blob object designates.
117 *
118 * @param pos
119 * where to start the part of the BLOB
120 * @param length
121 * the length of the part of the BLOB you want returned.
122 *
123 * @return the bytes stored in the blob starting at position
124 * <code>pos</code> and having a length of <code>length</code>.
125 *
126 * @throws SQLException
127 * if a database error occurs
128 */
129 public synchronized byte[] getBytes(long pos, int length) throws SQLException {
130 checkClosed();
131
132 if (pos < 1) {
133 throw SQLError.createSQLException(Messages.getString("Blob.2"), //$NON-NLS-1$
134 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
135 }
136
137 pos--;
138
139 if (pos > this.binaryData.length) {
140 throw SQLError.createSQLException("\"pos\" argument can not be larger than the BLOB's length.",
141 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
142 }
143
144 if (pos + length > this.binaryData.length) {
145 throw SQLError.createSQLException("\"pos\" + \"length\" arguments can not be larger than the BLOB's length.",
146 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
147 }
148
149 byte[] newData = new byte[length];
150 System.arraycopy(getBinaryData(), (int) (pos), newData, 0, length);
151
152 return newData;
153 }
154
155 /**
156 * Returns the number of bytes in the BLOB value designated by this Blob
157 * object.
158 *
159 * @return the length of this blob
160 *
161 * @throws SQLException
162 * if a database error occurs
163 */
164 public synchronized long length() throws SQLException {
165 checkClosed();
166
167 return getBinaryData().length;
168 }
169
170 /**
171 * @see java.sql.Blob#position(byte[], long)
172 */
173 public synchronized long position(byte[] pattern, long start) throws SQLException {
174 throw SQLError.createSQLException("Not implemented", this.exceptionInterceptor); //$NON-NLS-1$
175 }
176
177 /**
178 * Finds the position of the given pattern in this BLOB.
179 *
180 * @param pattern
181 * the pattern to find
182 * @param start
183 * where to start finding the pattern
184 *
185 * @return the position where the pattern is found in the BLOB, -1 if not
186 * found
187 *
188 * @throws SQLException
189 * if a database error occurs
190 */
191 public synchronized long position(java.sql.Blob pattern, long start) throws SQLException {
192 checkClosed();
193
194 return position(pattern.getBytes(0, (int) pattern.length()), start);
195 }
196
197 private synchronized void setBinaryData(byte[] newBinaryData) {
198 this.binaryData = newBinaryData;
199 }
200
201 /**
202 * @see Blob#setBinaryStream(long)
203 */
204 public synchronized OutputStream setBinaryStream(long indexToWriteAt)
205 throws SQLException {
206 checkClosed();
207
208 if (indexToWriteAt < 1) {
209 throw SQLError.createSQLException(Messages.getString("Blob.0"), //$NON-NLS-1$
210 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
211 }
212
213 WatchableOutputStream bytesOut = new WatchableOutputStream();
214 bytesOut.setWatcher(this);
215
216 if (indexToWriteAt > 0) {
217 bytesOut.write(this.binaryData, 0, (int) (indexToWriteAt - 1));
218 }
219
220 return bytesOut;
221 }
222
223 /**
224 * @see Blob#setBytes(long, byte[])
225 */
226 public synchronized int setBytes(long writeAt, byte[] bytes) throws SQLException {
227 checkClosed();
228
229 return setBytes(writeAt, bytes, 0, bytes.length);
230 }
231
232 /**
233 * @see Blob#setBytes(long, byte[], int, int)
234 */
235 public synchronized int setBytes(long writeAt, byte[] bytes, int offset, int length)
236 throws SQLException {
237 checkClosed();
238
239 OutputStream bytesOut = setBinaryStream(writeAt);
240
241 try {
242 bytesOut.write(bytes, offset, length);
243 } catch (IOException ioEx) {
244 SQLException sqlEx = SQLError.createSQLException(Messages.getString("Blob.1"), //$NON-NLS-1$
245 SQLError.SQL_STATE_GENERAL_ERROR, this.exceptionInterceptor);
246 sqlEx.initCause(ioEx);
247
248 throw sqlEx;
249 } finally {
250 try {
251 bytesOut.close();
252 } catch (IOException doNothing) {
253 ; // do nothing
254 }
255 }
256
257 return length;
258 }
259
260 /**
261 * @see com.mysql.jdbc.OutputStreamWatcher#streamClosed(byte[])
262 */
263 public synchronized void streamClosed(byte[] byteData) {
264 this.binaryData = byteData;
265 }
266
267 /**
268 * @see com.mysql.jdbc.OutputStreamWatcher#streamClosed(byte[])
269 */
270 public synchronized void streamClosed(WatchableOutputStream out) {
271 int streamSize = out.size();
272
273 if (streamSize < this.binaryData.length) {
274 out.write(this.binaryData, streamSize, this.binaryData.length
275 - streamSize);
276 }
277
278 this.binaryData = out.toByteArray();
279 }
280
281 /**
282 * Truncates the <code>BLOB</code> value that this <code>Blob</code>
283 * object represents to be <code>len</code> bytes in length.
284 * <p>
285 * <b>Note:</b> If the value specified for <code>len</code>
286 * is greater then the length+1 of the <code>BLOB</code> value then the
287 * behavior is undefined. Some JDBC drivers may throw a
288 * <code>SQLException</code> while other drivers may support this
289 * operation.
290 *
291 * @param len the length, in bytes, to which the <code>BLOB</code> value
292 * that this <code>Blob</code> object represents should be truncated
293 * @exception SQLException if there is an error accessing the
294 * <code>BLOB</code> value or if len is less than 0
295 * @exception SQLFeatureNotSupportedException if the JDBC driver does not support
296 * this method
297 * @since 1.4
298 */
299 public synchronized void truncate(long len) throws SQLException {
300 checkClosed();
301
302 if (len < 0) {
303 throw SQLError.createSQLException("\"len\" argument can not be < 1.",
304 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
305 }
306
307 if (len > this.binaryData.length) {
308 throw SQLError.createSQLException("\"len\" argument can not be larger than the BLOB's length.",
309 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
310 }
311
312 // TODO: Do this without copying byte[]s by maintaining some end pointer
313 // on the original data
314
315 byte[] newData = new byte[(int)len];
316 System.arraycopy(getBinaryData(), 0, newData, 0, (int)len);
317 this.binaryData = newData;
318 }
319
320 /**
321 * This method frees the <code>Blob</code> object and releases the resources that
322 * it holds. The object is invalid once the <code>free</code>
323 * method is called.
324 *<p>
325 * After <code>free</code> has been called, any attempt to invoke a
326 * method other than <code>free</code> will result in a <code>SQLException</code>
327 * being thrown. If <code>free</code> is called multiple times, the subsequent
328 * calls to <code>free</code> are treated as a no-op.
329 *<p>
330 *
331 * @throws SQLException if an error occurs releasing
332 * the Blob's resources
333 * @exception SQLFeatureNotSupportedException if the JDBC driver does not support
334 * this method
335 * @since 1.6
336 */
337
338 public synchronized void free() throws SQLException {
339 this.binaryData = null;
340 this.isClosed = true;
341 }
342
343 /**
344 * Returns an <code>InputStream</code> object that contains a partial <code>Blob</code> value,
345 * starting with the byte specified by pos, which is length bytes in length.
346 *
347 * @param pos the offset to the first byte of the partial value to be retrieved.
348 * The first byte in the <code>Blob</code> is at position 1
349 * @param length the length in bytes of the partial value to be retrieved
350 * @return <code>InputStream</code> through which the partial <code>Blob</code> value can be read.
351 * @throws SQLException if pos is less than 1 or if pos is greater than the number of bytes
352 * in the <code>Blob</code> or if pos + length is greater than the number of bytes
353 * in the <code>Blob</code>
354 *
355 * @exception SQLFeatureNotSupportedException if the JDBC driver does not support
356 * this method
357 * @since 1.6
358 */
359 public synchronized InputStream getBinaryStream(long pos, long length) throws SQLException {
360 checkClosed();
361
362 if (pos < 1) {
363 throw SQLError.createSQLException("\"pos\" argument can not be < 1.",
364 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
365 }
366
367 pos--;
368
369 if (pos > this.binaryData.length) {
370 throw SQLError.createSQLException("\"pos\" argument can not be larger than the BLOB's length.",
371 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
372 }
373
374 if (pos + length > this.binaryData.length) {
375 throw SQLError.createSQLException("\"pos\" + \"length\" arguments can not be larger than the BLOB's length.",
376 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
377 }
378
379 return new ByteArrayInputStream(getBinaryData(), (int)pos, (int)length);
380 }
381
382 private synchronized void checkClosed() throws SQLException {
383 if (this.isClosed) {
384 throw SQLError.createSQLException("Invalid operation on closed BLOB",
385 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
386 }
387 }
388 }