package io.ebeaninternal.server.type; import io.ebean.annotation.DbArray; import io.ebean.annotation.DbEnumType; import io.ebean.annotation.DbEnumValue; import io.ebean.annotation.EnumValue; import io.ebean.config.JsonConfig; import io.ebean.Platform; import io.ebean.config.ScalarTypeConverter; import io.ebean.config.ServerConfig; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DbPlatformType; import io.ebean.dbmigration.DbOffline; import io.ebean.plugin.ExtraTypeFactory; import io.ebeaninternal.server.core.bootup.BootupClasses; import io.ebeanservice.docstore.api.mapping.DocPropertyType; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; import org.joda.time.LocalTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.persistence.AttributeConverter; import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; import java.net.URI; import java.net.URL; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.time.DayOfWeek; import java.time.Duration; import java.time.Instant; import java.time.Month; import java.time.MonthDay; import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.Year; import java.time.YearMonth; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Calendar; import java.util.Currency; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import java.util.TimeZone; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; /** * Default implementation of TypeManager. * <p> * Manages the list of ScalarType that is available. * </p> */ public final class DefaultTypeManager implements TypeManager { private static final Logger logger = LoggerFactory.getLogger(DefaultTypeManager.class); private final ConcurrentHashMap<Class<?>, ScalarType<?>> typeMap; private final ConcurrentHashMap<Integer, ScalarType<?>> nativeMap; private final DefaultTypeFactory extraTypeFactory; private final ScalarType<?> hstoreType = new ScalarTypePostgresHstore(); private final ScalarTypeFile fileType = new ScalarTypeFile(); private final ScalarType<?> charType = new ScalarTypeChar(); private final ScalarType<?> charArrayType = new ScalarTypeCharArray(); private final ScalarType<?> longVarcharType = new ScalarTypeLongVarchar(); private final ScalarType<?> clobType = new ScalarTypeClob(); private final ScalarType<?> byteType = new ScalarTypeByte(); private final ScalarType<?> binaryType = new ScalarTypeBytesBinary(); private final ScalarType<?> blobType = new ScalarTypeBytesBlob(); private final ScalarType<?> varbinaryType = new ScalarTypeBytesVarbinary(); private final ScalarType<?> longVarbinaryType = new ScalarTypeBytesLongVarbinary(); private final ScalarType<?> shortType = new ScalarTypeShort(); private final ScalarType<?> integerType = new ScalarTypeInteger(); private final ScalarType<?> longType = new ScalarTypeLong(); private final ScalarType<?> doubleType = new ScalarTypeDouble(); private final ScalarType<?> floatType = new ScalarTypeFloat(); private final ScalarType<?> bigDecimalType = new ScalarTypeBigDecimal(); private final ScalarType<?> timeType = new ScalarTypeTime(); private final ScalarType<?> dateType = new ScalarTypeDate(); private final ScalarType<?> inetAddressType = new ScalarTypeInetAddress(); private final ScalarType<?> urlType = new ScalarTypeURL(); private final ScalarType<?> uriType = new ScalarTypeURI(); private final ScalarType<?> localeType = new ScalarTypeLocale(); private final ScalarType<?> currencyType = new ScalarTypeCurrency(); private final ScalarType<?> timeZoneType = new ScalarTypeTimeZone(); private final ScalarType<?> stringType = new ScalarTypeString(); private final ScalarType<?> classType = new ScalarTypeClass(); private final JsonConfig.DateTime jsonDateTime; private final Object objectMapper; private final boolean java7Present; private final boolean postgres; private final boolean offlineMigrationGeneration; // OPTIONAL ScalarTypes registered if Jackson/JsonNode is in the classpath /** * Jackson's JsonNode storage to Clob. */ private ScalarType<?> jsonNodeClob; /** * Jackson's JsonNode storage to Blob. */ private ScalarType<?> jsonNodeBlob; /** * Jackson's JsonNode storage to Varchar. */ private ScalarType<?> jsonNodeVarchar; /** * Jackson's JsonNode storage to Postgres JSON or Clob. */ private ScalarType<?> jsonNodeJson; /** * Jackson's JsonNode storage to Postgres JSONB or Clob. */ private ScalarType<?> jsonNodeJsonb; private final PlatformArrayTypeFactory arrayTypeListFactory; private final PlatformArrayTypeFactory arrayTypeSetFactory; /** * Create the DefaultTypeManager. */ public DefaultTypeManager(ServerConfig config, BootupClasses bootupClasses) { this.java7Present = config.getClassLoadConfig().isJava7Present(); this.jsonDateTime = config.getJsonDateTime(); this.typeMap = new ConcurrentHashMap<>(); this.nativeMap = new ConcurrentHashMap<>(); boolean objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent(); this.objectMapper = (objectMapperPresent) ? initObjectMapper(config) : null; this.extraTypeFactory = new DefaultTypeFactory(config); this.postgres = isPostgres(config.getDatabasePlatform()); this.arrayTypeListFactory = arrayTypeListFactory(postgres, config.getDatabasePlatform()); this.arrayTypeSetFactory = arrayTypeSetFactory(postgres, config.getDatabasePlatform()); this.offlineMigrationGeneration = DbOffline.isGenerateMigration(); initialiseStandard(jsonDateTime, config); initialiseJavaTimeTypes(jsonDateTime, config); initialiseJodaTypes(jsonDateTime, config); initialiseJacksonTypes(config); loadTypesFromProviders(config, objectMapper); if (bootupClasses != null) { initialiseCustomScalarTypes(bootupClasses); initialiseScalarConverters(bootupClasses); initialiseAttributeConverters(bootupClasses); } } /** * Return the factory to use to support DB ARRAY types. */ private PlatformArrayTypeFactory arrayTypeListFactory(boolean postgres, DatabasePlatform databasePlatform) { if (postgres) { return ScalarTypeArrayList.factory(); } else if (databasePlatform.isPlatform(Platform.H2)) { return ScalarTypeArrayListH2.factory(); } // not supported for this DB platform return null; } /** * Return the factory to use to support DB ARRAY types. */ private PlatformArrayTypeFactory arrayTypeSetFactory(boolean postgres, DatabasePlatform databasePlatform) { if (postgres) { return ScalarTypeArraySet.factory(); } else if (databasePlatform.isPlatform(Platform.H2)) { return ScalarTypeArraySetH2.factory(); } // not supported for this DB platform return null; } /** * Load custom scalar types registered via ExtraTypeFactory and ServiceLoader. */ private void loadTypesFromProviders(ServerConfig config, Object objectMapper) { ServiceLoader<ExtraTypeFactory> factories = ServiceLoader.load(ExtraTypeFactory.class); Iterator<ExtraTypeFactory> iterator = factories.iterator(); if (iterator.hasNext()) { // use the cacheFactory (via classpath service loader) ExtraTypeFactory plugin = iterator.next(); List<? extends ScalarType> types = plugin.createTypes(config, objectMapper); for (ScalarType type : types) { logger.debug("adding ScalarType {}", type.getClass()); addCustomType(type); } } } private boolean isPostgres(DatabasePlatform databasePlatform) { return databasePlatform.getPlatform() == Platform.POSTGRES; } /** * Register a custom ScalarType. */ @Override public void add(ScalarType<?> scalarType) { typeMap.put(scalarType.getType(), scalarType); 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<? extends Enum> enumClass) { Set<Class<?>> mappedClasses = new HashSet<>(); 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 (logger.isDebugEnabled()) { String msg = "ScalarType register [" + scalarType.getClass().getName() + "]"; msg += " for [" + scalarType.getType().getName() + "]"; logger.debug(msg); } } /** * Return the ScalarType for the given jdbc type as per java.sql.Types. */ @Override public ScalarType<?> getScalarType(int jdbcType) { return nativeMap.get(jdbcType); } /** * This can return null if no matching ScalarType is found. */ @Override 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; } @Override public ScalarType<?> getHstoreScalarType() { return (postgres) ? hstoreType : ScalarTypeJsonMap.typeFor(false, Types.VARCHAR); } @Override public ScalarType<?> getArrayScalarType(Class<?> type, DbArray dbArray, Type genericType) { Type valueType = getValueType(genericType); if (type.equals(List.class)) { if (arrayTypeListFactory != null) { return arrayTypeListFactory.typeFor(valueType); } // fallback to JSON storage in VARCHAR column return new ScalarTypeJsonList.Varchar(getDocType(valueType)); } else if (type.equals(Set.class)) { if (arrayTypeSetFactory != null) { return arrayTypeSetFactory.typeFor(valueType); } // fallback to JSON storage in VARCHAR column return new ScalarTypeJsonSet.Varchar(getDocType(valueType)); } throw new IllegalStateException("Type [" + type + "] not supported for @DbArray"); } @Override public ScalarType<?> getJsonScalarType(Class<?> type, int dbType, int dbLength, Type genericType) { if (type.equals(List.class)) { DocPropertyType docType = getDocType(genericType); if (isValueTypeSimple(genericType)) { return ScalarTypeJsonList.typeFor(postgres, dbType, docType); } else { return createJsonObjectMapperType(type, genericType, dbType, docType); } } if (type.equals(Set.class)) { DocPropertyType docType = getDocType(genericType); if (isValueTypeSimple(genericType)) { return ScalarTypeJsonSet.typeFor(postgres, dbType, docType); } else { return createJsonObjectMapperType(type, genericType, dbType, docType); } } if (type.equals(Map.class)) { if (isMapValueTypeObject(genericType)) { return ScalarTypeJsonMap.typeFor(postgres, dbType); } else { return createJsonObjectMapperType(type, genericType, dbType, DocPropertyType.OBJECT); } } if (type.equals(JsonNode.class)) { switch (dbType) { case Types.VARCHAR: return jsonNodeVarchar; case Types.BLOB: return jsonNodeBlob; case Types.CLOB: return jsonNodeClob; case DbPlatformType.JSONB: return jsonNodeJsonb; case DbPlatformType.JSON: return jsonNodeJson; default: return jsonNodeJson; } } return createJsonObjectMapperType(type, type, dbType, DocPropertyType.OBJECT); } private DocPropertyType getDocType(Type genericType) { if (genericType instanceof Class<?>) { ScalarType<?> found = typeMap.get(genericType); if (found != null) { return found.getDocType(); } } return DocPropertyType.OBJECT; } /** * Return true if value parameter type of the map is Object. */ private boolean isValueTypeSimple(Type genericType) { Type[] typeArgs = ((ParameterizedType) genericType).getActualTypeArguments(); return String.class.equals(typeArgs[0]) || Long.class.equals(typeArgs[0]); } private Type getValueType(Type genericType) { Type[] typeArgs = ((ParameterizedType) genericType).getActualTypeArguments(); return typeArgs[0]; } /** * Return true if value parameter type of the map is Object. */ private boolean isMapValueTypeObject(Type genericType) { Type[] typeArgs = ((ParameterizedType) genericType).getActualTypeArguments(); return Object.class.equals(typeArgs[1]) || "?".equals(typeArgs[1].toString()); } private ScalarType<?> createJsonObjectMapperType(Class<?> type, Type genericType, int dbType, DocPropertyType docType) { if (objectMapper == null) { throw new IllegalArgumentException("Type [" + type + "] unsupported for @DbJson mapping - Jackson ObjectMapper not present"); } return ScalarTypeJsonObjectMapper.createTypeFor(postgres, type, (ObjectMapper) objectMapper, genericType, dbType, docType); } /** * Return a ScalarType for a given class. * <p> * Used for java.util.Date and java.util.Calendar which can be mapped to * different jdbcTypes in a single system. * </p> */ @Override public ScalarType<?> getScalarType(Class<?> type, int jdbcType) { // File is a special Lob so check for that first if (File.class.equals(type)) { return fileType; } // check for Clob, LongVarchar etc ... // the reason being that String maps to multiple jdbc types // varchar, clob, longVarchar. ScalarType<?> scalarType = getLobTypes(jdbcType); if (scalarType != null) { // it is a specific Lob type... return scalarType; } scalarType = typeMap.get(type); if (scalarType != null) { if (jdbcType == 0 || scalarType.getJdbcType() == jdbcType) { // matching type return scalarType; } } // a util Date with jdbcType not matching server wide settings if (type.equals(java.util.Date.class)) { return extraTypeFactory.createUtilDate(jsonDateTime, jdbcType); } // a Calendar with jdbcType not matching server wide settings if (type.equals(java.util.Calendar.class)) { return extraTypeFactory.createCalendar(jsonDateTime, jdbcType); } throw new IllegalArgumentException("Unmatched ScalarType for " + type + " jdbcType:" + jdbcType); } /** * Return the types for the known lob types. * <p> * Kind of special case because these map multiple jdbc types to single Java * types - like String - Varchar, LongVarchar, Clob. For this reason I check * for the specific Lob types first before looking for a matching type. * </p> */ private ScalarType<?> getLobTypes(int jdbcType) { return getScalarType(jdbcType); } /** * Convert the Object to the required datatype. The * * @param value the Object value * @param toJdbcType the type as per java.sql.Types. */ public Object convert(Object value, int toJdbcType) { if (value == null) { return null; } ScalarType<?> type = nativeMap.get(toJdbcType); if (type != null) { return type.toJdbcType(value); } return value; } boolean isIntegerType(String s) { if (isLeadingZeros(s)) { return false; } try { Integer.parseInt(s); return true; } catch (NumberFormatException e) { return false; } } /** * Treat leading zeros as a non-integer for enum values. */ private boolean isLeadingZeros(String s) { return s.length() > 1 && s.charAt(0) == '0'; } /** * Create the Mapping of Enum fields to DB values using EnumValue annotations. * <p> * Return null if the EnumValue annotations are not present/used. * </p> */ private ScalarType<?> createEnumScalarType2(Class<?> enumType) { boolean integerType = true; Map<String, String> nameValueMap = new HashMap<>(); Field[] fields = enumType.getDeclaredFields(); for (Field field : fields) { EnumValue enumValue = field.getAnnotation(EnumValue.class); if (enumValue != null) { nameValueMap.put(field.getName(), enumValue.value()); if (integerType && !isIntegerType(enumValue.value())) { // will treat the values as strings integerType = false; } } } if (nameValueMap.isEmpty()) { // Not using EnumValue here return null; } return createEnumScalarType(enumType, nameValueMap, integerType, 0); } /** * Create a ScalarType for an Enum that has additional mapping. * <p> * The reason for this is that often in a DB there will be short codes used * such as A,I,N rather than the ACTIVE, INACTIVE, NEW. So there really needs * to be a mapping from the nicely named enumeration values to the typically * much shorter codes used in the DB. * </p> */ @Override public ScalarType<?> createEnumScalarType(Class<? extends Enum<?>> enumType) { Method[] methods = enumType.getMethods(); for (Method method : methods) { DbEnumValue dbValue = method.getAnnotation(DbEnumValue.class); if (dbValue != null) { boolean integerValues = DbEnumType.INTEGER == dbValue.storage(); return createEnumScalarTypeDbValue(enumType, method, integerValues); } } // look for EnumValue annotations instead return createEnumScalarType2(enumType); } /** * Create the Mapping of Enum fields to DB values using EnumValue annotations. * <p> * Return null if the EnumValue annotations are not present/used. * </p> */ private ScalarType<?> createEnumScalarTypeDbValue(Class<? extends Enum<?>> enumType, Method method, boolean integerType) { Map<String, String> nameValueMap = new HashMap<>(); Enum<?>[] enumConstants = enumType.getEnumConstants(); for (Enum<?> enumConstant : enumConstants) { try { Object value = method.invoke(enumConstant); nameValueMap.put(enumConstant.name(), value.toString()); } catch (Exception e) { throw new IllegalArgumentException("Error trying to invoke DbEnumValue method on " + enumConstant, e); } } if (nameValueMap.isEmpty()) { // Not using EnumValue here return null; } return createEnumScalarType(enumType, nameValueMap, integerType, 0); } /** * Given the name value mapping and integer/string type and explicit DB column * length create the ScalarType for the Enum. */ @SuppressWarnings({"unchecked", "rawtypes"}) private ScalarType<?> createEnumScalarType(Class enumType, Map<String, String> nameValueMap, boolean integerType, int dbColumnLength) { EnumToDbValueMap<?> beanDbMap = EnumToDbValueMap.create(integerType); int maxValueLen = 0; for (Map.Entry<String, String> entry : nameValueMap.entrySet()) { String name = entry.getKey(); String value = entry.getValue(); maxValueLen = Math.max(maxValueLen, value.length()); Object enumValue = Enum.valueOf(enumType, name.trim()); beanDbMap.add(enumValue, value, name.trim()); } if (dbColumnLength == 0 && !integerType) { dbColumnLength = maxValueLen; } return new ScalarTypeEnumWithMapping(beanDbMap, enumType, dbColumnLength); } /** * Automatically find any ScalarTypes by searching through the class path. * <p> * In avaje.properties define a list of packages in which ScalarTypes are * found. This will search for any class that implements the ScalarType * interface and register it with this TypeManager. * </p> */ private void initialiseCustomScalarTypes(BootupClasses bootupClasses) { for (Class<? extends ScalarType<?>> cls : bootupClasses.getScalarTypes()) { try { ScalarType<?> scalarType; if (objectMapper == null) { scalarType = cls.newInstance(); } else { try { // first try objectMapper constructor Constructor<? extends ScalarType<?>> constructor = cls.getConstructor(ObjectMapper.class); scalarType = constructor.newInstance((ObjectMapper) objectMapper); } catch (NoSuchMethodException e) { scalarType = cls.newInstance(); } } addCustomType(scalarType); } catch (Exception e) { String msg = "Error loading ScalarType [" + cls.getName() + "]"; logger.error(msg, e); } } } private void addCustomType(ScalarType<?> scalarType) { add(scalarType); } private Object initObjectMapper(ServerConfig serverConfig) { Object objectMapper = serverConfig.getObjectMapper(); if (objectMapper == null) { objectMapper = new ObjectMapper(); serverConfig.setObjectMapper(objectMapper); } return objectMapper; } @SuppressWarnings({"unchecked", "rawtypes"}) private void initialiseScalarConverters(BootupClasses bootupClasses) { List<Class<? extends ScalarTypeConverter<?, ?>>> foundTypes = bootupClasses.getScalarConverters(); for (Class<? extends ScalarTypeConverter<?, ?>> foundType : foundTypes) { try { Class<?>[] paramTypes = TypeReflectHelper.getParams(foundType, ScalarTypeConverter.class); if (paramTypes.length != 2) { throw new IllegalStateException("Expected 2 generics paramtypes but got: " + Arrays.toString(paramTypes)); } Class<?> logicalType = paramTypes[0]; Class<?> persistType = paramTypes[1]; ScalarType<?> wrappedType = getScalarType(persistType); if (wrappedType == null) { throw new IllegalStateException("Could not find ScalarType for: " + paramTypes[1]); } ScalarTypeConverter converter = foundType.newInstance(); ScalarTypeWrapper stw = new ScalarTypeWrapper(logicalType, wrappedType, converter); logger.debug("Register ScalarTypeWrapper from " + logicalType + " -> " + persistType + " using:" + foundType); add(stw); } catch (Exception e) { logger.error("Error registering ScalarTypeConverter [" + foundType.getName() + "]", e); } } } @SuppressWarnings({"unchecked", "rawtypes"}) private void initialiseAttributeConverters(BootupClasses bootupClasses) { List<Class<? extends AttributeConverter<?, ?>>> foundTypes = bootupClasses.getAttributeConverters(); for (Class<? extends AttributeConverter<?, ?>> foundType : foundTypes) { try { Class<?>[] paramTypes = TypeReflectHelper.getParams(foundType, AttributeConverter.class); if (paramTypes.length != 2) { throw new IllegalStateException("Expected 2 generics paramtypes but got: " + Arrays.toString(paramTypes)); } Class<?> logicalType = paramTypes[0]; Class<?> persistType = paramTypes[1]; ScalarType<?> wrappedType = getScalarType(persistType); if (wrappedType == null) { throw new IllegalStateException("Could not find ScalarType for: " + paramTypes[1]); } AttributeConverter converter = foundType.newInstance(); ScalarTypeWrapper stw = new ScalarTypeWrapper(logicalType, wrappedType, new AttributeConverterAdapter(converter)); logger.debug("Register ScalarTypeWrapper from " + logicalType + " -> " + persistType + " using:" + foundType); add(stw); } catch (Exception e) { logger.error("Error registering AttributeConverter [" + foundType.getName() + "]", e); } } } /** * Add support for Jackson's JsonNode mapping to Clob, Blob, Varchar, JSON and JSONB. */ private void initialiseJacksonTypes(ServerConfig config) { if (objectMapper != null) { logger.trace("Registering JsonNode type support"); ObjectMapper mapper = (ObjectMapper) objectMapper; jsonNodeClob = new ScalarTypeJsonNode.Clob(mapper); jsonNodeBlob = new ScalarTypeJsonNode.Blob(mapper); jsonNodeVarchar = new ScalarTypeJsonNode.Varchar(mapper); jsonNodeJson = jsonNodeClob; // Default for non-Postgres databases jsonNodeJsonb = jsonNodeClob; // Default for non-Postgres databases if (isPostgres(config.getDatabasePlatform())) { jsonNodeJson = new ScalarTypeJsonNodePostgres.JSON(mapper); jsonNodeJsonb = new ScalarTypeJsonNodePostgres.JSONB(mapper); } // add as default mapping for JsonNode (when not annotated with @DbJson etc) typeMap.put(JsonNode.class, jsonNodeJson); } } private 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()); typeMap.put(java.time.LocalDateTime.class, new ScalarTypeLocalDateTime(mode)); typeMap.put(OffsetDateTime.class, new ScalarTypeOffsetDateTime(mode)); typeMap.put(ZonedDateTime.class, new ScalarTypeZonedDateTime(mode)); typeMap.put(Instant.class, new ScalarTypeInstant(mode)); typeMap.put(DayOfWeek.class, new ScalarTypeDayOfWeek()); typeMap.put(Month.class, new ScalarTypeMonth()); typeMap.put(Year.class, new ScalarTypeYear()); typeMap.put(YearMonth.class, new ScalarTypeYearMonthDate()); typeMap.put(MonthDay.class, new ScalarTypeMonthDay()); typeMap.put(OffsetTime.class, new ScalarTypeOffsetTime()); typeMap.put(ZoneId.class, new ScalarTypeZoneId()); typeMap.put(ZoneOffset.class, new ScalarTypeZoneOffset()); boolean localTimeNanos = config.isLocalTimeWithNanos(); typeMap.put(java.time.LocalTime.class, (localTimeNanos) ? new ScalarTypeLocalTimeWithNanos() : new ScalarTypeLocalTime()); boolean durationNanos = config.isDurationWithNanos(); typeMap.put(Duration.class, (durationNanos) ? new ScalarTypeDurationWithNanos() : new ScalarTypeDuration()); } } /** * Detect if Joda classes are in the classpath and if so register the Joda data types. */ @SuppressWarnings("deprecation") private void initialiseJodaTypes(JsonConfig.DateTime mode, ServerConfig config) { // detect if Joda classes are in the classpath if (config.getClassLoadConfig().isJodaTimePresent()) { // Joda classes are in the classpath so register the types logger.debug("Registering Joda data types"); typeMap.put(LocalDateTime.class, new ScalarTypeJodaLocalDateTime(mode)); typeMap.put(DateTime.class, new ScalarTypeJodaDateTime(mode)); typeMap.put(LocalDate.class, new ScalarTypeJodaLocalDate()); typeMap.put(org.joda.time.DateMidnight.class, new ScalarTypeJodaDateMidnight()); String jodaLocalTimeMode = config.getJodaLocalTimeMode(); if ("normal".equalsIgnoreCase(jodaLocalTimeMode)) { // use the expected/normal local time zone typeMap.put(LocalTime.class, new ScalarTypeJodaLocalTime()); logger.debug("registered ScalarTypeJodaLocalTime"); } else if ("utc".equalsIgnoreCase(jodaLocalTimeMode)) { // use the old UTC based typeMap.put(LocalTime.class, new ScalarTypeJodaLocalTimeUTC()); logger.debug("registered ScalarTypeJodaLocalTimeUTC"); } } } /** * Register all the standard types supported. This is the standard JDBC types * plus some other common types such as java.util.Date and java.util.Calendar. */ private void initialiseStandard(JsonConfig.DateTime mode, ServerConfig config) { DatabasePlatform databasePlatform = config.getDatabasePlatform(); int platformClobType = databasePlatform.getClobDbType(); int platformBlobType = databasePlatform.getBlobDbType(); nativeMap.put(DbPlatformType.HSTORE, hstoreType); ScalarType<?> utilDateType = extraTypeFactory.createUtilDate(mode); typeMap.put(java.util.Date.class, utilDateType); ScalarType<?> calType = extraTypeFactory.createCalendar(mode); typeMap.put(Calendar.class, calType); ScalarType<?> mathBigIntType = extraTypeFactory.createMathBigInteger(); typeMap.put(BigInteger.class, mathBigIntType); ScalarTypeBool booleanType = extraTypeFactory.createBoolean(); typeMap.put(Boolean.class, booleanType); typeMap.put(boolean.class, booleanType); // register the boolean literals to the platform for DDL default values databasePlatform.setDbTrueLiteral(booleanType.getDbTrueLiteral()); databasePlatform.setDbFalseLiteral(booleanType.getDbFalseLiteral()); // always register Types.BOOLEAN to our boolean type nativeMap.put(Types.BOOLEAN, booleanType); if (booleanType.getJdbcType() == Types.BIT) { // for MapBeans ... BIT types are assumed to be booleans nativeMap.put(Types.BIT, booleanType); } ServerConfig.DbUuid dbUuid = config.getDbTypeConfig().getDbUuid(); if (offlineMigrationGeneration || (databasePlatform.isNativeUuidType() && dbUuid.useNativeType())) { typeMap.put(UUID.class, new ScalarTypeUUIDNative()); } else { // Store UUID as binary(16) or varchar(40) ScalarType<?> uuidType = dbUuid.useBinary() ? 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); typeMap.put(TimeZone.class, timeZoneType); typeMap.put(URL.class, urlType); typeMap.put(URI.class, uriType); // String types typeMap.put(char[].class, charArrayType); typeMap.put(char.class, charType); typeMap.put(String.class, stringType); nativeMap.put(Types.VARCHAR, stringType); nativeMap.put(Types.CHAR, stringType); nativeMap.put(Types.LONGVARCHAR, longVarcharType); // Class<?> typeMap.put(Class.class, classType); if (platformClobType == Types.CLOB) { nativeMap.put(Types.CLOB, clobType); } else { // for Postgres Clobs handled by Varchar ScalarType... ScalarType<?> platClobScalarType = nativeMap.get(platformClobType); if (platClobScalarType == null) { throw new IllegalArgumentException("Type for dbPlatform clobType [" + clobType + "] not found."); } nativeMap.put(Types.CLOB, platClobScalarType); } // Binary type typeMap.put(byte[].class, varbinaryType); nativeMap.put(Types.BINARY, binaryType); nativeMap.put(Types.VARBINARY, varbinaryType); nativeMap.put(Types.LONGVARBINARY, longVarbinaryType); if (platformBlobType == Types.BLOB) { nativeMap.put(Types.BLOB, blobType); } else { // for Postgres Blobs handled by LongVarbinary ScalarType... ScalarType<?> platBlobScalarType = nativeMap.get(platformBlobType); if (platBlobScalarType == null) { throw new IllegalArgumentException("Type for dbPlatform blobType [" + blobType + "] not found."); } nativeMap.put(Types.BLOB, platBlobScalarType); } // Number types typeMap.put(Byte.class, byteType); typeMap.put(byte.class, byteType); nativeMap.put(Types.TINYINT, byteType); typeMap.put(Short.class, shortType); typeMap.put(short.class, shortType); nativeMap.put(Types.SMALLINT, shortType); typeMap.put(Integer.class, integerType); typeMap.put(int.class, integerType); nativeMap.put(Types.INTEGER, integerType); typeMap.put(Long.class, longType); typeMap.put(long.class, longType); nativeMap.put(Types.BIGINT, longType); typeMap.put(Double.class, doubleType); typeMap.put(double.class, doubleType); nativeMap.put(Types.FLOAT, doubleType);// no this is not a bug nativeMap.put(Types.DOUBLE, doubleType); typeMap.put(Float.class, floatType); typeMap.put(float.class, floatType); nativeMap.put(Types.REAL, floatType);// no this is not a bug typeMap.put(BigDecimal.class, bigDecimalType); nativeMap.put(Types.DECIMAL, bigDecimalType); nativeMap.put(Types.NUMERIC, bigDecimalType); // Temporal types typeMap.put(Time.class, timeType); nativeMap.put(Types.TIME, timeType); typeMap.put(Date.class, dateType); nativeMap.put(Types.DATE, dateType); ScalarType<?> timestampType = new ScalarTypeTimestamp(mode); typeMap.put(Timestamp.class, timestampType); nativeMap.put(Types.TIMESTAMP, timestampType); } }