From d0e18df7456c15f29bc98aabbc180b2f8cdcd1c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20=22Wega=22=20Weglarz?= <82312488+ThomasWega@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:35:34 +0100 Subject: [PATCH 1/6] Added class loader option to the project --- core/src/main/java/dev/morphia/Datastore.java | 2 ++ .../main/java/dev/morphia/DatastoreImpl.java | 5 ++++ .../morphia/config/ClassNameConverter.java | 10 +++++-- .../config/CodecProviderConverter.java | 4 +++ .../DiscriminatorFunctionConverter.java | 8 +++++- .../morphia/config/ManualMorphiaConfig.java | 7 +++++ .../morphia/config/MapperOptionsWrapper.java | 5 ++++ .../dev/morphia/config/MorphiaConfig.java | 27 ++++++++++++------- .../morphia/config/MorphiaConfigHelper.java | 8 +++--- .../config/NamingStrategyConverter.java | 8 +++++- .../morphia/config/QueryFactoryConverter.java | 4 +++ .../morphia/mapping/DiscriminatorLookup.java | 9 +++++-- .../main/java/dev/morphia/mapping/Mapper.java | 10 ++++--- .../dev/morphia/mapping/MapperOptions.java | 19 ++++++++++--- .../dev/morphia/mapping/codec/ClassCodec.java | 9 ++++++- .../morphia/mapping/codec/Conversions.java | 1 + .../codec/MorphiaTypesCodecProvider.java | 2 +- .../codec/references/ReferenceCodec.java | 2 +- 18 files changed, 112 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/dev/morphia/Datastore.java b/core/src/main/java/dev/morphia/Datastore.java index fb199538828..cafcddb5f32 100644 --- a/core/src/main/java/dev/morphia/Datastore.java +++ b/core/src/main/java/dev/morphia/Datastore.java @@ -635,6 +635,8 @@ default UpdateResult update(Query query, dev.morphia.query.UpdateOperatio @MorphiaInternal Mapper getMapper(); + ClassLoader getClassLoader(); + /** * @param transaction the transaction wrapper * @param the return type diff --git a/core/src/main/java/dev/morphia/DatastoreImpl.java b/core/src/main/java/dev/morphia/DatastoreImpl.java index 0f03133fa91..43d30841627 100644 --- a/core/src/main/java/dev/morphia/DatastoreImpl.java +++ b/core/src/main/java/dev/morphia/DatastoreImpl.java @@ -513,6 +513,11 @@ public Mapper getMapper() { return mapper; } + @Override + public ClassLoader getClassLoader() { + return mapper.getClassLoader(); + } + @Override public void shardCollections() { var entities = getMapper().getMappedEntities() diff --git a/core/src/main/java/dev/morphia/config/ClassNameConverter.java b/core/src/main/java/dev/morphia/config/ClassNameConverter.java index 5b3947e3ff5..4c347e69ba4 100644 --- a/core/src/main/java/dev/morphia/config/ClassNameConverter.java +++ b/core/src/main/java/dev/morphia/config/ClassNameConverter.java @@ -11,17 +11,23 @@ */ @MorphiaInternal public class ClassNameConverter implements Converter { + private final MorphiaConfig morphiaConfig; + + public ClassNameConverter(MorphiaConfig morphiaConfig) { + this.morphiaConfig = morphiaConfig; + } + @Override public Object convert(String value) throws IllegalArgumentException, NullPointerException { return loadClass(value); } - static Object loadClass(String value) { + Object loadClass(String value) { if (value == null || value.trim().equals("")) { return null; } try { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader classLoader = morphiaConfig.classLoader(); return Class.forName(value, true, classLoader).getDeclaredConstructor().newInstance(); } catch (ReflectiveOperationException e) { throw new MappingException(e.getMessage(), e); diff --git a/core/src/main/java/dev/morphia/config/CodecProviderConverter.java b/core/src/main/java/dev/morphia/config/CodecProviderConverter.java index 4cde0efa1c5..85ce6f0aa24 100644 --- a/core/src/main/java/dev/morphia/config/CodecProviderConverter.java +++ b/core/src/main/java/dev/morphia/config/CodecProviderConverter.java @@ -10,6 +10,10 @@ */ @MorphiaInternal public class CodecProviderConverter extends ClassNameConverter { + public CodecProviderConverter(MorphiaConfig morphiaConfig) { + super(morphiaConfig); + } + @Override public CodecProvider convert(String value) throws IllegalArgumentException, NullPointerException { return (CodecProvider) super.convert(value); diff --git a/core/src/main/java/dev/morphia/config/DiscriminatorFunctionConverter.java b/core/src/main/java/dev/morphia/config/DiscriminatorFunctionConverter.java index ed76ff10aef..550f0168b9f 100644 --- a/core/src/main/java/dev/morphia/config/DiscriminatorFunctionConverter.java +++ b/core/src/main/java/dev/morphia/config/DiscriminatorFunctionConverter.java @@ -17,6 +17,12 @@ */ @MorphiaInternal public class DiscriminatorFunctionConverter implements Converter { + private final MorphiaConfig morphiaConfig; + + public DiscriminatorFunctionConverter(MorphiaConfig morphiaConfig) { + this.morphiaConfig = morphiaConfig; + } + @Override public DiscriminatorFunction convert(String value) throws IllegalArgumentException, NullPointerException { try { @@ -30,7 +36,7 @@ public DiscriminatorFunction convert(String value) throws IllegalArgumentExcepti case "simpleName": return simpleName(); default: - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader classLoader = morphiaConfig.classLoader(); return (DiscriminatorFunction) Class.forName(value, true, classLoader).getDeclaredConstructor() .newInstance(); } diff --git a/core/src/main/java/dev/morphia/config/ManualMorphiaConfig.java b/core/src/main/java/dev/morphia/config/ManualMorphiaConfig.java index c750bae46db..abd35151176 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(); } /** @@ -100,6 +102,11 @@ public Boolean applyCaps() { return orDefault(applyCaps, FALSE); } + @Override + public ClassLoader classLoader() { + return orDefault(classLoader, Thread.currentThread().getContextClassLoader()); + } + public Boolean applyDocumentValidations() { return orDefault(applyDocumentValidations, FALSE); } diff --git a/core/src/main/java/dev/morphia/config/MapperOptionsWrapper.java b/core/src/main/java/dev/morphia/config/MapperOptionsWrapper.java index f62a47a3d46..2f9a7505283 100644 --- a/core/src/main/java/dev/morphia/config/MapperOptionsWrapper.java +++ b/core/src/main/java/dev/morphia/config/MapperOptionsWrapper.java @@ -33,6 +33,11 @@ public MapperOptionsWrapper(MapperOptions options, String database) { this.database = database; } + @Override + public ClassLoader classLoader() { + return options.getClassLoader(); + } + @Override public Boolean applyCaps() { return 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..47abbda88f8 100644 --- a/core/src/main/java/dev/morphia/config/MorphiaConfig.java +++ b/core/src/main/java/dev/morphia/config/MorphiaConfig.java @@ -38,8 +38,8 @@ * tweaks might be made to improve the experience. As of 3.0, the experimental label will be dropped and the format fixed for the * existing configuration values. * - * @since 2.4 * @morphia.experimental + * @since 2.4 */ @MorphiaExperimental @ConfigMapping(prefix = "morphia") @@ -57,12 +57,15 @@ static MorphiaConfig load() { * 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. - * * @return the loaded configuration * @since 3.0 */ static MorphiaConfig load(String path) { - List configSources = classPathSources(path, currentThread().getContextClassLoader()); + return load(path, currentThread().getContextClassLoader()); + } + + 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(); @@ -107,6 +110,15 @@ default MorphiaConfig collectionNaming(NamingStrategy value) { @WithDefault("false") Boolean applyCaps(); + ClassLoader classLoader(); + + default MorphiaConfig classLoader(ClassLoader value) { + var newConfig = new ManualMorphiaConfig(this); + + newConfig.classLoader = value; + return newConfig; + } + /** * Updates this configuration with a new value and returns a new instance. The original instance is unchanged. * @@ -124,8 +136,8 @@ default MorphiaConfig applyCaps(Boolean value) { /** * If true, document validations will be enabled for entities/collections with validation mappings. * - * @mongodb.driver.manual core/document-validation/ * @return true if the validations should be applied + * @mongodb.driver.manual core/document-validation/ * @see Validation */ @WithDefault("false") @@ -341,7 +353,6 @@ default MorphiaConfig ignoreFinals(Boolean value) { * changed. * * @return the update configuration - * * @since 3.0 */ default MorphiaConfig legacy() { @@ -526,9 +537,8 @@ default MorphiaConfig uuidRepresentation(UuidRepresentation value) { } /** - * - * @hidden * @return true if models should be automatically loaded from prebuilt structures. + * @hidden * @morphia.internal */ @MorphiaInternal @@ -536,9 +546,8 @@ default MorphiaConfig uuidRepresentation(UuidRepresentation value) { Boolean autoImportModels(); /** - * - * @hidden * @return a new instance with the updated configuration + * @hidden * @morphia.internal */ @MorphiaInternal diff --git a/core/src/main/java/dev/morphia/config/MorphiaConfigHelper.java b/core/src/main/java/dev/morphia/config/MorphiaConfigHelper.java index d4f2bc4f938..39dd0d2ea9a 100644 --- a/core/src/main/java/dev/morphia/config/MorphiaConfigHelper.java +++ b/core/src/main/java/dev/morphia/config/MorphiaConfigHelper.java @@ -78,7 +78,7 @@ public static String dumpConfigurationFile(MorphiaConfig config, boolean showCom .sorted(Comparator.comparing(Method::getName)) .filter(m -> !Modifier.isStatic(m.getModifiers())) .filter(m -> m.getParameterCount() == 0 && !m.getReturnType().equals(MorphiaConfig.class)) - .map(m -> getEntry(prefix, m)) + .map(m -> getEntry(config, prefix, m)) .collect(Collectors.toList()); } @@ -92,15 +92,15 @@ public String toString() { .collect(joining("\n")); } - private Entry getEntry(String prefix, Method m) { + private Entry getEntry(MorphiaConfig morphiaConfig, String prefix, Method m) { WithConverter annotation = m.getAnnotation(WithConverter.class); Converter converter = null; if (annotation != null) { try { converter = annotation .value() - .getDeclaredConstructor() - .newInstance(); + .getDeclaredConstructor(morphiaConfig.getClass()) + .newInstance(morphiaConfig); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } diff --git a/core/src/main/java/dev/morphia/config/NamingStrategyConverter.java b/core/src/main/java/dev/morphia/config/NamingStrategyConverter.java index aa566547c19..5646324440c 100644 --- a/core/src/main/java/dev/morphia/config/NamingStrategyConverter.java +++ b/core/src/main/java/dev/morphia/config/NamingStrategyConverter.java @@ -18,6 +18,12 @@ */ @MorphiaInternal public class NamingStrategyConverter implements Converter { + private final MorphiaConfig morphiaConfig; + + public NamingStrategyConverter(MorphiaConfig morphiaConfig) { + this.morphiaConfig = morphiaConfig; + } + @Override public NamingStrategy convert(String value) throws IllegalArgumentException, NullPointerException { try { @@ -33,7 +39,7 @@ public NamingStrategy convert(String value) throws IllegalArgumentException, Nul case "snakeCase": return snakeCase(); default: - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader classLoader = morphiaConfig.classLoader(); return (NamingStrategy) Class.forName(value, true, classLoader).getDeclaredConstructor().newInstance(); } } catch (ReflectiveOperationException e) { diff --git a/core/src/main/java/dev/morphia/config/QueryFactoryConverter.java b/core/src/main/java/dev/morphia/config/QueryFactoryConverter.java index 73b0d9aeb95..097a13a2562 100644 --- a/core/src/main/java/dev/morphia/config/QueryFactoryConverter.java +++ b/core/src/main/java/dev/morphia/config/QueryFactoryConverter.java @@ -9,6 +9,10 @@ */ @MorphiaInternal public class QueryFactoryConverter extends ClassNameConverter { + public QueryFactoryConverter(MorphiaConfig morphiaConfig) { + super(morphiaConfig); + } + @Override public QueryFactory convert(String value) { return (QueryFactory) super.convert(value); diff --git a/core/src/main/java/dev/morphia/mapping/DiscriminatorLookup.java b/core/src/main/java/dev/morphia/mapping/DiscriminatorLookup.java index 58aee312782..2ce11ebe058 100644 --- a/core/src/main/java/dev/morphia/mapping/DiscriminatorLookup.java +++ b/core/src/main/java/dev/morphia/mapping/DiscriminatorLookup.java @@ -38,9 +38,14 @@ */ @MorphiaInternal public final class DiscriminatorLookup { + private final Mapper mapper; private final Map discriminatorClassMap = new ConcurrentHashMap<>(); private final Set packages = new ConcurrentSkipListSet<>(); + public DiscriminatorLookup(Mapper mapper) { + this.mapper = mapper; + } + /** * 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, mapper.getClassLoader()); } 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, mapper.getClassLoader()); } catch (ClassNotFoundException e) { // Ignore } diff --git a/core/src/main/java/dev/morphia/mapping/Mapper.java b/core/src/main/java/dev/morphia/mapping/Mapper.java index 08f9ea4a332..4c316a6ae16 100644 --- a/core/src/main/java/dev/morphia/mapping/Mapper.java +++ b/core/src/main/java/dev/morphia/mapping/Mapper.java @@ -89,7 +89,7 @@ public class Mapper { @MorphiaInternal public Mapper(MorphiaConfig config) { this.config = config; - discriminatorLookup = new DiscriminatorLookup(); + discriminatorLookup = new DiscriminatorLookup(this); } /** @@ -99,11 +99,15 @@ public Mapper(MorphiaConfig config) { */ public Mapper(Mapper other) { config = other.config; - discriminatorLookup = new DiscriminatorLookup(); + discriminatorLookup = new DiscriminatorLookup(this); other.mappedEntities.values().forEach(entity -> clone(entity)); listeners.addAll(other.listeners); } + public ClassLoader getClassLoader() { + return config.classLoader(); + } + @Nullable private EntityModel clone(@Nullable EntityModel original) { if (original == null) { @@ -618,7 +622,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, getClassLoader())); } catch (Throwable ignored) { } } diff --git a/core/src/main/java/dev/morphia/mapping/MapperOptions.java b/core/src/main/java/dev/morphia/mapping/MapperOptions.java index f8bbc3bdd3e..291aa7401e8 100644 --- a/core/src/main/java/dev/morphia/mapping/MapperOptions.java +++ b/core/src/main/java/dev/morphia/mapping/MapperOptions.java @@ -50,6 +50,7 @@ public class MapperOptions { private final QueryFactory queryFactory; private final boolean enablePolymorphicQueries; private final CodecProvider codecProvider; + private final ClassLoader classLoader; private MapperOptions(Builder builder) { autoImportModels = builder.autoImportModels; @@ -70,6 +71,7 @@ private MapperOptions(Builder builder) { storeEmpties = builder.storeEmpties(); storeNulls = builder.storeNulls(); uuidRepresentation = builder.uuidRepresentation(); + classLoader = builder.getClassLoader(); } /** @@ -103,6 +105,10 @@ public static Builder legacy() { .queryFactory(new LegacyQueryFactory()); } + public ClassLoader getClassLoader() { + return classLoader; + } + /** * @return true if {@link EntityModelImporter} instances should be loaded * @morphia.internal @@ -239,9 +245,9 @@ public boolean isStoreNulls() { } /** + * @return * @since 2.4 * @deprecated 3.0 will evaluate both field and getter/setters for annotation so this setting becomes vestigial - * @return */ @Deprecated(since = "2.4", forRemoval = true) public PropertyDiscovery propertyDiscovery() { @@ -270,7 +276,7 @@ public String toConfigFormat(String database, boolean showComplete) { /** * A builder class for setting mapping options - * + * * @deprecated use the new configuration file mechanism. See the * website docs for more information. */ @@ -296,6 +302,7 @@ public static final class Builder { private QueryFactory queryFactory = new DefaultQueryFactory(); private PropertyDiscovery propertyDiscovery = FIELDS; private MapperOptions options; + private ClassLoader classLoader; private Builder() { } @@ -318,6 +325,7 @@ private Builder(MapperOptions original) { uuidRepresentation = original.uuidRepresentation; queryFactory = original.queryFactory; propertyDiscovery = original.propertyDiscovery; + classLoader = original.classLoader; } /** @@ -374,6 +382,7 @@ public Builder cacheClassLookups(boolean cacheClassLookups) { */ @SuppressFBWarnings("EI_EXPOSE_REP2") public Builder classLoader(ClassLoader classLoader) { + this.classLoader = classLoader; return this; } @@ -513,9 +522,9 @@ public Builder mapSubPackages(boolean mapSubPackages) { * Determines how properties are discovered on mapped entities * * @param discovery the discovery strategy to use - * @deprecated 3.0 will evaluate both field and getter/setters for annotation so this setting becomes vestigial * @return this * @since 2.2 + * @deprecated 3.0 will evaluate both field and getter/setters for annotation so this setting becomes vestigial */ @Deprecated(since = "2.4", forRemoval = true) public Builder propertyDiscovery(PropertyDiscovery discovery) { @@ -602,6 +611,10 @@ private void assertNotLocked() { } } + public ClassLoader getClassLoader() { + return classLoader; + } + private DateStorage dateStorage() { return dateStorage; } 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..bef0e8c40d8 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java @@ -1,5 +1,6 @@ package dev.morphia.mapping.codec; +import dev.morphia.Datastore; import dev.morphia.mapping.MappingException; import org.bson.BsonReader; @@ -12,10 +13,16 @@ * Defines a codec for Class references */ public class ClassCodec implements Codec { + private final Datastore datastore; + + public ClassCodec(Datastore datastore) { + this.datastore = datastore; + } + @Override public Class decode(BsonReader reader, DecoderContext decoderContext) { try { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader classLoader = datastore.getClassLoader(); return Class.forName(reader.readString(), true, classLoader); } 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..79b8d4fc8c6 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/Conversions.java +++ b/core/src/main/java/dev/morphia/mapping/codec/Conversions.java @@ -99,6 +99,7 @@ private static void registerStringConversions() { register(Class.class, String.class, Class::getName); register(String.class, Class.class, className -> { try { + // FIXME still old classloader! return Thread.currentThread().getContextClassLoader().loadClass(className); } catch (ClassNotFoundException e) { throw new MappingException(e.getMessage(), e); 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..6ad490f382f 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)); addCodec(new CenterCodec()); addCodec(new KeyCodec(datastore)); addCodec(new LocaleCodec()); 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..b684c8b3032 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 @@ -344,7 +344,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(datastore.getClassLoader(), Default.WRAPPER) .getLoaded(); } From 388af9b3bdbcc0daf5174b5c4485a94a61929b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20=22Wega=22=20Weglarz?= <82312488+ThomasWega@users.noreply.github.com> Date: Sat, 14 Feb 2026 19:42:40 +0100 Subject: [PATCH 2/6] Revert "Added class loader option to the project" This reverts commit d0e18df7456c15f29bc98aabbc180b2f8cdcd1c0. --- core/src/main/java/dev/morphia/Datastore.java | 2 -- .../main/java/dev/morphia/DatastoreImpl.java | 5 ---- .../morphia/config/ClassNameConverter.java | 10 ++----- .../config/CodecProviderConverter.java | 4 --- .../DiscriminatorFunctionConverter.java | 8 +----- .../morphia/config/ManualMorphiaConfig.java | 7 ----- .../morphia/config/MapperOptionsWrapper.java | 5 ---- .../dev/morphia/config/MorphiaConfig.java | 27 +++++++------------ .../morphia/config/MorphiaConfigHelper.java | 8 +++--- .../config/NamingStrategyConverter.java | 8 +----- .../morphia/config/QueryFactoryConverter.java | 4 --- .../morphia/mapping/DiscriminatorLookup.java | 9 ++----- .../main/java/dev/morphia/mapping/Mapper.java | 10 +++---- .../dev/morphia/mapping/MapperOptions.java | 19 +++---------- .../dev/morphia/mapping/codec/ClassCodec.java | 9 +------ .../morphia/mapping/codec/Conversions.java | 1 - .../codec/MorphiaTypesCodecProvider.java | 2 +- .../codec/references/ReferenceCodec.java | 2 +- 18 files changed, 28 insertions(+), 112 deletions(-) diff --git a/core/src/main/java/dev/morphia/Datastore.java b/core/src/main/java/dev/morphia/Datastore.java index cafcddb5f32..fb199538828 100644 --- a/core/src/main/java/dev/morphia/Datastore.java +++ b/core/src/main/java/dev/morphia/Datastore.java @@ -635,8 +635,6 @@ default UpdateResult update(Query query, dev.morphia.query.UpdateOperatio @MorphiaInternal Mapper getMapper(); - ClassLoader getClassLoader(); - /** * @param transaction the transaction wrapper * @param the return type diff --git a/core/src/main/java/dev/morphia/DatastoreImpl.java b/core/src/main/java/dev/morphia/DatastoreImpl.java index 43d30841627..0f03133fa91 100644 --- a/core/src/main/java/dev/morphia/DatastoreImpl.java +++ b/core/src/main/java/dev/morphia/DatastoreImpl.java @@ -513,11 +513,6 @@ public Mapper getMapper() { return mapper; } - @Override - public ClassLoader getClassLoader() { - return mapper.getClassLoader(); - } - @Override public void shardCollections() { var entities = getMapper().getMappedEntities() diff --git a/core/src/main/java/dev/morphia/config/ClassNameConverter.java b/core/src/main/java/dev/morphia/config/ClassNameConverter.java index 4c347e69ba4..5b3947e3ff5 100644 --- a/core/src/main/java/dev/morphia/config/ClassNameConverter.java +++ b/core/src/main/java/dev/morphia/config/ClassNameConverter.java @@ -11,23 +11,17 @@ */ @MorphiaInternal public class ClassNameConverter implements Converter { - private final MorphiaConfig morphiaConfig; - - public ClassNameConverter(MorphiaConfig morphiaConfig) { - this.morphiaConfig = morphiaConfig; - } - @Override public Object convert(String value) throws IllegalArgumentException, NullPointerException { return loadClass(value); } - Object loadClass(String value) { + static Object loadClass(String value) { if (value == null || value.trim().equals("")) { return null; } try { - ClassLoader classLoader = morphiaConfig.classLoader(); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); return Class.forName(value, true, classLoader).getDeclaredConstructor().newInstance(); } catch (ReflectiveOperationException e) { throw new MappingException(e.getMessage(), e); diff --git a/core/src/main/java/dev/morphia/config/CodecProviderConverter.java b/core/src/main/java/dev/morphia/config/CodecProviderConverter.java index 85ce6f0aa24..4cde0efa1c5 100644 --- a/core/src/main/java/dev/morphia/config/CodecProviderConverter.java +++ b/core/src/main/java/dev/morphia/config/CodecProviderConverter.java @@ -10,10 +10,6 @@ */ @MorphiaInternal public class CodecProviderConverter extends ClassNameConverter { - public CodecProviderConverter(MorphiaConfig morphiaConfig) { - super(morphiaConfig); - } - @Override public CodecProvider convert(String value) throws IllegalArgumentException, NullPointerException { return (CodecProvider) super.convert(value); diff --git a/core/src/main/java/dev/morphia/config/DiscriminatorFunctionConverter.java b/core/src/main/java/dev/morphia/config/DiscriminatorFunctionConverter.java index 550f0168b9f..ed76ff10aef 100644 --- a/core/src/main/java/dev/morphia/config/DiscriminatorFunctionConverter.java +++ b/core/src/main/java/dev/morphia/config/DiscriminatorFunctionConverter.java @@ -17,12 +17,6 @@ */ @MorphiaInternal public class DiscriminatorFunctionConverter implements Converter { - private final MorphiaConfig morphiaConfig; - - public DiscriminatorFunctionConverter(MorphiaConfig morphiaConfig) { - this.morphiaConfig = morphiaConfig; - } - @Override public DiscriminatorFunction convert(String value) throws IllegalArgumentException, NullPointerException { try { @@ -36,7 +30,7 @@ public DiscriminatorFunction convert(String value) throws IllegalArgumentExcepti case "simpleName": return simpleName(); default: - ClassLoader classLoader = morphiaConfig.classLoader(); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); return (DiscriminatorFunction) Class.forName(value, true, classLoader).getDeclaredConstructor() .newInstance(); } diff --git a/core/src/main/java/dev/morphia/config/ManualMorphiaConfig.java b/core/src/main/java/dev/morphia/config/ManualMorphiaConfig.java index abd35151176..c750bae46db 100644 --- a/core/src/main/java/dev/morphia/config/ManualMorphiaConfig.java +++ b/core/src/main/java/dev/morphia/config/ManualMorphiaConfig.java @@ -48,7 +48,6 @@ public class ManualMorphiaConfig implements MorphiaConfig { Boolean storeEmpties; Boolean storeNulls; UuidRepresentation uuidRepresentation; - ClassLoader classLoader; /** * @hidden @@ -79,7 +78,6 @@ public ManualMorphiaConfig(MorphiaConfig base) { storeEmpties = base.storeEmpties(); storeNulls = base.storeNulls(); uuidRepresentation = base.uuidRepresentation(); - classLoader = base.classLoader(); } /** @@ -102,11 +100,6 @@ public Boolean applyCaps() { return orDefault(applyCaps, FALSE); } - @Override - public ClassLoader classLoader() { - return orDefault(classLoader, Thread.currentThread().getContextClassLoader()); - } - public Boolean applyDocumentValidations() { return orDefault(applyDocumentValidations, FALSE); } diff --git a/core/src/main/java/dev/morphia/config/MapperOptionsWrapper.java b/core/src/main/java/dev/morphia/config/MapperOptionsWrapper.java index 2f9a7505283..f62a47a3d46 100644 --- a/core/src/main/java/dev/morphia/config/MapperOptionsWrapper.java +++ b/core/src/main/java/dev/morphia/config/MapperOptionsWrapper.java @@ -33,11 +33,6 @@ public MapperOptionsWrapper(MapperOptions options, String database) { this.database = database; } - @Override - public ClassLoader classLoader() { - return options.getClassLoader(); - } - @Override public Boolean applyCaps() { return false; diff --git a/core/src/main/java/dev/morphia/config/MorphiaConfig.java b/core/src/main/java/dev/morphia/config/MorphiaConfig.java index 47abbda88f8..35635739123 100644 --- a/core/src/main/java/dev/morphia/config/MorphiaConfig.java +++ b/core/src/main/java/dev/morphia/config/MorphiaConfig.java @@ -38,8 +38,8 @@ * tweaks might be made to improve the experience. As of 3.0, the experimental label will be dropped and the format fixed for the * existing configuration values. * - * @morphia.experimental * @since 2.4 + * @morphia.experimental */ @MorphiaExperimental @ConfigMapping(prefix = "morphia") @@ -57,15 +57,12 @@ static MorphiaConfig load() { * 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. + * * @return the loaded configuration * @since 3.0 */ static MorphiaConfig load(String path) { - return load(path, currentThread().getContextClassLoader()); - } - - static MorphiaConfig load(String path, ClassLoader classLoader) { - List configSources = classPathSources(path, classLoader); + List configSources = classPathSources(path, currentThread().getContextClassLoader()); if (configSources.isEmpty()) { LoggerFactory.getLogger(MorphiaConfig.class).warn(Sofia.missingConfigFile(path)); return new ManualMorphiaConfig(); @@ -110,15 +107,6 @@ default MorphiaConfig collectionNaming(NamingStrategy value) { @WithDefault("false") Boolean applyCaps(); - ClassLoader classLoader(); - - default MorphiaConfig classLoader(ClassLoader value) { - var newConfig = new ManualMorphiaConfig(this); - - newConfig.classLoader = value; - return newConfig; - } - /** * Updates this configuration with a new value and returns a new instance. The original instance is unchanged. * @@ -136,8 +124,8 @@ default MorphiaConfig applyCaps(Boolean value) { /** * If true, document validations will be enabled for entities/collections with validation mappings. * - * @return true if the validations should be applied * @mongodb.driver.manual core/document-validation/ + * @return true if the validations should be applied * @see Validation */ @WithDefault("false") @@ -353,6 +341,7 @@ default MorphiaConfig ignoreFinals(Boolean value) { * changed. * * @return the update configuration + * * @since 3.0 */ default MorphiaConfig legacy() { @@ -537,8 +526,9 @@ default MorphiaConfig uuidRepresentation(UuidRepresentation value) { } /** - * @return true if models should be automatically loaded from prebuilt structures. + * * @hidden + * @return true if models should be automatically loaded from prebuilt structures. * @morphia.internal */ @MorphiaInternal @@ -546,8 +536,9 @@ default MorphiaConfig uuidRepresentation(UuidRepresentation value) { Boolean autoImportModels(); /** - * @return a new instance with the updated configuration + * * @hidden + * @return a new instance with the updated configuration * @morphia.internal */ @MorphiaInternal diff --git a/core/src/main/java/dev/morphia/config/MorphiaConfigHelper.java b/core/src/main/java/dev/morphia/config/MorphiaConfigHelper.java index 39dd0d2ea9a..d4f2bc4f938 100644 --- a/core/src/main/java/dev/morphia/config/MorphiaConfigHelper.java +++ b/core/src/main/java/dev/morphia/config/MorphiaConfigHelper.java @@ -78,7 +78,7 @@ public static String dumpConfigurationFile(MorphiaConfig config, boolean showCom .sorted(Comparator.comparing(Method::getName)) .filter(m -> !Modifier.isStatic(m.getModifiers())) .filter(m -> m.getParameterCount() == 0 && !m.getReturnType().equals(MorphiaConfig.class)) - .map(m -> getEntry(config, prefix, m)) + .map(m -> getEntry(prefix, m)) .collect(Collectors.toList()); } @@ -92,15 +92,15 @@ public String toString() { .collect(joining("\n")); } - private Entry getEntry(MorphiaConfig morphiaConfig, String prefix, Method m) { + private Entry getEntry(String prefix, Method m) { WithConverter annotation = m.getAnnotation(WithConverter.class); Converter converter = null; if (annotation != null) { try { converter = annotation .value() - .getDeclaredConstructor(morphiaConfig.getClass()) - .newInstance(morphiaConfig); + .getDeclaredConstructor() + .newInstance(); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } diff --git a/core/src/main/java/dev/morphia/config/NamingStrategyConverter.java b/core/src/main/java/dev/morphia/config/NamingStrategyConverter.java index 5646324440c..aa566547c19 100644 --- a/core/src/main/java/dev/morphia/config/NamingStrategyConverter.java +++ b/core/src/main/java/dev/morphia/config/NamingStrategyConverter.java @@ -18,12 +18,6 @@ */ @MorphiaInternal public class NamingStrategyConverter implements Converter { - private final MorphiaConfig morphiaConfig; - - public NamingStrategyConverter(MorphiaConfig morphiaConfig) { - this.morphiaConfig = morphiaConfig; - } - @Override public NamingStrategy convert(String value) throws IllegalArgumentException, NullPointerException { try { @@ -39,7 +33,7 @@ public NamingStrategy convert(String value) throws IllegalArgumentException, Nul case "snakeCase": return snakeCase(); default: - ClassLoader classLoader = morphiaConfig.classLoader(); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); return (NamingStrategy) Class.forName(value, true, classLoader).getDeclaredConstructor().newInstance(); } } catch (ReflectiveOperationException e) { diff --git a/core/src/main/java/dev/morphia/config/QueryFactoryConverter.java b/core/src/main/java/dev/morphia/config/QueryFactoryConverter.java index 097a13a2562..73b0d9aeb95 100644 --- a/core/src/main/java/dev/morphia/config/QueryFactoryConverter.java +++ b/core/src/main/java/dev/morphia/config/QueryFactoryConverter.java @@ -9,10 +9,6 @@ */ @MorphiaInternal public class QueryFactoryConverter extends ClassNameConverter { - public QueryFactoryConverter(MorphiaConfig morphiaConfig) { - super(morphiaConfig); - } - @Override public QueryFactory convert(String value) { return (QueryFactory) super.convert(value); diff --git a/core/src/main/java/dev/morphia/mapping/DiscriminatorLookup.java b/core/src/main/java/dev/morphia/mapping/DiscriminatorLookup.java index 2ce11ebe058..58aee312782 100644 --- a/core/src/main/java/dev/morphia/mapping/DiscriminatorLookup.java +++ b/core/src/main/java/dev/morphia/mapping/DiscriminatorLookup.java @@ -38,14 +38,9 @@ */ @MorphiaInternal public final class DiscriminatorLookup { - private final Mapper mapper; private final Map discriminatorClassMap = new ConcurrentHashMap<>(); private final Set packages = new ConcurrentSkipListSet<>(); - public DiscriminatorLookup(Mapper mapper) { - this.mapper = mapper; - } - /** * Adds a model to the map * @@ -69,7 +64,7 @@ public void addModel(EntityModel entityModel) { public Class lookup(String discriminator) { if (discriminatorClassMap.containsKey(discriminator)) { try { - return Class.forName(discriminatorClassMap.get(discriminator), true, mapper.getClassLoader()); + return Class.forName(discriminatorClassMap.get(discriminator), true, Thread.currentThread().getContextClassLoader()); } catch (ClassNotFoundException e) { throw new MappingException(e.getMessage(), e); } @@ -90,7 +85,7 @@ public Class lookup(String discriminator) { private Class getClassForName(String discriminator) { Class clazz = null; try { - clazz = Class.forName(discriminator, true, mapper.getClassLoader()); + clazz = Class.forName(discriminator, true, Thread.currentThread().getContextClassLoader()); } catch (ClassNotFoundException e) { // Ignore } diff --git a/core/src/main/java/dev/morphia/mapping/Mapper.java b/core/src/main/java/dev/morphia/mapping/Mapper.java index 4c316a6ae16..08f9ea4a332 100644 --- a/core/src/main/java/dev/morphia/mapping/Mapper.java +++ b/core/src/main/java/dev/morphia/mapping/Mapper.java @@ -89,7 +89,7 @@ public class Mapper { @MorphiaInternal public Mapper(MorphiaConfig config) { this.config = config; - discriminatorLookup = new DiscriminatorLookup(this); + discriminatorLookup = new DiscriminatorLookup(); } /** @@ -99,15 +99,11 @@ public Mapper(MorphiaConfig config) { */ public Mapper(Mapper other) { config = other.config; - discriminatorLookup = new DiscriminatorLookup(this); + discriminatorLookup = new DiscriminatorLookup(); other.mappedEntities.values().forEach(entity -> clone(entity)); listeners.addAll(other.listeners); } - public ClassLoader getClassLoader() { - return config.classLoader(); - } - @Nullable private EntityModel clone(@Nullable EntityModel original) { if (original == null) { @@ -622,7 +618,7 @@ private List getClasses(String packageName) try (ScanResult scanResult = classGraph.scan()) { for (ClassInfo classInfo : scanResult.getAllClasses()) { try { - classes.add(Class.forName(classInfo.getName(), true, getClassLoader())); + classes.add(Class.forName(classInfo.getName(), true, Thread.currentThread().getContextClassLoader())); } catch (Throwable ignored) { } } diff --git a/core/src/main/java/dev/morphia/mapping/MapperOptions.java b/core/src/main/java/dev/morphia/mapping/MapperOptions.java index 291aa7401e8..f8bbc3bdd3e 100644 --- a/core/src/main/java/dev/morphia/mapping/MapperOptions.java +++ b/core/src/main/java/dev/morphia/mapping/MapperOptions.java @@ -50,7 +50,6 @@ public class MapperOptions { private final QueryFactory queryFactory; private final boolean enablePolymorphicQueries; private final CodecProvider codecProvider; - private final ClassLoader classLoader; private MapperOptions(Builder builder) { autoImportModels = builder.autoImportModels; @@ -71,7 +70,6 @@ private MapperOptions(Builder builder) { storeEmpties = builder.storeEmpties(); storeNulls = builder.storeNulls(); uuidRepresentation = builder.uuidRepresentation(); - classLoader = builder.getClassLoader(); } /** @@ -105,10 +103,6 @@ public static Builder legacy() { .queryFactory(new LegacyQueryFactory()); } - public ClassLoader getClassLoader() { - return classLoader; - } - /** * @return true if {@link EntityModelImporter} instances should be loaded * @morphia.internal @@ -245,9 +239,9 @@ public boolean isStoreNulls() { } /** - * @return * @since 2.4 * @deprecated 3.0 will evaluate both field and getter/setters for annotation so this setting becomes vestigial + * @return */ @Deprecated(since = "2.4", forRemoval = true) public PropertyDiscovery propertyDiscovery() { @@ -276,7 +270,7 @@ public String toConfigFormat(String database, boolean showComplete) { /** * A builder class for setting mapping options - * + * * @deprecated use the new configuration file mechanism. See the * website docs for more information. */ @@ -302,7 +296,6 @@ public static final class Builder { private QueryFactory queryFactory = new DefaultQueryFactory(); private PropertyDiscovery propertyDiscovery = FIELDS; private MapperOptions options; - private ClassLoader classLoader; private Builder() { } @@ -325,7 +318,6 @@ private Builder(MapperOptions original) { uuidRepresentation = original.uuidRepresentation; queryFactory = original.queryFactory; propertyDiscovery = original.propertyDiscovery; - classLoader = original.classLoader; } /** @@ -382,7 +374,6 @@ public Builder cacheClassLookups(boolean cacheClassLookups) { */ @SuppressFBWarnings("EI_EXPOSE_REP2") public Builder classLoader(ClassLoader classLoader) { - this.classLoader = classLoader; return this; } @@ -522,9 +513,9 @@ public Builder mapSubPackages(boolean mapSubPackages) { * Determines how properties are discovered on mapped entities * * @param discovery the discovery strategy to use + * @deprecated 3.0 will evaluate both field and getter/setters for annotation so this setting becomes vestigial * @return this * @since 2.2 - * @deprecated 3.0 will evaluate both field and getter/setters for annotation so this setting becomes vestigial */ @Deprecated(since = "2.4", forRemoval = true) public Builder propertyDiscovery(PropertyDiscovery discovery) { @@ -611,10 +602,6 @@ private void assertNotLocked() { } } - public ClassLoader getClassLoader() { - return classLoader; - } - private DateStorage dateStorage() { return dateStorage; } 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 bef0e8c40d8..6e8967e7d05 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java @@ -1,6 +1,5 @@ package dev.morphia.mapping.codec; -import dev.morphia.Datastore; import dev.morphia.mapping.MappingException; import org.bson.BsonReader; @@ -13,16 +12,10 @@ * Defines a codec for Class references */ public class ClassCodec implements Codec { - private final Datastore datastore; - - public ClassCodec(Datastore datastore) { - this.datastore = datastore; - } - @Override public Class decode(BsonReader reader, DecoderContext decoderContext) { try { - ClassLoader classLoader = datastore.getClassLoader(); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); return Class.forName(reader.readString(), true, classLoader); } 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 79b8d4fc8c6..4aa781e666e 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/Conversions.java +++ b/core/src/main/java/dev/morphia/mapping/codec/Conversions.java @@ -99,7 +99,6 @@ private static void registerStringConversions() { register(Class.class, String.class, Class::getName); register(String.class, Class.class, className -> { try { - // FIXME still old classloader! return Thread.currentThread().getContextClassLoader().loadClass(className); } catch (ClassNotFoundException e) { throw new MappingException(e.getMessage(), e); 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 6ad490f382f..44a58c8f1df 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(datastore)); + addCodec(new ClassCodec()); addCodec(new CenterCodec()); addCodec(new KeyCodec(datastore)); addCodec(new LocaleCodec()); 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 b684c8b3032..43263c299e2 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 @@ -344,7 +344,7 @@ private Class makeProxy() { .intercept(InvocationHandlerAdapter.toField(FIELD_INVOCATION_HANDLER)) .defineField(FIELD_INVOCATION_HANDLER, InvocationHandler.class, Visibility.PRIVATE) .make() - .load(datastore.getClassLoader(), Default.WRAPPER) + .load(Thread.currentThread().getContextClassLoader(), Default.WRAPPER) .getLoaded(); } From 75527d0abad3dd1300ab98db16024d91c27f3f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20=22Wega=22=20Weglarz?= <82312488+ThomasWega@users.noreply.github.com> Date: Sat, 14 Feb 2026 19:50:13 +0100 Subject: [PATCH 3/6] Reworked class loader approach --- .../main/java/dev/morphia/DatastoreImpl.java | 7 ++++- core/src/main/java/dev/morphia/Morphia.java | 12 +++++++++ .../dev/morphia/config/MorphiaConfig.java | 25 +++++++++++++++++- .../morphia/mapping/DiscriminatorLookup.java | 9 +++++-- .../main/java/dev/morphia/mapping/Mapper.java | 26 ++++++++++++++++--- .../dev/morphia/mapping/codec/ClassCodec.java | 9 ++++++- .../morphia/mapping/codec/Conversions.java | 1 + .../codec/MorphiaTypesCodecProvider.java | 2 +- .../codec/references/ReferenceCodec.java | 2 +- 9 files changed, 83 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/dev/morphia/DatastoreImpl.java b/core/src/main/java/dev/morphia/DatastoreImpl.java index 0f03133fa91..cca8550b8a6 100644 --- a/core/src/main/java/dev/morphia/DatastoreImpl.java +++ b/core/src/main/java/dev/morphia/DatastoreImpl.java @@ -104,11 +104,16 @@ public class DatastoreImpl implements AdvancedDatastore { public List morphiaCodecProviders = new ArrayList<>(); private MongoDatabase database; private DatastoreOperations operations; + private ClassLoader classLoader; public DatastoreImpl(MongoClient client, MorphiaConfig config) { + this(client, config, Thread.currentThread().getContextClassLoader()); + } + + public DatastoreImpl(MongoClient client, MorphiaConfig config, ClassLoader classLoader) { this.mongoClient = client; this.database = mongoClient.getDatabase(config.database()); - this.mapper = new Mapper(config); + this.mapper = new Mapper(config, classLoader); this.queryFactory = mapper.getConfig().queryFactory(); importModels(); diff --git a/core/src/main/java/dev/morphia/Morphia.java b/core/src/main/java/dev/morphia/Morphia.java index 7c784fc7d41..fe30e3fed02 100644 --- a/core/src/main/java/dev/morphia/Morphia.java +++ b/core/src/main/java/dev/morphia/Morphia.java @@ -89,4 +89,16 @@ public static Datastore createDatastore(MongoClient mongoClient, MorphiaConfig c return new DatastoreImpl(mongoClient, config); } + /** + * Creates a Datastore configured via config file + * + * @param mongoClient the client to use + * @param config the configuration to use + * @param classLoader the classloader to use when scanning for entities and codecs. If null, the default classloader will be used. + * @return a Datastore that you can use to interact with MongoDB + * @since 3.0.0 + */ + public static Datastore createDatastore(MongoClient mongoClient, MorphiaConfig config, ClassLoader classLoader) { + return new DatastoreImpl(mongoClient, config, classLoader); + } } diff --git a/core/src/main/java/dev/morphia/config/MorphiaConfig.java b/core/src/main/java/dev/morphia/config/MorphiaConfig.java index 35635739123..35a9e82ff04 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,7 +72,20 @@ 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(); 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/Mapper.java b/core/src/main/java/dev/morphia/mapping/Mapper.java index 08f9ea4a332..4c4934d3cf2 100644 --- a/core/src/main/java/dev/morphia/mapping/Mapper.java +++ b/core/src/main/java/dev/morphia/mapping/Mapper.java @@ -78,6 +78,7 @@ public class Mapper { private final MorphiaConfig config; private final DiscriminatorLookup discriminatorLookup; private final Object entityRegistrationMonitor = new Object(); + private final ClassLoader classLoader; /** * Creates a Mapper with the given options. @@ -88,8 +89,22 @@ public class Mapper { */ @MorphiaInternal public Mapper(MorphiaConfig config) { + this(config, Thread.currentThread().getContextClassLoader()); + } + + /** + * Creates a Mapper with the given options and classloader. + * + * @param config the config to use + * @param classLoader the classloader to use when looking up classes by discriminator + * @morphia.internal + * @hidden + */ + @MorphiaInternal + public Mapper(MorphiaConfig config, ClassLoader classLoader) { this.config = config; - discriminatorLookup = new DiscriminatorLookup(); + this.classLoader = classLoader; + discriminatorLookup = new DiscriminatorLookup(classLoader); } /** @@ -99,7 +114,8 @@ public Mapper(MorphiaConfig config) { */ public Mapper(Mapper other) { config = other.config; - discriminatorLookup = new DiscriminatorLookup(); + classLoader = other.classLoader; + discriminatorLookup = new DiscriminatorLookup(other.getClassLoader()); other.mappedEntities.values().forEach(entity -> clone(entity)); listeners.addAll(other.listeners); } @@ -172,6 +188,10 @@ public PropertyModel findIdProperty(Class type) { return idField; } + public ClassLoader getClassLoader() { + return classLoader; + } + /** * Gets the class as defined by any discriminator field * @@ -618,7 +638,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, classLoader)); } catch (Throwable ignored) { } } 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..0dc5edf06fd 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java @@ -1,5 +1,6 @@ package dev.morphia.mapping.codec; +import dev.morphia.Datastore; import dev.morphia.mapping.MappingException; import org.bson.BsonReader; @@ -12,10 +13,16 @@ * Defines a codec for Class references */ public class ClassCodec implements Codec { + private final Datastore datastore; + + public ClassCodec(Datastore datastore) { + this.datastore = datastore; + } + @Override public Class decode(BsonReader reader, DecoderContext decoderContext) { try { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader classLoader = datastore.getMapper().getClassLoader(); return Class.forName(reader.readString(), true, classLoader); } 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..7fb01ff7386 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/Conversions.java +++ b/core/src/main/java/dev/morphia/mapping/codec/Conversions.java @@ -99,6 +99,7 @@ private static void registerStringConversions() { register(Class.class, String.class, Class::getName); register(String.class, Class.class, className -> { try { + // FIXME incorrect classloader return Thread.currentThread().getContextClassLoader().loadClass(className); } catch (ClassNotFoundException e) { throw new MappingException(e.getMessage(), e); 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..6ad490f382f 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)); addCodec(new CenterCodec()); addCodec(new KeyCodec(datastore)); addCodec(new LocaleCodec()); 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..354ea44fe07 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 @@ -344,7 +344,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(); } From 93832c660d6581bfce0ba39b264dc05a33f547aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20=22Wega=22=20Weglarz?= <82312488+ThomasWega@users.noreply.github.com> Date: Sat, 14 Feb 2026 21:22:55 +0100 Subject: [PATCH 4/6] Conversions now use classloader as well and all it's usages. --- core/src/main/java/dev/morphia/Datastore.java | 5 + .../main/java/dev/morphia/DatastoreImpl.java | 7 +- .../morphia/aggregation/AggregationImpl.java | 4 +- .../mapping/codec/ArrayFieldAccessor.java | 6 +- .../dev/morphia/mapping/codec/ClassCodec.java | 2 +- .../morphia/mapping/codec/Conversions.java | 109 ++++++++++++------ .../mapping/codec/MorphiaCodecProvider.java | 2 +- .../mapping/codec/MorphiaMapCodec.java | 2 +- .../MorphiaMapPropertyCodecProvider.java | 13 ++- .../mapping/codec/pojo/EntityDecoder.java | 2 +- .../mapping/codec/pojo/EntityModel.java | 7 ++ .../codec/pojo/EntityModelBuilder.java | 4 + .../mapping/codec/pojo/LifecycleDecoder.java | 4 +- .../mapping/codec/pojo/MorphiaCodec.java | 2 +- .../mapping/codec/pojo/PropertyModel.java | 2 +- .../mapping/codec/reader/DocumentReader.java | 6 +- .../codec/references/ReferenceCodec.java | 10 +- .../mapping/conventions/FieldDiscovery.java | 6 +- .../mapping/internal/ConstructorCreator.java | 2 +- .../query/internal/MorphiaKeyCursor.java | 2 +- .../dev/morphia/test/TemplatedTestBase.java | 2 +- .../test/java/dev/morphia/test/TestBase.java | 2 +- .../mapping/codec/DocumentReaderTest.java | 4 +- 23 files changed, 138 insertions(+), 67 deletions(-) diff --git a/core/src/main/java/dev/morphia/Datastore.java b/core/src/main/java/dev/morphia/Datastore.java index fb199538828..a62d9c85f2c 100644 --- a/core/src/main/java/dev/morphia/Datastore.java +++ b/core/src/main/java/dev/morphia/Datastore.java @@ -635,6 +635,11 @@ default UpdateResult update(Query query, dev.morphia.query.UpdateOperatio @MorphiaInternal Mapper getMapper(); + @MorphiaInternal + default ClassLoader getClassLoader() { + return getMapper().getClassLoader(); + } + /** * @param transaction the transaction wrapper * @param the return type diff --git a/core/src/main/java/dev/morphia/DatastoreImpl.java b/core/src/main/java/dev/morphia/DatastoreImpl.java index cca8550b8a6..6f09e63bed0 100644 --- a/core/src/main/java/dev/morphia/DatastoreImpl.java +++ b/core/src/main/java/dev/morphia/DatastoreImpl.java @@ -106,6 +106,11 @@ public class DatastoreImpl implements AdvancedDatastore { private DatastoreOperations operations; private ClassLoader classLoader; + /** + * @deprecated use {@link #DatastoreImpl(MongoClient, MorphiaConfig, ClassLoader)} instead to avoid issues in environments with custom + * class loading. + */ + @Deprecated(since = "2.5.3") public DatastoreImpl(MongoClient client, MorphiaConfig config) { this(client, config, Thread.currentThread().getContextClassLoader()); } @@ -646,7 +651,7 @@ public void refresh(T entity) { .iterator() .next(); - refreshCodec.decode(new DocumentReader(id), DecoderContext.builder().checkedDiscriminator(true).build()); + refreshCodec.decode(new DocumentReader(id, getClassLoader()), 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..51f8879e715 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.getClassLoader()), DecoderContext.builder().build()); } } 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..39df97d76a8 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/ArrayFieldAccessor.java +++ b/core/src/main/java/dev/morphia/mapping/codec/ArrayFieldAccessor.java @@ -20,6 +20,7 @@ public class ArrayFieldAccessor extends FieldAccessor { private final TypeData typeData; private final Class componentType; + private final ClassLoader classLoader; /** * Creates the accessor @@ -27,10 +28,11 @@ public class ArrayFieldAccessor extends FieldAccessor { * @param typeData the type data * @param field the field */ - public ArrayFieldAccessor(TypeData typeData, Field field) { + public ArrayFieldAccessor(TypeData typeData, Field field, ClassLoader classLoader) { super(field); this.typeData = typeData; componentType = field.getType().getComponentType(); + this.classLoader = classLoader; } @Override @@ -84,6 +86,6 @@ private Object convert(Object o, Class type) { return newArray; } - return Conversions.convert(o, type); + return Conversions.convert(o, type, classLoader); } } 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 0dc5edf06fd..bef0e8c40d8 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java @@ -22,7 +22,7 @@ public ClassCodec(Datastore datastore) { @Override public Class decode(BsonReader reader, DecoderContext decoderContext) { try { - ClassLoader classLoader = datastore.getMapper().getClassLoader(); + ClassLoader classLoader = datastore.getClassLoader(); return Class.forName(reader.readString(), true, classLoader); } 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 7fb01ff7386..36cf83fe3b9 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; @@ -34,7 +35,7 @@ public final class Conversions { private static final Logger LOG = LoggerFactory.getLogger(Conversions.class); - private static final Map, Map, Function>> CONVERSIONS = new ConcurrentHashMap<>(); + private static final Map, Map, BiFunction>> CONVERSIONS = new ConcurrentHashMap<>(); static { registerStringConversions(); @@ -97,10 +98,9 @@ private static void registerStringConversions() { } }); register(Class.class, String.class, Class::getName); - register(String.class, Class.class, className -> { + registerWithClassLoader(String.class, Class.class, (className, classLoader) -> { try { - // FIXME incorrect classloader - return Thread.currentThread().getContextClassLoader().loadClass(className); + return classLoader.loadClass(className); } catch (ClassNotFoundException e) { throw new MappingException(e.getMessage(), e); } @@ -125,43 +125,40 @@ 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); - * + * Attempts to convert a value to the given type using the current thread's context ClassLoader. * - * @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. + * @param value the value to convert + * @param target the target type + * @param the target type + * @return the potentially converted value + * @deprecated Use {@link #convert(Object, Class, ClassLoader)} instead to avoid issues in environments with custom class loading. */ - public static void register(Class source, Class target, Function function) { - register(source, target, function, null); + @Nullable + @Deprecated(since = "2.5.3", forRemoval = true) + public static T convert(@Nullable Object value, Class target) { + return convert(value, target, Thread.currentThread().getContextClassLoader()); } /** - * Attempts to convert a value to the given type + * Attempts to convert a value to the given type using the specified ClassLoader. * - * @param value the value to convert - * @param target the target type - * @param the target type + * @param value the value to convert + * @param target the target type + * @param classLoader the ClassLoader to use for class resolution + * @param the target type * @return the potentially converted value */ @SuppressWarnings({ "unchecked", "rawtypes" }) @Nullable - public static T convert(@Nullable Object value, Class target) { + public static T convert(@Nullable Object value, Class target, ClassLoader classLoader) { 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) { @@ -173,7 +170,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 @@ -187,28 +184,68 @@ 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) { + 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 static 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 static 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 static 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<>()) .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..39f220cc8cb 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/MorphiaCodecProvider.java +++ b/core/src/main/java/dev/morphia/mapping/codec/MorphiaCodecProvider.java @@ -54,7 +54,7 @@ public MorphiaCodecProvider(Datastore datastore) { propertyCodecProviders.add(provider); }); - propertyCodecProviders.addAll(List.of(new MorphiaMapPropertyCodecProvider(), + propertyCodecProviders.addAll(List.of(new MorphiaMapPropertyCodecProvider(datastore), new MorphiaCollectionPropertyCodecProvider())); } 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..01c7e2618da 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapCodec.java @@ -78,7 +78,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, datastore.getClassLoader())); if (entry.getValue() == null) { writer.writeNull(); } else { 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..87db6927f64 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,12 @@ @MorphiaInternal @SuppressWarnings("unchecked") class MorphiaMapPropertyCodecProvider extends MorphiaPropertyCodecProvider { + private final Datastore datastore; + + public MorphiaMapPropertyCodecProvider(Datastore datastore) { + this.datastore = datastore; + } + @Override public Codec get(TypeWithTypeParameters type, PropertyCodecRegistry registry) { if (Map.class.isAssignableFrom(type.getType())) { @@ -51,7 +58,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 +74,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, datastore.getClassLoader())); if (entry.getValue() == null) { writer.writeNull(); } else { @@ -82,7 +89,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, datastore.getClassLoader()); if (reader.getCurrentBsonType() == BsonType.NULL) { map.put(key, null); reader.readNull(); 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..c58866f2085 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 @@ -71,7 +71,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(convert(value, model.getTypeData().getType(), morphiaCodec.getDatastore().getClassLoader()), model); } } else { reader.skipValue(); 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..5ccd480f610 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 @@ -71,6 +71,7 @@ public class EntityModel { private final PropertyModel idProperty; private final PropertyModel versionProperty; private final List> listeners = new ArrayList<>(); + private final ClassLoader classLoader; /** * Creates a new instance @@ -137,6 +138,7 @@ public class EntityModel { } listeners.add(new OnEntityListenerAdapter(getType())); + this.classLoader = builder.mapper().getClassLoader(); } public EntityModel(EntityModel other) { @@ -193,6 +195,7 @@ public EntityModel(EntityModel other) { } listeners.add(new OnEntityListenerAdapter(getType())); + this.classLoader = other.classLoader; } /** @@ -379,6 +382,10 @@ public boolean hasLifecycle(Class type) { .anyMatch(listener -> listener.hasAnnotation(type)); } + public ClassLoader getClassLoader() { + return classLoader; + } + @Override public int hashCode() { return Objects.hash(annotations, propertyModelsByName, propertyModelsByMappedName, creatorFactory, diff --git a/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityModelBuilder.java b/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityModelBuilder.java index f6808230afb..889a8c80947 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityModelBuilder.java +++ b/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityModelBuilder.java @@ -330,6 +330,10 @@ public Class type() { return type; } + public Mapper mapper() { + return mapper; + } + /** * @return the name of the version property */ 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..c230342c396 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 @@ -50,8 +50,10 @@ public T decode(BsonReader reader, DecoderContext decoderContext) { } final MorphiaInstanceCreator instanceCreator = model.getInstanceCreator(); T entity = (T) instanceCreator.getInstance(); + model.callLifecycleMethods(PreLoad.class, entity, document, getMorphiaCodec().getDatastore()); - decodeProperties(new DocumentReader(document), decoderContext, instanceCreator, model); + decodeProperties(new DocumentReader(document, model.getClassLoader()), 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..882e3550fdb 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 @@ -91,7 +91,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, convert(new ObjectId(), idProperty.getType(), datastore.getClassLoader())); } else { LOG.warn(Sofia.noIdAndNotObjectId(entity.getClass().getName())); } 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..f3e2c2d0a6e 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 @@ -362,7 +362,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(), entityModel.getClassLoader())); } /** 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..03247f00333 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,6 +48,7 @@ public BsonType get(Class type) { } }; private final ReaderState start; + private final ClassLoader classLoader; private ReaderState current; /** @@ -55,9 +56,10 @@ public BsonType get(Class type) { * * @param document the document to read from */ - public DocumentReader(Document document) { + public DocumentReader(Document document, ClassLoader classLoader) { current = new DocumentState(this, document); start = current; + this.classLoader = classLoader; } /** @@ -147,7 +149,7 @@ public BsonType readBsonType() { @Override public long readDateTime() { - Long value = Conversions.convert(stage().value(), long.class); + Long value = Conversions.convert(stage().value(), long.class, classLoader); 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 354ea44fe07..0afa8f52c31 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 @@ -164,7 +164,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.getClassLoader()), decoderContext); } catch (CodecConfigurationException e) { throw new MappingException(Sofia.cannotFindTypeInDocument(), e); } @@ -176,7 +176,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.getClassLoader()), decoderContext); } id = new DBRef(ref.getDatabaseName(), ref.getCollectionName(), refId); } @@ -374,13 +374,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, datastore.getClassLoader()), 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, datastore.getClassLoader()), DecoderContext.builder().build()); return readSingle(id); } @@ -395,7 +395,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(Conversions.convert(entry.getKey(), keyType, mapper.getClassLoader()), 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..beb91b89ddd 100644 --- a/core/src/main/java/dev/morphia/mapping/conventions/FieldDiscovery.java +++ b/core/src/main/java/dev/morphia/mapping/conventions/FieldDiscovery.java @@ -35,7 +35,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.getClassLoader())) .modifiers(field.getModifiers()) .discoverMappedName(); } catch (NoSuchFieldException e) { @@ -55,9 +55,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, ClassLoader classLoader) { return field.getType().isArray() && !field.getType().getComponentType().equals(byte.class) - ? new ArrayFieldAccessor(typeData, field) + ? new ArrayFieldAccessor(typeData, field, classLoader) : 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..cef018a9147 100644 --- a/core/src/main/java/dev/morphia/mapping/internal/ConstructorCreator.java +++ b/core/src/main/java/dev/morphia/mapping/internal/ConstructorCreator.java @@ -68,7 +68,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(), model.getClassLoader()); 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..80776406e0f 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.getClassLoader()); 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..68bc3a02317 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 -> codec.decode(new DocumentReader(document, getMapper().getClassLoader()), 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..5c844c7d8c7 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.getClassLoader()); 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..2a5f580a66d 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().getClassLoader()), 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().getClassLoader()); } private void step(Consumer function) { From 7ebee8e88ca8f5b4bab670053d56e5ace4c01aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20=22Wega=22=20Weglarz?= <82312488+ThomasWega@users.noreply.github.com> Date: Sat, 14 Feb 2026 21:41:46 +0100 Subject: [PATCH 5/6] removed unnecessary code created by me --- core/src/main/java/dev/morphia/DatastoreImpl.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/src/main/java/dev/morphia/DatastoreImpl.java b/core/src/main/java/dev/morphia/DatastoreImpl.java index 6f09e63bed0..0f90e0c8bf9 100644 --- a/core/src/main/java/dev/morphia/DatastoreImpl.java +++ b/core/src/main/java/dev/morphia/DatastoreImpl.java @@ -104,13 +104,7 @@ public class DatastoreImpl implements AdvancedDatastore { public List morphiaCodecProviders = new ArrayList<>(); private MongoDatabase database; private DatastoreOperations operations; - private ClassLoader classLoader; - /** - * @deprecated use {@link #DatastoreImpl(MongoClient, MorphiaConfig, ClassLoader)} instead to avoid issues in environments with custom - * class loading. - */ - @Deprecated(since = "2.5.3") public DatastoreImpl(MongoClient client, MorphiaConfig config) { this(client, config, Thread.currentThread().getContextClassLoader()); } From 0fed3997f66e7212baa2461fd0b132f726e6a454 Mon Sep 17 00:00:00 2001 From: evanchooly Date: Wed, 25 Feb 2026 22:24:24 -0500 Subject: [PATCH 6/6] Rework classloader threading: Conversions owned by DatastoreImpl, passed through codec chain Move Conversions ownership from Mapper to DatastoreImpl to properly thread the custom ClassLoader through the entire codec pipeline. Conversions is now explicitly passed to MorphiaCodec, PropertyModel, and InstanceCreatorFactory rather than being accessed via Mapper. Also adds classLoader config property to MorphiaConfig. Co-Authored-By: Claude Opus 4.6 --- core/src/main/java/dev/morphia/Datastore.java | 5 -- .../main/java/dev/morphia/DatastoreImpl.java | 22 +++++-- core/src/main/java/dev/morphia/Morphia.java | 12 ---- .../morphia/aggregation/AggregationImpl.java | 2 +- .../morphia/config/ManualMorphiaConfig.java | 7 ++ .../dev/morphia/config/MorphiaConfig.java | 28 +++++++- .../mapping/InstanceCreatorFactory.java | 4 +- .../mapping/InstanceCreatorFactoryImpl.java | 15 +++-- .../main/java/dev/morphia/mapping/Mapper.java | 41 ++++++------ .../mapping/codec/ArrayFieldAccessor.java | 13 ++-- .../dev/morphia/mapping/codec/ClassCodec.java | 10 ++- .../morphia/mapping/codec/Conversions.java | 65 +++++++++---------- .../mapping/codec/MorphiaCodecProvider.java | 8 ++- .../mapping/codec/MorphiaMapCodec.java | 6 +- .../codec/MorphiaMapCodecProvider.java | 2 +- .../MorphiaMapPropertyCodecProvider.java | 8 ++- .../codec/MorphiaTypesCodecProvider.java | 2 +- .../mapping/codec/pojo/EntityDecoder.java | 5 +- .../mapping/codec/pojo/EntityModel.java | 13 ++-- .../codec/pojo/EntityModelBuilder.java | 4 -- .../mapping/codec/pojo/LifecycleDecoder.java | 4 +- .../mapping/codec/pojo/MorphiaCodec.java | 16 ++++- .../mapping/codec/pojo/PropertyModel.java | 5 +- .../codec/pojo/PropertyModelBuilder.java | 8 +++ .../mapping/codec/reader/DocumentReader.java | 11 ++-- .../codec/references/ReferenceCodec.java | 11 ++-- .../mapping/conventions/FieldDiscovery.java | 7 +- .../mapping/internal/ConstructorCreator.java | 5 +- .../query/internal/MorphiaKeyCursor.java | 2 +- .../dev/morphia/test/TemplatedTestBase.java | 2 +- .../test/java/dev/morphia/test/TestBase.java | 2 +- .../mapping/codec/DocumentReaderTest.java | 4 +- docs/antora.yml | 4 +- .../complete-morphia-config.properties | 4 ++ .../examples/legacy-morphia-config.properties | 4 ++ .../minimal-morphia-config.properties | 6 +- 36 files changed, 211 insertions(+), 156 deletions(-) diff --git a/core/src/main/java/dev/morphia/Datastore.java b/core/src/main/java/dev/morphia/Datastore.java index a62d9c85f2c..fb199538828 100644 --- a/core/src/main/java/dev/morphia/Datastore.java +++ b/core/src/main/java/dev/morphia/Datastore.java @@ -635,11 +635,6 @@ default UpdateResult update(Query query, dev.morphia.query.UpdateOperatio @MorphiaInternal Mapper getMapper(); - @MorphiaInternal - default ClassLoader getClassLoader() { - return getMapper().getClassLoader(); - } - /** * @param transaction the transaction wrapper * @param the return type diff --git a/core/src/main/java/dev/morphia/DatastoreImpl.java b/core/src/main/java/dev/morphia/DatastoreImpl.java index 0f90e0c8bf9..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; @@ -106,13 +108,10 @@ public class DatastoreImpl implements AdvancedDatastore { private DatastoreOperations operations; public DatastoreImpl(MongoClient client, MorphiaConfig config) { - this(client, config, Thread.currentThread().getContextClassLoader()); - } - - public DatastoreImpl(MongoClient client, MorphiaConfig config, ClassLoader classLoader) { this.mongoClient = client; this.database = mongoClient.getDatabase(config.database()); - this.mapper = new Mapper(config, classLoader); + this.conversions = new Conversions(config.classLoader()); + this.mapper = new Mapper(config, conversions); this.queryFactory = mapper.getConfig().queryFactory(); importModels(); @@ -146,12 +145,23 @@ public DatastoreImpl(MongoClient client, MorphiaConfig config, ClassLoader class 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)); @@ -645,7 +655,7 @@ public void refresh(T entity) { .iterator() .next(); - refreshCodec.decode(new DocumentReader(id, getClassLoader()), 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/Morphia.java b/core/src/main/java/dev/morphia/Morphia.java index fe30e3fed02..7c784fc7d41 100644 --- a/core/src/main/java/dev/morphia/Morphia.java +++ b/core/src/main/java/dev/morphia/Morphia.java @@ -89,16 +89,4 @@ public static Datastore createDatastore(MongoClient mongoClient, MorphiaConfig c return new DatastoreImpl(mongoClient, config); } - /** - * Creates a Datastore configured via config file - * - * @param mongoClient the client to use - * @param config the configuration to use - * @param classLoader the classloader to use when scanning for entities and codecs. If null, the default classloader will be used. - * @return a Datastore that you can use to interact with MongoDB - * @since 3.0.0 - */ - public static Datastore createDatastore(MongoClient mongoClient, MorphiaConfig config, ClassLoader classLoader) { - return new DatastoreImpl(mongoClient, config, classLoader); - } } diff --git a/core/src/main/java/dev/morphia/aggregation/AggregationImpl.java b/core/src/main/java/dev/morphia/aggregation/AggregationImpl.java index 51f8879e715..b0dc49125ee 100644 --- a/core/src/main/java/dev/morphia/aggregation/AggregationImpl.java +++ b/core/src/main/java/dev/morphia/aggregation/AggregationImpl.java @@ -403,7 +403,7 @@ public ServerAddress getServerAddress() { private R map(Document next) { next.remove(discriminator); - return codec.decode(new DocumentReader(next, datastore.getClassLoader()), 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 35a9e82ff04..e5234e2a61d 100644 --- a/core/src/main/java/dev/morphia/config/MorphiaConfig.java +++ b/core/src/main/java/dev/morphia/config/MorphiaConfig.java @@ -88,15 +88,39 @@ 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/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 4c4934d3cf2..6553a549b07 100644 --- a/core/src/main/java/dev/morphia/mapping/Mapper.java +++ b/core/src/main/java/dev/morphia/mapping/Mapper.java @@ -28,6 +28,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; @@ -78,33 +79,21 @@ public class Mapper { private final MorphiaConfig config; private final DiscriminatorLookup discriminatorLookup; private final Object entityRegistrationMonitor = new Object(); - private final ClassLoader classLoader; + private final Conversions conversions; /** - * Creates a Mapper with the given options. - * - * @param config the config to use - * @morphia.internal - * @hidden - */ - @MorphiaInternal - public Mapper(MorphiaConfig config) { - this(config, Thread.currentThread().getContextClassLoader()); - } - - /** - * Creates a Mapper with the given options and classloader. + * Creates a Mapper with the given options and Conversions instance. * * @param config the config to use - * @param classLoader the classloader to use when looking up classes by discriminator + * @param conversions the Conversions instance to use * @morphia.internal * @hidden */ @MorphiaInternal - public Mapper(MorphiaConfig config, ClassLoader classLoader) { + public Mapper(MorphiaConfig config, Conversions conversions) { this.config = config; - this.classLoader = classLoader; - discriminatorLookup = new DiscriminatorLookup(classLoader); + this.conversions = conversions; + discriminatorLookup = new DiscriminatorLookup(conversions.getClassLoader()); } /** @@ -114,7 +103,7 @@ public Mapper(MorphiaConfig config, ClassLoader classLoader) { */ public Mapper(Mapper other) { config = other.config; - classLoader = other.classLoader; + conversions = other.conversions; discriminatorLookup = new DiscriminatorLookup(other.getClassLoader()); other.mappedEntities.values().forEach(entity -> clone(entity)); listeners.addAll(other.listeners); @@ -189,7 +178,17 @@ public PropertyModel findIdProperty(Class type) { } public ClassLoader getClassLoader() { - return classLoader; + return conversions.getClassLoader(); + } + + /** + * @return the Conversions instance used by this Mapper + * @morphia.internal + * @hidden + */ + @MorphiaInternal + public Conversions getConversions() { + return conversions; } /** @@ -638,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, classLoader)); + 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 39df97d76a8..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,19 +20,20 @@ public class ArrayFieldAccessor extends FieldAccessor { private final TypeData typeData; private final Class componentType; - private final ClassLoader classLoader; + 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, ClassLoader classLoader) { + public ArrayFieldAccessor(TypeData typeData, Field field, Conversions conversions) { super(field); this.typeData = typeData; componentType = field.getType().getComponentType(); - this.classLoader = classLoader; + this.conversions = conversions; } @Override @@ -86,6 +87,6 @@ private Object convert(Object o, Class type) { return newArray; } - return Conversions.convert(o, type, classLoader); + 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 bef0e8c40d8..bfd3e9c6cc6 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java @@ -1,6 +1,5 @@ package dev.morphia.mapping.codec; -import dev.morphia.Datastore; import dev.morphia.mapping.MappingException; import org.bson.BsonReader; @@ -13,17 +12,16 @@ * Defines a codec for Class references */ public class ClassCodec implements Codec { - private final Datastore datastore; + private final Conversions conversions; - public ClassCodec(Datastore datastore) { - this.datastore = datastore; + public ClassCodec(Conversions conversions) { + this.conversions = conversions; } @Override public Class decode(BsonReader reader, DecoderContext decoderContext) { try { - ClassLoader classLoader = datastore.getClassLoader(); - 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 36cf83fe3b9..4b5f6a7dcd0 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/Conversions.java +++ b/core/src/main/java/dev/morphia/mapping/codec/Conversions.java @@ -32,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, BiFunction>> 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); @@ -82,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 -> { @@ -98,9 +113,9 @@ private static void registerStringConversions() { } }); register(Class.class, String.class, Class::getName); - registerWithClassLoader(String.class, Class.class, (className, classLoader) -> { + registerWithClassLoader(String.class, Class.class, (className, cl) -> { try { - return classLoader.loadClass(className); + return cl.loadClass(className); } catch (ClassNotFoundException e) { throw new MappingException(e.getMessage(), e); } @@ -125,32 +140,16 @@ private static void registerStringConversions() { } /** - * Attempts to convert a value to the given type using the current thread's context ClassLoader. + * Attempts to convert a value to the given type. * * @param value the value to convert * @param target the target type * @param the target type * @return the potentially converted value - * @deprecated Use {@link #convert(Object, Class, ClassLoader)} instead to avoid issues in environments with custom class loading. - */ - @Nullable - @Deprecated(since = "2.5.3", forRemoval = true) - public static T convert(@Nullable Object value, Class target) { - return convert(value, target, Thread.currentThread().getContextClassLoader()); - } - - /** - * Attempts to convert a value to the given type using the specified ClassLoader. - * - * @param value the value to convert - * @param target the target type - * @param classLoader the ClassLoader to use for class resolution - * @param the target type - * @return the potentially converted value */ @SuppressWarnings({ "unchecked", "rawtypes" }) @Nullable - public static T convert(@Nullable Object value, Class target, ClassLoader classLoader) { + public T convert(@Nullable Object value, Class target) { if (value == null) { return (T) convertNull(target); } @@ -158,7 +157,7 @@ public static T convert(@Nullable Object value, Class target, ClassLoader if (fromType.equals(target)) { return (T) value; } - final BiFunction function = CONVERSIONS + final BiFunction function = conversions .computeIfAbsent(fromType, (f) -> new ConcurrentHashMap<>()) .get(target); if (function == null) { @@ -192,7 +191,7 @@ private static Object convertNull(Class toType) { * @param the source 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); } @@ -206,7 +205,7 @@ public static void register(Class source, Class target, Function the source type * @param the target type */ - public static void register(Class source, Class target, Function function, + public void register(Class source, Class target, Function function, @Nullable String warning) { // Wrap Function as BiFunction (ignore ClassLoader parameter) BiFunction biFunction = (s, cl) -> function.apply(s); @@ -222,7 +221,7 @@ public static void register(Class source, Class target, Function the source type * @param the target type */ - public static void registerWithClassLoader(Class source, Class target, + public void registerWithClassLoader(Class source, Class target, BiFunction function) { registerWithClassLoader(source, target, function, null); } @@ -237,7 +236,7 @@ public static void registerWithClassLoader(Class source, Class targ * @param the source type * @param the target type */ - public static void registerWithClassLoader(Class source, Class target, + public void registerWithClassLoader(Class source, Class target, BiFunction function, @Nullable String warning) { final BiFunction conversion = warning == null ? function @@ -247,7 +246,7 @@ public static void registerWithClassLoader(Class source, Class targ } 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 39f220cc8cb..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(datastore), + 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 01c7e2618da..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, datastore.getClassLoader())); + 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 87db6927f64..f5cb493988b 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapPropertyCodecProvider.java +++ b/core/src/main/java/dev/morphia/mapping/codec/MorphiaMapPropertyCodecProvider.java @@ -30,9 +30,11 @@ @SuppressWarnings("unchecked") class MorphiaMapPropertyCodecProvider extends MorphiaPropertyCodecProvider { private final Datastore datastore; + private final Conversions conversions; - public MorphiaMapPropertyCodecProvider(Datastore datastore) { + public MorphiaMapPropertyCodecProvider(Datastore datastore, Conversions conversions) { this.datastore = datastore; + this.conversions = conversions; } @Override @@ -74,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, datastore.getClassLoader())); + writer.writeName(conversions.convert(key, String.class)); if (entry.getValue() == null) { writer.writeNull(); } else { @@ -89,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, datastore.getClassLoader()); + 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 6ad490f382f..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(datastore)); + 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 c58866f2085..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(), morphiaCodec.getDatastore().getClassLoader()), 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 5ccd480f610..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; @@ -71,7 +72,6 @@ public class EntityModel { private final PropertyModel idProperty; private final PropertyModel versionProperty; private final List> listeners = new ArrayList<>(); - private final ClassLoader classLoader; /** * Creates a new instance @@ -138,7 +138,6 @@ public class EntityModel { } listeners.add(new OnEntityListenerAdapter(getType())); - this.classLoader = builder.mapper().getClassLoader(); } public EntityModel(EntityModel other) { @@ -195,7 +194,6 @@ public EntityModel(EntityModel other) { } listeners.add(new OnEntityListenerAdapter(getType())); - this.classLoader = other.classLoader; } /** @@ -287,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); } /** @@ -382,10 +381,6 @@ public boolean hasLifecycle(Class type) { .anyMatch(listener -> listener.hasAnnotation(type)); } - public ClassLoader getClassLoader() { - return classLoader; - } - @Override public int hashCode() { return Objects.hash(annotations, propertyModelsByName, propertyModelsByMappedName, creatorFactory, diff --git a/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityModelBuilder.java b/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityModelBuilder.java index 889a8c80947..f6808230afb 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityModelBuilder.java +++ b/core/src/main/java/dev/morphia/mapping/codec/pojo/EntityModelBuilder.java @@ -330,10 +330,6 @@ public Class type() { return type; } - public Mapper mapper() { - return mapper; - } - /** * @return the name of the version property */ 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 c230342c396..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,11 +48,11 @@ 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, model.getClassLoader()), decoderContext, instanceCreator, + decodeProperties(new DocumentReader(document, getMorphiaCodec().getConversions()), decoderContext, instanceCreator, model); model.callLifecycleMethods(PostLoad.class, entity, document, getMorphiaCodec().getDatastore()); 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 882e3550fdb..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(), datastore.getClassLoader())); + 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 f3e2c2d0a6e..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(), entityModel.getClassLoader())); + 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 03247f00333..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,18 +48,19 @@ public BsonType get(Class type) { } }; private final ReaderState start; - private final ClassLoader classLoader; + 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, ClassLoader classLoader) { + public DocumentReader(Document document, Conversions conversions) { current = new DocumentState(this, document); start = current; - this.classLoader = classLoader; + this.conversions = conversions; } /** @@ -149,7 +150,7 @@ public BsonType readBsonType() { @Override public long readDateTime() { - Long value = Conversions.convert(stage().value(), long.class, classLoader); + 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 0afa8f52c31..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, datastore.getClassLoader()), 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, datastore.getClassLoader()), decoderContext); + .decode(new DocumentReader((Document) refId, datastore.getMapper().getConversions()), decoderContext); } id = new DBRef(ref.getDatabaseName(), ref.getCollectionName(), refId); } @@ -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, datastore.getClassLoader()), 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, datastore.getClassLoader()), 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, mapper.getClassLoader()), 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 beb91b89ddd..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, mapper.getClassLoader())) + .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, ClassLoader classLoader) { + private PropertyAccessor getAccessor(Field field, TypeData typeData, Conversions conversions) { return field.getType().isArray() && !field.getType().getComponentType().equals(byte.class) - ? new ArrayFieldAccessor(typeData, field, classLoader) + ? 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 cef018a9147..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(), model.getClassLoader()); + 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 80776406e0f..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, datastore.getClassLoader()); + 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 68bc3a02317..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, getMapper().getClassLoader()), 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 5c844c7d8c7..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, mapper.getClassLoader()); + 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 2a5f580a66d..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, getMapper().getClassLoader()), 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, getMapper().getClassLoader()); + reader = new DocumentReader(document, getMapper().getConversions()); } private void step(Consumer function) { diff --git a/docs/antora.yml b/docs/antora.yml index f858a0f1b68..6e51ab7fde9 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -6,5 +6,5 @@ nav: - "modules/ROOT/nav.adoc" asciidoc: attributes: - version: "2.5.1" - srcRef: "https://github.com/MorphiaOrg/morphia/tree/2.5.x" \ No newline at end of file + version: "2.5.2" + srcRef: "https://github.com/MorphiaOrg/morphia/blob/master" \ No newline at end of file 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