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

com.github.nagyesta.lowkeyvault.service.common.impl.ConcurrentVersionedEntityMultiMap Maven / Gradle / Ivy

package com.github.nagyesta.lowkeyvault.service.common.impl;

import com.github.nagyesta.lowkeyvault.model.v7_2.common.constants.RecoveryLevel;
import com.github.nagyesta.lowkeyvault.service.EntityId;
import com.github.nagyesta.lowkeyvault.service.common.BaseVaultEntity;
import com.github.nagyesta.lowkeyvault.service.common.VersionedEntityMultiMap;
import com.github.nagyesta.lowkeyvault.service.exception.NotFoundException;
import lombok.NonNull;
import org.springframework.util.Assert;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ConcurrentVersionedEntityMultiMap, ME extends RE>
        implements VersionedEntityMultiMap {

    private final BiFunction versionCreateFunction;
    private final Map> entities;
    private final Map> versions;
    private final RecoveryLevel recoveryLevel;
    private final Integer recoverableDays;
    private final boolean deleted;

    public ConcurrentVersionedEntityMultiMap(@NonNull final RecoveryLevel recoveryLevel,
                                             final Integer recoverableDays,
                                             @NonNull final BiFunction versionCreateFunction,
                                             final boolean deleted) {
        this.versionCreateFunction = versionCreateFunction;
        recoveryLevel.checkValidRecoverableDays(recoverableDays);
        this.recoveryLevel = recoveryLevel;
        this.recoverableDays = recoverableDays;
        this.deleted = deleted;
        entities = new ConcurrentHashMap<>();
        versions = new ConcurrentHashMap<>();
    }

    @Override
    public List listLatestEntities() {
        return streamAllLatestEntities()
                .collect(Collectors.toList());
    }

    @Override
    public List listLatestNonManagedEntities() {
        return streamAllLatestEntities()
                .filter(entity -> !entity.isManaged())
                .collect(Collectors.toList());
    }

    @Override
    public Deque getVersions(@NonNull final K entityId) {
        if (!versions.containsKey(entityId.id())
                || versions.get(entityId.id()).isEmpty()) {
            throw new NotFoundException("Key not found: " + entityId);
        }
        return new LinkedList<>(versions.get(entityId.id()));
    }

    @Override
    public boolean containsName(@NonNull final String name) {
        return entities.containsKey(name);
    }

    @Override
    public boolean containsEntityMatching(final String name, final Predicate predicate) {
        return containsName(name) && entities.get(name).values().stream().anyMatch(predicate);
    }

    @Override
    public boolean containsEntity(@NonNull final K entityId) {
        return containsName(entityId.id()) && entities.get(entityId.id()).containsKey(entityId.version());
    }

    @Override
    public void assertContainsEntity(@NonNull final V entityId) {
        if (!containsEntity(entityId)) {
            throw new NotFoundException("Entity not found: " + entityId);
        }
    }

    @Override
    public V getLatestVersionOfEntity(@NonNull final K entityId) {
        final Deque availableVersions = getVersions(entityId);
        return versionCreateFunction.apply(entityId.id(), availableVersions.getLast());
    }

    @Override
    public RE getReadOnlyEntity(@NonNull final V entityId) {
        return getEntity(entityId);
    }

    @Override
    public ME getEntity(@NonNull final V entityId) {
        assertContainsEntity(entityId);
        return entities.get(entityId.id()).get(entityId.version());
    }

    @Override
    public void put(@NonNull final V entityId, @NonNull final ME entity) {
        entities.computeIfAbsent(entityId.id(), id -> new ConcurrentHashMap<>()).put(entityId.version(), entity);
        versions.computeIfAbsent(entityId.id(), id -> new ConcurrentLinkedDeque<>()).add(entityId.version());
    }

    @Override
    public  R getEntity(@NonNull final V entityId, @NonNull final Class type) {
        return type.cast(this.getEntity(entityId));
    }

    @Override
    public RecoveryLevel getRecoveryLevel() {
        return recoveryLevel;
    }

    @Override
    public Optional getRecoverableDays() {
        return Optional.ofNullable(recoverableDays);
    }

    @Override
    public boolean isDeleted() {
        return deleted;
    }

    @Override
    public void moveTo(@NonNull final K entityId,
                       @NonNull final VersionedEntityMultiMap destination,
                       @NonNull final Function applyToAll) {
        final Map toKeep = entities.remove(entityId.id());
        final Deque versions = this.versions.remove(entityId.id());
        if (recoveryLevel.isRecoverable()) {
            versions.forEach(version -> destination
                    .put(versionCreateFunction.apply(entityId.id(), version), applyToAll.apply(toKeep.get(version))));
        }
    }

    @Override
    public void purgeExpired() {
        Assert.state(isDeleted(), "Purge cannot be called when map is not in deleted role.");
        final List purgeable = entities.entrySet().stream()
                .filter(e -> e.getValue().values().stream().anyMatch(ME::isPurgeExpired))
                .map(Map.Entry::getKey)
                .toList();
        purgeable.forEach(key -> {
            entities.remove(key);
            versions.remove(key);
        });
    }

    @Override
    public void purgeDeleted(@NonNull final K entityId) {
        Assert.state(isDeleted(), "Purge cannot be called when map is not in deleted role.");
        final Map map = entities.get(entityId.id());
        Assert.state(map.values().stream().allMatch(ME::canPurge), "The selected elements cannot be purged.");
        entities.remove(entityId.id());
        versions.remove(entityId.id());
    }

    @Override
    public void forEachEntity(@NonNull final Consumer entityConsumer) {
        entities.values().forEach(entityVersions -> entityVersions.values().forEach(entityConsumer));
    }

    private Stream streamAllLatestEntities() {
        return entities.keySet().stream()
                .map(name -> versionCreateFunction.apply(name, versions.get(name).getLast()))
                .sorted(Comparator.comparing(EntityId::id))
                .map(this::getEntity);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy