From 221f5ad879cd639c85ce1d91dd4aa78efc6c1ac0 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Thu, 21 Apr 2016 11:33:26 +1200 Subject: [PATCH] #651 - ENH: Add type support for java.nio.file.Path ... such that it stores the underlying URI as VARCHAR/string --- .../avaje/ebean/config/ClassLoadConfig.java | 7 ++++ .../server/type/DefaultTypeManager.java | 40 ++++++++++++++----- .../server/type/ScalarTypePath.java | 40 +++++++++++++++++++ .../server/type/TypeManager.java | 4 +- .../server/type/ScalarTypePathTest.java | 36 +++++++++++++++++ .../tests/model/types/SomeNewTypesBean.java | 11 +++++ .../com/avaje/tests/types/TestNewTypes.java | 8 ++++ 7 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePath.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypePathTest.java diff --git a/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java b/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java index 9c2342f08b..6519d1b050 100644 --- a/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java +++ b/src/main/java/com/avaje/ebean/config/ClassLoadConfig.java @@ -33,6 +33,13 @@ public boolean isJavaTimePresent() { return isPresent("java.time.LocalDate"); } + /** + * Return true if Java7 is present. + */ + public boolean isJava7Present() { + return isPresent("java.nio.file.Path"); + } + /** * Return true if the Joda types are available and should be supported. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java index 168c41f05f..65b8e6e83d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java @@ -125,6 +125,8 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { private final boolean objectMapperPresent; + private final boolean java7Present; + // OPTIONAL ScalarTypes registered if Jackson/JsonNode is in the classpath /** @@ -153,6 +155,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { */ public DefaultTypeManager(ServerConfig config, BootupClasses bootupClasses) { + this.java7Present = config.getClassLoadConfig().isJava7Present(); this.jsonDateTime = config.getJsonDateTime(); this.checkImmutable = new CheckImmutable(this); this.reflectScalarBuilder = new ReflectionBasedTypeBuilder(this); @@ -282,19 +285,33 @@ public ScalarType getScalarType(int jdbcType) { /** * This can return null if no matching ScalarType is found. */ - @SuppressWarnings("unchecked") - public ScalarType getScalarType(Class type) { - ScalarType found = (ScalarType) typeMap.get(type); + public ScalarType getScalarType(Class type) { + ScalarType found = typeMap.get(type); if (found == null) { if (type.getName().equals("org.joda.time.LocalTime")) { throw new IllegalStateException( "ScalarType of Joda LocalTime not defined. You need to set ServerConfig.jodaLocalTimeMode to" + " either 'normal' or 'utc'. UTC is the old mode using UTC timezone but local time zone is now preferred as 'normal' mode."); } + found = checkInterfaceTypes(type); } return found; } + private ScalarType checkInterfaceTypes(Class type) { + if (java7Present) { + return checkJava7InterfaceTypes(type); + } + return null; + } + + private ScalarType checkJava7InterfaceTypes(Class type) { + if (java.nio.file.Path.class.isAssignableFrom(type)) { + return typeMap.get(java.nio.file.Path.class); + } + return null; + } + public ScalarDataReader getScalarDataReader(Class propertyType, int sqlType) { if (sqlType == 0) { @@ -362,11 +379,11 @@ public ScalarType getJsonScalarType(Class type, int dbType) { *

