#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);
}
}
}
}
}
@@ -0,0 +1,51 @@
package com.avaje.tests.model.types;
import javax.persistence.*;
import java.io.File;
@Entity
public class SomeFileBean {
@Id
Long id;
@Version
Long version;
String name;
@Lob
File file;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
}
@@ -0,0 +1,111 @@
package com.avaje.tests.types;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.types.SomeFileBean;
import org.junit.Test;
import java.io.File;
import java.net.URL;
import static org.junit.Assert.*;
public class TestFileType extends BaseTestCase {
File file = getFile("/profile-image.jpg");
File file2 = getFile("/java-64.png");
@Test
public void test_insertNullFile() {
assertTrue(file.exists());
assertTrue(file2.exists());
SomeFileBean bean0 = new SomeFileBean();
bean0.setName("afile");
Ebean.save(bean0);
SomeFileBean bean1 = Ebean.find(SomeFileBean.class)
.select("name, file")
.setId(bean0.getId())
.findUnique();
assertEquals("afile", bean1.getName());
assertNull(bean1.getFile());
bean1.setFile(file);
Ebean.save(bean1);
SomeFileBean bean2 = Ebean.find(SomeFileBean.class)
.select("name, file")
.setId(bean0.getId())
.findUnique();
assertEquals("afile", bean2.getName());
assertNotNull(bean2.getFile());
System.out.println("test_insertNullFile: bean2: "+bean2.getFile());
Ebean.delete(bean1);
}
@Test
public void test_insertUpdateDelete() {
assertTrue(file.exists());
assertTrue(file2.exists());
SomeFileBean bean0 = new SomeFileBean();
bean0.setName("afile");
bean0.setFile(file);
Ebean.save(bean0);
SomeFileBean bean1 = Ebean.find(SomeFileBean.class)
.select("name, file")
.setId(bean0.getId())
.findUnique();
assertEquals("afile", bean1.getName());
assertNotNull(bean1.getFile());
assertEquals(file.length(), bean1.getFile().length());
System.out.println("t2 bean1: " + bean1.getFile().getAbsoluteFile());
bean1.setName("mod-file");
bean1.setFile(file2);
// update to file2
Ebean.save(bean1);
SomeFileBean bean2 = Ebean.find(SomeFileBean.class)
.select("name, file")
.setId(bean0.getId())
.findUnique();
assertEquals(file2.length(), bean2.getFile().length());
System.out.println("t2 bean3: " + bean2.getFile().getAbsoluteFile());
// update to null
bean2.setFile(null);
bean2.setName("setNull");
Ebean.save(bean2);
SomeFileBean bean3 = Ebean.find(SomeFileBean.class)
.select("name, file")
.setId(bean0.getId())
.findUnique();
assertNull(bean3.getFile());
System.out.println("t2 bean3: " + bean3.getFile());
bean3.setName("changeOnlyName");
Ebean.save(bean3);
Ebean.delete(bean3);
}
private File getFile(String resource) {
URL url = getClass().getResource(resource);
return new File(url.getFile());
}
}
@@ -0,0 +1,65 @@
package com.avaje.tests.types;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.BeanState;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.types.SomeFileBean;
import org.junit.Test;
import java.io.File;
import java.net.URL;
import java.util.Set;
import static org.junit.Assert.*;
public class TestFileTypeFetching extends BaseTestCase {
File file = getFile("/profile-image.jpg");
File file2 = getFile("/java-64.png");
@Test
public void test_lazyFetch_statelessUpdate() {
assertTrue(file.exists());
SomeFileBean bean0 = new SomeFileBean();
bean0.setName("one");
bean0.setFile(file);
Ebean.save(bean0);
SomeFileBean bean1 = Ebean.find(SomeFileBean.class)
.setId(bean0.getId())
.findUnique();
BeanState beanState = Ebean.getBeanState(bean1);
Set<String> loadedProps = beanState.getLoadedProps();
assertTrue(loadedProps.contains("name"));
assertFalse(loadedProps.contains("file"));
File file1 = bean1.getFile();
assertEquals(file.length(), file1.length());
SomeFileBean statelessUpdateBean = new SomeFileBean();
statelessUpdateBean.setId(bean0.getId());
statelessUpdateBean.setFile(file2);
// perform stateless update (handy)
Ebean.update(statelessUpdateBean);
SomeFileBean bean2 = Ebean.find(SomeFileBean.class)
.select("file")
.setId(bean0.getId())
.findUnique();
assertEquals(file2.length(), bean2.getFile().length());
Ebean.delete(bean1);
}
private File getFile(String resource) {
URL url = getClass().getResource(resource);
return new File(url.getFile());
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB