DBLargeBinary.java
- /*
- * Copyright 2013 Gregory Graham.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package nz.co.gregs.dbvolution.datatypes;
- import java.io.*;
- import static java.nio.charset.StandardCharsets.UTF_8;
- import java.sql.*;
- import java.util.*;
- import java.util.logging.*;
- import nz.co.gregs.dbvolution.*;
- import nz.co.gregs.dbvolution.columns.LargeBinaryColumn;
- import nz.co.gregs.dbvolution.databases.definitions.DBDefinition;
- import nz.co.gregs.dbvolution.internal.query.LargeObjectHandlerType;
- import nz.co.gregs.dbvolution.exceptions.DBRuntimeException;
- import nz.co.gregs.dbvolution.exceptions.IncorrectRowProviderInstanceSuppliedException;
- import nz.co.gregs.dbvolution.query.RowDefinition;
- import nz.co.gregs.dbvolution.utility.comparators.ByteArrayComparator;
- import org.apache.commons.codec.binary.Base64;
- /**
- *
- * Implements the abstractions necessary to handle arbitrary byte streams and
- * files stored in the database.
- *
- * <p>
- * Use DBLargeBinary for files and streams. Store exceptionally long text in
- * {@link DBLargeText} and Java instances/objects as {@link DBJavaObject} for
- * greater convenience.
- *
- * <p>
- * Generally DBLargeBinary is declared inside your DBRow sub-class as:
- * {@code @DBColumn public DBLargeBinary myBinaryColumn = new DBLargeBinary();}
- *
- * <p>
- * DBLargeBinary is the standard type of {@link DBLargeObject BLOB columns}.
- *
- * <p style="color: #F90;">Support DBvolution at
- * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
- *
- * @author Gregory Graham
- */
- import nz.co.gregs.dbvolution.results.LargeBinaryResult;
- public class DBLargeBinary extends DBLargeObject<byte[]> implements LargeBinaryResult{
- private static final long serialVersionUID = 1;
- /**
- * The Default constructor for a DBBinaryObject.
- *
- */
- public DBLargeBinary() {
- super();
- }
- /**
- * Creates a column expression with a large object result from the expression
- * provided.
- *
- * <p>
- * Used in {@link DBReport}, and some {@link DBRow}, sub-classes to derive
- * data from the database prior to retrieval.
- *
- * @param aThis an expression that will result in a large object value
- */
- public DBLargeBinary(LargeBinaryResult aThis) {
- super(aThis);
- }
- public DBLargeBinary(byte[] value) {
- super();
- setByteArray(value);
- }
- /**
- *
- * <p style="color: #F90;">Support DBvolution at
- * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
- *
- * @return the standard SQL datatype that corresponds to this QDT as a String
- */
- @Override
- public String getSQLDatatype() {
- return "BLOB";
- }
- /**
- * Sets the value of this DBLargeBinary to the byte array supplied.
- *
- * @param byteArray byteArray
- */
- @Override
- public void setValue(byte[] byteArray) {
- setByteArray(byteArray);
- }
- private void setByteArray(byte[] byteArray) {
- super.setLiteralValue(byteArray);
- }
- /**
- * Sets the value of this DBLargeBinary to the InputStream supplied.
- *
- * <p>
- * The input stream will not be read until the containing DBRow is
- * saved/inserted.
- *
- * @param inputViaStream inputViaStream
- */
- public void setValue(InputStream inputViaStream) {
- setValue(getBytesFromInputStream(inputViaStream));
- }
- /**
- * Sets the value of this DBLargeBinary to the file supplied.
- *
- * <p>
- * Unlike {@link #setValue(java.io.InputStream) setting an InputStream}, the
- * file is read immediately and stored internally. If you would prefer to
- * delay the reading of the file, wrap the file in a {@link FileInputStream}.
- *
- * @param fileToRead fileToRead
- * @throws java.io.IOException java.io.IOException
- */
- public void setValue(File fileToRead) throws IOException {
- setFromFileSystem(fileToRead);
- }
- /**
- * Set the value of the DBLargeBinary to the String provided.
- *
- * @param string string
- */
- public void setValue(String string) {
- setValue(string.getBytes(UTF_8));
- }
- void setValue(DBLargeBinary newLiteralValue) {
- setValue(newLiteralValue.getValue());
- }
- private byte[] getFromBinaryStream(ResultSet resultSet, String fullColumnName) throws SQLException {
- byte[] bytes = new byte[]{};
- try (InputStream inputStream = resultSet.getBinaryStream(fullColumnName)) {
- if (inputStream == null) {
- this.setToNull();
- } else {
- bytes = getBytesFromInputStream(inputStream);
- }
- } catch (IOException ex) {
- Logger.getLogger(DBLargeBinary.class.getName()).log(Level.SEVERE, null, ex);
- }
- return bytes;
- }
- private byte[] getFromBLOB(ResultSet resultSet, String fullColumnName) throws SQLException {
- byte[] bytes = new byte[]{};
- Blob blob = resultSet.getBlob(fullColumnName);
- if (resultSet.wasNull()) {
- blob = null;
- }
- if (blob == null) {
- this.setToNull();
- } else {
- InputStream inputStream = blob.getBinaryStream();
- bytes = getBytesFromInputStream(inputStream);
- try {
- inputStream.close();
- } catch (IOException ex) {
- Logger.getLogger(DBLargeBinary.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
- return bytes;
- }
- private byte[] getBytesFromInputStream(InputStream inputStream) {
- InputStream input = new BufferedInputStream(inputStream);
- List<byte[]> byteArrays = new ArrayList<>();
- try {
- byte[] resultSetBytes;
- final int byteArrayDefaultSize = 100000;
- resultSetBytes = new byte[byteArrayDefaultSize];
- int bytesRead = input.read(resultSetBytes);
- while (bytesRead > 0) {
- if (bytesRead == byteArrayDefaultSize) {
- byteArrays.add(resultSetBytes);
- } else {
- byte[] shortBytes = new byte[bytesRead];
- System.arraycopy(resultSetBytes, 0, shortBytes, 0, bytesRead);
- byteArrays.add(shortBytes);
- }
- resultSetBytes = new byte[byteArrayDefaultSize];
- bytesRead = input.read(resultSetBytes);
- }
- } catch (IOException ex) {
- Logger.getLogger(DBLargeBinary.class.getName()).log(Level.SEVERE, null, ex);
- throw new DBRuntimeException("Failed to read input", ex);
- } finally {
- try {
- input.close();
- } catch (IOException ex) {
- Logger.getLogger(DBLargeBinary.class.getName()).log(Level.SEVERE, null, ex);
- throw new DBRuntimeException("Failed to close input", ex);
- }
- }
- return concatAllByteArrays(byteArrays);
- }
- public static byte[] concatAllByteArrays(List<byte[]> bytes) {
- if (bytes.isEmpty()) {
- return new byte[]{};
- } else {
- byte[] first = bytes.get(0);
- bytes.remove(0);
- byte[][] rest = bytes.toArray(new byte[][]{});
- int totalLength = first.length;
- for (byte[] array : rest) {
- totalLength += array.length;
- }
- byte[] result = Arrays.copyOf(first, totalLength);
- int offset = first.length;
- for (byte[] array : rest) {
- System.arraycopy(array, 0, result, offset, array.length);
- offset += array.length;
- }
- return result;
- }
- }
- private byte[] getFromGetBytes(ResultSet resultSet, String fullColumnName) throws SQLException {
- byte[] bytes = resultSet.getBytes(fullColumnName);
- return bytes;
- }
- private byte[] getFromCharacterReader(ResultSet resultSet, String fullColumnName) throws SQLException {
- byte[] decodeBuffer = new byte[]{};
- Reader inputReader = null;
- try {
- inputReader = resultSet.getCharacterStream(fullColumnName);
- } catch (NullPointerException nullEx) {
- ;// NullPointerException is thrown by a SQLite-JDBC bug sometimes.
- }
- if (inputReader != null) {
- if (resultSet.wasNull()) {
- this.setToNull();
- } else {
- BufferedReader input = new BufferedReader(inputReader);
- List<byte[]> byteArrays = new ArrayList<>();
- try {
- char[] resultSetBytes;
- final int byteArrayDefaultSize = 100000;
- resultSetBytes = new char[byteArrayDefaultSize];
- int bytesRead = input.read(resultSetBytes);
- while (bytesRead > 0) {
- if (bytesRead == byteArrayDefaultSize) {
- byteArrays.add(String.valueOf(resultSetBytes).getBytes(UTF_8));
- } else {
- char[] shortBytes = new char[bytesRead];
- System.arraycopy(resultSetBytes, 0, shortBytes, 0, bytesRead);
- byteArrays.add(String.valueOf(shortBytes).getBytes(UTF_8));
- }
- resultSetBytes = new char[byteArrayDefaultSize];
- bytesRead = input.read(resultSetBytes);
- }
- } catch (IOException ex) {
- Logger.getLogger(DBLargeBinary.class.getName()).log(Level.SEVERE, null, ex);
- throw new DBRuntimeException("Failed to read input", ex);
- } finally {
- try {
- input.close();
- } catch (IOException ex) {
- Logger.getLogger(DBLargeBinary.class.getName()).log(Level.SEVERE, null, ex);
- throw new DBRuntimeException("Failed to close input", ex);
- }
- }
- byte[] bytes = concatAllByteArrays(byteArrays);
- decodeBuffer = Base64.decodeBase64(bytes);
- }
- }
- return decodeBuffer;
- }
- private byte[] getFromCLOB(ResultSet resultSet, String fullColumnName) throws SQLException {
- byte[] bytes = new byte[]{};
- Clob clob = resultSet.getClob(fullColumnName);
- if (resultSet.wasNull() || clob == null) {
- this.setToNull();
- } else {
- final Reader characterStream = clob.getCharacterStream();
- try {
- BufferedReader input = new BufferedReader(characterStream);
- List<byte[]> byteArrays = new ArrayList<>();
- try {
- char[] resultSetBytes;
- final int byteArrayDefaultSize = 100000;
- resultSetBytes = new char[byteArrayDefaultSize];
- int bytesRead = input.read(resultSetBytes);
- while (bytesRead > 0) {
- if (bytesRead == byteArrayDefaultSize) {
- byteArrays.add(String.valueOf(resultSetBytes).getBytes(UTF_8));
- } else {
- char[] shortBytes = new char[bytesRead];
- System.arraycopy(resultSetBytes, 0, shortBytes, 0, bytesRead);
- byteArrays.add(String.valueOf(shortBytes).getBytes(UTF_8));
- }
- resultSetBytes = new char[byteArrayDefaultSize];
- bytesRead = input.read(resultSetBytes);
- }
- } catch (IOException ex) {
- Logger.getLogger(DBLargeBinary.class.getName()).log(Level.SEVERE, null, ex);
- throw new DBRuntimeException("Failed to read input", ex);
- } finally {
- try {
- input.close();
- } catch (IOException ex) {
- Logger.getLogger(DBLargeBinary.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
- bytes = concatAllByteArrays(byteArrays);
- } finally {
- try {
- characterStream.close();
- } catch (IOException ex) {
- Logger.getLogger(DBLargeBinary.class.getName()).log(Level.SEVERE, null, ex);
- throw new DBRuntimeException("Failed to close input", ex);
- }
- }
- }
- return bytes;
- }
- @Override
- public String formatValueForSQLStatement(DBDefinition db) {
- throw new UnsupportedOperationException("Binary datatypes like " + this.getClass().getSimpleName() + " do not have a simple SQL representation. Do not call getSQLValue(), use the getInputStream() method instead.");
- }
- /**
- * Tries to set the DBDyteArray to the contents of the supplied file.
- *
- * <p>
- * Convenience method for {@link #setFromFileSystem(java.io.File) }.
- *
- * @param originalFile originalFile
- * <p style="color: #F90;">Support DBvolution at
- * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
- * @throws java.io.FileNotFoundException java.io.FileNotFoundException
- * @throws java.io.IOException java.io.IOException
- *
- *
- */
- public void setFromFileSystem(String originalFile) throws FileNotFoundException, IOException {
- File file = new File(originalFile);
- setFromFileSystem(file);
- }
- /**
- * Tries to set the DBDyteArray to the contents of the supplied file.
- *
- * <p>
- * Convenience method for {@link #setFromFileSystem(java.io.File) }.
- *
- * @param originalFile originalFile
- * <p style="color: #F90;">Support DBvolution at
- * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
- * @throws java.io.FileNotFoundException java.io.FileNotFoundException
- * @throws java.io.IOException java.io.IOException
- *
- *
- */
- public void setFromFileSystem(DBString originalFile) throws FileNotFoundException, IOException {
- File file = new File(originalFile.stringValue());
- setFromFileSystem(file);
- }
- /**
- * Tries to set the DBDyteArray to the contents of the supplied file.
- *
- * @param originalFile originalFile
- * <p style="color: #F90;">Support DBvolution at
- * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
- * @throws java.io.FileNotFoundException java.io.FileNotFoundException
- * @throws java.io.IOException java.io.IOException
- *
- *
- */
- private void setFromFileSystem(File originalFile) throws FileNotFoundException, IOException {
- byte[] bytes = new byte[(int) originalFile.length()];
- InputStream input = null;
- try {
- int totalBytesRead = 0;
- input = new BufferedInputStream(new FileInputStream(originalFile));
- while (totalBytesRead < bytes.length) {
- int bytesRemaining = bytes.length - totalBytesRead;
- int bytesRead = input.read(bytes, totalBytesRead, bytesRemaining);
- if (bytesRead > 0) {
- totalBytesRead += bytesRead;
- }
- }
- /*
- the above style is a bit tricky: it places bytes into the 'result' array;
- 'result' is an output parameter;
- the while loop usually has a single iteration only.
- */
- } finally {
- if (input != null) {
- input.close();
- }
- }
- setValue(bytes);
- }
- /**
- * Tries to write the contents of this DBLargeBinary to the file supplied.
- *
- * <p>
- * Convenience method for {@link #writeToFileSystem(java.io.File) }.
- *
- * @param originalFile originalFile
- * @throws java.io.FileNotFoundException java.io.FileNotFoundException
- * @throws java.io.IOException java.io.IOException
- */
- public void writeToFileSystem(String originalFile) throws FileNotFoundException, IOException {
- File file = new File(originalFile);
- writeToFileSystem(file);
- }
- /**
- * Tries to write the contents of this DBLargeBinary to the file supplied.
- *
- * <p>
- * Convenience method for {@link #writeToFileSystem(java.io.File) }.
- *
- * @param originalFile originalFile
- * @throws java.io.FileNotFoundException java.io.FileNotFoundException
- * @throws java.io.IOException java.io.IOException
- */
- public void writeToFileSystem(DBString originalFile) throws FileNotFoundException, IOException {
- writeToFileSystem(originalFile.toString());
- }
- /**
- * Tries to write the contents of this DBLargeBinary to the file supplied.
- *
- * <p>
- * Convenience method for {@link #writeToFileSystem(java.io.File) }.
- *
- * @param originalFile originalFile
- * @throws java.io.FileNotFoundException java.io.FileNotFoundException
- * @throws java.io.IOException java.io.IOException
- */
- public void writeToFileSystem(File originalFile) throws FileNotFoundException, IOException {
- if (getLiteralValue() != null && originalFile != null) {
- if (!originalFile.exists()) {
- boolean createNewFile = originalFile.createNewFile();
- if (!createNewFile) {
- boolean delete = originalFile.delete();
- if (!delete) {
- throw new IOException("Unable to delete file: " + originalFile.getPath() + " could not be deleted, check the permissions of the file, directory, drive, and current user.");
- }
- createNewFile = originalFile.createNewFile();
- if (!createNewFile) {
- throw new IOException("Unable to create file: " + originalFile.getPath() + " could not be created, check the permissions of the file, directory, drive, and current user.");
- }
- }
- }
- if (originalFile.exists()) {
- try (BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(originalFile))) {
- output.write(getBytes());
- output.flush();
- }
- } else {
- throw new FileNotFoundException("Unable Create File: the file \"" + originalFile.getAbsolutePath() + " could not be found or created.");
- }
- }
- }
- /**
- * Returns the internal InputStream.
- *
- * <p>
- * Remember to close the InputStream.
- *
- * <p style="color: #F90;">Support DBvolution at
- * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
- *
- * @return an InputStream to read the bytes.
- */
- @Override
- public InputStream getInputStream() {
- return new BufferedInputStream(new ByteArrayInputStream(getBytes()));
- }
- /**
- * Returns the byte[] used internally to store the value of this
- * DBLargeBinary.
- *
- * <p style="color: #F90;">Support DBvolution at
- * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
- *
- * @return the byte[] value of this DBLargeBinary.
- */
- public byte[] getBytes() {
- final byte[] litVal = this.getLiteralValue();
- if (litVal != null) {
- return litVal;
- }
- return new byte[]{};
- }
- @Override
- public String stringValue() {
- byte[] value = this.getValue();
- if (this.isNull()) {
- return super.stringValue();
- } else {
- return new String(value, UTF_8);
- }
- }
- @Override
- public int getSize() {
- final byte[] bytes = getBytes();
- if (bytes != null) {
- return bytes.length;
- } else {
- return 0;
- }
- }
- @Override
- public byte[] getValue() {
- return getBytes();
- }
- @Override
- public DBLargeBinary getQueryableDatatypeForExpressionValue() {
- return new DBLargeBinary();
- }
- @Override
- public boolean isAggregator() {
- return false;
- }
- @Override
- public Set<DBRow> getTablesInvolved() {
- return new HashSet<>();
- }
- @Override
- protected byte[] getFromResultSet(DBDefinition defn, ResultSet resultSet, String fullColumnName) throws SQLException {
- byte[] bytes = new byte[]{};
- LargeObjectHandlerType handler = defn.preferredLargeObjectReader(this);
- switch (handler) {
- case BLOB:
- bytes = getFromBLOB(resultSet, fullColumnName);
- break;
- case BASE64:
- bytes = getFromBase64(resultSet, fullColumnName);
- break;
- case BINARYSTREAM:
- bytes = getFromBinaryStream(resultSet, fullColumnName);
- break;
- case CHARSTREAM:
- bytes = getFromCharacterReader(resultSet, fullColumnName);
- break;
- case CLOB:
- bytes = getFromCLOB(resultSet, fullColumnName);
- break;
- case STRING:
- bytes = getFromString(resultSet, fullColumnName);
- break;
- case JAVAOBJECT:
- bytes = getFromJavaObject(resultSet, fullColumnName);
- break;
- case BYTE:
- bytes = getFromGetBytes(resultSet, fullColumnName);
- break;
- }
- return bytes;
- }
- @Override
- public boolean getIncludesNull() {
- return false;
- }
- @Override
- protected void setValueFromStandardStringEncoding(String encodedValue) {
- throw new UnsupportedOperationException("DBLargeBinaryObject does not support setValueFromStandardStringEncoding(String) yet.");
- }
- private byte[] getFromBase64(ResultSet resultSet, String fullColumnName) {
- throw new UnsupportedOperationException("DBLargeBinaryObject does not support getFromBase64(ResultSet, String) yet.");
- }
- private byte[] getFromString(ResultSet resultSet, String fullColumnName) {
- throw new UnsupportedOperationException("DBLargeBinaryObject does not support getFromString(ResultSet,String) yet.");
- }
- private byte[] getFromJavaObject(ResultSet resultSet, String fullColumnName) {
- throw new UnsupportedOperationException("DBLargeBinaryObject does not support getFromJavaObject(ResultSet, String) yet.");
- }
- @Override
- public LargeBinaryColumn getColumn(RowDefinition row) throws IncorrectRowProviderInstanceSuppliedException {
- return new LargeBinaryColumn(row, this);
- }
- /**
- * Indicates whether object is NULL within the database
- *
- * <p>
- * Databases and Java both use the term NULL but for slightly different
- * meanings.
- *
- * <p>
- * This method indicates whether the field represented by this object is NULL
- * in the database sense.
- *
- * <p style="color: #F90;">Support DBvolution at
- * <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
- *
- * @return TRUE if this object represents a NULL database value, otherwise
- * FALSE
- */
- @Override
- public boolean isNull() {
- return super.isNull() && getLiteralValue() == null;//&& this.byteStream == null;
- }
- @Override
- public synchronized DBLargeBinary copy() {
- DBLargeBinary result = (DBLargeBinary) super.copy();
- return result;
- }
- @Override
- public Comparator<byte[]> getComparator() {
- return new ByteArrayComparator();
- }
- }