diff --git a/spring/couchbase-springboot-v3-driver/pom.xml b/spring/couchbase-springboot-v3-driver/pom.xml new file mode 100644 index 0000000..2a2b7f5 --- /dev/null +++ b/spring/couchbase-springboot-v3-driver/pom.xml @@ -0,0 +1,100 @@ + + + 4.0.0 + + + io.mongock + spring-jdk17 + 5.3.6-SNAPSHOT + + + couchbase-springboot-v3-driver + jar + + + + + io.mongock + mongock-driver-couchbase-bom + ${mongock.community.version} + pom + import + + + + + + + + io.mongock + mongock-driver-core + + + io.mongock + mongock-api + + + io.mongock + couchbase-driver + + + io.mongock + mongock-springboot-v3 + ${project.version} + + + + + org.springframework.boot + spring-boot-autoconfigure + ${spring-boot-3.version} + + + org.springframework.boot + spring-boot-configuration-processor + ${spring-boot-3.version} + true + + + org.springframework.data + spring-data-couchbase + ${spring-data-5.couchbase.version} + + + com.couchbase.client + java-client + ${couchbase-java-client.version} + + + + + io.mongock + mongock-test-util + ${mongock.community.version} + test + + + org.springframework.boot + spring-boot-starter-test + ${spring-boot-3.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} + test + + + org.slf4j + slf4j-simple + ${slf4j-api.version} + test + + + + + + \ No newline at end of file diff --git a/spring/couchbase-springboot-v3-driver/src/main/java/io/mongock/driver/couchbase/springboot/config/CouchbaseConfiguration.java b/spring/couchbase-springboot-v3-driver/src/main/java/io/mongock/driver/couchbase/springboot/config/CouchbaseConfiguration.java new file mode 100644 index 0000000..c4ac909 --- /dev/null +++ b/spring/couchbase-springboot-v3-driver/src/main/java/io/mongock/driver/couchbase/springboot/config/CouchbaseConfiguration.java @@ -0,0 +1,42 @@ +package io.mongock.driver.couchbase.springboot.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration properties for Mongock Couchbase. + * + * @author Tigran Babloyan + */ +@Configuration +@ConfigurationProperties("mongock.couchbase") +public class CouchbaseConfiguration { + /** + * The custom scope to be used by the Mongock. + * Can be used for Couchbase Server 7+ to set custom scope on the stored data. + * + * Note: If scope is set the collection needs to be set as well. + */ + private String scope; + /** + * The custom collection to be used by the Mongock. + * Can be used for Couchbase Server 7+ to set custom collection on the stored data. + */ + private String collection; + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getCollection() { + return collection; + } + + public void setCollection(String collection) { + this.collection = collection; + } +} diff --git a/spring/couchbase-springboot-v3-driver/src/main/java/io/mongock/driver/couchbase/springboot/config/CouchbaseSpringbootContext.java b/spring/couchbase-springboot-v3-driver/src/main/java/io/mongock/driver/couchbase/springboot/config/CouchbaseSpringbootContext.java new file mode 100644 index 0000000..c7d360b --- /dev/null +++ b/spring/couchbase-springboot-v3-driver/src/main/java/io/mongock/driver/couchbase/springboot/config/CouchbaseSpringbootContext.java @@ -0,0 +1,52 @@ +package io.mongock.driver.couchbase.springboot.config; + +import com.couchbase.client.java.Collection; +import io.mongock.api.config.MongockConfiguration; +import io.mongock.driver.api.driver.ConnectionDriver; +import io.mongock.driver.couchbase.driver.CouchbaseDriver; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.couchbase.CouchbaseClientFactory; +import org.springframework.util.StringUtils; + + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Mongock Couchbase support. + * + * @author Tigran Babloyan + */ +@AutoConfiguration +@ConditionalOnExpression("${mongock.enabled:true}") +@ConditionalOnBean({MongockConfiguration.class, CouchbaseClientFactory.class}) +@EnableConfigurationProperties(CouchbaseConfiguration.class) +@AutoConfigureAfter(CouchbaseDataAutoConfiguration.class) +public class CouchbaseSpringbootContext { + + @Bean + public ConnectionDriver connectionDriver(CouchbaseClientFactory couchbaseClientFactory, + CouchbaseConfiguration couchbaseConfiguration, + MongockConfiguration mongockConfig) { + Collection collection = isCustomCollection(couchbaseConfiguration) ? + couchbaseClientFactory.withScope(couchbaseConfiguration.getScope()).getCollection(couchbaseConfiguration.getCollection()) : + couchbaseClientFactory.getDefaultCollection(); + CouchbaseDriver driver = CouchbaseDriver.withLockStrategy(couchbaseClientFactory.getCluster(), + collection, + mongockConfig.getLockAcquiredForMillis(), + mongockConfig.getLockQuitTryingAfterMillis(), + mongockConfig.getLockTryFrequencyMillis()); + driver.setIndexCreation(mongockConfig.isIndexCreation()); + return driver; + } + + private boolean isCustomCollection(CouchbaseConfiguration couchbaseConfiguration){ + return StringUtils.hasText(couchbaseConfiguration.getCollection()) && + StringUtils.hasText(couchbaseConfiguration.getScope()); + } +} diff --git a/spring/couchbase-springboot-v3-driver/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring/couchbase-springboot-v3-driver/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..b535b48 --- /dev/null +++ b/spring/couchbase-springboot-v3-driver/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +io.mongock.driver.couchbase.springboot.config.CouchbaseSpringbootContext \ No newline at end of file diff --git a/spring/couchbase-springboot-v3-driver/src/test/java/io/mongock/driver/couchbase/springboot/config/CouchbaseSpringbootContextTest.java b/spring/couchbase-springboot-v3-driver/src/test/java/io/mongock/driver/couchbase/springboot/config/CouchbaseSpringbootContextTest.java new file mode 100644 index 0000000..77eef54 --- /dev/null +++ b/spring/couchbase-springboot-v3-driver/src/test/java/io/mongock/driver/couchbase/springboot/config/CouchbaseSpringbootContextTest.java @@ -0,0 +1,114 @@ +package io.mongock.driver.couchbase.springboot.config; + +import com.couchbase.client.core.io.CollectionIdentifier; +import com.couchbase.client.java.Bucket; +import com.couchbase.client.java.Cluster; +import com.couchbase.client.java.Collection; +import com.couchbase.client.java.Scope; +import io.mongock.api.config.MongockConfiguration; +import io.mongock.driver.api.driver.ConnectionDriver; +import io.mongock.driver.couchbase.driver.CouchbaseDriver; +import io.mongock.runner.springboot.base.config.MongockSpringConfiguration; +import io.mongock.util.test.ReflectionUtils; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.data.couchbase.SimpleCouchbaseClientFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +class CouchbaseSpringbootContextTest { + + private static final String NONE_DEFAULT_NAME = "nondefault"; + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + void test_driver_autoconfigured() { + contextRunner + .withPropertyValues("mongock.enabled=true") + .withBean(SimpleCouchbaseClientFactory.class, mockCluster(), "mongock", null) + .withConfiguration(AutoConfigurations.of( + MongockConfiguration.class, + CouchbaseSpringbootContext.class)) + .run(context -> { + assertThat(context).hasSingleBean(ConnectionDriver.class); + CouchbaseDriver driver = context.getBean(CouchbaseDriver.class); + assertThat(collectionName(driver)).isEqualTo(CollectionIdentifier.DEFAULT_COLLECTION); + assertThat(driver.isIndexCreation()).isTrue(); + } + ); + } + + @Test + void test_driver_missing_on_mongock_disabled() { + contextRunner + .withPropertyValues("mongock.enabled=false") + .withBean(SimpleCouchbaseClientFactory.class, mockCluster(), "mongock", null) + .withConfiguration(AutoConfigurations.of( + MongockConfiguration.class, + CouchbaseSpringbootContext.class)) + .run((context) -> assertThat(context) + .doesNotHaveBean(ConnectionDriver.class)); + } + + @Test + void test_context_collection_modifications() { + contextRunner + .withPropertyValues("mongock.enabled=true", "mongock.couchbase.scope=nondefault", "mongock.couchbase.collection=nondefault") + .withBean(SimpleCouchbaseClientFactory.class, mockCluster(), "mongock", null) + .withConfiguration(AutoConfigurations.of( + MongockConfiguration.class, + CouchbaseSpringbootContext.class)) + .run(context -> { + assertThat(context).hasSingleBean(ConnectionDriver.class); + CouchbaseDriver driver = context.getBean(CouchbaseDriver.class); + assertThat(collectionName(driver)).isEqualTo(NONE_DEFAULT_NAME); + assertThat(driver.isIndexCreation()).isTrue(); + } + ); + } + + @Test + void test_context_index_creation_disabled() { + contextRunner + .withPropertyValues("mongock.enabled=true", "mongock.index-creation=false", "mongock.indexCreation=false") + .withBean(SimpleCouchbaseClientFactory.class, mockCluster(), "mongock", null) + .withUserConfiguration( + MongockSpringConfiguration.class, + CouchbaseSpringbootContext.class + ) + .run(context -> { + assertThat(context).hasSingleBean(ConnectionDriver.class); + CouchbaseDriver driver = context.getBean(CouchbaseDriver.class); + assertThat(driver.isIndexCreation()).isFalse(); + } + ); + } + + private String collectionName(ConnectionDriver connectionDriver) { + Collection collection = (Collection) ReflectionUtils.getPrivateField(connectionDriver, CouchbaseDriver.class, "collection"); + return collection.name(); + } + + private Cluster mockCluster() { + Cluster cluster = mock(Cluster.class); + Bucket bucket = mock(Bucket.class); + Scope defaultScope = mock(Scope.class); + Scope customeScope = mock(Scope.class); + Collection defaultCollection = mock(Collection.class); + doReturn(CollectionIdentifier.DEFAULT_COLLECTION).when(defaultCollection).name(); + Collection customCollection = mock(Collection.class); + doReturn(NONE_DEFAULT_NAME).when(customCollection).name(); + doReturn(bucket).when(cluster).bucket(any()); + doReturn(defaultScope).when(bucket).defaultScope(); + doReturn(defaultScope).when(bucket).scope(eq(CollectionIdentifier.DEFAULT_SCOPE)); + doReturn(customeScope).when(bucket).scope(eq(NONE_DEFAULT_NAME)); + doReturn(defaultCollection).when(bucket).defaultCollection(); + doReturn(defaultCollection).when(defaultScope).collection(any()); + doReturn(customCollection).when(customeScope).collection(any()); + doReturn(CollectionIdentifier.DEFAULT_SCOPE).when(defaultScope).name(); + return cluster; + } + +} diff --git a/spring/pom.xml b/spring/pom.xml index 7ae0ecb..0fa8993 100644 --- a/spring/pom.xml +++ b/spring/pom.xml @@ -17,10 +17,13 @@ [3.0.0-RC1, 4.0.0) 4.0.0-RC1 4.8.0-beta0 + 5.2.1 + 3.5.1 mongock-springboot-v3 mongodb-springdata-v4-driver + couchbase-springboot-v3-driver