diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DScriptRunner.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DScriptRunner.java index 394f1faea..fb2fbd0a2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DScriptRunner.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DScriptRunner.java @@ -67,7 +67,7 @@ final class DScriptRunner implements ScriptRunner { } try (InputStream inputStream = UrlHelper.openNoCache(resource); - Reader reader = IOUtils.newReader(inputStream)) { + Reader reader = IOUtils.newReader(inputStream)) { return readContent(reader); } catch (IOException e) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 8c3438d4b..a673e1596 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -200,7 +200,7 @@ public final class DefaultTypeManager implements TypeManager { List> types = plugin.createTypes(config, objectMapper); for (ScalarType type : types) { log.debug("adding ScalarType {}", type.getClass()); - addCustomType(type); + add(type); } } } @@ -218,25 +218,6 @@ public final class DefaultTypeManager implements TypeManager { logAdd(scalarType); } - /** - * Register the ScalarType for an enum. This is special in the sense that an Enum - * can have many classes if it uses method overrides and we need to register all - * the variations/classes for the enum. - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - @Override - public void addEnumType(ScalarType scalarType, Class enumClass) { - Set> mappedClasses = new HashSet<>(); - mappedClasses.add(enumClass); - for (Object value : EnumSet.allOf(enumClass).toArray()) { - mappedClasses.add(value.getClass()); - } - for (Class cls : mappedClasses) { - typeMap.put(cls, scalarType); - } - logAdd(scalarType); - } - private void logAdd(ScalarType scalarType) { if (log.isTraceEnabled()) { String msg = "ScalarType register [" + scalarType.getClass().getName() + "]"; @@ -282,19 +263,44 @@ public final class DefaultTypeManager implements TypeManager { "ScalarType of Joda LocalTime not defined. You need to set DatabaseConfig.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); + found = checkInheritedTypes(type); } - return found; + return found != ScalarTypeNotFound.INSTANCE ? found : null; // Do not return ScalarTypeNotFound, otherwise checks will fail } - private ScalarType checkInterfaceTypes(Class type) { - for (Entry, ScalarType> entry : typeMap.entrySet()) { - if (entry.getKey().isAssignableFrom(type)) { - typeMap.put(type, entry.getValue()); - return entry.getValue(); + + /** + * Checks the typeMap for inherited types. + * + * If e.g. type is a GregorianCalendar, then this method + * will check the class hierarchy and will probably return a + * ScalarTypeCalendar To speed up a second lookup, it will write + * back the found scalarType to typeMap. + * + * @param type the for which to search for a ScalarType + * @return either a valid ScalarType if one could be found or {@link ScalarTypeNotFound#INSTANCE} if not + */ + private ScalarType checkInheritedTypes(Class type) { + // first step loop through inheritance chain + Class parent = type; + while (parent != null && parent != Object.class) { + ScalarType found = typeMap.get(parent); + if (found != null && found != ScalarTypeNotFound.INSTANCE) { + typeMap.put(type, found); // store type for next lookup + return found; } + // second step - loop through interfaces of this type + for (Class iface: parent.getInterfaces()) { + found = checkInheritedTypes(iface); + if (found != null && found != ScalarTypeNotFound.INSTANCE) { + typeMap.put(type, found); // store type for next lookup + return found; + } + } + parent = parent.getSuperclass(); } - return null; + typeMap.put(type, ScalarTypeNotFound.INSTANCE); + return ScalarTypeNotFound.INSTANCE; // no success } @Override @@ -399,7 +405,7 @@ public final class DefaultTypeManager implements TypeManager { private DocPropertyType getDocType(Type genericType) { if (genericType instanceof Class) { - ScalarType found = typeMap.get(genericType); + ScalarType found = getScalarType((Class)genericType); if (found != null) { return found.getDocType(); } @@ -457,7 +463,7 @@ public final class DefaultTypeManager implements TypeManager { return scalarType; } - scalarType = typeMap.get(type); + scalarType = getScalarType(type); if (scalarType != null) { if (jdbcType == 0 || scalarType.getJdbcType() == jdbcType) { // matching type @@ -575,7 +581,7 @@ public final class DefaultTypeManager implements TypeManager { // use JPA normal Enum type (without mapping) scalarEnum = createEnumScalarTypePerSpec(enumType, type); } - addEnumType(scalarEnum, enumType); + add(scalarEnum); return scalarEnum; } @@ -665,16 +671,13 @@ public final class DefaultTypeManager implements TypeManager { scalarType = cls.getDeclaredConstructor().newInstance(); } } - addCustomType(scalarType); + add(scalarType); } catch (Exception e) { log.error("Error loading ScalarType [" + cls.getName() + "]", e); } } } - private void addCustomType(ScalarType scalarType) { - add(scalarType); - } private Object initObjectMapper(DatabaseConfig config) { Object objectMapper = config.getObjectMapper(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeNotFound.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeNotFound.java new file mode 100644 index 000000000..7fbb5b5e9 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeNotFound.java @@ -0,0 +1,140 @@ +package io.ebeaninternal.server.type; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.sql.SQLException; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; + +import io.ebean.core.type.DataBinder; +import io.ebean.core.type.DataReader; +import io.ebean.core.type.DocPropertyType; +import io.ebean.core.type.ScalarType; + +/** + * Class is required as "null" key in ConcurrentHashMap. + * @author Roland Praml, FOCONIS AG + * + */ +class ScalarTypeNotFound implements ScalarType { + + public static final ScalarTypeNotFound INSTANCE = new ScalarTypeNotFound(); + private ScalarTypeNotFound() { } + @Override + public boolean isBinaryType() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isMutable() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDirty(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public int getLength() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isJdbcNative() { + throw new UnsupportedOperationException(); + } + + @Override + public int getJdbcType() { + throw new UnsupportedOperationException(); + } + + @Override + public Class getType() { + throw new UnsupportedOperationException(); + } + + @Override + public Void read(DataReader reader) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public void loadIgnore(DataReader reader) { + throw new UnsupportedOperationException(); + } + + @Override + public void bind(DataBinder binder, Void value) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public Object toJdbcType(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Void toBeanType(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public String formatValue(Void value) { + throw new UnsupportedOperationException(); + } + + @Override + public String format(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Void parse(String value) { + throw new UnsupportedOperationException(); + } + + @Override + public DocPropertyType getDocType() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDateTimeCapable() { + throw new UnsupportedOperationException(); + } + + @Override + public long asVersion(Void value) { + throw new UnsupportedOperationException(); + } + + @Override + public Void convertFromMillis(long dateTime) { + throw new UnsupportedOperationException(); + } + + @Override + public Void readData(DataInput dataInput) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void writeData(DataOutput dataOutput, Void v) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public Void jsonRead(JsonParser parser) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void jsonWrite(JsonGenerator writer, Void value) throws IOException { + throw new UnsupportedOperationException(); + } + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/TypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/TypeManager.java index 692e65681..079782972 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/TypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/TypeManager.java @@ -17,12 +17,6 @@ public interface TypeManager { */ void add(ScalarType scalarType); - /** - * Register a ScalarType for an Enum with can have multiple classes. - */ - @SuppressWarnings("rawtypes") - void addEnumType(ScalarType type, Class myEnumClass); - /** * Return the scalar type for the given logical type. */ diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/MChecksumTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/MChecksumTest.java index 65756f58f..035b90583 100644 --- a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/MChecksumTest.java +++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/MChecksumTest.java @@ -7,7 +7,7 @@ import java.io.File; import static org.assertj.core.api.Assertions.assertThat; public class MChecksumTest { - + @Test public void calculate() { File file = new File("src/test/resources/dbmigration/index-special-chars/1.0__hello.sql"); @@ -15,4 +15,12 @@ public class MChecksumTest { assertThat(MChecksum.calculate(file)).isEqualTo(1859426839); } + + @Test + public void calculateWithSpecialChars() { + File file = new File("src/test/resources/dbmigration/index-special-chars/1.0__hello.sql"); + assertThat(file).exists(); + + assertThat(MChecksum.calculate(file)).isEqualTo(1859426839); + } } diff --git a/ebean-test/src/test/java/io/ebean/json/EJsonTests.java b/ebean-test/src/test/java/io/ebean/json/EJsonTests.java index 6be980a40..1063e4d36 100644 --- a/ebean-test/src/test/java/io/ebean/json/EJsonTests.java +++ b/ebean-test/src/test/java/io/ebean/json/EJsonTests.java @@ -45,7 +45,6 @@ public class EJsonTests { File temp = Files.createTempFile("some", ".json").toFile(); try (Writer writer = IOUtils.newWriter(temp)) { - Map map = new LinkedHashMap<>(); map.put("foo", "bar"); EJson.write(map, writer); diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/type/TestTypeManager.java b/ebean-test/src/test/java/io/ebeaninternal/server/type/TestTypeManager.java index c28da92a7..383671363 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/server/type/TestTypeManager.java +++ b/ebean-test/src/test/java/io/ebeaninternal/server/type/TestTypeManager.java @@ -15,6 +15,8 @@ import org.tests.model.ivo.converter.MoneyTypeConverter; import javax.persistence.EnumType; import java.sql.SQLException; import java.sql.Types; +import java.util.Calendar; +import java.util.GregorianCalendar; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @@ -29,7 +31,6 @@ public class TestTypeManager extends BaseTestCase { DefaultTypeManager typeManager = createTypeManager(); ScalarType type = typeManager.createEnumScalarType(MyEnum.class, null); - typeManager.addEnumType(type, MyEnum.class); Object val = type.read(new DummyDataReader("A")); assertThat(val).isEqualTo(MyEnum.Aval); @@ -131,6 +132,13 @@ public class TestTypeManager extends BaseTestCase { return new DefaultTypeManager(config, bootupClasses); } + @Test + public void testCalendar() throws SQLException { + + DefaultTypeManager typeManager = createTypeManager(); + ScalarType typeB = typeManager.getScalarType(GregorianCalendar.class); + assertThat(typeB).isInstanceOf(ScalarTypeCalendar.class); + } /** * Test double DataReader implementation. */ diff --git a/ebean-test/src/test/java/org/tests/model/types/SomeNewTypesBean.java b/ebean-test/src/test/java/org/tests/model/types/SomeNewTypesBean.java index 2596dbcba..1a0fee0bf 100644 --- a/ebean-test/src/test/java/org/tests/model/types/SomeNewTypesBean.java +++ b/ebean-test/src/test/java/org/tests/model/types/SomeNewTypesBean.java @@ -7,6 +7,7 @@ import javax.persistence.Version; import java.nio.file.Path; import java.sql.Date; import java.time.*; +import java.util.Calendar; @Entity public class SomeNewTypesBean { @@ -58,6 +59,8 @@ public class SomeNewTypesBean { Duration duration; + Calendar calendar; + public Long getId() { return id; } @@ -217,4 +220,12 @@ public class SomeNewTypesBean { public void setDuration(Duration duration) { this.duration = duration; } + + public Calendar getCalendar() { + return calendar; + } + + public void setCalendar(final Calendar calendar) { + this.calendar = calendar; + } } diff --git a/ebean-test/src/test/java/org/tests/types/TestNewTypes.java b/ebean-test/src/test/java/org/tests/types/TestNewTypes.java index 35732801e..90ffd842a 100644 --- a/ebean-test/src/test/java/org/tests/types/TestNewTypes.java +++ b/ebean-test/src/test/java/org/tests/types/TestNewTypes.java @@ -10,6 +10,7 @@ import org.tests.model.types.SomeNewTypesBean; import java.io.File; import java.nio.file.Paths; import java.time.*; +import java.util.Calendar; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -44,6 +45,7 @@ public class TestNewTypes extends BaseTestCase { bean.setPath(Paths.get(TEMP_PATH)); bean.setPeriod(Period.of(4,3,2)); bean.setDuration(Duration.ofMinutes(5)); + bean.setCalendar(Calendar.getInstance()); DB.save(bean); @@ -104,6 +106,10 @@ public class TestNewTypes extends BaseTestCase { list = DB.find(SomeNewTypesBean.class).where().eq("duration", Duration.ofMinutes(5)).findList(); assertThat(list).isNotEmpty(); + // Calendar.getInstance() returns an implementation, which then has to be remapped to ScalarTypeCalendar + list = DB.find(SomeNewTypesBean.class).where().le("calendar", Calendar.getInstance()).findList(); + assertThat(list).isNotEmpty(); + SomeNewTypesBean fetched = DB.find(SomeNewTypesBean.class, bean.getId()); assertEquals(bean.getZoneId(), fetched.getZoneId()); @@ -122,6 +128,7 @@ public class TestNewTypes extends BaseTestCase { assertEquals(bean.getPath(), fetched.getPath()); assertEquals(bean.getPeriod(), fetched.getPeriod()); assertEquals(bean.getDuration(), fetched.getDuration()); + assertEquals(bean.getCalendar(), fetched.getCalendar()); String asJson = DB.json().toJson(fetched); @@ -139,10 +146,10 @@ public class TestNewTypes extends BaseTestCase { assertThat(toBean.getOffsetDateTime()).isEqualToIgnoringNanos(bean.getOffsetDateTime()); assertEquals(bean.getLocalTime().toSecondOfDay(), toBean.getLocalTime().toSecondOfDay()); assertEquals(bean.getInstant().toEpochMilli() / 1000, toBean.getInstant().toEpochMilli() / 1000); - // FIXME: This test fails on Windows with: expected:<\tmp> but was: assertEquals(bean.getPath(), toBean.getPath()); assertEquals(bean.getPeriod(), toBean.getPeriod()); assertEquals(bean.getDuration(), toBean.getDuration()); + assertEquals(bean.getCalendar(), toBean.getCalendar()); }