#211 - ENH: Add support for File as a bean type ... for streaming into/from db

This commit is contained in:
rbygrave
2014-11-26 22:45:33 +13:00
parent 983391af94
commit 2195d7ecbc
9 changed files with 446 additions and 7 deletions
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.type;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
@@ -114,7 +115,15 @@ public class DataBind {
public void setChar(char v) throws SQLException {
pstmt.setString(++pos, String.valueOf(v));
}
public void setBlob(InputStream inputStream, long length) throws SQLException {
pstmt.setBlob(++pos, inputStream, length);
}
public void setBlob(InputStream inputStream) throws SQLException {
pstmt.setBlob(++pos, inputStream);
}
public void setBlob(byte[] bytes) throws SQLException {
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
pstmt.setBinaryStream(++pos, is, bytes.length);
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.Array;
import java.sql.SQLException;
@@ -52,4 +53,5 @@ public interface DataReader {
public Object getObject() throws SQLException;
public InputStream getBinaryStream() throws SQLException;
}
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
import java.io.File;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
@@ -66,6 +67,8 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
private final DefaultTypeFactory extraTypeFactory;
private final ScalarTypeFile fileType = new ScalarTypeFile();
private final ScalarType<?> charType = new ScalarTypeChar();
private final ScalarType<?> charArrayType = new ScalarTypeCharArray();
@@ -127,8 +130,8 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
*/
public DefaultTypeManager(ServerConfig config, BootupClasses bootupClasses) {
int clobType = config == null ? Types.CLOB : config.getDatabasePlatform().getClobDbType();
int blobType = config == null ? Types.BLOB : config.getDatabasePlatform().getBlobDbType();
int clobType = config.getDatabasePlatform().getClobDbType();
int blobType = config.getDatabasePlatform().getBlobDbType();
this.jsonDateTime = config.getJsonDateTime();
this.checkImmutable = new CheckImmutable(this);
@@ -296,7 +299,12 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
@SuppressWarnings("unchecked")
public <T> ScalarType<T> getScalarType(Class<T> type, int jdbcType) {
// check for Clob, LongVarchar etc first...
// File is a special Lob so check for that first
if (File.class.equals(type)) {
return (ScalarType<T>) fileType;
}
// check for Clob, LongVarchar etc ...
// the reason being that String maps to multiple jdbc types
// varchar, clob, longVarchar.
ScalarType<?> scalarType = getLobTypes(jdbcType);
@@ -321,8 +329,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
return (ScalarType<T>) extraTypeFactory.createCalendar(jsonDateTime, jdbcType);
}
String msg = "Unmatched ScalarType for " + type + " jdbcType:" + jdbcType;
throw new RuntimeException(msg);
throw new RuntimeException("Unmatched ScalarType for " + type + " jdbcType:" + jdbcType);
}
/**
@@ -650,7 +657,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
* plus some other common types such as java.util.Date and java.util.Calendar.
*/
protected void initialiseStandard(JsonConfig.DateTime mode, int platformClobType, int platformBlobType, boolean binaryUUID) {
ScalarType<?> utilDateType = extraTypeFactory.createUtilDate(mode);
typeMap.put(java.util.Date.class, utilDateType);
@@ -675,6 +682,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
ScalarType<?> uuidType = (binaryUUID) ? new ScalarTypeUUIDBinary() : new ScalarTypeUUIDVarchar();
typeMap.put(UUID.class, uuidType);
typeMap.put(File.class, fileType);
typeMap.put(InetAddress.class, inetAddressType);
typeMap.put(Locale.class, localeType);
typeMap.put(Currency.class, currencyType);
@@ -0,0 +1,193 @@
package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.text.TextException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.sql.SQLException;
import java.sql.Types;
/**
* ScalarType for streaming between a File and the database.
*/
public class ScalarTypeFile extends ScalarTypeBase<File> {
private static Logger logger = LoggerFactory.getLogger(ScalarTypeFile.class);
private final String prefix;
private final String suffix;
private final File directory;
private final int bufferSize;
/**
* Construct with reasonable defaults of Blob and 8096 buffer size.
*/
public ScalarTypeFile() {
this(Types.BLOB, "db-", null, null, 8096);
}
/**
* Create the ScalarTypeFile.
*/
public ScalarTypeFile(int jdbcType, String prefix, String suffix, File directory, int bufferSize) {
super(File.class, false, jdbcType);
this.prefix = prefix;
this.suffix = suffix;
this.directory = directory;
this.bufferSize = bufferSize;
}
private InputStream getInputStream(File value) throws IOException {
FileInputStream fi = new FileInputStream(value);
return new BufferedInputStream(fi, bufferSize);
}
private OutputStream getOutputStream(File value) throws IOException {
FileOutputStream fi = new FileOutputStream(value);
return new BufferedOutputStream(fi, bufferSize);
}
@Override
public File read(DataReader dataReader) throws SQLException {
InputStream is = dataReader.getBinaryStream();
if (is == null) {
return null;
}
try {
// stream from db into our temp file
File tempFile = File.createTempFile(prefix, suffix, directory);
OutputStream os = getOutputStream(tempFile);
pump(is, os);
return tempFile;
} catch (IOException e) {
throw new SQLException("Error reading db file inputStream", e);
}
}
@Override
public void bind(DataBind b, File value) throws SQLException {
if (value == null) {
b.setNull(jdbcType);
} else {
try {
// stream from our file to the db
InputStream fi = getInputStream(value);
b.setBlob(fi, value.length());
} catch (IOException e) {
throw new SQLException("Error trying to set file inputStream", e);
}
}
}
@Override
public Object toJdbcType(Object value) {
return value;
}
@Override
public File toBeanType(Object value) {
return (File) value;
}
@Override
public void jsonWrite(JsonGenerator ctx, String name, File value) throws IOException {
ctx.writeFieldName(name);
InputStream is = getInputStream(value);
ctx.writeBinary(is, (int) value.length());
}
@Override
public File jsonRead(JsonParser ctx, JsonToken event) throws IOException {
File tempFile = File.createTempFile(prefix, suffix, directory);
OutputStream os = getOutputStream(tempFile);
ctx.readBinaryValue(os);
os.flush();
os.close();
return tempFile;
}
@Override
public String formatValue(File file) {
throw new TextException("Not supported");
}
@Override
public File parse(String value) {
throw new TextException("Not supported");
}
@Override
public File convertFromMillis(long systemTimeMillis) {
throw new TextException("Not supported");
}
@Override
public boolean isDateTimeCapable() {
return false;
}
@Override
public File readData(DataInput dataInput) throws IOException {
// skip reading large file
return null;
}
public void writeData(DataOutput dataOutput, File file) throws IOException {
// skip writing large file
}
/**
* Helper method to pump bytes from input to output.
*/
public long pump(InputStream is, OutputStream out) throws IOException {
long totalBytes = 0;
InputStream input = null;
OutputStream output = null;
try {
input = new BufferedInputStream(is, bufferSize);
output = new BufferedOutputStream(out, bufferSize);
byte[] buffer = new byte[bufferSize];
int length;
while (((length = input.read(buffer)) > 0)) {
output.write(buffer, 0, length);
totalBytes += length;
}
output.flush();
return totalBytes;
} finally {
if (output != null) {
try {
output.close();
} catch (IOException e) {
logger.error("Error when closing outputstream", e);
}
}
if (input != null) {
try {
input.close();
} catch (IOException e) {
logger.error("Error when closing inputstream ", e);
}
}
}
}
}