org.babyfish.jimmer.sql.ScalarProviderManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jimmer-sql Show documentation
Show all versions of jimmer-sql Show documentation
A revolutionary ORM framework for both java and kotlin
package org.babyfish.jimmer.sql;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.ArrayType;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.SimpleType;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.kotlin.KotlinModule;
import org.babyfish.jimmer.impl.util.PropCache;
import org.babyfish.jimmer.impl.util.ClassCache;
import org.babyfish.jimmer.jackson.ImmutableModule;
import org.babyfish.jimmer.meta.ImmutableProp;
import org.babyfish.jimmer.meta.ImmutableType;
import org.babyfish.jimmer.meta.ModelException;
import org.babyfish.jimmer.sql.dialect.Dialect;
import org.babyfish.jimmer.sql.meta.ScalarTypeStrategy;
import org.babyfish.jimmer.sql.runtime.AbstractScalarProvider;
import org.babyfish.jimmer.sql.runtime.DbLiteral;
import org.babyfish.jimmer.sql.runtime.ScalarProvider;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.Function;
class ScalarProviderManager implements ScalarTypeStrategy {
private static final Set> GENERIC_TYPES;
private static final ObjectMapper DEFAULT_OBJECT_MAPPER;
private final ClassCache> typeScalarProviderCache =
new ClassCache<>(this::createProvider, true);
private final PropCache> propScalarProviderCache =
new PropCache<>(this::createProvider, true);
private final Map, ScalarProvider, ?>> customizedTypeScalarProviderMap;
private final Map> customizedPropScalarProviderMap;
private final Map, ObjectMapper> serializedTypeObjectMapperMap;
private final Map serializedPropObjectMapperMap;
private final Function> defaultJsonProviderCreator;
private final EnumType.Strategy defaultEnumStrategy;
private final Dialect dialect;
ScalarProviderManager(
Map, ScalarProvider, ?>> customizedTypeScalarProviderMap,
Map> customizedPropScalarProviderMap,
Map, ObjectMapper> serializedTypeObjectMapperMap,
Map serializedPropObjectMapperMap,
Function> defaultJsonProviderCreator,
EnumType.Strategy defaultEnumStrategy,
Dialect dialect
) {
this.customizedTypeScalarProviderMap = new HashMap<>(customizedTypeScalarProviderMap);
this.customizedPropScalarProviderMap = new HashMap<>(customizedPropScalarProviderMap);
this.serializedTypeObjectMapperMap = new HashMap<>(serializedTypeObjectMapperMap);
this.serializedPropObjectMapperMap = new HashMap<>(serializedPropObjectMapperMap);
this.defaultJsonProviderCreator = defaultJsonProviderCreator;
this.defaultEnumStrategy = defaultEnumStrategy;
this.dialect = dialect;
}
@Override
public Class> getOverriddenSqlType(ImmutableProp prop) {
ScalarProvider, ?> provider = getProvider(prop);
if (provider == null) {
return null;
}
return provider.getSqlType();
}
public ScalarProvider, ?> getProvider(ImmutableProp prop) {
return propScalarProviderCache.get(prop.toOriginal());
}
public ScalarProvider, ?> getProvider(Class> type) {
return typeScalarProviderCache.get(type);
}
private ScalarProvider, ?> createProvider(ImmutableProp prop) {
ScalarProvider, ?> provider = customizedPropScalarProvider(prop);
if (provider != null) {
return provider;
}
if (prop.getReturnClass() == UUID.class) {
Column column = prop.getAnnotation(Column.class);
if (column != null && !column.sqlType().isEmpty()) {
switch (column.sqlType().toLowerCase()) {
case "char":
case "nchar":
case "varchar":
case "nvarchar":
case "varchar2":
case "nvarchar2":
case "text":
return ScalarProvider.uuidByString();
case "binary":
case "varbinary":
case "bytea":
case "byte[]":
return ScalarProvider.uuidByByteArray();
}
}
}
Serialized serialized = prop.getAnnotation(Serialized.class);
if (serialized == null) {
return typeScalarProviderCache.get(prop.getReturnClass());
}
if (defaultJsonProviderCreator != null) {
return defaultJsonProviderCreator.apply(prop);
}
return createJsonProvider(
prop.getReturnClass(),
jacksonType(prop.getGenericType()),
serializedPropObjectMapper(prop)
);
}
@SuppressWarnings({"unchecked", "rawtypes"})
private ScalarProvider, ?> createProvider(Class> type) {
if (DbLiteral.class.isAssignableFrom(type)) {
return null;
}
ScalarProvider, ?> provider = customizedTypeScalarProviderMap.get(type);
if (provider != null) {
return provider;
}
EnumType enumType = type.getAnnotation(EnumType.class);
Serialized serialized = type.getAnnotation(Serialized.class);
if (enumType != null && serialized != null) {
throw new ModelException(
"Illegal type \"" +
type +
"\", it cannot be decorated by both @" +
EnumType.class.getName() +
" and @" +
Serialized.class.getName()
);
}
if (enumType != null && !type.isEnum()) {
throw new ModelException(
"Illegal type \"" +
type +
"\", it cannot be decorated by @EnumType because it is not enum"
);
}
if (enumType != null && enumType.value() == EnumType.Strategy.ORDINAL) {
return newEnumByIntProvider((Class)type);
}
if (enumType != null && enumType.value() == EnumType.Strategy.NAME) {
return newEnumByStringProvider((Class)type);
}
if (type.isEnum()) {
if (defaultEnumStrategy == EnumType.Strategy.ORDINAL) {
return newEnumByIntProvider((Class)type);
}
return newEnumByStringProvider((Class)type);
}
if (serialized != null) {
return createJsonProvider(
type,
SimpleType.constructUnsafe(type),
serializedTypeObjectMapper(type)
);
}
return null;
}
private > ScalarProvider newEnumByStringProvider(Class enumType) {
return ScalarProvider.enumProviderByString(enumType, it -> {
for (E enumValue: enumType.getEnumConstants()) {
Field enumField;
try {
enumField = enumType.getField(enumValue.name());
} catch (NoSuchFieldException ex) {
throw new AssertionError("Internal bug", ex);
}
EnumItem enumItem = enumField.getAnnotation(EnumItem.class);
if (enumItem == null) {
break;
}
if (enumItem.ordinal() != -1) {
throw new ModelException(
"Illegal enum type \"" +
enumType.getName() +
"\", it is mapped by name, not ordinal, " +
"but ordinal of the @EnumItem of \"" +
enumField.getName() +
"\" is configured"
);
}
if (!enumItem.name().isEmpty()) {
it.map(enumValue, enumItem.name());
}
}
});
}
private > ScalarProvider, ?> newEnumByIntProvider(Class enumType) {
return ScalarProvider.enumProviderByInt(enumType, it -> {
for (E enumValue: enumType.getEnumConstants()) {
Field enumField;
try {
enumField = enumType.getField(enumValue.name());
} catch (NoSuchFieldException ex) {
throw new AssertionError("Internal bug", ex);
}
EnumItem enumItem = enumField.getAnnotation(EnumItem.class);
if (enumItem == null) {
break;
}
if (!enumItem.name().isEmpty()) {
throw new ModelException(
"Illegal enum type \"" +
enumType.getName() +
"\", it is mapped by ordinal, not name, " +
"but name of the @EnumItem of \"" +
enumField.getName() +
"\" is configured"
);
}
if (enumItem.ordinal() != -1) {
it.map(enumValue, enumItem.ordinal());
}
}
});
}
@SuppressWarnings("unchecked")
private ScalarProvider, ?> createJsonProvider(Class> type, JavaType javaType, ObjectMapper objectMapper) {
return new AbstractScalarProvider