diff --git a/core/src/main/java/dev/morphia/DatastoreImpl.java b/core/src/main/java/dev/morphia/DatastoreImpl.java index 0f03133fa91..fa5286e7b6c 100644 --- a/core/src/main/java/dev/morphia/DatastoreImpl.java +++ b/core/src/main/java/dev/morphia/DatastoreImpl.java @@ -48,6 +48,7 @@ import dev.morphia.mapping.Mapper; import dev.morphia.mapping.MappingException; import dev.morphia.mapping.ShardKeyType; +import dev.morphia.mapping.codec.Conversions; import dev.morphia.mapping.codec.EnumCodecProvider; import dev.morphia.mapping.codec.MorphiaCodecProvider; import dev.morphia.mapping.codec.MorphiaMapCodecProvider; @@ -98,6 +99,7 @@ public class DatastoreImpl implements AdvancedDatastore { private static final Logger LOG = LoggerFactory.getLogger(Datastore.class); private final MongoClient mongoClient; + private final Conversions conversions; private final Mapper mapper; private final QueryFactory queryFactory; private final CodecRegistry codecRegistry; @@ -108,7 +110,8 @@ public class DatastoreImpl implements AdvancedDatastore { public DatastoreImpl(MongoClient client, MorphiaConfig config) { this.mongoClient = client; this.database = mongoClient.getDatabase(config.database()); - this.mapper = new Mapper(config); + this.conversions = new Conversions(config.classLoader()); + this.mapper = new Mapper(config, conversions); this.queryFactory = mapper.getConfig().queryFactory(); importModels(); @@ -142,12 +145,23 @@ public DatastoreImpl(MongoClient client, MorphiaConfig config) { public DatastoreImpl(DatastoreImpl datastore) { this.mongoClient = datastore.mongoClient; this.database = mongoClient.getDatabase(datastore.mapper.getConfig().database()); + this.conversions = datastore.conversions; this.mapper = datastore.mapper.copy(); this.queryFactory = datastore.queryFactory; this.operations = datastore.operations; codecRegistry = buildRegistry(); } + /** + * @return the Conversions instance used by this Datastore + * @morphia.internal + * @hidden + */ + @MorphiaInternal + public Conversions getConversions() { + return conversions; + } + private CodecRegistry buildRegistry() { morphiaCodecProviders.add(new MorphiaCodecProvider(this)); @@ -641,7 +655,7 @@ public void refresh(T entity) { .iterator() .next(); - refreshCodec.decode(new DocumentReader(id), DecoderContext.builder().checkedDiscriminator(true).build()); + refreshCodec.decode(new DocumentReader(id, conversions), DecoderContext.builder().checkedDiscriminator(true).build()); } @Override diff --git a/core/src/main/java/dev/morphia/aggregation/AggregationImpl.java b/core/src/main/java/dev/morphia/aggregation/AggregationImpl.java index 438c89cb263..b0dc49125ee 100644 --- a/core/src/main/java/dev/morphia/aggregation/AggregationImpl.java +++ b/core/src/main/java/dev/morphia/aggregation/AggregationImpl.java @@ -354,7 +354,7 @@ public Aggregation addStage(Stage stage) { return this; } - private static class MappingCursor implements MongoCursor { + private class MappingCursor implements MongoCursor { private final MongoCursor results; private final Codec codec; private final String discriminator; @@ -403,7 +403,7 @@ public ServerAddress getServerAddress() { private R map(Document next) { next.remove(discriminator); - return codec.decode(new DocumentReader(next), DecoderContext.builder().build()); + return codec.decode(new DocumentReader(next, datastore.getConversions()), DecoderContext.builder().build()); } } diff --git a/core/src/main/java/dev/morphia/config/ManualMorphiaConfig.java b/core/src/main/java/dev/morphia/config/ManualMorphiaConfig.java index c750bae46db..4b3e2e40df5 100644 --- a/core/src/main/java/dev/morphia/config/ManualMorphiaConfig.java +++ b/core/src/main/java/dev/morphia/config/ManualMorphiaConfig.java @@ -48,6 +48,7 @@ public class ManualMorphiaConfig implements MorphiaConfig { Boolean storeEmpties; Boolean storeNulls; UuidRepresentation uuidRepresentation; + ClassLoader classLoader; /** * @hidden @@ -78,6 +79,7 @@ public ManualMorphiaConfig(MorphiaConfig base) { storeEmpties = base.storeEmpties(); storeNulls = base.storeNulls(); uuidRepresentation = base.uuidRepresentation(); + classLoader = base.classLoader(); } /** @@ -95,6 +97,11 @@ public static ManualMorphiaConfig configure(MorphiaConfig base) { return new ManualMorphiaConfig(base); } + @Override + public ClassLoader classLoader() { + return orDefault(classLoader, Thread.currentThread().getContextClassLoader()); + } + @Override public Boolean applyCaps() { return orDefault(applyCaps, FALSE); diff --git a/core/src/main/java/dev/morphia/config/MorphiaConfig.java b/core/src/main/java/dev/morphia/config/MorphiaConfig.java index 35635739123..e5234e2a61d 100644 --- a/core/src/main/java/dev/morphia/config/MorphiaConfig.java +++ b/core/src/main/java/dev/morphia/config/MorphiaConfig.java @@ -53,6 +53,16 @@ static MorphiaConfig load() { return load(MorphiaConfigHelper.MORPHIA_CONFIG_PROPERTIES); } + /** + * Tries to load a configuration from the default location using the given classloader. + * + * @param classLoader the classloader to use when loading resources from the classpath + * @return the loaded config + */ + static MorphiaConfig load(ClassLoader classLoader) { + return load(MorphiaConfigHelper.MORPHIA_CONFIG_PROPERTIES, classLoader); + } + /** * Parses and loads the configuration found at the given location * @@ -62,18 +72,55 @@ static MorphiaConfig load() { * @since 3.0 */ static MorphiaConfig load(String path) { - List configSources = classPathSources(path, currentThread().getContextClassLoader()); + return load(path, currentThread().getContextClassLoader()); + } + + /** + * Parses and loads the configuration found at the given location + * + * @param path the location of the configuration to load. This can be a file path, a classpath resource, a URL, etc. + * @param classLoader the classloader to use when loading resources from the classpath + * + * @return the loaded configuration + * @since 3.0 + */ + static MorphiaConfig load(String path, ClassLoader classLoader) { + List configSources = classPathSources(path, classLoader); if (configSources.isEmpty()) { LoggerFactory.getLogger(MorphiaConfig.class).warn(Sofia.missingConfigFile(path)); - return new ManualMorphiaConfig(); + return new ManualMorphiaConfig().classLoader(classLoader); } - return new SmallRyeConfigBuilder() + MorphiaConfig config = new SmallRyeConfigBuilder() .addDefaultInterceptors() .withMapping(MorphiaConfig.class) .withSources(configSources) .addDefaultSources() .build() .getConfigMapping(MorphiaConfig.class); + return config.classLoader(classLoader); + } + + /** + * The ClassLoader to use for class resolution. Defaults to the current thread's context ClassLoader. + * + * @return the ClassLoader + * @since 2.5 + */ + default ClassLoader classLoader() { + return currentThread().getContextClassLoader(); + } + + /** + * Updates this configuration with a new ClassLoader and returns a new instance. The original instance is unchanged. + * + * @param value the new ClassLoader + * @return a new instance with the updated configuration + * @since 2.5 + */ + default MorphiaConfig classLoader(ClassLoader value) { + var newConfig = new ManualMorphiaConfig(this); + newConfig.classLoader = value; + return newConfig; } /** diff --git a/core/src/main/java/dev/morphia/mapping/DiscriminatorLookup.java b/core/src/main/java/dev/morphia/mapping/DiscriminatorLookup.java index 58aee312782..86ca7a186a2 100644 --- a/core/src/main/java/dev/morphia/mapping/DiscriminatorLookup.java +++ b/core/src/main/java/dev/morphia/mapping/DiscriminatorLookup.java @@ -40,6 +40,11 @@ public final class DiscriminatorLookup { private final Map discriminatorClassMap = new ConcurrentHashMap<>(); private final Set packages = new ConcurrentSkipListSet<>(); + private final ClassLoader classLoader; + + public DiscriminatorLookup(ClassLoader classLoader) { + this.classLoader = classLoader; + } /** * Adds a model to the map @@ -64,7 +69,7 @@ public void addModel(EntityModel entityModel) { public Class lookup(String discriminator) { if (discriminatorClassMap.containsKey(discriminator)) { try { - return Class.forName(discriminatorClassMap.get(discriminator), true, Thread.currentThread().getContextClassLoader()); + return Class.forName(discriminatorClassMap.get(discriminator), true, classLoader); } catch (ClassNotFoundException e) { throw new MappingException(e.getMessage(), e); } @@ -85,7 +90,7 @@ public Class lookup(String discriminator) { private Class getClassForName(String discriminator) { Class clazz = null; try { - clazz = Class.forName(discriminator, true, Thread.currentThread().getContextClassLoader()); + clazz = Class.forName(discriminator, true, classLoader); } catch (ClassNotFoundException e) { // Ignore } diff --git a/core/src/main/java/dev/morphia/mapping/InstanceCreatorFactory.java b/core/src/main/java/dev/morphia/mapping/InstanceCreatorFactory.java index 04b5cca0ae2..998ff85c183 100644 --- a/core/src/main/java/dev/morphia/mapping/InstanceCreatorFactory.java +++ b/core/src/main/java/dev/morphia/mapping/InstanceCreatorFactory.java @@ -1,5 +1,6 @@ package dev.morphia.mapping; +import dev.morphia.mapping.codec.Conversions; import dev.morphia.mapping.codec.MorphiaInstanceCreator; /** @@ -8,7 +9,8 @@ public interface InstanceCreatorFactory { /** + * @param conversions the Conversions instance to use * @return a new ClassAccessor instance */ - MorphiaInstanceCreator create(); + MorphiaInstanceCreator create(Conversions conversions); } diff --git a/core/src/main/java/dev/morphia/mapping/InstanceCreatorFactoryImpl.java b/core/src/main/java/dev/morphia/mapping/InstanceCreatorFactoryImpl.java index 420779d8413..3e65bd84931 100644 --- a/core/src/main/java/dev/morphia/mapping/InstanceCreatorFactoryImpl.java +++ b/core/src/main/java/dev/morphia/mapping/InstanceCreatorFactoryImpl.java @@ -1,9 +1,10 @@ package dev.morphia.mapping; import java.lang.reflect.Constructor; -import java.util.function.Supplier; +import java.util.function.Function; import dev.morphia.annotations.internal.MorphiaInternal; +import dev.morphia.mapping.codec.Conversions; import dev.morphia.mapping.codec.MorphiaInstanceCreator; import dev.morphia.mapping.codec.pojo.EntityModel; import dev.morphia.mapping.internal.ConstructorCreator; @@ -22,7 +23,7 @@ public class InstanceCreatorFactoryImpl implements InstanceCreatorFactory { private static final Logger LOG = LoggerFactory.getLogger(InstanceCreatorFactoryImpl.class); private final EntityModel model; - private Supplier creator; + private Function creator; /** * Creates a factory for this type @@ -35,20 +36,20 @@ public InstanceCreatorFactoryImpl(EntityModel model) { } @Override - public MorphiaInstanceCreator create() { + public MorphiaInstanceCreator create(Conversions conversions) { if (creator == null) { if (!model.getType().isInterface()) { Constructor constructor = ConstructorCreator.bestConstructor(model); if (constructor != null) { - creator = () -> new ConstructorCreator(model, constructor); + creator = (c) -> new ConstructorCreator(model, constructor, c); } else { LOG.debug("using old creator approach: " + model.getType().getName()); try { Constructor declared = model.getType().getDeclaredConstructor(); - creator = () -> new NoArgCreator(declared); + creator = (c) -> new NoArgCreator(declared); } catch (NoSuchMethodException e) { Constructor full = ConstructorCreator.getFullConstructor(model); - creator = () -> new ConstructorCreator(model, full); + creator = (c) -> new ConstructorCreator(model, full, c); } } } @@ -58,6 +59,6 @@ public MorphiaInstanceCreator create() { } } - return creator.get(); + return creator.apply(conversions); } } diff --git a/core/src/main/java/dev/morphia/mapping/Mapper.java b/core/src/main/java/dev/morphia/mapping/Mapper.java index 81bd71d149a..2105387d7cd 100644 --- a/core/src/main/java/dev/morphia/mapping/Mapper.java +++ b/core/src/main/java/dev/morphia/mapping/Mapper.java @@ -29,6 +29,7 @@ import dev.morphia.annotations.PrePersist; import dev.morphia.annotations.internal.MorphiaInternal; import dev.morphia.config.MorphiaConfig; +import dev.morphia.mapping.codec.Conversions; import dev.morphia.mapping.codec.pojo.EntityModel; import dev.morphia.mapping.codec.pojo.EntityModelBuilder; import dev.morphia.mapping.codec.pojo.PropertyModel; @@ -79,18 +80,21 @@ public class Mapper { private final MorphiaConfig config; private final DiscriminatorLookup discriminatorLookup; private final Object entityRegistrationMonitor = new Object(); + private final Conversions conversions; /** - * Creates a Mapper with the given options. + * Creates a Mapper with the given options and Conversions instance. * - * @param config the config to use + * @param config the config to use + * @param conversions the Conversions instance to use * @morphia.internal * @hidden */ @MorphiaInternal - public Mapper(MorphiaConfig config) { + public Mapper(MorphiaConfig config, Conversions conversions) { this.config = config; - discriminatorLookup = new DiscriminatorLookup(); + this.conversions = conversions; + discriminatorLookup = new DiscriminatorLookup(conversions.getClassLoader()); } /** @@ -100,7 +104,8 @@ public Mapper(MorphiaConfig config) { */ public Mapper(Mapper other) { config = other.config; - discriminatorLookup = new DiscriminatorLookup(); + conversions = other.conversions; + discriminatorLookup = new DiscriminatorLookup(other.getClassLoader()); other.mappedEntities.values().forEach(entity -> clone(entity)); listeners.addAll(other.listeners); } @@ -173,6 +178,20 @@ public PropertyModel findIdProperty(Class type) { return idField; } + public ClassLoader getClassLoader() { + return conversions.getClassLoader(); + } + + /** + * @return the Conversions instance used by this Mapper + * @morphia.internal + * @hidden + */ + @MorphiaInternal + public Conversions getConversions() { + return conversions; + } + /** * Gets the class as defined by any discriminator field * @@ -618,7 +637,7 @@ private List getClasses(String packageName) try (ScanResult scanResult = classGraph.scan()) { for (ClassInfo classInfo : scanResult.getAllClasses()) { try { - classes.add(Class.forName(classInfo.getName(), true, Thread.currentThread().getContextClassLoader())); + classes.add(Class.forName(classInfo.getName(), true, conversions.getClassLoader())); } catch (Throwable ignored) { } } diff --git a/core/src/main/java/dev/morphia/mapping/codec/ArrayFieldAccessor.java b/core/src/main/java/dev/morphia/mapping/codec/ArrayFieldAccessor.java index 5027284e965..807aa2d7fcf 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/ArrayFieldAccessor.java +++ b/core/src/main/java/dev/morphia/mapping/codec/ArrayFieldAccessor.java @@ -20,17 +20,20 @@ public class ArrayFieldAccessor extends FieldAccessor { private final TypeData typeData; private final Class componentType; + private final Conversions conversions; /** * Creates the accessor * - * @param typeData the type data - * @param field the field + * @param typeData the type data + * @param field the field + * @param conversions the Conversions instance to use */ - public ArrayFieldAccessor(TypeData typeData, Field field) { + public ArrayFieldAccessor(TypeData typeData, Field field, Conversions conversions) { super(field); this.typeData = typeData; componentType = field.getType().getComponentType(); + this.conversions = conversions; } @Override @@ -84,6 +87,6 @@ private Object convert(Object o, Class type) { return newArray; } - return Conversions.convert(o, type); + return conversions.convert(o, type); } } diff --git a/core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java b/core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java index 6e8967e7d05..bfd3e9c6cc6 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java @@ -12,11 +12,16 @@ * Defines a codec for Class references */ public class ClassCodec implements Codec { + private final Conversions conversions; + + public ClassCodec(Conversions conversions) { + this.conversions = conversions; + } + @Override public Class decode(BsonReader reader, DecoderContext decoderContext) { try { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - return Class.forName(reader.readString(), true, classLoader); + return Class.forName(reader.readString(), true, conversions.getClassLoader()); } catch (ClassNotFoundException e) { throw new MappingException(e.getMessage(), e); } diff --git a/core/src/main/java/dev/morphia/mapping/codec/Conversions.java b/core/src/main/java/dev/morphia/mapping/codec/Conversions.java index 4aa781e666e..4b5f6a7dcd0 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/Conversions.java +++ b/core/src/main/java/dev/morphia/mapping/codec/Conversions.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiFunction; import java.util.function.Function; import com.mongodb.lang.Nullable; @@ -31,12 +32,30 @@ * @morphia.internal */ @MorphiaInternal -public final class Conversions { +public class Conversions { private static final Logger LOG = LoggerFactory.getLogger(Conversions.class); - private static final Map, Map, Function>> CONVERSIONS = new ConcurrentHashMap<>(); + private final Map, Map, BiFunction>> conversions = new ConcurrentHashMap<>(); + private final ClassLoader classLoader; - static { + /** + * Creates a new Conversions instance with the given ClassLoader. + * + * @param classLoader the ClassLoader to use for class resolution + */ + public Conversions(ClassLoader classLoader) { + this.classLoader = classLoader; + registerDefaults(); + } + + /** + * @return the ClassLoader used by this Conversions instance + */ + public ClassLoader getClassLoader() { + return classLoader; + } + + private void registerDefaults() { registerStringConversions(); register(Binary.class, byte[].class, Binary::getData); @@ -81,10 +100,7 @@ public final class Conversions { }); } - private Conversions() { - } - - private static void registerStringConversions() { + private void registerStringConversions() { register(String.class, BigDecimal.class, BigDecimal::new); register(String.class, ObjectId.class, ObjectId::new); register(String.class, Character.class, s -> { @@ -97,9 +113,9 @@ private static void registerStringConversions() { } }); register(Class.class, String.class, Class::getName); - register(String.class, Class.class, className -> { + registerWithClassLoader(String.class, Class.class, (className, cl) -> { try { - return Thread.currentThread().getContextClassLoader().loadClass(className); + return cl.loadClass(className); } catch (ClassNotFoundException e) { throw new MappingException(e.getMessage(), e); } @@ -124,24 +140,7 @@ private static void registerStringConversions() { } /** - * Register a conversion between two types. For example, to register the conversion of {@link Date} to a {@link Long}, this method - * could be invoked as follows: - * - * register(Date.class, Long.class, Date::getTime); - * - * - * @param source the source type - * @param target the target type - * @param function the function that performs the conversion. This is often just a method reference. - * @param the source type - * @param the target type. - */ - public static void register(Class source, Class target, Function function) { - register(source, target, function, null); - } - - /** - * Attempts to convert a value to the given type + * Attempts to convert a value to the given type. * * @param value the value to convert * @param target the target type @@ -150,17 +149,15 @@ public static void register(Class source, Class target, Function T convert(@Nullable Object value, Class target) { + public T convert(@Nullable Object value, Class target) { if (value == null) { return (T) convertNull(target); } - final Class fromType = value.getClass(); if (fromType.equals(target)) { return (T) value; } - - final Function function = CONVERSIONS + final BiFunction function = conversions .computeIfAbsent(fromType, (f) -> new ConcurrentHashMap<>()) .get(target); if (function == null) { @@ -172,7 +169,7 @@ public static T convert(@Nullable Object value, Class target) { } return (T) value; } - return (T) function.apply(value); + return (T) function.apply(value, classLoader); } @Nullable @@ -186,30 +183,70 @@ private static Object convertNull(Class toType) { } /** - * Register a conversion between two types. For example, to register the conversion of {@link Date} to a {@link Long}, this method - * could be invoked as follows: - * - * register(Date.class, Long.class, Date::getTime); - * + * Register a conversion between two types. * * @param source the source type * @param target the target type - * @param function the function that performs the conversion. This is often just a method reference. - * @param warning if non-null, this will be the message logged on the WARN level indicating the conversion is taking place. + * @param function the function that performs the conversion * @param the source type - * @param the target type. + * @param the target type */ - public static void register(Class source, Class target, Function function, + public void register(Class source, Class target, Function function) { + register(source, target, function, null); + } + + /** + * Register a conversion between two types. + * + * @param source the source type + * @param target the target type + * @param function the function that performs the conversion + * @param warning if non-null, this will be logged on WARN level + * @param the source type + * @param the target type + */ + public void register(Class source, Class target, Function function, @Nullable String warning) { - final Function conversion = warning == null + // Wrap Function as BiFunction (ignore ClassLoader parameter) + BiFunction biFunction = (s, cl) -> function.apply(s); + registerWithClassLoader(source, target, biFunction, warning); + } + + /** + * Register a conversion between two types with ClassLoader support. + * + * @param source the source type + * @param target the target type + * @param function the function that performs the conversion (receives ClassLoader) + * @param the source type + * @param the target type + */ + public void registerWithClassLoader(Class source, Class target, + BiFunction function) { + registerWithClassLoader(source, target, function, null); + } + + /** + * Register a conversion between two types with ClassLoader support. + * + * @param source the source type + * @param target the target type + * @param function the function that performs the conversion (receives ClassLoader) + * @param warning if non-null, this will be logged on WARN level + * @param the source type + * @param the target type + */ + public void registerWithClassLoader(Class source, Class target, + BiFunction function, @Nullable String warning) { + final BiFunction conversion = warning == null ? function - : s -> { + : (s, cl) -> { if (LOG.isWarnEnabled()) { LOG.warn(warning); } - return function.apply(s); + return function.apply(s, cl); }; - CONVERSIONS.computeIfAbsent(source, (Class c) -> new HashMap<>()) + conversions.computeIfAbsent(source, (Class c) -> new HashMap<>()) .put(target, conversion); } diff --git a/core/src/main/java/dev/morphia/mapping/codec/MorphiaCodecProvider.java b/core/src/main/java/dev/morphia/mapping/codec/MorphiaCodecProvider.java index 4c02af5f766..c839003a6b2 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/MorphiaCodecProvider.java +++ b/core/src/main/java/dev/morphia/mapping/codec/MorphiaCodecProvider.java @@ -36,6 +36,7 @@ public class MorphiaCodecProvider implements CodecProvider { private final Map, Codec> codecs = new HashMap<>(); private final Mapper mapper; + private final Conversions conversions; private final List propertyCodecProviders = new ArrayList<>(); private Datastore datastore; @@ -47,6 +48,7 @@ public class MorphiaCodecProvider implements CodecProvider { public MorphiaCodecProvider(Datastore datastore) { this.datastore = datastore; this.mapper = datastore.getMapper(); + this.conversions = mapper.getConversions(); // Load user-provided custom codecs first, to prevent the defaults from overriding them. ServiceLoader providers = ServiceLoader.load(MorphiaPropertyCodecProvider.class); @@ -54,7 +56,7 @@ public MorphiaCodecProvider(Datastore datastore) { propertyCodecProviders.add(provider); }); - propertyCodecProviders.addAll(List.of(new MorphiaMapPropertyCodecProvider(), + propertyCodecProviders.addAll(List.of(new MorphiaMapPropertyCodecProvider(datastore, conversions), new MorphiaCollectionPropertyCodecProvider())); } @@ -73,7 +75,7 @@ public Codec get(Class type, CodecRegistry registry) { MorphiaCodec codec = (MorphiaCodec) codecs.get(type); if (codec == null && (mapper.isMapped(type) || mapper.isMappable(type))) { EntityModel model = mapper.getEntityModel(type); - codec = new MorphiaCodec<>(datastore, model, propertyCodecProviders, mapper.getDiscriminatorLookup(), registry); + codec = new MorphiaCodec<>(datastore, model, propertyCodecProviders, mapper.getDiscriminatorLookup(), registry, conversions); if (model.hasLifecycle(PostPersist.class) || model.hasLifecycle(PrePersist.class) || mapper.hasInterceptors()) { codec.setEncoder(new LifecycleEncoder(codec)); } @@ -97,7 +99,7 @@ public Codec get(Class type, CodecRegistry registry) { @Nullable public Codec getRefreshCodec(T entity, CodecRegistry registry) { EntityModel model = mapper.getEntityModel(entity.getClass()); - return new MorphiaCodec<>(datastore, model, propertyCodecProviders, mapper.getDiscriminatorLookup(), registry) { + return new MorphiaCodec<>(datastore, model, propertyCodecProviders, mapper.getDiscriminatorLookup(), registry, conversions) { @Override protected EntityDecoder getDecoder() { return new EntityDecoder<>(this) { diff --git a/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapCodec.java b/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapCodec.java index c4a72e99513..a461ac0980f 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapCodec.java @@ -34,11 +34,13 @@ public class MorphiaMapCodec implements Codec { private Supplier factory; private Datastore datastore; + private final Conversions conversions; private final Codec valueCodec; - public MorphiaMapCodec(DatastoreImpl datastore, Class clazz, Codec valueCodec) { + public MorphiaMapCodec(DatastoreImpl datastore, Class clazz, Codec valueCodec, Conversions conversions) { this.datastore = datastore; + this.conversions = conversions; this.valueCodec = valueCodec; try { Constructor ctor = clazz.getDeclaredConstructor(); @@ -78,7 +80,7 @@ public void encode(BsonWriter writer, Map map, EncoderContext encoderContext) { document(writer, () -> { for (Entry entry : ((Map) map).entrySet()) { final Object key = entry.getKey(); - writer.writeName(Conversions.convert(key, String.class)); + writer.writeName(conversions.convert(key, String.class)); if (entry.getValue() == null) { writer.writeNull(); } else { diff --git a/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapCodecProvider.java b/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapCodecProvider.java index 15bb34b189b..f968bd9cd71 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapCodecProvider.java +++ b/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapCodecProvider.java @@ -32,7 +32,7 @@ public Codec get(Class clazz, List typeArguments, CodecRegistry return (Codec) new BsonDocumentCodec(registry); } else if (Map.class.isAssignableFrom(clazz) && !Document.class.isAssignableFrom(clazz)) { Class valueType = typeArguments.size() == 2 ? (Class) typeArguments.get(1) : Object.class; - return (Codec) new MorphiaMapCodec(datastore, clazz, registry.get(valueType)); + return (Codec) new MorphiaMapCodec(datastore, clazz, registry.get(valueType), datastore.getConversions()); } return null; } diff --git a/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapPropertyCodecProvider.java b/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapPropertyCodecProvider.java index 8b5f4f9ef6a..f5cb493988b 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapPropertyCodecProvider.java +++ b/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapPropertyCodecProvider.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.Map.Entry; +import dev.morphia.Datastore; import dev.morphia.annotations.internal.MorphiaInternal; import dev.morphia.mapping.codec.pojo.TypeData; @@ -28,6 +29,14 @@ @MorphiaInternal @SuppressWarnings("unchecked") class MorphiaMapPropertyCodecProvider extends MorphiaPropertyCodecProvider { + private final Datastore datastore; + private final Conversions conversions; + + public MorphiaMapPropertyCodecProvider(Datastore datastore, Conversions conversions) { + this.datastore = datastore; + this.conversions = conversions; + } + @Override public Codec get(TypeWithTypeParameters type, PropertyCodecRegistry registry) { if (Map.class.isAssignableFrom(type.getType())) { @@ -51,7 +60,7 @@ public Codec get(TypeWithTypeParameters type, PropertyCodecRegistry re return null; } - private static class MapCodec implements Codec> { + private class MapCodec implements Codec> { private final Class> encoderClass; private final Class keyType; private final Codec codec; @@ -67,7 +76,7 @@ public void encode(BsonWriter writer, Map map, EncoderContext encoderConte document(writer, () -> { for (Entry entry : map.entrySet()) { final K key = entry.getKey(); - writer.writeName(Conversions.convert(key, String.class)); + writer.writeName(conversions.convert(key, String.class)); if (entry.getValue() == null) { writer.writeNull(); } else { @@ -82,7 +91,7 @@ public Map decode(BsonReader reader, DecoderContext context) { reader.readStartDocument(); Map map = getInstance(); while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { - final K key = Conversions.convert(reader.readName(), keyType); + final K key = conversions.convert(reader.readName(), keyType); if (reader.getCurrentBsonType() == BsonType.NULL) { map.put(key, null); reader.readNull(); diff --git a/core/src/main/java/dev/morphia/mapping/codec/MorphiaTypesCodecProvider.java b/core/src/main/java/dev/morphia/mapping/codec/MorphiaTypesCodecProvider.java index 44a58c8f1df..c542efb9715 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/MorphiaTypesCodecProvider.java +++ b/core/src/main/java/dev/morphia/mapping/codec/MorphiaTypesCodecProvider.java @@ -28,7 +28,7 @@ public MorphiaTypesCodecProvider(DatastoreImpl datastore) { addCodec(new MorphiaDateCodec(datastore)); addCodec(new MorphiaLocalDateTimeCodec(datastore)); addCodec(new MorphiaLocalTimeCodec()); - addCodec(new ClassCodec()); + addCodec(new ClassCodec(datastore.getConversions())); addCodec(new CenterCodec()); addCodec(new KeyCodec(datastore)); addCodec(new LocaleCodec()); diff --git a/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityDecoder.java b/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityDecoder.java index 94f145bb77f..1210c389380 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityDecoder.java +++ b/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityDecoder.java @@ -18,7 +18,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static dev.morphia.mapping.codec.Conversions.convert; import static java.lang.String.format; /** @@ -71,7 +70,7 @@ protected void decodeModel(BsonReader reader, DecoderContext decoderContext, } catch (BsonInvalidOperationException e) { mark.reset(); final Object value = morphiaCodec.getDatastore().getCodecRegistry().get(Object.class).decode(reader, decoderContext); - instanceCreator.set(convert(value, model.getTypeData().getType()), model); + instanceCreator.set(morphiaCodec.getConversions().convert(value, model.getTypeData().getType()), model); } } else { reader.skipValue(); @@ -118,7 +117,7 @@ protected Codec getCodecFromDocument(BsonReader reader, boolean useDiscrimina } protected MorphiaInstanceCreator getInstanceCreator() { - return classModel.getInstanceCreator(); + return classModel.getInstanceCreator(morphiaCodec.getConversions()); } protected MorphiaCodec getMorphiaCodec() { diff --git a/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityModel.java b/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityModel.java index ab5a48fb502..3c1efe86fb3 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityModel.java +++ b/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityModel.java @@ -28,6 +28,7 @@ import dev.morphia.mapping.InstanceCreatorFactory; import dev.morphia.mapping.InstanceCreatorFactoryImpl; import dev.morphia.mapping.MappingException; +import dev.morphia.mapping.codec.Conversions; import dev.morphia.mapping.codec.MorphiaInstanceCreator; import dev.morphia.mapping.lifecycle.EntityListenerAdapter; import dev.morphia.mapping.lifecycle.OnEntityListenerAdapter; @@ -284,10 +285,11 @@ public PropertyModel getIdProperty() { } /** + * @param conversions the Conversions instance to use * @return a new InstanceCreator instance for the ClassModel */ - public MorphiaInstanceCreator getInstanceCreator() { - return creatorFactory.create(); + public MorphiaInstanceCreator getInstanceCreator(Conversions conversions) { + return creatorFactory.create(conversions); } /** diff --git a/core/src/main/java/dev/morphia/mapping/codec/pojo/LifecycleDecoder.java b/core/src/main/java/dev/morphia/mapping/codec/pojo/LifecycleDecoder.java index 683b51c5b98..b2a42921397 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/pojo/LifecycleDecoder.java +++ b/core/src/main/java/dev/morphia/mapping/codec/pojo/LifecycleDecoder.java @@ -48,10 +48,12 @@ public T decode(BsonReader reader, DecoderContext decoderContext) { } } } - final MorphiaInstanceCreator instanceCreator = model.getInstanceCreator(); + final MorphiaInstanceCreator instanceCreator = model.getInstanceCreator(getMorphiaCodec().getConversions()); T entity = (T) instanceCreator.getInstance(); + model.callLifecycleMethods(PreLoad.class, entity, document, getMorphiaCodec().getDatastore()); - decodeProperties(new DocumentReader(document), decoderContext, instanceCreator, model); + decodeProperties(new DocumentReader(document, getMorphiaCodec().getConversions()), decoderContext, instanceCreator, + model); model.callLifecycleMethods(PostLoad.class, entity, document, getMorphiaCodec().getDatastore()); return entity; diff --git a/core/src/main/java/dev/morphia/mapping/codec/pojo/MorphiaCodec.java b/core/src/main/java/dev/morphia/mapping/codec/pojo/MorphiaCodec.java index 63e0a0a35cf..99feb6ed037 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/pojo/MorphiaCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/pojo/MorphiaCodec.java @@ -7,6 +7,7 @@ import dev.morphia.mapping.DiscriminatorLookup; import dev.morphia.mapping.Mapper; import dev.morphia.mapping.MappingException; +import dev.morphia.mapping.codec.Conversions; import dev.morphia.mapping.codec.PropertyCodecRegistryImpl; import dev.morphia.sofia.Sofia; @@ -24,7 +25,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static dev.morphia.mapping.codec.Conversions.convert; import static org.bson.codecs.configuration.CodecRegistries.fromCodecs; import static org.bson.codecs.configuration.CodecRegistries.fromRegistries; @@ -45,6 +45,7 @@ public class MorphiaCodec implements CollectibleCodec { private final CodecRegistry registry; private final PropertyCodecRegistry propertyCodecRegistry; private final DiscriminatorLookup discriminatorLookup; + private final Conversions conversions; private EntityEncoder encoder; private EntityDecoder decoder; private Datastore datastore; @@ -60,9 +61,11 @@ public class MorphiaCodec implements CollectibleCodec { */ public MorphiaCodec(Datastore datastore, EntityModel model, List propertyCodecProviders, - DiscriminatorLookup discriminatorLookup, CodecRegistry registry) { + DiscriminatorLookup discriminatorLookup, CodecRegistry registry, + Conversions conversions) { this.datastore = datastore; this.discriminatorLookup = discriminatorLookup; + this.conversions = conversions; this.entityModel = model; this.registry = fromRegistries(fromCodecs(this), registry); @@ -91,7 +94,7 @@ public Object generateIdIfAbsentFromDocument(Object entity) { if (!documentHasId(entity)) { if (idProperty != null) { if (ObjectId.class.equals(idProperty.getType()) || String.class.equals(idProperty.getType())) { - idProperty.setValue(entity, convert(new ObjectId(), idProperty.getType())); + idProperty.setValue(entity, conversions.convert(new ObjectId(), idProperty.getType())); } else { LOG.warn(Sofia.noIdAndNotObjectId(entity.getClass().getName())); } @@ -113,6 +116,13 @@ public BsonValue getDocumentId(Object document) { throw new UnsupportedOperationException(); } + /** + * @return the Conversions instance + */ + public Conversions getConversions() { + return conversions; + } + /** * @return the datastore * @since 2.3 diff --git a/core/src/main/java/dev/morphia/mapping/codec/pojo/PropertyModel.java b/core/src/main/java/dev/morphia/mapping/codec/pojo/PropertyModel.java index f52fa966bbe..96ff9cf7dbf 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/pojo/PropertyModel.java +++ b/core/src/main/java/dev/morphia/mapping/codec/pojo/PropertyModel.java @@ -70,11 +70,13 @@ public final class PropertyModel { private final Map, Annotation> annotationMap = new HashMap<>(); private final List loadNames; // List of stored names in order of trying, contains nameToStore and potential aliases private final EntityModel entityModel; + private final Conversions conversions; private Codec codec; private Class normalizedType; PropertyModel(PropertyModelBuilder builder) { entityModel = builder.owner(); + conversions = builder.conversions(); name = Objects.requireNonNull(builder.name(), Sofia.notNull("name")); mappedName = Objects.requireNonNull(builder.mappedName(), Sofia.notNull("name")); typeData = Objects.requireNonNull(builder.typeData(), Sofia.notNull("typeData")); @@ -97,6 +99,7 @@ public final class PropertyModel { public PropertyModel(EntityModel owner, PropertyModel other) { entityModel = owner; + conversions = other.conversions; name = other.name; typeData = other.typeData; @@ -362,7 +365,7 @@ public boolean isTransient() { * @param value the value to set */ public void setValue(Object instance, @Nullable Object value) { - accessor.set(instance, Conversions.convert(value, getType())); + accessor.set(instance, conversions.convert(value, getType())); } /** diff --git a/core/src/main/java/dev/morphia/mapping/codec/pojo/PropertyModelBuilder.java b/core/src/main/java/dev/morphia/mapping/codec/pojo/PropertyModelBuilder.java index 93df8138e98..06539d52c98 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/pojo/PropertyModelBuilder.java +++ b/core/src/main/java/dev/morphia/mapping/codec/pojo/PropertyModelBuilder.java @@ -30,6 +30,7 @@ import dev.morphia.annotations.internal.MorphiaInternal; import dev.morphia.config.MorphiaConfig; import dev.morphia.mapping.Mapper; +import dev.morphia.mapping.codec.Conversions; import dev.morphia.mapping.codec.MorphiaPropertySerialization; import org.bson.codecs.pojo.PropertyAccessor; @@ -311,6 +312,13 @@ public String toString() { .toString(); } + /** + * @return the Conversions instance from the mapper + */ + Conversions conversions() { + return mapper.getConversions(); + } + /** * @return the type data */ diff --git a/core/src/main/java/dev/morphia/mapping/codec/reader/DocumentReader.java b/core/src/main/java/dev/morphia/mapping/codec/reader/DocumentReader.java index 58546d21f80..6509dbf5b71 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/reader/DocumentReader.java +++ b/core/src/main/java/dev/morphia/mapping/codec/reader/DocumentReader.java @@ -48,16 +48,19 @@ public BsonType get(Class type) { } }; private final ReaderState start; + private final Conversions conversions; private ReaderState current; /** * Construct a new instance. * - * @param document the document to read from + * @param document the document to read from + * @param conversions the Conversions instance to use */ - public DocumentReader(Document document) { + public DocumentReader(Document document, Conversions conversions) { current = new DocumentState(this, document); start = current; + this.conversions = conversions; } /** @@ -147,7 +150,7 @@ public BsonType readBsonType() { @Override public long readDateTime() { - Long value = Conversions.convert(stage().value(), long.class); + Long value = conversions.convert(stage().value(), long.class); if (value != null) { return value; } diff --git a/core/src/main/java/dev/morphia/mapping/codec/references/ReferenceCodec.java b/core/src/main/java/dev/morphia/mapping/codec/references/ReferenceCodec.java index 43263c299e2..e50feaad324 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/references/ReferenceCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/references/ReferenceCodec.java @@ -24,7 +24,6 @@ import dev.morphia.mapping.Mapper; import dev.morphia.mapping.MappingException; import dev.morphia.mapping.codec.BaseReferenceCodec; -import dev.morphia.mapping.codec.Conversions; import dev.morphia.mapping.codec.pojo.EntityModel; import dev.morphia.mapping.codec.pojo.PropertyHandler; import dev.morphia.mapping.codec.pojo.PropertyModel; @@ -164,7 +163,7 @@ public static Object processId(Datastore datastore, Object decode, DecoderContex try { id = datastore.getCodecRegistry() .get(datastore.getMapper().getClass(document)) - .decode(new DocumentReader(document), decoderContext); + .decode(new DocumentReader(document, datastore.getMapper().getConversions()), decoderContext); } catch (CodecConfigurationException e) { throw new MappingException(Sofia.cannotFindTypeInDocument(), e); } @@ -176,7 +175,7 @@ public static Object processId(Datastore datastore, Object decode, DecoderContex if (refId instanceof Document) { refId = datastore.getCodecRegistry() .get(Object.class) - .decode(new DocumentReader((Document) refId), decoderContext); + .decode(new DocumentReader((Document) refId, datastore.getMapper().getConversions()), decoderContext); } id = new DBRef(ref.getDatabaseName(), ref.getCollectionName(), refId); } @@ -344,7 +343,7 @@ private Class makeProxy() { .intercept(InvocationHandlerAdapter.toField(FIELD_INVOCATION_HANDLER)) .defineField(FIELD_INVOCATION_HANDLER, InvocationHandler.class, Visibility.PRIVATE) .make() - .load(Thread.currentThread().getContextClassLoader(), Default.WRAPPER) + .load(mapper.getClassLoader(), Default.WRAPPER) .getLoaded(); } @@ -374,13 +373,13 @@ private List mapToEntitiesIfNecessary(List value) { Codec codec = getDatastore().getCodecRegistry().get(getEntityModelForField().getType()); return value.stream() .filter(v -> v instanceof Document && ((Document) v).containsKey("_id")) - .map(d -> codec.decode(new DocumentReader((Document) d), DecoderContext.builder().build())) + .map(d -> codec.decode(new DocumentReader((Document) d, mapper.getConversions()), DecoderContext.builder().build())) .collect(Collectors.toList()); } MorphiaReference readDocument(Document value) { final Object id = getDatastore().getCodecRegistry().get(Object.class) - .decode(new DocumentReader(value), DecoderContext.builder().build()); + .decode(new DocumentReader(value, mapper.getConversions()), DecoderContext.builder().build()); return readSingle(id); } @@ -395,7 +394,7 @@ MorphiaReference readMap(Map value) { final Map ids = new LinkedHashMap<>(); Class keyType = getTypeData().getTypeParameters().get(0).getType(); for (Entry entry : value.entrySet()) { - ids.put(Conversions.convert(entry.getKey(), keyType), entry.getValue()); + ids.put(mapper.getConversions().convert(entry.getKey(), keyType), entry.getValue()); } return new MapReference(datastore, ids, getEntityModelForField()); diff --git a/core/src/main/java/dev/morphia/mapping/conventions/FieldDiscovery.java b/core/src/main/java/dev/morphia/mapping/conventions/FieldDiscovery.java index 5ca5db02c61..6eac4989514 100644 --- a/core/src/main/java/dev/morphia/mapping/conventions/FieldDiscovery.java +++ b/core/src/main/java/dev/morphia/mapping/conventions/FieldDiscovery.java @@ -10,6 +10,7 @@ import dev.morphia.mapping.Mapper; import dev.morphia.mapping.MappingException; import dev.morphia.mapping.codec.ArrayFieldAccessor; +import dev.morphia.mapping.codec.Conversions; import dev.morphia.mapping.codec.FieldAccessor; import dev.morphia.mapping.codec.pojo.EntityModelBuilder; import dev.morphia.mapping.codec.pojo.TypeData; @@ -35,7 +36,7 @@ public void apply(Mapper mapper, EntityModelBuilder builder) { .name(field.getName()) .typeData(typeData) .annotations(List.of(field.getDeclaredAnnotations())) - .accessor(getAccessor(getTargetField(builder, field), typeData)) + .accessor(getAccessor(getTargetField(builder, field), typeData, mapper.getConversions())) .modifiers(field.getModifiers()) .discoverMappedName(); } catch (NoSuchFieldException e) { @@ -55,9 +56,9 @@ private Field getTargetField(EntityModelBuilder builder, @NonNull Field field) t return builder.targetType().getDeclaredField(field.getName()); } - private PropertyAccessor getAccessor(Field field, TypeData typeData) { + private PropertyAccessor getAccessor(Field field, TypeData typeData, Conversions conversions) { return field.getType().isArray() && !field.getType().getComponentType().equals(byte.class) - ? new ArrayFieldAccessor(typeData, field) + ? new ArrayFieldAccessor(typeData, field, conversions) : new FieldAccessor(field); } } diff --git a/core/src/main/java/dev/morphia/mapping/internal/ConstructorCreator.java b/core/src/main/java/dev/morphia/mapping/internal/ConstructorCreator.java index 9edb1d76595..85b90027220 100644 --- a/core/src/main/java/dev/morphia/mapping/internal/ConstructorCreator.java +++ b/core/src/main/java/dev/morphia/mapping/internal/ConstructorCreator.java @@ -50,9 +50,10 @@ public class ConstructorCreator implements MorphiaInstanceCreator { /** * @param model the model * @param constructor the constructor to use + * @param conversions the Conversions instance to use */ @SuppressFBWarnings("EI_EXPOSE_REP2") - public ConstructorCreator(EntityModel model, Constructor constructor) { + public ConstructorCreator(EntityModel model, Constructor constructor, Conversions conversions) { this.model = model; this.constructor = constructor; this.constructor.setAccessible(true); @@ -68,7 +69,7 @@ public ConstructorCreator(EntityModel model, Constructor constructor) { throw new MappingException(Sofia.unnamedConstructorParameter(model.getType().getName())); } BiFunction old = positions.put(name, (Object[] params, Object v) -> { - params[finalI] = Conversions.convert(v, parameter.getType()); + params[finalI] = conversions.convert(v, parameter.getType()); return null; }); diff --git a/core/src/main/java/dev/morphia/query/internal/MorphiaKeyCursor.java b/core/src/main/java/dev/morphia/query/internal/MorphiaKeyCursor.java index 0026b96d641..1d0d0cee3b9 100644 --- a/core/src/main/java/dev/morphia/query/internal/MorphiaKeyCursor.java +++ b/core/src/main/java/dev/morphia/query/internal/MorphiaKeyCursor.java @@ -117,7 +117,7 @@ private I fromDocument(Class type, Document document) { aClass = mapper.getClass(document); } - DocumentReader reader = new DocumentReader(document); + DocumentReader reader = new DocumentReader(document, datastore.getMapper().getConversions()); return datastore.getCodecRegistry() .get(aClass) diff --git a/core/src/test/java/dev/morphia/test/TemplatedTestBase.java b/core/src/test/java/dev/morphia/test/TemplatedTestBase.java index 566dd1beaa5..452f849fd92 100644 --- a/core/src/test/java/dev/morphia/test/TemplatedTestBase.java +++ b/core/src/test/java/dev/morphia/test/TemplatedTestBase.java @@ -221,7 +221,7 @@ private List map(Class entityClass, List documents) { DecoderContext context = DecoderContext.builder().build(); return documents.stream() - .map(document -> codec.decode(new DocumentReader(document), context)) + .map(document -> (D) codec.decode(new DocumentReader(document, getMapper().getConversions()), context)) .collect(toList()); } } diff --git a/core/src/test/java/dev/morphia/test/TestBase.java b/core/src/test/java/dev/morphia/test/TestBase.java index 39c81482fef..db51c8c7c08 100644 --- a/core/src/test/java/dev/morphia/test/TestBase.java +++ b/core/src/test/java/dev/morphia/test/TestBase.java @@ -192,7 +192,7 @@ protected T fromDocument(Class type, Document document) { aClass = mapper.getClass(document); } - DocumentReader reader = new DocumentReader(document); + DocumentReader reader = new DocumentReader(document, mapper.getConversions()); return getDs().getCodecRegistry() .get(aClass) diff --git a/core/src/test/java/dev/morphia/test/mapping/codec/DocumentReaderTest.java b/core/src/test/java/dev/morphia/test/mapping/codec/DocumentReaderTest.java index 1f8bd52773b..48547211428 100644 --- a/core/src/test/java/dev/morphia/test/mapping/codec/DocumentReaderTest.java +++ b/core/src/test/java/dev/morphia/test/mapping/codec/DocumentReaderTest.java @@ -93,7 +93,7 @@ public void nestedDatabaseRead() { Document first = collection.find().first(); Parent decode = getDs().getCodecRegistry().get(Parent.class) - .decode(new DocumentReader(first), DecoderContext.builder().build()); + .decode(new DocumentReader(first, getMapper().getConversions()), DecoderContext.builder().build()); assertEquals(parent, decode); } @@ -280,7 +280,7 @@ private void readDocument(int count) { } private void setup(Document document) { - reader = new DocumentReader(document); + reader = new DocumentReader(document, getMapper().getConversions()); } private void step(Consumer function) { diff --git a/docs/modules/ROOT/examples/complete-morphia-config.properties b/docs/modules/ROOT/examples/complete-morphia-config.properties index 4b6fcc9e971..2172fbdec20 100644 --- a/docs/modules/ROOT/examples/complete-morphia-config.properties +++ b/docs/modules/ROOT/examples/complete-morphia-config.properties @@ -15,6 +15,10 @@ morphia.apply-indexes=false ###### morphia.auto-import-models=false ###### +# Required +###### +morphia.class-loader=jdk.internal.loader.ClassLoaders$AppClassLoader +###### # Optional ###### morphia.codec-provider= diff --git a/docs/modules/ROOT/examples/legacy-morphia-config.properties b/docs/modules/ROOT/examples/legacy-morphia-config.properties index 54bb6cdf4b8..a7edcad3d54 100644 --- a/docs/modules/ROOT/examples/legacy-morphia-config.properties +++ b/docs/modules/ROOT/examples/legacy-morphia-config.properties @@ -3,6 +3,10 @@ ###### morphia.auto-import-models=false ###### +# Required +###### +morphia.class-loader=jdk.internal.loader.ClassLoaders$AppClassLoader +###### # default=camelCase # possible values=camelCase, identity, kebabCase, lowerCase, snakeCase, fqcn ###### diff --git a/docs/modules/ROOT/examples/minimal-morphia-config.properties b/docs/modules/ROOT/examples/minimal-morphia-config.properties index c7c75f1aab2..afc46e46e20 100644 --- a/docs/modules/ROOT/examples/minimal-morphia-config.properties +++ b/docs/modules/ROOT/examples/minimal-morphia-config.properties @@ -1,4 +1,8 @@ ###### # default=true ###### -morphia.auto-import-models=false \ No newline at end of file +morphia.auto-import-models=false +###### +# Required +###### +morphia.class-loader=jdk.internal.loader.ClassLoaders$AppClassLoader \ No newline at end of file