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 }