18 changed files with 15 additions and 602 deletions
@ -1,417 +0,0 @@
@@ -1,417 +0,0 @@
|
||||
/* |
||||
* Copyright 2024-2025 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.client.model.vault.RangeOptions; |
||||
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 |
||||
* @author Ross Lawley |
||||
* @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 String NOT_SUPPORTED_ON_4 = "%s is not supported on Mongo Client 4"; |
||||
|
||||
private static final @Nullable Method getStreamFactoryFactory = ReflectionUtils.findMethod(MongoClientSettings.class, |
||||
"getStreamFactoryFactory"); |
||||
|
||||
private static final @Nullable Method setBucketSize = ReflectionUtils.findMethod(IndexOptions.class, "bucketSize", |
||||
Double.class); |
||||
|
||||
private static final @Nullable Method setTrimFactor; |
||||
|
||||
static { |
||||
|
||||
// method name changed in between
|
||||
Method trimFactor = ReflectionUtils.findMethod(RangeOptions.class, "setTrimFactor", Integer.class); |
||||
if (trimFactor != null) { |
||||
setTrimFactor = trimFactor; |
||||
} else { |
||||
setTrimFactor = ReflectionUtils.findMethod(RangeOptions.class, "trimFactor", Integer.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 {@link RangeOptions}. |
||||
* |
||||
* @param options |
||||
* @return |
||||
*/ |
||||
public static RangeOptionsAdapter rangeOptionsAdapter(RangeOptions options) { |
||||
return trimFactor -> { |
||||
|
||||
if (!MongoClientVersion.isVersion5orNewer() || setTrimFactor == null) { |
||||
throw new UnsupportedOperationException(NOT_SUPPORTED_ON_4.formatted("RangeOptions.trimFactor")); |
||||
} |
||||
|
||||
ReflectionUtils.invokeMethod(setTrimFactor, options, trimFactor); |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* 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 interface RangeOptionsAdapter { |
||||
void trimFactor(Integer trimFactor); |
||||
} |
||||
|
||||
@SuppressWarnings({ "unchecked", "DataFlowIssue" }) |
||||
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); |
||||
} |
||||
|
||||
@SuppressWarnings({ "unchecked", "DataFlowIssue" }) |
||||
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); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,49 +0,0 @@
@@ -1,49 +0,0 @@
|
||||
/* |
||||
* Copyright 2024-2025 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