*/ @SuppressWarnings("unchecked") - public ScalarType getScalarType(Class type, int jdbcType) { + public ScalarType getScalarType(Class type, int jdbcType) { // File is a special Lob so check for that first if (File.class.equals(type)) { - return (ScalarType) fileType; + return fileType; } // check for Clob, LongVarchar etc ... @@ -375,23 +392,23 @@ public ScalarType getScalarType(Class type, int jdbcType) { ScalarType scalarType = getLobTypes(jdbcType); if (scalarType != null) { // it is a specific Lob type... - return (ScalarType) scalarType; + return scalarType; } scalarType = typeMap.get(type); if (scalarType != null) { if (jdbcType == 0 || scalarType.getJdbcType() == jdbcType) { // matching type - return (ScalarType) scalarType; + return scalarType; } } // a util Date with jdbcType not matching server wide settings if (type.equals(java.util.Date.class)) { - return (ScalarType) extraTypeFactory.createUtilDate(jsonDateTime, jdbcType); + return extraTypeFactory.createUtilDate(jsonDateTime, jdbcType); } // a Calendar with jdbcType not matching server wide settings if (type.equals(java.util.Calendar.class)) { - return (ScalarType) extraTypeFactory.createCalendar(jsonDateTime, jdbcType); + return extraTypeFactory.createCalendar(jsonDateTime, jdbcType); } throw new IllegalArgumentException("Unmatched ScalarType for " + type + " jdbcType:" + jdbcType); @@ -772,6 +789,11 @@ protected void initialiseJacksonTypes(ServerConfig config) { } protected void initialiseJavaTimeTypes(JsonConfig.DateTime mode, ServerConfig config) { + + if (java7Present) { + typeMap.put(java.nio.file.Path.class, new ScalarTypePath()); + } + if (config.getClassLoadConfig().isJavaTimePresent()) { logger.debug("Registering java.time data types"); typeMap.put(java.time.LocalDate.class, new ScalarTypeLocalDate()); diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePath.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePath.java new file mode 100644 index 0000000000..56b13abdae --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePath.java @@ -0,0 +1,40 @@ +package com.avaje.ebeaninternal.server.type; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * ScalarType for java.nio.file.Path which converts to and from a VARCHAR database column. + */ +public class ScalarTypePath extends ScalarTypeBaseVarchar { + + public ScalarTypePath() { + super(Path.class); + } + + @Override + public Path convertFromDbString(String dbValue) { + try { + return Paths.get(new URI(dbValue)); + } catch (URISyntaxException e) { + throw new RuntimeException("Error with Path URI [" + dbValue + "] " + e); + } + } + + @Override + public String convertToDbString(Path beanValue) { + return beanValue.toUri().toString(); + } + + @Override + public String formatValue(Path path) { + return convertToDbString(path); + } + + @Override + public Path parse(String value) { + return convertFromDbString(value); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java index 7ed5fa2619..56c2119d3c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java @@ -42,14 +42,14 @@ public interface TypeManager { /** * Return the ScalarType for a given logical type. */ - ScalarType getScalarType(Class type); + ScalarType getScalarType(Class type); /** * For java.util.Date and java.util.Calendar additionally pass the jdbc type * that you would like the ScalarType to map to. This is because these types * can map to different java.sql.Types depending on the property. */ - ScalarType getScalarType(Class type, int jdbcType); + ScalarType getScalarType(Class type, int jdbcType); /** * Create a ScalarType for an Enum using a mapping (rather than JPA Ordinal diff --git a/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypePathTest.java b/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypePathTest.java new file mode 100644 index 0000000000..641a82d736 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypePathTest.java @@ -0,0 +1,36 @@ +package com.avaje.ebeaninternal.server.type; + +import org.junit.Test; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.Assert.assertEquals; + +public class ScalarTypePathTest { + + private ScalarTypePath type = new ScalarTypePath(); + + @Test + public void convertFromDbString() throws Exception { + + Path path = Paths.get("."); + + String asString = type.convertToDbString(path); + Path converted = type.convertFromDbString(asString); + + assertEquals(path, converted); + } + + @Test + public void formatAndParse() throws Exception { + + Path path = Paths.get("."); + + String asString = type.formatValue(path); + Path converted = type.parse(asString); + + assertEquals(path, converted); + } + +} \ No newline at end of file diff --git a/src/test/java/com/avaje/tests/model/types/SomeNewTypesBean.java b/src/test/java/com/avaje/tests/model/types/SomeNewTypesBean.java index 17a218c2e9..cc3015ddd2 100644 --- a/src/test/java/com/avaje/tests/model/types/SomeNewTypesBean.java +++ b/src/test/java/com/avaje/tests/model/types/SomeNewTypesBean.java @@ -4,6 +4,7 @@ import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Version; +import java.nio.file.Path; import java.time.*; @Entity @@ -41,6 +42,8 @@ public class SomeNewTypesBean { ZoneOffset zoneOffset; + Path path; + public Long getId() { return id; } @@ -144,4 +147,12 @@ public ZoneOffset getZoneOffset() { public void setZoneOffset(ZoneOffset zoneOffset) { this.zoneOffset = zoneOffset; } + + public Path getPath() { + return path; + } + + public void setPath(Path path) { + this.path = path; + } } diff --git a/src/test/java/com/avaje/tests/types/TestNewTypes.java b/src/test/java/com/avaje/tests/types/TestNewTypes.java index bd807b1898..9955ed93e0 100644 --- a/src/test/java/com/avaje/tests/types/TestNewTypes.java +++ b/src/test/java/com/avaje/tests/types/TestNewTypes.java @@ -6,6 +6,7 @@ import org.junit.Test; import java.io.IOException; +import java.nio.file.Paths; import java.time.*; import java.util.List; @@ -30,6 +31,7 @@ public void testInsertUpdate() throws IOException { bean.setZoneId(ZoneId.systemDefault()); bean.setZoneOffset(ZonedDateTime.now().getOffset()); bean.setYearMonth(YearMonth.of(2014, 9)); + bean.setPath(Paths.get("/tmp")); Ebean.save(bean); @@ -69,6 +71,9 @@ public void testInsertUpdate() throws IOException { list = Ebean.find(SomeNewTypesBean.class).where().le("month", Month.SEPTEMBER).findList(); assertTrue(!list.isEmpty()); + list = Ebean.find(SomeNewTypesBean.class).where().eq("path", Paths.get("/tmp")).findList(); + assertTrue(!list.isEmpty()); + SomeNewTypesBean fetched = Ebean.find(SomeNewTypesBean.class, bean.getId()); assertEquals(bean.getZoneId(), fetched.getZoneId()); @@ -80,6 +85,7 @@ public void testInsertUpdate() throws IOException { assertEquals(bean.getLocalDateTime(), fetched.getLocalDateTime()); assertEquals(bean.getOffsetDateTime(), fetched.getOffsetDateTime()); assertEquals(bean.getInstant(), fetched.getInstant()); + assertEquals(bean.getPath(), fetched.getPath()); String asJson = Ebean.json().toJson(fetched); @@ -94,6 +100,7 @@ public void testInsertUpdate() throws IOException { assertEquals(bean.getLocalDateTime(), toBean.getLocalDateTime()); assertEquals(bean.getOffsetDateTime(), toBean.getOffsetDateTime()); assertEquals(bean.getInstant(), toBean.getInstant()); + assertEquals(bean.getPath(), toBean.getPath()); } @@ -115,6 +122,7 @@ public void testInsertNull() { assertNull(fetched.getLocalDateTime()); assertNull(fetched.getOffsetDateTime()); assertNull(fetched.getInstant()); + assertNull(fetched.getPath()); } }