Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drivers/couchbase/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Parent project for all the community Couchbase drivers
121 changes: 121 additions & 0 deletions drivers/couchbase/couchbase-driver/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>couchbase</artifactId>
<groupId>io.mongock</groupId>
<version>5.2.5-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<name>Mongock driver for couchbase-driver</name>
<artifactId>couchbase-driver</artifactId>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongock-driver-couchbase-bom</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- CORE DEPENDENCIES -->
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongock-driver-core</artifactId>
</dependency>
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongock-api</artifactId>
</dependency>
<dependency>
<groupId>com.couchbase.client</groupId>
<artifactId>java-client</artifactId>
<version>${couchbase-java-client.version}</version>
</dependency>


<!-- TEST -->
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongock-test-runner</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.mongock</groupId>
<artifactId>mongock-test-util</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito-core.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>${test-containers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${test-containers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>couchbase</artifactId>
<version>${test-containers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j-api.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>${awaitility.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M9</version>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.mongock.driver.couchbase.driver;

import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.Collection;
import io.mongock.driver.api.entry.ChangeEntryService;
import io.mongock.driver.core.driver.NonTransactionalConnectionDriverBase;
import io.mongock.driver.core.lock.LockRepository;
import io.mongock.driver.couchbase.repository.CouchbaseChangeEntryRepository;
import io.mongock.driver.couchbase.repository.CouchbaseLockRepository;

import static io.mongock.utils.Constants.*;

/**
* NonTransactionalConnectionDriverBase implementation for Couchbase.
*
* Note: Transactions are not supported for backwards compatibility with previous versions of Couchbase Servers (prior 6.6.1).
*
* @author Tigran Babloyan
*/
public class CouchbaseDriver extends NonTransactionalConnectionDriverBase {

private final Collection collection;
private final Cluster cluster;
private CouchbaseChangeEntryRepository changeEntryRepository;
private CouchbaseLockRepository lockRepository;

protected CouchbaseDriver(Cluster cluster, Collection collection, long lockAcquiredForMillis, long lockQuitTryingAfterMillis, long lockTryFrequencyMillis) {
super(lockAcquiredForMillis, lockQuitTryingAfterMillis, lockTryFrequencyMillis);
this.collection = collection;
this.cluster = cluster;
}

@Override
public ChangeEntryService getChangeEntryService() {
if (changeEntryRepository == null) {
changeEntryRepository = new CouchbaseChangeEntryRepository(cluster, collection);
changeEntryRepository.setIndexCreation(isIndexCreation());
}
return changeEntryRepository;
}

@Override
protected LockRepository getLockRepository() {
if (lockRepository == null) {
lockRepository = new CouchbaseLockRepository(cluster, collection);
lockRepository.setIndexCreation(isIndexCreation());
}
return lockRepository;
}

public static CouchbaseDriver withDefaultLock(Cluster cluster, Collection collection) {
return CouchbaseDriver.withLockStrategy(cluster, collection, DEFAULT_LOCK_ACQUIRED_FOR_MILLIS, DEFAULT_QUIT_TRYING_AFTER_MILLIS, DEFAULT_TRY_FREQUENCY_MILLIS);
}

public static CouchbaseDriver withLockStrategy(Cluster cluster,
Collection collection,
long lockAcquiredForMillis,
long lockQuitTryingAfterMillis,
long lockTryFrequencyMillis) {
return new CouchbaseDriver(cluster, collection, lockAcquiredForMillis, lockQuitTryingAfterMillis, lockTryFrequencyMillis);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.mongock.driver.couchbase.entry;

import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonCreator;
import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonInclude;
import com.couchbase.client.java.json.JsonObject;
import io.mongock.driver.api.entry.ChangeEntry;
import io.mongock.driver.api.entry.ChangeState;
import io.mongock.driver.api.entry.ChangeType;

import java.util.Date;

/**
* ChangeEntry implementation for Couchbase, basically adds a way to deserialize the object from JSON.
*
* @author Tigran Babloyan
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class CouchbaseChangeEntry extends ChangeEntry {

@JsonCreator
public CouchbaseChangeEntry(JsonObject jsonObject){
super(jsonObject.getString(ChangeEntry.KEY_EXECUTION_ID),
jsonObject.getString(ChangeEntry.KEY_CHANGE_ID),
jsonObject.getString(ChangeEntry.KEY_AUTHOR),
jsonObject.get(ChangeEntry.KEY_TIMESTAMP) != null ? new Date (jsonObject.getLong(ChangeEntry.KEY_TIMESTAMP)) : null,
jsonObject.get(ChangeEntry.KEY_STATE) != null ? ChangeState.valueOf(jsonObject.getString(ChangeEntry.KEY_STATE)) : null,
jsonObject.get(ChangeEntry.KEY_TYPE) != null ? ChangeType.valueOf(jsonObject.getString(ChangeEntry.KEY_TYPE)) : null,
jsonObject.getString(ChangeEntry.KEY_CHANGELOG_CLASS),
jsonObject.getString(ChangeEntry.KEY_CHANGESET_METHOD),
jsonObject.getLong(ChangeEntry.KEY_EXECUTION_MILLIS),
jsonObject.getString(ChangeEntry.KEY_EXECUTION_HOST_NAME),
jsonObject.get(ChangeEntry.KEY_METADATA) != null ? jsonObject.getObject(ChangeEntry.KEY_METADATA).toMap() : null,
jsonObject.getString(ChangeEntry.KEY_ERROR_TRACE),
jsonObject.getBoolean(ChangeEntry.KEY_SYSTEM_CHANGE));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.mongock.driver.couchbase.lock;

import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonCreator;
import com.couchbase.client.java.json.JsonObject;
import io.mongock.driver.core.lock.LockEntry;
import io.mongock.driver.couchbase.repository.CouchbaseRepositoryBase;

import java.util.Date;

/**
* LockEntry implementation for Couchbase, basically adds a way to deserialize the object from JSON.
*
* @author Tigran Babloyan
*/
public class CouchbaseLockEntry extends LockEntry {
private final String docType;
@JsonCreator
public CouchbaseLockEntry(JsonObject jsonObject) {
super(jsonObject.getString(LockEntry.KEY_FIELD),
jsonObject.getString(LockEntry.STATUS_FIELD),
jsonObject.getString(LockEntry.OWNER_FIELD),
new Date(jsonObject.getLong(LockEntry.EXPIRES_AT_FIELD)));
this.docType = jsonObject.getString(CouchbaseRepositoryBase.DOCUMENT_TYPE_KEY);
}

public String getDocType() {
return docType;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.mongock.driver.couchbase.repository;

import io.mongock.driver.api.entry.ChangeEntry;

public class ChangeEntryKeyGenerator {

public static final String KEY_PREFIX = "mongockChangeEntry-";
public String toKey(ChangeEntry changeEntry) {
return new StringBuilder()
.append(KEY_PREFIX)
.append(changeEntry.getExecutionId())
.append('-')
.append(changeEntry.getAuthor())
.append('-')
.append(changeEntry.getChangeId()).toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package io.mongock.driver.couchbase.repository;

import com.couchbase.client.core.error.CouchbaseException;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.json.JsonObject;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
import com.couchbase.client.java.kv.UpsertOptions;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryResult;
import com.couchbase.client.java.query.QueryScanConsistency;
import io.mongock.api.exception.MongockException;
import io.mongock.driver.api.entry.ChangeEntry;
import io.mongock.driver.api.entry.ChangeEntryService;
import io.mongock.driver.couchbase.entry.CouchbaseChangeEntry;
import io.mongock.driver.couchbase.util.N1QLQueryProvider;
import io.mongock.utils.field.FieldInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
* Couchbase change entry repository.
* Uses simple KV operations to store change entries.
*
* @author Tigran Babloyan
*/
public class CouchbaseChangeEntryRepository extends CouchbaseRepositoryBase<ChangeEntry> implements ChangeEntryService {
private final static Logger logger = LoggerFactory.getLogger(CouchbaseChangeEntryRepository.class);
private final static String DOCUMENT_TYPE_CHANGE_ENTRY = "mongockChangeEntry";

private static final Set<String> QUERY_FIELDS = new LinkedHashSet<>();

/**
* Scan for all fields annotated with {@link io.mongock.utils.field.Field} which are primary
* and add them to the query fields.
*/
static {
// this one is requited to distinguish between change entries and other documents
QUERY_FIELDS.add(DOCUMENT_TYPE_KEY);
Arrays.stream(ChangeEntry.class.getDeclaredFields())
.map(field -> field.getAnnotation(io.mongock.utils.field.Field.class))
.filter(Objects::nonNull)
.filter(field -> io.mongock.utils.field.Field.KeyType.PRIMARY.equals(field.type()))
.forEach(field -> QUERY_FIELDS.add(field.value()));
}

private final ChangeEntryKeyGenerator keyGenerator = new ChangeEntryKeyGenerator();

public CouchbaseChangeEntryRepository(Cluster cluster, Collection collection) {
super(cluster, collection, QUERY_FIELDS);
}

@Override
public List<ChangeEntry> getEntriesLog() {
QueryResult result = cluster.query(N1QLQueryProvider.selectAllChangesQuery(collection.bucketName(), collection.scopeName(), collection.name()),
QueryOptions.queryOptions().parameters(JsonObject.create().put("type", DOCUMENT_TYPE_CHANGE_ENTRY)).scanConsistency(QueryScanConsistency.REQUEST_PLUS));
return result
.rowsAsObject()
.stream()
.map(CouchbaseChangeEntry::new)
.collect(Collectors.toList());
}

@Override
public void saveOrUpdate(ChangeEntry changeEntry) throws MongockException {
String key = keyGenerator.toKey(changeEntry);
logger.debug("Saving change entry with key {}", key);
try{
collection.upsert(key, toEntity(changeEntry),
UpsertOptions.upsertOptions().durability(PersistTo.ACTIVE, ReplicateTo.NONE));
} catch (CouchbaseException couchbaseException){
logger.warn("Error saving change entry with key {}", key, couchbaseException);
throw new MongockException(couchbaseException);
}
}

@Override
public void ensureField(Field field) {
// nothing to do for couchbase
}

@Override
public JsonObject mapFieldInstances(List<FieldInstance> fieldInstanceList) {
JsonObject document = super.mapFieldInstances(fieldInstanceList);
document.put(DOCUMENT_TYPE_KEY, DOCUMENT_TYPE_CHANGE_ENTRY);
return document;
}
}
Loading