Browse Source
We now are compatible with MongoDB driver versions 4 and 5. Driver versions can be interchanged and our adapter bridges changed methods via reflection. Usage of removed functionality is either ignored or fails with an exception. Original pull request: #4624 Closes: #4578pull/4633/head
33 changed files with 1557 additions and 128 deletions
@ -0,0 +1,188 @@
@@ -0,0 +1,188 @@
|
||||
/* |
||||
* Copyright 2023 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; |
||||
|
||||
import java.lang.reflect.Method; |
||||
import java.net.InetAddress; |
||||
import java.net.InetSocketAddress; |
||||
|
||||
import com.mongodb.ServerAddress; |
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.springframework.data.mongodb.core.MongoClientSettingsFactoryBean; |
||||
import org.springframework.data.mongodb.util.MongoClientVersion; |
||||
import org.springframework.util.ClassUtils; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import com.mongodb.MongoClientSettings; |
||||
import com.mongodb.MongoClientSettings.Builder; |
||||
import com.mongodb.client.MapReduceIterable; |
||||
import com.mongodb.client.model.IndexOptions; |
||||
import com.mongodb.reactivestreams.client.MapReducePublisher; |
||||
|
||||
/** |
||||
* @author Christoph Strobl |
||||
* @since 2023/12 |
||||
*/ |
||||
public class MongoCompatibilityAdapter { |
||||
|
||||
private static final String NO_LONGER_SUPPORTED = "%s is no longer supported on Mongo Client 5+"; |
||||
|
||||
public static ClientSettingsBuilderAdapter clientSettingsBuilderAdapter(MongoClientSettings.Builder builder) { |
||||
return new MongoStreamFactoryFactorySettingsConfigurer(builder)::setStreamFactory; |
||||
} |
||||
|
||||
public static ClientSettingsAdapter clientSettingsAdapter(MongoClientSettings clientSettings) { |
||||
return new ClientSettingsAdapter() { |
||||
@Override |
||||
public <T> T getStreamFactoryFactory() { |
||||
if (MongoClientVersion.is5PlusClient()) { |
||||
return null; |
||||
} |
||||
|
||||
Method getStreamFactoryFactory = ReflectionUtils.findMethod(MongoClientSettings.class, |
||||
"getStreamFactoryFactory"); |
||||
return getStreamFactoryFactory != null |
||||
? (T) ReflectionUtils.invokeMethod(getStreamFactoryFactory, clientSettings) |
||||
: null; |
||||
} |
||||
}; |
||||
} |
||||
|
||||
public static IndexOptionsAdapter indexOptionsAdapter(IndexOptions options) { |
||||
return new IndexOptionsAdapter() { |
||||
@Override |
||||
public void setBucketSize(Double bucketSize) { |
||||
|
||||
if (MongoClientVersion.is5PlusClient()) { |
||||
throw new UnsupportedOperationException(NO_LONGER_SUPPORTED.formatted("IndexOptions.bucketSize")); |
||||
} |
||||
|
||||
Method setBucketSize = ReflectionUtils.findMethod(IndexOptions.class, "bucketSize", Double.class); |
||||
ReflectionUtils.invokeMethod(setBucketSize, options, bucketSize); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
@SuppressWarnings({ "deprecation" }) |
||||
public static MapReduceIterableAdapter mapReduceIterableAdapter(MapReduceIterable<?> iterable) { |
||||
return sharded -> { |
||||
if (MongoClientVersion.is5PlusClient()) { |
||||
throw new UnsupportedOperationException(NO_LONGER_SUPPORTED.formatted("sharded")); |
||||
} |
||||
|
||||
Method shardedMethod = ReflectionUtils.findMethod(iterable.getClass(), "MapReduceIterable.sharded", |
||||
boolean.class); |
||||
ReflectionUtils.invokeMethod(shardedMethod, iterable, shardedMethod); |
||||
}; |
||||
} |
||||
|
||||
public static MapReducePublisherAdapter mapReducePublisherAdapter(MapReducePublisher<?> publisher) { |
||||
return sharded -> { |
||||
if (MongoClientVersion.is5PlusClient()) { |
||||
throw new UnsupportedOperationException(NO_LONGER_SUPPORTED.formatted("sharded")); |
||||
} |
||||
|
||||
Method shardedMethod = ReflectionUtils.findMethod(publisher.getClass(), "MapReduceIterable.sharded", |
||||
boolean.class); |
||||
ReflectionUtils.invokeMethod(shardedMethod, publisher, shardedMethod); |
||||
}; |
||||
} |
||||
|
||||
public static ServerAddressAdapter serverAddressAdapter(ServerAddress serverAddress) { |
||||
return new ServerAddressAdapter() { |
||||
@Override |
||||
public InetSocketAddress getSocketAddress() { |
||||
|
||||
if(MongoClientVersion.is5PlusClient()) { |
||||
return null; |
||||
} |
||||
|
||||
Method serverAddressMethod = ReflectionUtils.findMethod(serverAddress.getClass(), "getSocketAddress"); |
||||
Object value = ReflectionUtils.invokeMethod(serverAddressMethod, serverAddress); |
||||
return value != null ? InetSocketAddress.class.cast(value) : null; |
||||
} |
||||
}; |
||||
} |
||||
|
||||
public interface IndexOptionsAdapter { |
||||
void setBucketSize(Double bucketSize); |
||||
} |
||||
|
||||
public interface ClientSettingsAdapter { |
||||
<T> T getStreamFactoryFactory(); |
||||
} |
||||
|
||||
public interface ClientSettingsBuilderAdapter { |
||||
<T> void setStreamFactoryFactory(T streamFactory); |
||||
} |
||||
|
||||
public interface MapReduceIterableAdapter { |
||||
void sharded(boolean sharded); |
||||
} |
||||
|
||||
public interface MapReducePublisherAdapter { |
||||
void sharded(boolean sharded); |
||||
} |
||||
|
||||
public interface ServerAddressAdapter { |
||||
InetSocketAddress getSocketAddress(); |
||||
} |
||||
|
||||
static class MongoStreamFactoryFactorySettingsConfigurer { |
||||
|
||||
private static final Log logger = LogFactory.getLog(MongoClientSettingsFactoryBean.class); |
||||
|
||||
private static final String STREAM_FACTORY_NAME = "com.mongodb.connection.StreamFactoryFactory"; |
||||
private static final boolean STREAM_FACTORY_PRESENT = ClassUtils.isPresent(STREAM_FACTORY_NAME, |
||||
MongoCompatibilityAdapter.class.getClassLoader()); |
||||
private final MongoClientSettings.Builder settingsBuilder; |
||||
|
||||
static boolean isStreamFactoryPresent() { |
||||
return STREAM_FACTORY_PRESENT; |
||||
} |
||||
|
||||
public MongoStreamFactoryFactorySettingsConfigurer(Builder settingsBuilder) { |
||||
this.settingsBuilder = settingsBuilder; |
||||
} |
||||
|
||||
void setStreamFactory(Object streamFactory) { |
||||
|
||||
if (MongoClientVersion.is5PlusClient()) { |
||||
logger.warn("StreamFactoryFactory is no longer available. Use TransportSettings instead."); |
||||
} |
||||
|
||||
if (isStreamFactoryPresent()) { //
|
||||
try { |
||||
Class<?> streamFactoryType = ClassUtils.forName(STREAM_FACTORY_NAME, |
||||
streamFactory.getClass().getClassLoader()); |
||||
if (!ClassUtils.isAssignable(streamFactoryType, streamFactory.getClass())) { |
||||
throw new IllegalArgumentException("Expected %s but found %s".formatted(streamFactoryType, streamFactory)); |
||||
} |
||||
|
||||
Method setter = ReflectionUtils.findMethod(settingsBuilder.getClass(), "streamFactoryFactory", |
||||
streamFactoryType); |
||||
if (setter != null) { |
||||
ReflectionUtils.invokeMethod(setter, settingsBuilder, streamFactoryType.cast(streamFactory)); |
||||
} |
||||
} catch (ClassNotFoundException e) { |
||||
throw new IllegalArgumentException("Cannot set StreamFactoryFactory for %s".formatted(settingsBuilder), e); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,378 @@
@@ -0,0 +1,378 @@
|
||||
/* |
||||
* Copyright 2024 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.util; |
||||
|
||||
import java.lang.reflect.Method; |
||||
import java.net.InetSocketAddress; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.reactivestreams.Publisher; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ClassUtils; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import com.mongodb.MongoClientSettings; |
||||
import com.mongodb.MongoClientSettings.Builder; |
||||
import com.mongodb.ServerAddress; |
||||
import com.mongodb.client.ClientSession; |
||||
import com.mongodb.client.MapReduceIterable; |
||||
import com.mongodb.client.MongoDatabase; |
||||
import com.mongodb.client.MongoIterable; |
||||
import com.mongodb.client.model.IndexOptions; |
||||
import com.mongodb.reactivestreams.client.MapReducePublisher; |
||||
|
||||
/** |
||||
* Compatibility adapter to bridge functionality across different MongoDB driver versions. |
||||
* <p> |
||||
* This class is for internal use within the framework and should not be used by applications. |
||||
* |
||||
* @author Christoph Strobl |
||||
* @since 4.3 |
||||
*/ |
||||
public class MongoCompatibilityAdapter { |
||||
|
||||
private static final String NO_LONGER_SUPPORTED = "%s is no longer supported on Mongo Client 5 or newer"; |
||||
|
||||
private static final @Nullable Method getStreamFactoryFactory = ReflectionUtils.findMethod(MongoClientSettings.class, |
||||
"getStreamFactoryFactory"); |
||||
|
||||
private static final @Nullable Method setBucketSize = ReflectionUtils.findMethod(IndexOptions.class, "bucketSize", |
||||
Double.class); |
||||
|
||||
/** |
||||
* Return a compatibility adapter for {@link MongoClientSettings.Builder}. |
||||
* |
||||
* @param builder |
||||
* @return |
||||
*/ |
||||
public static ClientSettingsBuilderAdapter clientSettingsBuilderAdapter(MongoClientSettings.Builder builder) { |
||||
return new MongoStreamFactoryFactorySettingsConfigurer(builder)::setStreamFactory; |
||||
} |
||||
|
||||
/** |
||||
* Return a compatibility adapter for {@link MongoClientSettings}. |
||||
* |
||||
* @param clientSettings |
||||
* @return |
||||
*/ |
||||
public static ClientSettingsAdapter clientSettingsAdapter(MongoClientSettings clientSettings) { |
||||
return new ClientSettingsAdapter() { |
||||
@Override |
||||
public <T> T getStreamFactoryFactory() { |
||||
|
||||
if (MongoClientVersion.isVersion5OrNewer() || getStreamFactoryFactory == null) { |
||||
return null; |
||||
} |
||||
|
||||
return (T) ReflectionUtils.invokeMethod(getStreamFactoryFactory, clientSettings); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Return a compatibility adapter for {@link IndexOptions}. |
||||
* |
||||
* @param options |
||||
* @return |
||||
*/ |
||||
public static IndexOptionsAdapter indexOptionsAdapter(IndexOptions options) { |
||||
return bucketSize -> { |
||||
|
||||
if (MongoClientVersion.isVersion5OrNewer() || setBucketSize == null) { |
||||
throw new UnsupportedOperationException(NO_LONGER_SUPPORTED.formatted("IndexOptions.bucketSize")); |
||||
} |
||||
|
||||
ReflectionUtils.invokeMethod(setBucketSize, options, bucketSize); |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Return a compatibility adapter for {@code MapReduceIterable}. |
||||
* |
||||
* @param iterable |
||||
* @return |
||||
*/ |
||||
@SuppressWarnings("deprecation") |
||||
public static MapReduceIterableAdapter mapReduceIterableAdapter(Object iterable) { |
||||
return sharded -> { |
||||
|
||||
if (MongoClientVersion.isVersion5OrNewer()) { |
||||
throw new UnsupportedOperationException(NO_LONGER_SUPPORTED.formatted("sharded")); |
||||
} |
||||
|
||||
// Use MapReduceIterable to avoid package-protected access violations to
|
||||
// com.mongodb.client.internal.MapReduceIterableImpl
|
||||
Method shardedMethod = ReflectionUtils.findMethod(MapReduceIterable.class, "sharded", boolean.class); |
||||
ReflectionUtils.invokeMethod(shardedMethod, iterable, sharded); |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Return a compatibility adapter for {@code MapReducePublisher}. |
||||
* |
||||
* @param publisher |
||||
* @return |
||||
*/ |
||||
@SuppressWarnings("deprecation") |
||||
public static MapReducePublisherAdapter mapReducePublisherAdapter(Object publisher) { |
||||
return sharded -> { |
||||
|
||||
if (MongoClientVersion.isVersion5OrNewer()) { |
||||
throw new UnsupportedOperationException(NO_LONGER_SUPPORTED.formatted("sharded")); |
||||
} |
||||
|
||||
// Use MapReducePublisher to avoid package-protected access violations to MapReducePublisherImpl
|
||||
Method shardedMethod = ReflectionUtils.findMethod(MapReducePublisher.class, "sharded", boolean.class); |
||||
ReflectionUtils.invokeMethod(shardedMethod, publisher, sharded); |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Return a compatibility adapter for {@link ServerAddress}. |
||||
* |
||||
* @param serverAddress |
||||
* @return |
||||
*/ |
||||
public static ServerAddressAdapter serverAddressAdapter(ServerAddress serverAddress) { |
||||
return () -> { |
||||
|
||||
if (MongoClientVersion.isVersion5OrNewer()) { |
||||
return null; |
||||
} |
||||
|
||||
Method serverAddressMethod = ReflectionUtils.findMethod(ServerAddress.class, "getSocketAddress"); |
||||
Object value = ReflectionUtils.invokeMethod(serverAddressMethod, serverAddress); |
||||
return value != null ? InetSocketAddress.class.cast(value) : null; |
||||
}; |
||||
} |
||||
|
||||
public static MongoDatabaseAdapterBuilder mongoDatabaseAdapter() { |
||||
return MongoDatabaseAdapter::new; |
||||
} |
||||
|
||||
public static ReactiveMongoDatabaseAdapterBuilder reactiveMongoDatabaseAdapter() { |
||||
return ReactiveMongoDatabaseAdapter::new; |
||||
} |
||||
|
||||
public interface IndexOptionsAdapter { |
||||
void setBucketSize(double bucketSize); |
||||
} |
||||
|
||||
public interface ClientSettingsAdapter { |
||||
@Nullable |
||||
<T> T getStreamFactoryFactory(); |
||||
} |
||||
|
||||
public interface ClientSettingsBuilderAdapter { |
||||
<T> void setStreamFactoryFactory(T streamFactory); |
||||
} |
||||
|
||||
public interface MapReduceIterableAdapter { |
||||
void sharded(boolean sharded); |
||||
} |
||||
|
||||
public interface MapReducePublisherAdapter { |
||||
void sharded(boolean sharded); |
||||
} |
||||
|
||||
public interface ServerAddressAdapter { |
||||
@Nullable |
||||
InetSocketAddress getSocketAddress(); |
||||
} |
||||
|
||||
public interface MongoDatabaseAdapterBuilder { |
||||
MongoDatabaseAdapter forDb(com.mongodb.client.MongoDatabase db); |
||||
} |
||||
|
||||
public static class MongoDatabaseAdapter { |
||||
|
||||
@Nullable //
|
||||
private static final Method LIST_COLLECTION_NAMES_METHOD; |
||||
|
||||
@Nullable //
|
||||
private static final Method LIST_COLLECTION_NAMES_METHOD_SESSION; |
||||
|
||||
private static final Class<?> collectionNamesReturnType; |
||||
|
||||
private final MongoDatabase db; |
||||
|
||||
static { |
||||
|
||||
if (MongoClientVersion.isSyncClientPresent()) { |
||||
|
||||
LIST_COLLECTION_NAMES_METHOD = ReflectionUtils.findMethod(MongoDatabase.class, "listCollectionNames"); |
||||
LIST_COLLECTION_NAMES_METHOD_SESSION = ReflectionUtils.findMethod(MongoDatabase.class, "listCollectionNames", |
||||
ClientSession.class); |
||||
|
||||
if (MongoClientVersion.isVersion5OrNewer()) { |
||||
try { |
||||
collectionNamesReturnType = ClassUtils.forName("com.mongodb.client.ListCollectionNamesIterable", |
||||
MongoDatabaseAdapter.class.getClassLoader()); |
||||
} catch (ClassNotFoundException e) { |
||||
throw new IllegalStateException("Unable to load com.mongodb.client.ListCollectionNamesIterable", e); |
||||
} |
||||
} else { |
||||
try { |
||||
collectionNamesReturnType = ClassUtils.forName("com.mongodb.client.MongoIterable", |
||||
MongoDatabaseAdapter.class.getClassLoader()); |
||||
} catch (ClassNotFoundException e) { |
||||
throw new IllegalStateException("Unable to load com.mongodb.client.ListCollectionNamesIterable", e); |
||||
} |
||||
} |
||||
} else { |
||||
LIST_COLLECTION_NAMES_METHOD = null; |
||||
LIST_COLLECTION_NAMES_METHOD_SESSION = null; |
||||
collectionNamesReturnType = Object.class; |
||||
} |
||||
} |
||||
|
||||
public MongoDatabaseAdapter(MongoDatabase db) { |
||||
this.db = db; |
||||
} |
||||
|
||||
public Class<? extends MongoIterable<String>> collectionNameIterableType() { |
||||
return (Class<? extends MongoIterable<String>>) collectionNamesReturnType; |
||||
} |
||||
|
||||
public MongoIterable<String> listCollectionNames() { |
||||
|
||||
Assert.state(LIST_COLLECTION_NAMES_METHOD != null, "No method listCollectionNames present for %s".formatted(db)); |
||||
return (MongoIterable<String>) ReflectionUtils.invokeMethod(LIST_COLLECTION_NAMES_METHOD, db); |
||||
} |
||||
|
||||
public MongoIterable<String> listCollectionNames(ClientSession clientSession) { |
||||
Assert.state(LIST_COLLECTION_NAMES_METHOD != null, |
||||
"No method listCollectionNames(ClientSession) present for %s".formatted(db)); |
||||
return (MongoIterable<String>) ReflectionUtils.invokeMethod(LIST_COLLECTION_NAMES_METHOD_SESSION, db, |
||||
clientSession); |
||||
} |
||||
} |
||||
|
||||
public interface ReactiveMongoDatabaseAdapterBuilder { |
||||
ReactiveMongoDatabaseAdapter forDb(com.mongodb.reactivestreams.client.MongoDatabase db); |
||||
} |
||||
|
||||
public static class ReactiveMongoDatabaseAdapter { |
||||
|
||||
@Nullable //
|
||||
private static final Method LIST_COLLECTION_NAMES_METHOD; |
||||
|
||||
@Nullable //
|
||||
private static final Method LIST_COLLECTION_NAMES_METHOD_SESSION; |
||||
|
||||
private static final Class<?> collectionNamesReturnType; |
||||
|
||||
private final com.mongodb.reactivestreams.client.MongoDatabase db; |
||||
|
||||
static { |
||||
|
||||
if (MongoClientVersion.isReactiveClientPresent()) { |
||||
|
||||
LIST_COLLECTION_NAMES_METHOD = ReflectionUtils |
||||
.findMethod(com.mongodb.reactivestreams.client.MongoDatabase.class, "listCollectionNames"); |
||||
LIST_COLLECTION_NAMES_METHOD_SESSION = ReflectionUtils.findMethod( |
||||
com.mongodb.reactivestreams.client.MongoDatabase.class, "listCollectionNames", |
||||
com.mongodb.reactivestreams.client.ClientSession.class); |
||||
|
||||
if (MongoClientVersion.isVersion5OrNewer()) { |
||||
try { |
||||
collectionNamesReturnType = ClassUtils.forName( |
||||
"com.mongodb.reactivestreams.client.ListCollectionNamesPublisher", |
||||
ReactiveMongoDatabaseAdapter.class.getClassLoader()); |
||||
} catch (ClassNotFoundException e) { |
||||
throw new IllegalStateException("com.mongodb.reactivestreams.client.ListCollectionNamesPublisher", e); |
||||
} |
||||
} else { |
||||
try { |
||||
collectionNamesReturnType = ClassUtils.forName("org.reactivestreams.Publisher", |
||||
ReactiveMongoDatabaseAdapter.class.getClassLoader()); |
||||
} catch (ClassNotFoundException e) { |
||||
throw new IllegalStateException("org.reactivestreams.Publisher", e); |
||||
} |
||||
} |
||||
} else { |
||||
LIST_COLLECTION_NAMES_METHOD = null; |
||||
LIST_COLLECTION_NAMES_METHOD_SESSION = null; |
||||
collectionNamesReturnType = Object.class; |
||||
} |
||||
} |
||||
|
||||
ReactiveMongoDatabaseAdapter(com.mongodb.reactivestreams.client.MongoDatabase db) { |
||||
this.db = db; |
||||
} |
||||
|
||||
public Class<? extends Publisher<String>> collectionNamePublisherType() { |
||||
return (Class<? extends Publisher<String>>) collectionNamesReturnType; |
||||
|
||||
} |
||||
|
||||
public Publisher<String> listCollectionNames() { |
||||
Assert.state(LIST_COLLECTION_NAMES_METHOD != null, "No method listCollectionNames present for %s".formatted(db)); |
||||
return (Publisher<String>) ReflectionUtils.invokeMethod(LIST_COLLECTION_NAMES_METHOD, db); |
||||
} |
||||
|
||||
public Publisher<String> listCollectionNames(com.mongodb.reactivestreams.client.ClientSession clientSession) { |
||||
Assert.state(LIST_COLLECTION_NAMES_METHOD != null, |
||||
"No method listCollectionNames(ClientSession) present for %s".formatted(db)); |
||||
return (Publisher<String>) ReflectionUtils.invokeMethod(LIST_COLLECTION_NAMES_METHOD_SESSION, db, clientSession); |
||||
} |
||||
} |
||||
|
||||
static class MongoStreamFactoryFactorySettingsConfigurer { |
||||
|
||||
private static final Log logger = LogFactory.getLog(MongoStreamFactoryFactorySettingsConfigurer.class); |
||||
|
||||
private static final String STREAM_FACTORY_NAME = "com.mongodb.connection.StreamFactoryFactory"; |
||||
private static final boolean STREAM_FACTORY_PRESENT = ClassUtils.isPresent(STREAM_FACTORY_NAME, |
||||
MongoCompatibilityAdapter.class.getClassLoader()); |
||||
private final MongoClientSettings.Builder settingsBuilder; |
||||
|
||||
static boolean isStreamFactoryPresent() { |
||||
return STREAM_FACTORY_PRESENT; |
||||
} |
||||
|
||||
public MongoStreamFactoryFactorySettingsConfigurer(Builder settingsBuilder) { |
||||
this.settingsBuilder = settingsBuilder; |
||||
} |
||||
|
||||
void setStreamFactory(Object streamFactory) { |
||||
|
||||
if (MongoClientVersion.isVersion5OrNewer() && isStreamFactoryPresent()) { |
||||
logger.warn("StreamFactoryFactory is no longer available. Use TransportSettings instead."); |
||||
return; |
||||
} |
||||
|
||||
try { |
||||
Class<?> streamFactoryType = ClassUtils.forName(STREAM_FACTORY_NAME, streamFactory.getClass().getClassLoader()); |
||||
|
||||
if (!ClassUtils.isAssignable(streamFactoryType, streamFactory.getClass())) { |
||||
throw new IllegalArgumentException("Expected %s but found %s".formatted(streamFactoryType, streamFactory)); |
||||
} |
||||
|
||||
Method setter = ReflectionUtils.findMethod(settingsBuilder.getClass(), "streamFactoryFactory", |
||||
streamFactoryType); |
||||
if (setter != null) { |
||||
ReflectionUtils.invokeMethod(setter, settingsBuilder, streamFactoryType.cast(streamFactory)); |
||||
} |
||||
} catch (ReflectiveOperationException e) { |
||||
throw new IllegalArgumentException("Cannot set StreamFactoryFactory for %s".formatted(settingsBuilder), e); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,128 @@
@@ -0,0 +1,128 @@
|
||||
/* |
||||
* Copyright 2024 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 static org.springframework.aot.hint.MemberCategory.*; |
||||
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.*; |
||||
|
||||
import java.util.function.Predicate; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.springframework.aot.hint.RuntimeHints; |
||||
import org.springframework.aot.hint.TypeReference; |
||||
import org.springframework.data.mongodb.test.util.ClassPathExclusions; |
||||
|
||||
import com.mongodb.MongoClientSettings; |
||||
import com.mongodb.ServerAddress; |
||||
import com.mongodb.UnixServerAddress; |
||||
import com.mongodb.client.MapReduceIterable; |
||||
import com.mongodb.client.model.IndexOptions; |
||||
import com.mongodb.reactivestreams.client.MapReducePublisher; |
||||
|
||||
/** |
||||
* Unit Tests for {@link MongoRuntimeHints}. |
||||
* |
||||
* @author Christoph Strobl |
||||
*/ |
||||
class MongoRuntimeHintsUnitTests { |
||||
|
||||
@Test // GH-4578
|
||||
@ClassPathExclusions(packages = { "com.mongodb.client", "com.mongodb.reactivestreams.client" }) |
||||
void shouldRegisterGeneralCompatibilityHints() { |
||||
|
||||
RuntimeHints runtimeHints = new RuntimeHints(); |
||||
|
||||
new MongoRuntimeHints().registerHints(runtimeHints, this.getClass().getClassLoader()); |
||||
|
||||
Predicate<RuntimeHints> expected = reflection().onType(MongoClientSettings.class) |
||||
.withMemberCategory(INVOKE_PUBLIC_METHODS) |
||||
.and(reflection().onType(MongoClientSettings.Builder.class).withMemberCategory(INVOKE_PUBLIC_METHODS)) |
||||
.and(reflection().onType(IndexOptions.class).withMemberCategory(INVOKE_PUBLIC_METHODS)) |
||||
.and(reflection().onType(ServerAddress.class).withMemberCategory(INVOKE_PUBLIC_METHODS)) |
||||
.and(reflection().onType(UnixServerAddress.class).withMemberCategory(INVOKE_PUBLIC_METHODS)) |
||||
.and(reflection().onType(TypeReference.of("com.mongodb.connection.StreamFactoryFactory")) |
||||
.withMemberCategory(INTROSPECT_PUBLIC_METHODS)); |
||||
|
||||
assertThat(runtimeHints).matches(expected); |
||||
} |
||||
|
||||
@Test // GH-4578
|
||||
@ClassPathExclusions(packages = { "com.mongodb.reactivestreams.client" }) |
||||
void shouldRegisterSyncCompatibilityHintsIfPresent() { |
||||
|
||||
RuntimeHints runtimeHints = new RuntimeHints(); |
||||
|
||||
new MongoRuntimeHints().registerHints(runtimeHints, this.getClass().getClassLoader()); |
||||
|
||||
Predicate<RuntimeHints> expected = reflection().onType(MapReduceIterable.class) |
||||
.withMemberCategory(INVOKE_PUBLIC_METHODS) |
||||
.and(reflection().onType(TypeReference.of("com.mongodb.client.internal.MapReduceIterableImpl")) |
||||
.withMemberCategory(INVOKE_PUBLIC_METHODS)); |
||||
|
||||
assertThat(runtimeHints).matches(expected); |
||||
} |
||||
|
||||
@Test // GH-4578
|
||||
@ClassPathExclusions(packages = { "com.mongodb.client" }) |
||||
void shouldNotRegisterSyncCompatibilityHintsIfClientNotPresent() { |
||||
|
||||
RuntimeHints runtimeHints = new RuntimeHints(); |
||||
|
||||
new MongoRuntimeHints().registerHints(runtimeHints, this.getClass().getClassLoader()); |
||||
|
||||
Predicate<RuntimeHints> expected = reflection().onType(TypeReference.of("com.mongodb.client.MapReduceIterable")) |
||||
.withMemberCategory(INVOKE_PUBLIC_METHODS).negate() |
||||
.and(reflection().onType(TypeReference.of("com.mongodb.client.internal.MapReduceIterableImpl")) |
||||
.withMemberCategory(INVOKE_PUBLIC_METHODS).negate()); |
||||
|
||||
assertThat(runtimeHints).matches(expected); |
||||
} |
||||
|
||||
@Test // GH-4578
|
||||
@ClassPathExclusions(packages = { "com.mongodb.client" }) |
||||
void shouldRegisterReactiveCompatibilityHintsIfPresent() { |
||||
|
||||
RuntimeHints runtimeHints = new RuntimeHints(); |
||||
|
||||
new MongoRuntimeHints().registerHints(runtimeHints, this.getClass().getClassLoader()); |
||||
|
||||
Predicate<RuntimeHints> expected = reflection().onType(MapReducePublisher.class) |
||||
.withMemberCategory(INVOKE_PUBLIC_METHODS) |
||||
.and(reflection().onType(TypeReference.of("com.mongodb.reactivestreams.client.internal.MapReducePublisherImpl")) |
||||
.withMemberCategory(INVOKE_PUBLIC_METHODS)); |
||||
|
||||
assertThat(runtimeHints).matches(expected); |
||||
} |
||||
|
||||
@Test // GH-4578
|
||||
@ClassPathExclusions(packages = { "com.mongodb.reactivestreams.client" }) |
||||
void shouldNotRegisterReactiveCompatibilityHintsIfClientNotPresent() { |
||||
|
||||
RuntimeHints runtimeHints = new RuntimeHints(); |
||||
|
||||
new MongoRuntimeHints().registerHints(runtimeHints, this.getClass().getClassLoader()); |
||||
|
||||
Predicate<RuntimeHints> expected = reflection() |
||||
.onType(TypeReference.of("com.mongodb.reactivestreams.client.MapReducePublisher")) |
||||
.withMemberCategory(INVOKE_PUBLIC_METHODS).negate() |
||||
.and(reflection().onType(TypeReference.of("com.mongodb.reactivestreams.client.internal.MapReducePublisherImpl")) |
||||
.withMemberCategory(INVOKE_PUBLIC_METHODS).negate()); |
||||
|
||||
assertThat(runtimeHints).matches(expected); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
/* |
||||
* Copyright 2024 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.test.util; |
||||
|
||||
import java.lang.annotation.Documented; |
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
|
||||
/** |
||||
* Annotation used to exclude entries from the classpath. |
||||
* Simplified version of <a href="https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ClassPathExclusions.java">ClassPathExclusions</a>. |
||||
* |
||||
* @author Christoph Strobl |
||||
*/ |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target({ ElementType.TYPE, ElementType.METHOD }) |
||||
@Documented |
||||
@ExtendWith(ClassPathExclusionsExtension.class) |
||||
public @interface ClassPathExclusions { |
||||
|
||||
/** |
||||
* One or more packages that should be excluded from the classpath. |
||||
* |
||||
* @return the excluded packages |
||||
*/ |
||||
String[] packages(); |
||||
|
||||
} |
||||
@ -0,0 +1,129 @@
@@ -0,0 +1,129 @@
|
||||
/* |
||||
* Copyright 2024 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.test.util; |
||||
|
||||
import java.lang.reflect.Method; |
||||
|
||||
import org.junit.jupiter.api.extension.ExtensionContext; |
||||
import org.junit.jupiter.api.extension.InvocationInterceptor; |
||||
import org.junit.jupiter.api.extension.ReflectiveInvocationContext; |
||||
import org.junit.platform.engine.discovery.DiscoverySelectors; |
||||
import org.junit.platform.launcher.Launcher; |
||||
import org.junit.platform.launcher.LauncherDiscoveryRequest; |
||||
import org.junit.platform.launcher.TestPlan; |
||||
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; |
||||
import org.junit.platform.launcher.core.LauncherFactory; |
||||
import org.junit.platform.launcher.listeners.SummaryGeneratingListener; |
||||
import org.junit.platform.launcher.listeners.TestExecutionSummary; |
||||
import org.springframework.util.CollectionUtils; |
||||
|
||||
/** |
||||
* Simplified version of <a href="https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathClassLoader.java">ModifiedClassPathExtension</a>. |
||||
* |
||||
* @author Christoph Strobl |
||||
*/ |
||||
class ClassPathExclusionsExtension implements InvocationInterceptor { |
||||
|
||||
@Override |
||||
public void interceptBeforeAllMethod(Invocation<Void> invocation, |
||||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable { |
||||
intercept(invocation, extensionContext); |
||||
} |
||||
|
||||
@Override |
||||
public void interceptBeforeEachMethod(Invocation<Void> invocation, |
||||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable { |
||||
intercept(invocation, extensionContext); |
||||
} |
||||
|
||||
@Override |
||||
public void interceptAfterEachMethod(Invocation<Void> invocation, |
||||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable { |
||||
intercept(invocation, extensionContext); |
||||
} |
||||
|
||||
@Override |
||||
public void interceptAfterAllMethod(Invocation<Void> invocation, |
||||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable { |
||||
intercept(invocation, extensionContext); |
||||
} |
||||
|
||||
@Override |
||||
public void interceptTestMethod(Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext, |
||||
ExtensionContext extensionContext) throws Throwable { |
||||
interceptMethod(invocation, invocationContext, extensionContext); |
||||
} |
||||
|
||||
@Override |
||||
public void interceptTestTemplateMethod(Invocation<Void> invocation, |
||||
ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable { |
||||
interceptMethod(invocation, invocationContext, extensionContext); |
||||
} |
||||
|
||||
private void interceptMethod(Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext, |
||||
ExtensionContext extensionContext) throws Throwable { |
||||
|
||||
if (isModifiedClassPathClassLoader(extensionContext)) { |
||||
invocation.proceed(); |
||||
return; |
||||
} |
||||
|
||||
Class<?> testClass = extensionContext.getRequiredTestClass(); |
||||
Method testMethod = invocationContext.getExecutable(); |
||||
PackageExcludingClassLoader modifiedClassLoader = PackageExcludingClassLoader.get(testClass, testMethod); |
||||
if (modifiedClassLoader == null) { |
||||
invocation.proceed(); |
||||
return; |
||||
} |
||||
invocation.skip(); |
||||
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); |
||||
Thread.currentThread().setContextClassLoader(modifiedClassLoader); |
||||
try { |
||||
runTest(extensionContext.getUniqueId()); |
||||
} finally { |
||||
Thread.currentThread().setContextClassLoader(originalClassLoader); |
||||
} |
||||
} |
||||
|
||||
private void runTest(String testId) throws Throwable { |
||||
|
||||
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() |
||||
.selectors(DiscoverySelectors.selectUniqueId(testId)).build(); |
||||
Launcher launcher = LauncherFactory.create(); |
||||
TestPlan testPlan = launcher.discover(request); |
||||
SummaryGeneratingListener listener = new SummaryGeneratingListener(); |
||||
launcher.registerTestExecutionListeners(listener); |
||||
launcher.execute(testPlan); |
||||
TestExecutionSummary summary = listener.getSummary(); |
||||
if (!CollectionUtils.isEmpty(summary.getFailures())) { |
||||
throw summary.getFailures().get(0).getException(); |
||||
} |
||||
} |
||||
|
||||
private void intercept(Invocation<Void> invocation, ExtensionContext extensionContext) throws Throwable { |
||||
if (isModifiedClassPathClassLoader(extensionContext)) { |
||||
invocation.proceed(); |
||||
return; |
||||
} |
||||
invocation.skip(); |
||||
} |
||||
|
||||
private boolean isModifiedClassPathClassLoader(ExtensionContext extensionContext) { |
||||
Class<?> testClass = extensionContext.getRequiredTestClass(); |
||||
ClassLoader classLoader = testClass.getClassLoader(); |
||||
return classLoader.getClass().getName().equals(PackageExcludingClassLoader.class.getName()); |
||||
} |
||||
} |
||||
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
/* |
||||
* Copyright 2024 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.test.util; |
||||
|
||||
import java.lang.annotation.Documented; |
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
/** |
||||
* @author Christoph Strobl |
||||
* @see ClassPathExclusions |
||||
*/ |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target({ ElementType.TYPE, ElementType.METHOD }) |
||||
@Documented |
||||
@ClassPathExclusions(packages = { "com.mongodb.reactivestreams.client" }) |
||||
public @interface ExcludeReactiveClientFromClassPath { |
||||
|
||||
} |
||||
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
/* |
||||
* Copyright 2024 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.test.util; |
||||
|
||||
import java.lang.annotation.Documented; |
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
/** |
||||
* @author Christoph Strobl |
||||
* @see ClassPathExclusions |
||||
*/ |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target({ ElementType.TYPE, ElementType.METHOD }) |
||||
@Documented |
||||
@ClassPathExclusions(packages = { "com.mongodb.client" }) |
||||
public @interface ExcludeSyncClientFromClassPath { |
||||
|
||||
} |
||||
@ -0,0 +1,142 @@
@@ -0,0 +1,142 @@
|
||||
/* |
||||
* Copyright 2024 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.test.util; |
||||
|
||||
import java.io.File; |
||||
import java.lang.management.ManagementFactory; |
||||
import java.lang.reflect.Method; |
||||
import java.net.URL; |
||||
import java.net.URLClassLoader; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.EnumSet; |
||||
import java.util.List; |
||||
import java.util.Objects; |
||||
import java.util.Set; |
||||
import java.util.function.BiConsumer; |
||||
import java.util.function.BinaryOperator; |
||||
import java.util.function.Function; |
||||
import java.util.function.Supplier; |
||||
import java.util.stream.Collector; |
||||
import java.util.stream.Stream; |
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
/** |
||||
* Simplified version of <a href="https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/classpath/ModifiedClassPathClassLoader.java">ModifiedClassPathClassLoader</a>. |
||||
* |
||||
* @author Christoph Strobl |
||||
*/ |
||||
class PackageExcludingClassLoader extends URLClassLoader { |
||||
|
||||
private final Set<String> excludedPackages; |
||||
private final ClassLoader junitLoader; |
||||
|
||||
PackageExcludingClassLoader(URL[] urls, ClassLoader parent, Collection<String> excludedPackages, |
||||
ClassLoader junitClassLoader) { |
||||
|
||||
super(urls, parent); |
||||
this.excludedPackages = Set.copyOf(excludedPackages); |
||||
this.junitLoader = junitClassLoader; |
||||
} |
||||
|
||||
@Override |
||||
public Class<?> loadClass(String name) throws ClassNotFoundException { |
||||
|
||||
if (name.startsWith("org.junit") || name.startsWith("org.hamcrest")) { |
||||
return Class.forName(name, false, this.junitLoader); |
||||
} |
||||
|
||||
String packageName = ClassUtils.getPackageName(name); |
||||
if (this.excludedPackages.contains(packageName)) { |
||||
throw new ClassNotFoundException(name); |
||||
} |
||||
return super.loadClass(name); |
||||
} |
||||
|
||||
static PackageExcludingClassLoader get(Class<?> testClass, Method testMethod) { |
||||
|
||||
List<String> excludedPackages = readExcludedPackages(testClass, testMethod); |
||||
|
||||
if (excludedPackages.isEmpty()) { |
||||
return null; |
||||
} |
||||
|
||||
ClassLoader testClassClassLoader = testClass.getClassLoader(); |
||||
Stream<URL> urls = null; |
||||
if (testClassClassLoader instanceof URLClassLoader urlClassLoader) { |
||||
urls = Stream.of(urlClassLoader.getURLs()); |
||||
} else { |
||||
urls = Stream.of(ManagementFactory.getRuntimeMXBean().getClassPath().split(File.pathSeparator)) |
||||
.map(PackageExcludingClassLoader::toURL); |
||||
} |
||||
|
||||
return new PackageExcludingClassLoader(urls.toArray(URL[]::new), testClassClassLoader.getParent(), excludedPackages, |
||||
testClassClassLoader); |
||||
} |
||||
|
||||
private static List<String> readExcludedPackages(Class<?> testClass, Method testMethod) { |
||||
|
||||
return Stream.of( //
|
||||
AnnotatedElementUtils.findMergedAnnotation(testClass, ClassPathExclusions.class), |
||||
AnnotatedElementUtils.findMergedAnnotation(testMethod, ClassPathExclusions.class) //
|
||||
).filter(Objects::nonNull) //
|
||||
.map(ClassPathExclusions::packages) //
|
||||
.collect(new CombingArrayCollector<String>()); |
||||
} |
||||
|
||||
private static URL toURL(String entry) { |
||||
try { |
||||
return new File(entry).toURI().toURL(); |
||||
} catch (Exception ex) { |
||||
throw new IllegalArgumentException(ex); |
||||
} |
||||
} |
||||
|
||||
private static class CombingArrayCollector<T> implements Collector<T[], List<T>, List<T>> { |
||||
|
||||
@Override |
||||
public Supplier<List<T>> supplier() { |
||||
return ArrayList::new; |
||||
} |
||||
|
||||
@Override |
||||
public BiConsumer<List<T>, T[]> accumulator() { |
||||
return (target, values) -> target.addAll(Arrays.asList(values)); |
||||
} |
||||
|
||||
@Override |
||||
public BinaryOperator<List<T>> combiner() { |
||||
return (r1, r2) -> { |
||||
r1.addAll(r2); |
||||
return r1; |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
public Function<List<T>, List<T>> finisher() { |
||||
return i -> (List<T>) i; |
||||
} |
||||
|
||||
@Override |
||||
public Set<Characteristics> characteristics() { |
||||
return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH)); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
/* |
||||
* Copyright 2024 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.util; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.springframework.data.mongodb.test.util.ClassPathExclusions; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
import com.mongodb.internal.build.MongoDriverVersion; |
||||
|
||||
/** |
||||
* Tests for {@link MongoClientVersion}. |
||||
* |
||||
* @author Christoph Strobl |
||||
*/ |
||||
class MongoClientVersionUnitTests { |
||||
|
||||
@Test // GH-4578
|
||||
void parsesClientVersionCorrectly() { |
||||
assertThat(MongoClientVersion.isVersion5OrNewer()).isEqualTo(MongoDriverVersion.VERSION.startsWith("5")); |
||||
} |
||||
|
||||
@Test // GH-4578
|
||||
@ClassPathExclusions(packages = { "com.mongodb.internal.build" }) |
||||
void fallsBackToClassLookupIfDriverVersionNotPresent() { |
||||
assertThat(MongoClientVersion.isVersion5OrNewer()).isEqualTo( |
||||
ClassUtils.isPresent("com.mongodb.internal.connection.StreamFactoryFactory", this.getClass().getClassLoader())); |
||||
} |
||||
} |
||||
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
/* |
||||
* Copyright 2024 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.util; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.springframework.data.mongodb.test.util.ExcludeReactiveClientFromClassPath; |
||||
import org.springframework.data.mongodb.test.util.ExcludeSyncClientFromClassPath; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
/** |
||||
* @author Christoph Strobl |
||||
*/ |
||||
class MongoCompatibilityAdapterUnitTests { |
||||
|
||||
@Test // GH-4578
|
||||
@ExcludeReactiveClientFromClassPath |
||||
void returnsListCollectionNameIterableTypeCorrectly() { |
||||
|
||||
String expectedType = MongoClientVersion.isVersion5OrNewer() ? "ListCollectionNamesIterable" : "MongoIterable"; |
||||
assertThat(MongoCompatibilityAdapter.mongoDatabaseAdapter().forDb(null).collectionNameIterableType()) |
||||
.satisfies(type -> assertThat(ClassUtils.getShortName(type)).isEqualTo(expectedType)); |
||||
|
||||
} |
||||
|
||||
@Test // GH-4578
|
||||
@ExcludeSyncClientFromClassPath |
||||
void returnsListCollectionNamePublisherTypeCorrectly() { |
||||
|
||||
String expectedType = MongoClientVersion.isVersion5OrNewer() ? "ListCollectionNamesPublisher" : "Publisher"; |
||||
assertThat(MongoCompatibilityAdapter.reactiveMongoDatabaseAdapter().forDb(null).collectionNamePublisherType()) |
||||
.satisfies(type -> assertThat(ClassUtils.getShortName(type)).isEqualTo(expectedType)); |
||||
|
||||
} |
||||
} |
||||
Loading…
Reference in new issue