All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.otto.synapse.state.NitriteStateRepository Maven / Gradle / Ivy

Go to download

A library used at otto.de to implement Spring Boot based event-sourcing microservices.

There is a newer version: 0.33.1
Show newest version
package de.otto.synapse.state;

import com.fasterxml.jackson.core.type.TypeReference;
import org.dizitart.no2.*;

import java.io.Closeable;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

import static com.google.common.collect.Sets.newHashSet;
import static de.otto.synapse.translator.ObjectMappers.currentObjectMapper;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static java.util.stream.StreamSupport.stream;
import static org.dizitart.no2.Document.createDocument;
import static org.dizitart.no2.IndexOptions.indexOptions;
import static org.dizitart.no2.IndexType.NonUnique;
import static org.dizitart.no2.IndexType.Unique;
import static org.dizitart.no2.filters.Filters.ALL;
import static org.dizitart.no2.filters.Filters.eq;

/**
 * A StateRepository with extra functionality for secondary indexes, queries, sorting and paging of
 * results.
 *
 * 

This implementation is using a Nitrite Database * to store the entities either on heap, off heap or in a file system.

* @param */ public class NitriteStateRepository implements StateRepository, Closeable { private static final String IDX_ID = "_idx_id"; private static final TypeReference> JSON_MAP = new TypeReference>() {}; private static final Document ID_PROJECTION = createDocument(IDX_ID, null); private final String name; private final Class valueType; private final Nitrite nitrite; private final NitriteCollection collection; private final ReadWriteLock lock = new ReentrantReadWriteLock(); public NitriteStateRepository(final String name, final Class valueType, final Set indexedFields, final NitriteBuilder builder) { this.name = name; this.valueType = valueType; this.nitrite = builder.openOrCreate(); this.collection = nitrite.getCollection(name); this.collection.createIndex(IDX_ID, indexOptions(Unique)); indexedFields.forEach(field -> collection.createIndex(field, indexOptions(NonUnique))); } public NitriteStateRepository(final String name, final Class valueType, final Set indexedFields) { this(name, valueType, indexedFields, Nitrite.builder().compressed()); } @Override public String getName() { return name; } @Override public Set keySet() { try { lock.readLock().lock(); return stream(collection .find() .project(ID_PROJECTION) .spliterator(), false) .map(d -> d.getOrDefault(IDX_ID, "").toString()) .collect(toSet()); } finally { lock.readLock().unlock(); } } public Collection findBy(final Filter filter) { return findInternal(() -> collection.find(filter)); } public Collection findBy(final FindOptions findOptions) { return findInternal(() -> collection.find(findOptions)); } public Collection findBy(final Filter filter, final FindOptions findOptions) { return findInternal(() -> collection.find(filter, findOptions)); } public Collection findBy(final String key, final Object value) { return findInternal(() -> collection.find(eq(key, value))); } private Collection findInternal(final Supplier findFunc) { try { lock.readLock().lock(); final Cursor documents = findFunc.get(); return stream(documents.spliterator(), false) .map(document -> currentObjectMapper().convertValue(document, valueType)) .collect(toList()); } finally { lock.readLock().unlock(); } } @Override public Optional get(String key) { try { lock.readLock().lock(); final Document document = collection .find(eq(IDX_ID, key)) .firstOrDefault(); return document != null ? of(currentObjectMapper().convertValue(document, valueType)) : empty(); } finally { lock.readLock().unlock(); } } @Override public void consumeAll(final BiConsumer consumer) { try { lock.readLock().lock(); collection.find().forEach(document -> { final String key = document.get(IDX_ID).toString(); final V value = currentObjectMapper().convertValue(document, valueType); if (key == null) { throw new IllegalStateException("Unexpected null value found for required field '" + IDX_ID + "'"); } consumer.accept(key, value); }); } finally { lock.readLock().unlock(); } } @Override public Optional put(String key, V value) { try { lock.writeLock().lock(); final Map mapValue = currentObjectMapper().convertValue(value, JSON_MAP); final Document document = new Document(mapValue); document.put(IDX_ID, key); final Optional previous = get(key); collection.insert(document); return previous; } finally { lock.writeLock().unlock(); } } @Override public Optional compute(final String key, final BiFunction, ? extends V> remappingFunction) { try { lock.writeLock().lock(); final Optional previous = get(key); final V computed = remappingFunction.apply(key, previous); if (previous.isPresent()) { if (computed != null) { final Map mapValue = currentObjectMapper().convertValue(computed, JSON_MAP); final Document document = new Document(mapValue); document.put(IDX_ID, key); collection.update(eq(IDX_ID, key), document); } else { collection.remove(eq(IDX_ID, key)); } } else { if (computed != null) { final Map mapValue = currentObjectMapper().convertValue(computed, JSON_MAP); final Document document = new Document(mapValue); document.put(IDX_ID, key); collection.insert(document); } } return Optional.ofNullable(computed); } finally { lock.writeLock().unlock(); } } @Override public Optional remove(String key) { try { lock.writeLock().lock(); Optional previous = get(key); collection.remove(eq(IDX_ID, key)); return previous; } finally { lock.writeLock().unlock(); } } @Override public void clear() { try { lock.writeLock().lock(); collection.remove(ALL); } finally { lock.writeLock().unlock(); } } @Override public long size() { try { lock.readLock().lock(); return collection.size(); } finally { lock.readLock().unlock(); } } @Override public void close() { try { lock.writeLock().lock(); nitrite.close(); } finally { lock.writeLock().unlock(); } } public static NitriteStateRepository.Builder builder(Class clazz) { return new NitriteStateRepository.Builder<>(clazz); } public static final class Builder { private final Class clazz; private String name; private Set indexedFields = newHashSet(); private NitriteBuilder nitriteBuilder = Nitrite.builder(); private Builder(Class clazz) { this.clazz = clazz; this.name = clazz.getSimpleName(); } public NitriteStateRepository.Builder with(Function func) { func.apply(nitriteBuilder); return this; } public NitriteStateRepository.Builder withName(final String val) { name = val; return this; } public NitriteStateRepository.Builder withIndexed(final String... fields) { return withIndexed(newHashSet(fields)); } public NitriteStateRepository.Builder withIndexed(final Set fields) { this.indexedFields.addAll(fields); return this; } public NitriteStateRepository build() { return new NitriteStateRepository(name, clazz, indexedFields, nitriteBuilder); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy