From de992809129b6d37d335d08ae082ca42ee5ea91a Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Fri, 5 Jun 2026 09:45:49 +0900 Subject: [PATCH] Add back 3.x API for 3.4 release --- .../couchbase/lite/WorkManagerReplication.kt | 14 +- .../java/com/couchbase/lite/Defaults.java | 2 + .../lite/ReplicatorConfiguration.java | 46 ++++- .../couchbase/lite/ConfigurationFactories.kt | 178 +++++++++++++++++- .../com/couchbase/lite/ConfigFactoryTest.kt | 106 ++++++++++- 5 files changed, 334 insertions(+), 12 deletions(-) diff --git a/android-ktx/lib/src/main/kotlin/com/couchbase/lite/WorkManagerReplication.kt b/android-ktx/lib/src/main/kotlin/com/couchbase/lite/WorkManagerReplication.kt index ef2c9543..86803fed 100644 --- a/android-ktx/lib/src/main/kotlin/com/couchbase/lite/WorkManagerReplication.kt +++ b/android-ktx/lib/src/main/kotlin/com/couchbase/lite/WorkManagerReplication.kt @@ -120,9 +120,19 @@ class WorkManagerReplicatorConfiguration private constructor(replConfig: Replica companion object { /** * Factory method for WorkManagerReplicatorConfiguration. + * @deprecated Use from(collections: Set, target: Endpoint) */ - fun from(collections: Set, target: Endpoint) = - WorkManagerReplicatorConfiguration(ReplicatorConfiguration(collections, target)) + + @Deprecated( + "Use from(collections: Set, target: Endpoint)", + replaceWith = ReplaceWith("WorkManagerReplicatorConfiguration.from(collections: Set, target: Endpoint)") + ) + fun from(target: Endpoint) = WorkManagerReplicatorConfiguration(ReplicatorConfiguration(target)) + + /** + * Factory method for WorkManagerReplicatorConfiguration. + */ + fun from(collections: Set, target: Endpoint) = WorkManagerReplicatorConfiguration(ReplicatorConfiguration(collections, target)) /** * Factory method for WorkManagerReplicatorConfiguration. diff --git a/common/main/java/com/couchbase/lite/Defaults.java b/common/main/java/com/couchbase/lite/Defaults.java index 545c0a4a..8403e9b7 100644 --- a/common/main/java/com/couchbase/lite/Defaults.java +++ b/common/main/java/com/couchbase/lite/Defaults.java @@ -114,6 +114,8 @@ private Replicator() { } * Max wait time between retry attempts in seconds */ public static final int MAX_ATTEMPTS_WAIT_TIME = 300; + @Deprecated + public static final int MAX_ATTEMPT_WAIT_TIME = MAX_ATTEMPTS_WAIT_TIME; /** * Purge documents when a user loses access diff --git a/common/main/java/com/couchbase/lite/ReplicatorConfiguration.java b/common/main/java/com/couchbase/lite/ReplicatorConfiguration.java index d5b5052e..c16e704e 100644 --- a/common/main/java/com/couchbase/lite/ReplicatorConfiguration.java +++ b/common/main/java/com/couchbase/lite/ReplicatorConfiguration.java @@ -16,10 +16,12 @@ package com.couchbase.lite; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.util.Map; import com.couchbase.lite.internal.ImmutableReplicatorConfiguration; +import com.couchbase.lite.internal.utils.Preconditions; public final class ReplicatorConfiguration extends AbstractReplicatorConfiguration { @@ -28,6 +30,42 @@ public final class ReplicatorConfiguration extends AbstractReplicatorConfigurati // Constructors //--------------------------------------------- + /** + * Create a Replicator Configuration for the given database and target endpoint. + * + *

When using this constructor, the default collection of the provided + * database will be automatically included in the configuration.

+ * + *

If you do not intend to replicate the default collection, use + * ReplicatorConfiguration(Endpoint) instead, and explicitly add + * the intended collections to avoid unintended behavior.

+ * + * @param database the database to be synchronized + * @param target the endpoint with which to synchronize it + * @deprecated Use ReplicatorConfiguration(java.util.Collection<CollectionConfiguration>, Endpoint) + */ + @Deprecated + public ReplicatorConfiguration(@NonNull Database database, @NonNull Endpoint target) { + super( + Preconditions.assertNotNull(database, "database"), + configureDefaultCollection(database), + target); + } + + /** + * Create a Replicator Configuration for the given target endpoint + * + *

This constructor does not configure any collections by default. + * Use {@link #addCollection(Collection, CollectionConfiguration)} or + * {@link #addCollections(java.util.Collection, CollectionConfiguration)} to + * configure collections to replicate.

+ * + * @param target the target endpoint + * @deprecated Use ReplicatorConfiguration(java.util.Collection<CollectionConfiguration>, Endpoint) + */ + @Deprecated + public ReplicatorConfiguration(@NonNull Endpoint target) { super(null, null, target); } + /** * Creates a Replicator Configuration with a set of collection configurations and * the target endpoint. @@ -50,11 +88,11 @@ public ReplicatorConfiguration(@NonNull java.util.Collection collections) { + ReplicatorConfiguration(@NonNull Endpoint target, @Nullable Map collections) { super( - AbstractDatabase.getDbForCollections(collections.keySet()), - collections, - target); + (collections == null) ? null : AbstractDatabase.getDbForCollections(collections.keySet()), + collections, + target); } @NonNull diff --git a/common/main/kotlin/com/couchbase/lite/ConfigurationFactories.kt b/common/main/kotlin/com/couchbase/lite/ConfigurationFactories.kt index 483e31d7..41adce76 100644 --- a/common/main/kotlin/com/couchbase/lite/ConfigurationFactories.kt +++ b/common/main/kotlin/com/couchbase/lite/ConfigurationFactories.kt @@ -15,6 +15,7 @@ // package com.couchbase.lite +import com.couchbase.lite.internal.getCollectionConfigs import java.security.cert.X509Certificate @@ -75,9 +76,85 @@ val ReplicatorConfigurationFactory: ReplicatorConfiguration? = null * @param acceptParentDomainCookies Advanced: accept cookies for parent domains. * * @see com.couchbase.lite.ReplicatorConfiguration + * @deprecated Use ReplicatorConfigurationFactory?.newConfig(Endpoint, Set, ...) */ +@Suppress("DEPRECATION") +@Deprecated( + "Use ReplicatorConfiguration?.newConfig(Set, Endpoint ...)", + replaceWith = ReplaceWith("ReplicatorConfiguration?.newConfig(Set, Endpoint ...)") +) +fun ReplicatorConfiguration?.newConfig( + target: Endpoint? = null, + collections: Map, CollectionConfiguration?>? = null, + type: ReplicatorType? = null, + continuous: Boolean? = null, + authenticator: Authenticator? = null, + headers: Map? = null, + pinnedServerCertificate: X509Certificate? = null, + maxAttempts: Int? = null, + maxAttemptWaitTime: Int? = null, + heartbeat: Int? = null, + enableAutoPurge: Boolean? = null, + acceptParentDomainCookies: Boolean? = null +): ReplicatorConfiguration { + val endPt = + target ?: this?.target ?: throw IllegalArgumentException("A ReplicatorConfiguration must specify an endpoint") + val config = if (collections == null) { + ReplicatorConfiguration(endPt, getCollectionConfigs(this)) + } else { + val rc = ReplicatorConfiguration(endPt) + // Lint will flip out if you try to use `forEach` here. + for (e: Map.Entry, CollectionConfiguration?> in collections) { + rc.addCollections(e.key, e.value) + } + rc + } + + copyReplConfig( + this, + config, + type, + continuous, + authenticator, + headers, + maxAttempts, + maxAttemptWaitTime, + heartbeat, + enableAutoPurge, + acceptParentDomainCookies + ) + + (pinnedServerCertificate + ?: this?.pinnedServerX509Certificate)?.let { config.setPinnedServerX509Certificate(it) } + + return config +} + +/** + * Create a ReplicatorConfiguration, overriding the receiver's + * values with the passed parameters. + * + * Note: A document that is blocked by a document Id filter will not be auto-purged + * regardless of the setting of the enableAutoPurge property + * + * @param target (required) The replication target endpoint. + * @param collections a set of collections configurations. + * @param type replicator type: push, pull, or push and pull: default is push and pull. + * @param continuous continuous flag: true for continuous, false by default. + * @param authenticator connection authenticator. + * @param headers extra HTTP headers to send in all requests to the remote target. + * @param pinnedServerCertificate target server's SSL certificate. + * @param maxAttempts max retry attempts after connection failure. + * @param maxAttemptWaitTime max time between retry attempts (exponential backoff). + * @param heartbeat heartbeat interval, in seconds. + * @param enableAutoPurge auto-purge enabled. + * @param acceptParentDomainCookies Advanced: accept cookies for parent domains. + * + * @see com.couchbase.lite.ReplicatorConfiguration + */ + fun ReplicatorConfiguration?.newConfig( - collections : Set, + collections: Set, target: Endpoint, type: ReplicatorType? = null, continuous: Boolean? = null, @@ -90,7 +167,7 @@ fun ReplicatorConfiguration?.newConfig( enableAutoPurge: Boolean? = null, acceptParentDomainCookies: Boolean? = null ): ReplicatorConfiguration { - val config = ReplicatorConfiguration(this?.collections?:collections, target) + val config = ReplicatorConfiguration(this?.collectionConfigs?:collections, target) copyReplConfig( this, @@ -127,3 +204,100 @@ fun ReplicatorConfiguration?.newConfig( ) fun DatabaseConfiguration?.create(databasePath: String? = null) = this.newConfig(databasePath) +/** + * Create a ReplicatorConfiguration, overriding the receiver's + * values with the passed parameters: + * + * Note: A document that is blocked by a document Id filter will not be auto-purged + * regardless of the setting of the enableAutoPurge property + * + * Warning: This factory method configures only the default collection! + * Using it on a configuration that describes any collections other than the default + * will loose all information associated with those collections + * + * @param database the local database + * @param target (required) The replication endpoint. + * @param type replicator type: push, pull, or push and pull: default is push and pull. + * @param continuous continuous flag: true for continuous. False by default. + * @param authenticator connection authenticator. + * @param headers extra HTTP headers to send in all requests to the remote target. + * @param pinnedServerCertificate target server's SSL certificate. + * @param channels Sync Gateway channel names. + * @param documentIDs IDs of documents to be replicated: default is all documents. + * @param pushFilter filter for pushed documents. + * @param pullFilter filter for pulled documents. + * @param conflictResolver conflict resolver. + * @param maxAttempts max retry attempts after connection failure. + * @param maxAttemptWaitTime max time between retry attempts (exponential backoff). + * @param heartbeat heartbeat interval, in seconds. + * @param enableAutoPurge auto-purge enabled. + * @param acceptParentDomainCookies Advanced: accept cookies for parent domains. + * + * @see com.couchbase.lite.ReplicatorConfiguration + * @deprecated Use ReplicatorConfiguration?.newConfig(Set, Endpoint ...) + */ +@Suppress("DEPRECATION") +@Deprecated( + "Use ReplicatorConfiguration?.newConfig(Set, Endpoint ...)", + replaceWith = ReplaceWith("ReplicatorConfiguration?.newConfig(Set, Endpoint ...)") +) +fun ReplicatorConfiguration?.create( + database: Database? = null, + target: Endpoint? = null, + type: ReplicatorType? = null, + continuous: Boolean? = null, + authenticator: Authenticator? = null, + headers: Map? = null, + pinnedServerCertificate: ByteArray? = null, + channels: List? = null, + documentIDs: List? = null, + pushFilter: ReplicationFilter? = null, + pullFilter: ReplicationFilter? = null, + conflictResolver: ConflictResolver? = null, + maxAttempts: Int? = null, + maxAttemptWaitTime: Int? = null, + heartbeat: Int? = null, + enableAutoPurge: Boolean? = null, + acceptParentDomainCookies: Boolean? = null +): ReplicatorConfiguration { + // ReplicatorConfiguration.getDatabase throws an ISE on null database + val db = database ?: try { + this?.database + } catch (e: CouchbaseLiteError) { + null + } + ?: throw IllegalArgumentException("A ReplicatorConfiguration must specify a database") + checkDbCollections(db, this?.collections) + + val config = ReplicatorConfiguration( + db, + target ?: this?.target ?: throw IllegalArgumentException("A ReplicatorConfiguration must specify an endpoint") + ) + + copyReplConfig( + this, + config, + type, + continuous, + authenticator, + headers, + maxAttempts, + maxAttemptWaitTime, + heartbeat, + enableAutoPurge, + acceptParentDomainCookies + ) + + copyLegacyReplConfig( + this, + config, + pinnedServerCertificate, + channels, + documentIDs, + pushFilter, + pullFilter, + conflictResolver + ) + + return config +} diff --git a/common/test/kotlin/com/couchbase/lite/ConfigFactoryTest.kt b/common/test/kotlin/com/couchbase/lite/ConfigFactoryTest.kt index 07439db3..164fcea1 100644 --- a/common/test/kotlin/com/couchbase/lite/ConfigFactoryTest.kt +++ b/common/test/kotlin/com/couchbase/lite/ConfigFactoryTest.kt @@ -64,13 +64,63 @@ class ConfigFactoryTest : BaseDbTest() { ///// ReplicatorConfiguration + // Create config a config with no collection + @Test + fun testReplConfigNullCollections() { + val target = testEndpoint + val config = ReplicatorConfigurationFactory.newConfig( + target = target, + type = ReplicatorType.PUSH, + continuous = true, + authenticator = testAuthenticator, + headers = testHeaders, + maxAttempts = 20, + heartbeat = 100, + enableAutoPurge = false + ) + + Assert.assertEquals(target, config.target) + Assert.assertEquals(ReplicatorType.PUSH, config.type) + Assert.assertTrue(config.isContinuous) + Assert.assertEquals(testAuthenticator, config.authenticator) + Assert.assertEquals(testHeaders, config.headers) + Assert.assertEquals(20, config.maxAttempts) + Assert.assertEquals(100, config.heartbeat) + Assert.assertEquals(false, config.isAutoPurgeEnabled) + } + + // Create config with an empty collection + @Test + fun testReplConfigEmptyCollections() { + val target = testEndpoint + val config = ReplicatorConfigurationFactory.newConfig( + collections = emptyMap(), + target = target, + type = ReplicatorType.PUSH, + continuous = true, + authenticator = testAuthenticator, + headers = testHeaders, + maxAttempts = 20, + heartbeat = 100, + enableAutoPurge = false + ) + + Assert.assertEquals(target, config.target) + Assert.assertEquals(ReplicatorType.PUSH, config.type) + Assert.assertTrue(config.isContinuous) + Assert.assertEquals(testAuthenticator, config.authenticator) + Assert.assertEquals(testHeaders, config.headers) + Assert.assertEquals(20, config.maxAttempts) + Assert.assertEquals(100, config.heartbeat) + Assert.assertEquals(false, config.isAutoPurgeEnabled) + } + // Create config with explicitly configured default collection @Test - fun testCreateReplicatorConfigurationWithCollections() { + fun testReplConfigCollectionsWithDefault() { val target = testEndpoint val collConfig1 = CollectionConfigurationFactory.newConfig( - testDatabase.defaultCollection, channels = testChannels, conflictResolver = testResolver, documentIDs = testDocIds, @@ -90,7 +140,7 @@ class ConfigFactoryTest : BaseDbTest() { enableAutoPurge = false ) - Assert.assertEquals(collConfig1.collection, config.collections.map { it.collection }.first()) + Assert.assertEquals(collConfig1.collection, config.collectionConfigs.map { it.collection }.first()) Assert.assertEquals(target, config.target) Assert.assertEquals(ReplicatorType.PUSH, config.type) Assert.assertTrue(config.isContinuous) @@ -100,8 +150,56 @@ class ConfigFactoryTest : BaseDbTest() { Assert.assertEquals(100, config.heartbeat) Assert.assertEquals(false, config.isAutoPurgeEnabled) - val collConfig2 = config.collections.first() + val collConfig2 = config.getCollectionConfiguration(testDatabase.defaultCollection) + Assert.assertNotNull(collConfig2) + Assert.assertNotSame(collConfig1, collConfig2) + Assert.assertEquals(testChannels, collConfig2!!.channels) + Assert.assertEquals(testResolver, collConfig2.conflictResolver) + Assert.assertEquals(testDocIds, collConfig2.documentIDs) + Assert.assertEquals(testPushFilter, collConfig2.pushFilter) + Assert.assertEquals(testPullFilter, collConfig2.pullFilter) + } + + // Create config with a configured non-default collection + @Test + fun testReplConfigCollectionsWithoutDefault() { + val collConfig1 = CollectionConfigurationFactory.newConfig( + channels = testChannels, + conflictResolver = testResolver, + documentIDs = testDocIds, + pushFilter = testPushFilter, + pullFilter = testPullFilter + ) + val config = ReplicatorConfigurationFactory.newConfig( + testEndpoint, + mapOf(listOf(testCollection) to collConfig1), + type = ReplicatorType.PUSH, + continuous = true, + authenticator = testAuthenticator, + headers = testHeaders, + maxAttempts = 20, + heartbeat = 100, + enableAutoPurge = false + ) + + Assert.assertEquals(testEndpoint, config.target) + Assert.assertEquals(ReplicatorType.PUSH, config.type) + Assert.assertTrue(config.isContinuous) + Assert.assertEquals(testAuthenticator, config.authenticator) + Assert.assertEquals(testHeaders, config.headers) + Assert.assertEquals(20, config.maxAttempts) + Assert.assertEquals(100, config.heartbeat) + Assert.assertEquals(false, config.isAutoPurgeEnabled) + + val colls = config.collectionConfigs + Assert.assertNotNull(colls) + Assert.assertEquals(1, colls.size) + Assert.assertTrue(colls.contains(testCollection)) + + val collConfig2 = config.getCollectionConfiguration(testCollection) + Assert.assertNotNull(collConfig2) + Assert.assertNotSame(collConfig1, collConfig2!!) Assert.assertEquals(testChannels, collConfig2.channels) Assert.assertEquals(testResolver, collConfig2.conflictResolver) Assert.assertEquals(testDocIds, collConfig2.documentIDs)