Skip to content

Commit 93832c6

Browse files
committed
Conversions now use classloader as well and all it's usages.
1 parent 75527d0 commit 93832c6

23 files changed

Lines changed: 138 additions & 67 deletions

core/src/main/java/dev/morphia/Datastore.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,11 @@ default <T> UpdateResult update(Query<T> query, dev.morphia.query.UpdateOperatio
635635
@MorphiaInternal
636636
Mapper getMapper();
637637

638+
@MorphiaInternal
639+
default ClassLoader getClassLoader() {
640+
return getMapper().getClassLoader();
641+
}
642+
638643
/**
639644
* @param transaction the transaction wrapper
640645
* @param <T> the return type

core/src/main/java/dev/morphia/DatastoreImpl.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ public class DatastoreImpl implements AdvancedDatastore {
106106
private DatastoreOperations operations;
107107
private ClassLoader classLoader;
108108

109+
/**
110+
* @deprecated use {@link #DatastoreImpl(MongoClient, MorphiaConfig, ClassLoader)} instead to avoid issues in environments with custom
111+
* class loading.
112+
*/
113+
@Deprecated(since = "2.5.3")
109114
public DatastoreImpl(MongoClient client, MorphiaConfig config) {
110115
this(client, config, Thread.currentThread().getContextClassLoader());
111116
}
@@ -646,7 +651,7 @@ public <T> void refresh(T entity) {
646651
.iterator()
647652
.next();
648653

649-
refreshCodec.decode(new DocumentReader(id), DecoderContext.builder().checkedDiscriminator(true).build());
654+
refreshCodec.decode(new DocumentReader(id, getClassLoader()), DecoderContext.builder().checkedDiscriminator(true).build());
650655
}
651656

652657
@Override

core/src/main/java/dev/morphia/aggregation/AggregationImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ public Aggregation<T> addStage(Stage stage) {
354354
return this;
355355
}
356356

357-
private static class MappingCursor<R> implements MongoCursor<R> {
357+
private class MappingCursor<R> implements MongoCursor<R> {
358358
private final MongoCursor<Document> results;
359359
private final Codec<R> codec;
360360
private final String discriminator;
@@ -403,7 +403,7 @@ public ServerAddress getServerAddress() {
403403

404404
private R map(Document next) {
405405
next.remove(discriminator);
406-
return codec.decode(new DocumentReader(next), DecoderContext.builder().build());
406+
return codec.decode(new DocumentReader(next, datastore.getClassLoader()), DecoderContext.builder().build());
407407
}
408408
}
409409

core/src/main/java/dev/morphia/mapping/codec/ArrayFieldAccessor.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,19 @@ public class ArrayFieldAccessor extends FieldAccessor {
2020

2121
private final TypeData<?> typeData;
2222
private final Class<?> componentType;
23+
private final ClassLoader classLoader;
2324

2425
/**
2526
* Creates the accessor
2627
*
2728
* @param typeData the type data
2829
* @param field the field
2930
*/
30-
public ArrayFieldAccessor(TypeData<?> typeData, Field field) {
31+
public ArrayFieldAccessor(TypeData<?> typeData, Field field, ClassLoader classLoader) {
3132
super(field);
3233
this.typeData = typeData;
3334
componentType = field.getType().getComponentType();
35+
this.classLoader = classLoader;
3436
}
3537

3638
@Override
@@ -84,6 +86,6 @@ private Object convert(Object o, Class<?> type) {
8486

8587
return newArray;
8688
}
87-
return Conversions.convert(o, type);
89+
return Conversions.convert(o, type, classLoader);
8890
}
8991
}

core/src/main/java/dev/morphia/mapping/codec/ClassCodec.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public ClassCodec(Datastore datastore) {
2222
@Override
2323
public Class decode(BsonReader reader, DecoderContext decoderContext) {
2424
try {
25-
ClassLoader classLoader = datastore.getMapper().getClassLoader();
25+
ClassLoader classLoader = datastore.getClassLoader();
2626
return Class.forName(reader.readString(), true, classLoader);
2727
} catch (ClassNotFoundException e) {
2828
throw new MappingException(e.getMessage(), e);

core/src/main/java/dev/morphia/mapping/codec/Conversions.java

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.Map;
1010
import java.util.UUID;
1111
import java.util.concurrent.ConcurrentHashMap;
12+
import java.util.function.BiFunction;
1213
import java.util.function.Function;
1314

1415
import com.mongodb.lang.Nullable;
@@ -34,7 +35,7 @@
3435
public final class Conversions {
3536
private static final Logger LOG = LoggerFactory.getLogger(Conversions.class);
3637

37-
private static final Map<Class<?>, Map<Class<?>, Function<?, ?>>> CONVERSIONS = new ConcurrentHashMap<>();
38+
private static final Map<Class<?>, Map<Class<?>, BiFunction<?, ClassLoader, ?>>> CONVERSIONS = new ConcurrentHashMap<>();
3839

3940
static {
4041
registerStringConversions();
@@ -97,10 +98,9 @@ private static void registerStringConversions() {
9798
}
9899
});
99100
register(Class.class, String.class, Class::getName);
100-
register(String.class, Class.class, className -> {
101+
registerWithClassLoader(String.class, Class.class, (className, classLoader) -> {
101102
try {
102-
// FIXME incorrect classloader
103-
return Thread.currentThread().getContextClassLoader().loadClass(className);
103+
return classLoader.loadClass(className);
104104
} catch (ClassNotFoundException e) {
105105
throw new MappingException(e.getMessage(), e);
106106
}
@@ -125,43 +125,40 @@ private static void registerStringConversions() {
125125
}
126126

127127
/**
128-
* Register a conversion between two types. For example, to register the conversion of {@link Date} to a {@link Long}, this method
129-
* could be invoked as follows:
130-
* <code>
131-
* register(Date.class, Long.class, Date::getTime);
132-
* </code>
128+
* Attempts to convert a value to the given type using the current thread's context ClassLoader.
133129
*
134-
* @param source the source type
135-
* @param target the target type
136-
* @param function the function that performs the conversion. This is often just a method reference.
137-
* @param <S> the source type
138-
* @param <T> the target type.
130+
* @param value the value to convert
131+
* @param target the target type
132+
* @param <T> the target type
133+
* @return the potentially converted value
134+
* @deprecated Use {@link #convert(Object, Class, ClassLoader)} instead to avoid issues in environments with custom class loading.
139135
*/
140-
public static <S, T> void register(Class<S> source, Class<T> target, Function<S, T> function) {
141-
register(source, target, function, null);
136+
@Nullable
137+
@Deprecated(since = "2.5.3", forRemoval = true)
138+
public static <T> T convert(@Nullable Object value, Class<T> target) {
139+
return convert(value, target, Thread.currentThread().getContextClassLoader());
142140
}
143141

144142
/**
145-
* Attempts to convert a value to the given type
143+
* Attempts to convert a value to the given type using the specified ClassLoader.
146144
*
147-
* @param value the value to convert
148-
* @param target the target type
149-
* @param <T> the target type
145+
* @param value the value to convert
146+
* @param target the target type
147+
* @param classLoader the ClassLoader to use for class resolution
148+
* @param <T> the target type
150149
* @return the potentially converted value
151150
*/
152151
@SuppressWarnings({ "unchecked", "rawtypes" })
153152
@Nullable
154-
public static <T> T convert(@Nullable Object value, Class<T> target) {
153+
public static <T> T convert(@Nullable Object value, Class<T> target, ClassLoader classLoader) {
155154
if (value == null) {
156155
return (T) convertNull(target);
157156
}
158-
159157
final Class<?> fromType = value.getClass();
160158
if (fromType.equals(target)) {
161159
return (T) value;
162160
}
163-
164-
final Function function = CONVERSIONS
161+
final BiFunction function = CONVERSIONS
165162
.computeIfAbsent(fromType, (f) -> new ConcurrentHashMap<>())
166163
.get(target);
167164
if (function == null) {
@@ -173,7 +170,7 @@ public static <T> T convert(@Nullable Object value, Class<T> target) {
173170
}
174171
return (T) value;
175172
}
176-
return (T) function.apply(value);
173+
return (T) function.apply(value, classLoader);
177174
}
178175

179176
@Nullable
@@ -187,28 +184,68 @@ private static Object convertNull(Class<?> toType) {
187184
}
188185

189186
/**
190-
* Register a conversion between two types. For example, to register the conversion of {@link Date} to a {@link Long}, this method
191-
* could be invoked as follows:
192-
* <code>
193-
* register(Date.class, Long.class, Date::getTime);
194-
* </code>
187+
* Register a conversion between two types.
195188
*
196189
* @param source the source type
197190
* @param target the target type
198-
* @param function the function that performs the conversion. This is often just a method reference.
199-
* @param warning if non-null, this will be the message logged on the WARN level indicating the conversion is taking place.
191+
* @param function the function that performs the conversion
200192
* @param <S> the source type
201-
* @param <T> the target type.
193+
* @param <T> the target type
194+
*/
195+
public static <S, T> void register(Class<S> source, Class<T> target, Function<S, T> function) {
196+
register(source, target, function, null);
197+
}
198+
199+
/**
200+
* Register a conversion between two types.
201+
*
202+
* @param source the source type
203+
* @param target the target type
204+
* @param function the function that performs the conversion
205+
* @param warning if non-null, this will be logged on WARN level
206+
* @param <S> the source type
207+
* @param <T> the target type
202208
*/
203209
public static <S, T> void register(Class<S> source, Class<T> target, Function<S, T> function,
204210
@Nullable String warning) {
205-
final Function<S, T> conversion = warning == null
211+
// Wrap Function as BiFunction (ignore ClassLoader parameter)
212+
BiFunction<S, ClassLoader, T> biFunction = (s, cl) -> function.apply(s);
213+
registerWithClassLoader(source, target, biFunction, warning);
214+
}
215+
216+
/**
217+
* Register a conversion between two types with ClassLoader support.
218+
*
219+
* @param source the source type
220+
* @param target the target type
221+
* @param function the function that performs the conversion (receives ClassLoader)
222+
* @param <S> the source type
223+
* @param <T> the target type
224+
*/
225+
public static <S, T> void registerWithClassLoader(Class<S> source, Class<T> target,
226+
BiFunction<S, ClassLoader, T> function) {
227+
registerWithClassLoader(source, target, function, null);
228+
}
229+
230+
/**
231+
* Register a conversion between two types with ClassLoader support.
232+
*
233+
* @param source the source type
234+
* @param target the target type
235+
* @param function the function that performs the conversion (receives ClassLoader)
236+
* @param warning if non-null, this will be logged on WARN level
237+
* @param <S> the source type
238+
* @param <T> the target type
239+
*/
240+
public static <S, T> void registerWithClassLoader(Class<S> source, Class<T> target,
241+
BiFunction<S, ClassLoader, T> function, @Nullable String warning) {
242+
final BiFunction<S, ClassLoader, T> conversion = warning == null
206243
? function
207-
: s -> {
244+
: (s, cl) -> {
208245
if (LOG.isWarnEnabled()) {
209246
LOG.warn(warning);
210247
}
211-
return function.apply(s);
248+
return function.apply(s, cl);
212249
};
213250
CONVERSIONS.computeIfAbsent(source, (Class<?> c) -> new HashMap<>())
214251
.put(target, conversion);

core/src/main/java/dev/morphia/mapping/codec/MorphiaCodecProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public MorphiaCodecProvider(Datastore datastore) {
5454
propertyCodecProviders.add(provider);
5555
});
5656

57-
propertyCodecProviders.addAll(List.of(new MorphiaMapPropertyCodecProvider(),
57+
propertyCodecProviders.addAll(List.of(new MorphiaMapPropertyCodecProvider(datastore),
5858
new MorphiaCollectionPropertyCodecProvider()));
5959
}
6060

core/src/main/java/dev/morphia/mapping/codec/MorphiaMapCodec.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void encode(BsonWriter writer, Map map, EncoderContext encoderContext) {
7878
document(writer, () -> {
7979
for (Entry<?, ?> entry : ((Map<?, ?>) map).entrySet()) {
8080
final Object key = entry.getKey();
81-
writer.writeName(Conversions.convert(key, String.class));
81+
writer.writeName(Conversions.convert(key, String.class, datastore.getClassLoader()));
8282
if (entry.getValue() == null) {
8383
writer.writeNull();
8484
} else {

core/src/main/java/dev/morphia/mapping/codec/MorphiaMapPropertyCodecProvider.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.Map;
77
import java.util.Map.Entry;
88

9+
import dev.morphia.Datastore;
910
import dev.morphia.annotations.internal.MorphiaInternal;
1011
import dev.morphia.mapping.codec.pojo.TypeData;
1112

@@ -28,6 +29,12 @@
2829
@MorphiaInternal
2930
@SuppressWarnings("unchecked")
3031
class MorphiaMapPropertyCodecProvider extends MorphiaPropertyCodecProvider {
32+
private final Datastore datastore;
33+
34+
public MorphiaMapPropertyCodecProvider(Datastore datastore) {
35+
this.datastore = datastore;
36+
}
37+
3138
@Override
3239
public <T> Codec<T> get(TypeWithTypeParameters<T> type, PropertyCodecRegistry registry) {
3340
if (Map.class.isAssignableFrom(type.getType())) {
@@ -51,7 +58,7 @@ public <T> Codec<T> get(TypeWithTypeParameters<T> type, PropertyCodecRegistry re
5158
return null;
5259
}
5360

54-
private static class MapCodec<K, V> implements Codec<Map<K, V>> {
61+
private class MapCodec<K, V> implements Codec<Map<K, V>> {
5562
private final Class<Map<K, V>> encoderClass;
5663
private final Class<K> keyType;
5764
private final Codec<V> codec;
@@ -67,7 +74,7 @@ public void encode(BsonWriter writer, Map<K, V> map, EncoderContext encoderConte
6774
document(writer, () -> {
6875
for (Entry<K, V> entry : map.entrySet()) {
6976
final K key = entry.getKey();
70-
writer.writeName(Conversions.convert(key, String.class));
77+
writer.writeName(Conversions.convert(key, String.class, datastore.getClassLoader()));
7178
if (entry.getValue() == null) {
7279
writer.writeNull();
7380
} else {
@@ -82,7 +89,7 @@ public Map<K, V> decode(BsonReader reader, DecoderContext context) {
8289
reader.readStartDocument();
8390
Map<K, V> map = getInstance();
8491
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
85-
final K key = Conversions.convert(reader.readName(), keyType);
92+
final K key = Conversions.convert(reader.readName(), keyType, datastore.getClassLoader());
8693
if (reader.getCurrentBsonType() == BsonType.NULL) {
8794
map.put(key, null);
8895
reader.readNull();

core/src/main/java/dev/morphia/mapping/codec/pojo/EntityDecoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ protected void decodeModel(BsonReader reader, DecoderContext decoderContext,
7171
} catch (BsonInvalidOperationException e) {
7272
mark.reset();
7373
final Object value = morphiaCodec.getDatastore().getCodecRegistry().get(Object.class).decode(reader, decoderContext);
74-
instanceCreator.set(convert(value, model.getTypeData().getType()), model);
74+
instanceCreator.set(convert(value, model.getTypeData().getType(), morphiaCodec.getDatastore().getClassLoader()), model);
7575
}
7676
} else {
7777
reader.skipValue();

0 commit comments

Comments
 (0)