DBJavaObject.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 nz.co.gregs.dbvolution.utility.comparators.HashCodeComparator;
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.DBRow;
import nz.co.gregs.dbvolution.columns.JavaObjectColumn;
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.results.JavaObjectResult;
import nz.co.gregs.dbvolution.results.LargeObjectResult;
import org.apache.commons.codec.binary.Base64;
/**
*
* Implements the abstractions required for handling Java Objects stored in the
* database
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @author Gregory Graham
* @param <O> the specific type of the objects to be stored.
*/
public class DBJavaObject<O> extends DBLargeObject<O> implements JavaObjectResult<O> {
private static final long serialVersionUID = 1;
private transient InputStream byteStream = null;
private O literalObject;
private boolean internalValueHasBeenSet = false;
@Override
public String getSQLDatatype() {
return "JAVA_OBJECT";
}
public DBJavaObject() {
}
public DBJavaObject(LargeObjectResult<O> blobExpression) {
super(blobExpression);
}
public DBJavaObject(O blobExpression) {
super(blobExpression);
}
@SuppressWarnings("unchecked")
private void setInternalValue(O newLiteralValue) {
if (!internalValueHasBeenSet) {
if (newLiteralValue instanceof DBJavaObject) {
final DBJavaObject<O> valBytes = (DBJavaObject<O>) newLiteralValue;
setValue(valBytes.getValue());
} else {
try {
literalObject = newLiteralValue;
ByteArrayOutputStream tempByteStream = new ByteArrayOutputStream();
ObjectOutputStream oStream = new ObjectOutputStream(tempByteStream);
oStream.writeObject(literalObject);
setLiteralValue(literalObject);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
internalValueHasBeenSet = true;
}
}
}
@SuppressWarnings("unchecked")
@Override
public O getValue() {
setInternalValue(getLiteralValue());
return literalObject;
}
@Override
public String toString() {
setInternalValue(getLiteralValue());
if (literalObject == null) {
return "NULL";
} else {
return literalObject.toString();
}
}
@SuppressWarnings("unchecked")
private O getFromBinaryStream(ResultSet resultSet, String fullColumnName) throws SQLException {
O returnValue = null;
InputStream inputStream;
inputStream = resultSet.getBinaryStream(fullColumnName);
if (resultSet.wasNull()) {
inputStream = null;
}
if (inputStream == null) {
this.setToNull();
} else {
final BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
try {
try ( ObjectInputStream input = new ObjectInputStream(bufferedInputStream)) {
returnValue = (O) input.readObject();
}
} catch (IOException | ClassNotFoundException ex) {
Logger.getLogger(DBJavaObject.class.getName()).log(Level.SEVERE, null, ex);
}
try {
inputStream.close();
} catch (IOException ex) {
Logger.getLogger(DBJavaObject.class.getName()).log(Level.SEVERE, null, ex);
}
}
return returnValue;
}
@SuppressWarnings("unchecked")
private O getFromBLOB(ResultSet resultSet, String fullColumnName) throws SQLException {
O returnValue = null;
Blob blob = resultSet.getBlob(fullColumnName);
if (resultSet.wasNull()) {
blob = null;
}
if (blob == null) {
this.setToNull();
} else {
InputStream inputStream = blob.getBinaryStream();
if (inputStream == null) {
this.setToNull();
} else {
final BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
try {
try ( ObjectInputStream input = new ObjectInputStream(bufferedInputStream)) {
returnValue = (O) input.readObject();
}
} catch (IOException | ClassNotFoundException ex) {
Logger.getLogger(DBJavaObject.class.getName()).log(Level.SEVERE, null, ex);
}
try {
inputStream.close();
} catch (IOException ex) {
Logger.getLogger(DBJavaObject.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
return returnValue;
}
@SuppressWarnings("unchecked")
private O getFromGetBytes(ResultSet resultSet, String fullColumnName) throws SQLException {
try {
byte[] bytes = resultSet.getBytes(fullColumnName);
if (bytes != null) {
try ( ObjectInputStream input = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
return (O) input.readObject();
}
}
} catch (IOException | ClassNotFoundException ex) {
Logger.getLogger(DBJavaObject.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
public static byte[] concatAllByteArrays(List<byte[]> bytes) {
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;
}
@SuppressWarnings("unchecked")
private O getFromCharacterReader(ResultSet resultSet, String fullColumnName) throws SQLException, IOException {
O obj = null;
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 {
try ( 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("DBJavaObject.getFromCharacterReader: Unable to get from Character Reader", ex);
}
byte[] bytes = concatAllByteArrays(byteArrays);
byte[] decodeBuffer = Base64.decodeBase64(bytes);
try ( ObjectInputStream decodedInput = new ObjectInputStream(new ByteArrayInputStream(decodeBuffer))) {
obj = (O) decodedInput.readObject();
} catch (ClassNotFoundException ex) {
Logger.getLogger(DBJavaObject.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
return obj;
}
@SuppressWarnings("unchecked")
private O getFromCLOB(ResultSet resultSet, String fullColumnName) throws SQLException {
O returnValue = null;
Clob clob = resultSet.getClob(fullColumnName);
if (resultSet.wasNull() || clob == null) {
this.setToNull();
} else {
try {
List<byte[]> byteArrays = new ArrayList<>();
try ( BufferedReader input = new BufferedReader(clob.getCharacterStream())) {
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 get from BLOB", ex);
}
byte[] bytes = concatAllByteArrays(byteArrays);
try ( ObjectInputStream objectInput = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
returnValue = (O) objectInput.readObject();
}
}
} catch (IOException | ClassNotFoundException ex) {
Logger.getLogger(DBJavaObject.class.getName()).log(Level.SEVERE, null, ex);
}
}
return returnValue;
}
@Override
public String formatValueForSQLStatement(DBDefinition db) {
throw new UnsupportedOperationException("DBJavaObject does not support formatValueForSQLStatement(DBDefinition) yet.");
}
@Override
public DBJavaObject<O> getQueryableDatatypeForExpressionValue() {
return new DBJavaObject<>();
}
@Override
public boolean isAggregator() {
return false;
}
@Override
public Set<DBRow> getTablesInvolved() {
return new HashSet<>();
}
/**
* Returns the internal 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() {
if (byteStream == null) {
try {
byteStream = new ByteArrayInputStream(getBytes());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
return byteStream;
}
/**
* Returns the byte[] used internally to store the value of this DBJavaObject.
*
* <p style="color: #F90;">Support DBvolution at
* <a href="http://patreon.com/dbvolution" target=new>Patreon</a></p>
*
* @return the byte[] value of this DBJavaObject.
* @throws java.io.IOException java.io.IOException
*
*/
public byte[] getBytes() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(out);
os.writeObject(getLiteralValue());
return out.toByteArray();
}
@Override
public String stringValue() {
if (this.isNull()) {
return super.stringValue();
} else {
O value = this.getValue();
return "" + value;
}
}
@Override
public int getSize() {
try {
return getBytes().length;
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
@Override
protected O getFromResultSet(DBDefinition defn, ResultSet resultSet, String fullColumnName) throws SQLException {
O obj = null;
LargeObjectHandlerType handler = defn.preferredLargeObjectReader(this);
switch (handler) {
case BLOB:
obj = getFromBLOB(resultSet, fullColumnName);
break;
case BASE64:
obj = getFromBase64(resultSet, fullColumnName);
break;
case BINARYSTREAM:
obj = getFromBinaryStream(resultSet, fullColumnName);
break;
case CHARSTREAM:
try {
obj = getFromCharacterReader(resultSet, fullColumnName);
} catch (IOException exp) {
throw new DBRuntimeException("DBJavaObject.getFromResultSet: Failed to get from Character Reader", exp);
}
break;
case CLOB:
obj = getFromCLOB(resultSet, fullColumnName);
break;
case STRING:
obj = getFromString(resultSet, fullColumnName);
break;
case JAVAOBJECT:
obj = getFromJavaObject(resultSet, fullColumnName);
break;
case BYTE:
obj = getFromGetBytes(resultSet, fullColumnName);
break;
}
return obj;
}
@Override
public boolean getIncludesNull() {
return false;
}
@Override
protected void setValueFromStandardStringEncoding(String encodedValue) {
throw new UnsupportedOperationException("DBJavaObject does not support setValueFromStandardStringEncoding(String) yet.");
}
private O getFromString(ResultSet resultSet, String fullColumnName) {
throw new UnsupportedOperationException("DBJavaObject does not support getFromString(ResultSet, String) yet.");
}
@SuppressWarnings("unchecked")
private O getFromJavaObject(ResultSet resultSet, String fullColumnName) throws SQLException {
O returnValue = null;
Object blob = resultSet.getObject(fullColumnName);
if (resultSet.wasNull()) {
blob = null;
}
if (blob == null) {
this.setToNull();
} else {
returnValue = (O) blob;
}
return returnValue;
}
private O getFromBase64(ResultSet resultSet, String fullColumnName) {
throw new UnsupportedOperationException("DBJavaObject does not support getFromBase64(ResultSet, String) yet.");
}
@Override
public JavaObjectColumn<O> getColumn(RowDefinition row) throws IncorrectRowProviderInstanceSuppliedException {
return new JavaObjectColumn<O>(row, this);
}
@Override
public Comparator<O> getComparator() {
return new HashCodeComparator<O>();
}
@Override
public DBJavaObject<O> copy() {
return (DBJavaObject<O>) super.copy();
}
}