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 42c9e2ded..941d5745c 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 @@ -43,7 +43,6 @@ import java.sql.Timestamp; import java.sql.Types; import java.time.*; import java.util.*; -import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; /** @@ -194,7 +193,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); } } } @@ -212,25 +211,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() + "]"; @@ -270,25 +250,50 @@ public final class DefaultTypeManager implements TypeManager { @Override public ScalarType getScalarType(Class type) { ScalarType found = typeMap.get(type); - if (found == null) { + if (found == null || found == ScalarTypeNotFound.INSTANCE) { + found = null; if (type.getName().equals("org.joda.time.LocalTime")) { throw new IllegalStateException( "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; } - 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 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 + * @return + */ + 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; + } + parent = parent.getSuperclass(); + } + // second step - loop through interface chain + for (Class iface: type.getInterfaces()) { + ScalarType found = checkInheritedTypes(iface); + if (found != null && found != ScalarTypeNotFound.INSTANCE) { + typeMap.put(type, found); // store type for next lookup + return found; } } - return null; + typeMap.put(type, ScalarTypeNotFound.INSTANCE); + return null; // no success } @Override @@ -394,8 +399,8 @@ public final class DefaultTypeManager implements TypeManager { private DocPropertyType getDocType(Type genericType) { if (genericType instanceof Class) { - ScalarType found = typeMap.get(genericType); - if (found != null) { + ScalarType found = getScalarType((Class)genericType); + if (found != null && found != ScalarTypeNotFound.INSTANCE) { return found.getDocType(); } } @@ -452,7 +457,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 @@ -570,7 +575,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; } @@ -660,16 +665,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. */