diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/MongoRuntimeHints.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/MongoRuntimeHints.java index e9f5a8d20..347a00f7f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/MongoRuntimeHints.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/MongoRuntimeHints.java @@ -31,6 +31,8 @@ import org.springframework.data.mongodb.core.mapping.event.ReactiveAfterConvertC import org.springframework.data.mongodb.core.mapping.event.ReactiveAfterSaveCallback; import org.springframework.data.mongodb.core.mapping.event.ReactiveBeforeConvertCallback; import org.springframework.data.mongodb.core.mapping.event.ReactiveBeforeSaveCallback; +import org.springframework.data.mongodb.repository.support.QuerydslMongoPredicateExecutor; +import org.springframework.data.mongodb.repository.support.ReactiveQuerydslMongoPredicateExecutor; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; @@ -62,13 +64,15 @@ class MongoRuntimeHints implements RuntimeHintsRegistrar { TypeReference.of(ReactiveAfterSaveCallback.class)), builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS)); - } + + registerQuerydslHints(hints, classLoader); } private static void registerTransactionProxyHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { - if (MongoAotPredicates.isSyncClientPresent(classLoader) && ClassUtils.isPresent("org.springframework.aop.SpringProxy", classLoader)) { + if (MongoAotPredicates.isSyncClientPresent(classLoader) + && ClassUtils.isPresent("org.springframework.aop.SpringProxy", classLoader)) { hints.proxies().registerJdkProxy(TypeReference.of("com.mongodb.client.MongoDatabase"), TypeReference.of("org.springframework.aop.SpringProxy"), @@ -78,4 +82,28 @@ class MongoRuntimeHints implements RuntimeHintsRegistrar { TypeReference.of("org.springframework.core.DecoratingProxy")); } } + + /** + * Register hints for Querydsl integration. + * + * @param hints must not be {@literal null}. + * @param classLoader can be {@literal null}. + * @since 4.0.1 + */ + private static void registerQuerydslHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { + + if (ClassUtils.isPresent("com.querydsl.core.types.Predicate", classLoader)) { + + if (isReactorPresent()) { + hints.reflection().registerType(ReactiveQuerydslMongoPredicateExecutor.class, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + + } + + if (MongoAotPredicates.isSyncClientPresent(classLoader)) { + hints.reflection().registerType(QuerydslMongoPredicateExecutor.class, MemberCategory.INVOKE_PUBLIC_METHODS, + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + } + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuerySupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuerySupport.java index d8fec024e..0e4fa8093 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuerySupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbQuerySupport.java @@ -22,8 +22,6 @@ import org.bson.codecs.DocumentCodec; import org.bson.json.JsonMode; import org.bson.json.JsonWriterSettings; -import org.springframework.beans.DirectFieldAccessor; - import com.mongodb.MongoClientSettings; import com.querydsl.core.support.QueryMixin; import com.querydsl.core.types.OrderSpecifier; @@ -49,11 +47,10 @@ abstract class SpringDataMongodbQuerySupport) fieldAccessor.getPropertyValue("queryMixin"); + this.superQueryMixin = super.getQueryMixin(); } /** diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/MongoRuntimeHintsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/MongoRuntimeHintsUnitTests.java new file mode 100644 index 000000000..179c16440 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/MongoRuntimeHintsUnitTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.aot; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.data.mongodb.classloading.HidingClassLoader; +import org.springframework.data.mongodb.repository.support.QuerydslMongoPredicateExecutor; +import org.springframework.data.mongodb.repository.support.ReactiveQuerydslMongoPredicateExecutor; + +import com.mongodb.client.MongoClient; + +/** + * @author Christoph Strobl + */ +class MongoRuntimeHintsUnitTests { + + @Test // GH-4244 + void doesNotRegisterTypesForQuerydslIntegrationWhenQuerydslNotPresent() { + + RuntimeHints runtimeHints = new RuntimeHints(); + new MongoRuntimeHints().registerHints(runtimeHints, new HidingClassLoader("com.querydsl")); + + assertThat(runtimeHints) + .matches(RuntimeHintsPredicates.reflection().onType(QuerydslMongoPredicateExecutor.class).negate() + .and(RuntimeHintsPredicates.reflection().onType(ReactiveQuerydslMongoPredicateExecutor.class).negate())); + + } + + @Test // GH-4244 + void registersTypesForQuerydslIntegration() { + + RuntimeHints runtimeHints = new RuntimeHints(); + new MongoRuntimeHints().registerHints(runtimeHints, null); + + assertThat(runtimeHints).matches(RuntimeHintsPredicates.reflection().onType(QuerydslMongoPredicateExecutor.class) + .and(RuntimeHintsPredicates.reflection().onType(ReactiveQuerydslMongoPredicateExecutor.class))); + } + + @Test // GH-4244 + void onlyRegistersReactiveTypesForQuerydslIntegrationWhenNoSyncClientPresent() { + + RuntimeHints runtimeHints = new RuntimeHints(); + new MongoRuntimeHints().registerHints(runtimeHints, HidingClassLoader.hide(MongoClient.class)); + + assertThat(runtimeHints).matches(RuntimeHintsPredicates.reflection().onType(QuerydslMongoPredicateExecutor.class) + .negate().and(RuntimeHintsPredicates.reflection().onType(ReactiveQuerydslMongoPredicateExecutor.class))); + } + + @Test // GH-4244 + @Disabled("TODO: ReactiveWrappers does not support ClassLoader") + void doesNotRegistersReactiveTypesForQuerydslIntegrationWhenReactorNotPresent() { + + RuntimeHints runtimeHints = new RuntimeHints(); + new MongoRuntimeHints().registerHints(runtimeHints, new HidingClassLoader("reactor.core")); + + assertThat(runtimeHints).matches(RuntimeHintsPredicates.reflection().onType(QuerydslMongoPredicateExecutor.class) + .and(RuntimeHintsPredicates.reflection().onType(ReactiveQuerydslMongoPredicateExecutor.class).negate())); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/classloading/HidingClassLoader.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/classloading/HidingClassLoader.java new file mode 100644 index 000000000..1e8f66207 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/classloading/HidingClassLoader.java @@ -0,0 +1,105 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.classloading; + +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + +import org.springframework.instrument.classloading.ShadowingClassLoader; +import org.springframework.util.Assert; + +/** + * is intended for testing code that depends on the presence/absence of certain classes. Classes can be: + * + * + * @author Jens Schauder + * @author Oliver Gierke + * @author Christoph Strobl + */ +public class HidingClassLoader extends ShadowingClassLoader { + + private final Collection hidden; + + public HidingClassLoader(String... hidden) { + this(Arrays.asList(hidden)); + } + + public HidingClassLoader(Collection hidden) { + + super(URLClassLoader.getSystemClassLoader(), false); + + this.hidden = hidden; + } + + /** + * Creates a new {@link HidingClassLoader} with the packages of the given classes hidden. + * + * @param packages must not be {@literal null}. + * @return + */ + public static HidingClassLoader hide(Class... packages) { + + Assert.notNull(packages, "Packages must not be null"); + + return new HidingClassLoader(Arrays.stream(packages)// + .map(it -> it.getPackage().getName())// + .collect(Collectors.toList())); + } + + public static HidingClassLoader hideTypes(Class... types) { + + Assert.notNull(types, "Types must not be null!"); + + return new HidingClassLoader(Arrays.stream(types)// + .map(it -> it.getName())// + .collect(Collectors.toList())); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + + Class loaded = super.loadClass(name); + checkIfHidden(loaded); + return loaded; + } + + @Override + protected boolean isEligibleForShadowing(String className) { + return isExcluded(className); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + + Class loaded = super.findClass(name); + checkIfHidden(loaded); + return loaded; + } + + private void checkIfHidden(Class type) throws ClassNotFoundException { + + if (hidden.stream().anyMatch(it -> type.getName().startsWith(it))) { + throw new ClassNotFoundException(); + } + } +}