mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#211 - ENH: Add support for File as a bean type ... for streaming into/from db
This commit is contained in:
@@ -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 |
Reference in New Issue
Block a user