/******************************************************************************* * Copyright (c) 2014 BestSolution.at and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation * Christoph Caks <ccaks@bestsolution.at> - finished implementation *******************************************************************************/ package at.bestsolution.persistence.java.internal; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.sql.Blob; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; public class LocalBlob implements Blob { private File storageFile; private RandomAccessFile storage; private boolean blobInvalid = false; private void checkInvalid() throws SQLException { if (blobInvalid) { throw new SQLException("BLOB invalid (already freed)"); } } private void checkPointer(long pointer) throws SQLException { if (pointer < 1) { throw new SQLException("pointer must be >= 1"); } } private File createStorageFile() throws IOException { final File storageFile = File.createTempFile("emap", "blob"); storageFile.deleteOnExit(); return storageFile; } private RandomAccessFile createStorage(File storageFile) throws IOException { return new RandomAccessFile(storageFile, "rw"); } private RandomAccessFile getStorage() throws IOException { if (storage == null) { storageFile = createStorageFile(); storage = createStorage(storageFile); } return storage; } @Override protected void finalize() throws Throwable { super.finalize(); try { if (storage != null) { storage.close(); storage = null; } } catch (IOException e) { } if (storageFile != null) { if (storageFile.delete()) { storageFile = null; } } } @Override public void free() throws SQLException { try { if (storage != null) { storage.close(); storage = null; } if (storageFile != null) { storageFile.delete(); storageFile = null; } } catch (IOException e) { throw new SQLException(e); } finally { blobInvalid = true; } } @Override public InputStream getBinaryStream() throws SQLException { checkInvalid(); try { return new RAF_InputStream(getStorage()); } catch (IOException e) { throw new SQLException(e); } } @Override public InputStream getBinaryStream(long pos, long length) throws SQLException { checkInvalid(); checkPointer(pos); throw new UnsupportedOperationException(); } @Override public byte[] getBytes(long pos, int length) throws SQLException { checkInvalid(); checkPointer(pos); try { byte[] buf = new byte[length]; getStorage().seek(pos-1); getStorage().read(buf, 0, length); return buf; } catch (IOException e) { throw new SQLException(e); } } @Override public long length() throws SQLException { checkInvalid(); try { return getStorage().length(); } catch (IOException e) { throw new SQLException(e); } } @Override public long position(byte[] pattern, long start) throws SQLException { checkInvalid(); throw new SQLFeatureNotSupportedException(); } @Override public long position(Blob pattern, long start) throws SQLException { checkInvalid(); throw new SQLFeatureNotSupportedException(); } @Override public OutputStream setBinaryStream(long pos) throws SQLException { checkInvalid(); checkPointer(pos); try { return new RAF_OutputStream(getStorage(), pos-1); } catch (IOException e) { throw new SQLException(e); } } @Override public int setBytes(long pos, byte[] bytes) throws SQLException { return this.setBytes(pos, bytes, 0, bytes.length); } @Override public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException { checkInvalid(); checkPointer(pos); try { getStorage().seek(pos-1); getStorage().write(bytes, offset, len); return len; } catch (IOException e) { throw new SQLException(e); } } @Override public void truncate(long len) throws SQLException { checkInvalid(); try { getStorage().setLength(len); } catch (IOException e) { throw new SQLException(e); } } static class RAF_OutputStream extends OutputStream { private final RandomAccessFile raf; private long offset; public RAF_OutputStream(RandomAccessFile raf) { this(raf, 0); } public RAF_OutputStream(RandomAccessFile raf, long offset) { this.raf = raf; this.offset = offset; } @Override public void write(byte[] b) throws IOException { this.write(b, 0, b.length); } @Override public void write(byte[] b, int off, int len) throws IOException { raf.seek(offset); raf.write(b, off, len); offset += len; } @Override public void write(int b) throws IOException { raf.seek(offset); raf.write(b); offset += 1; } } static class RAF_InputStream extends InputStream { private final RandomAccessFile raf; private int offset; public RAF_InputStream(RandomAccessFile raf) { this.raf = raf; this.offset = 0; } @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } @Override public int read(byte[] b, int off, int len) throws IOException { if (offset >= raf.length()) { return -1; } raf.seek(offset); int readLen = raf.read(b, off, len); if (readLen != -1) { offset += readLen; } return readLen; } @Override public int read() throws IOException { if (offset >= raf.length()) { return -1; } raf.seek(offset); int r = raf.read(); offset += 1; return r; } } }