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

io.fluxcapacitor.javaclient.modeling.ModifiableAggregateRoot Maven / Gradle / Ivy

There is a newer version: 0.1015.0
Show newest version
/*
 * Copyright (c) Flux Capacitor IP B.V. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.fluxcapacitor.javaclient.modeling;

import io.fluxcapacitor.common.handling.HandlerInvoker;
import io.fluxcapacitor.javaclient.common.Message;
import io.fluxcapacitor.javaclient.common.serialization.DeserializingMessage;
import io.fluxcapacitor.javaclient.common.serialization.Serializer;
import io.fluxcapacitor.javaclient.persisting.eventsourcing.Apply;
import io.fluxcapacitor.javaclient.publishing.DispatchInterceptor;
import io.fluxcapacitor.javaclient.tracking.handling.Invocation;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

import static io.fluxcapacitor.common.MessageType.EVENT;
import static io.fluxcapacitor.javaclient.modeling.EventPublication.DEFAULT;
import static io.fluxcapacitor.javaclient.modeling.EventPublication.IF_MODIFIED;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toMap;

@ToString(onlyExplicitlyIncluded = true)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class ModifiableAggregateRoot extends DelegatingEntity implements AggregateRoot {

    private static final ThreadLocal>> activeAggregates =
            ThreadLocal.withInitial(LinkedHashMap::new);

    @SuppressWarnings("unchecked")
    public static  Optional> getIfActive(Object aggregateId) {
        return ofNullable((ModifiableAggregateRoot) activeAggregates.get().get(aggregateId));
    }

    public static Map> getActiveAggregatesFor(@NonNull Object entityId) {
        List> candidates = activeAggregates.get().values().stream()
                .filter(a -> a.getEntity(entityId).isPresent()).collect(Collectors.toList());
        Comparator> byPresent = Comparator.comparing(
                a -> a.getEntity(entityId).map(Entity::isPresent).orElse(false));
        Comparator> byOrder = Comparator.comparing(candidates::indexOf);
        LinkedHashMap> result =
                candidates.stream()
                        .sorted(byPresent.thenComparing(byOrder))
                        .collect(toMap(e -> e.id().toString(), Entity::type, (a, b) -> b, LinkedHashMap::new));
        return result;
    }

    public static  Entity load(
            Object aggregateId, Supplier> loader, boolean commitInBatch, EventPublication eventPublication,
            EntityHelper entityHelper, Serializer serializer, DispatchInterceptor dispatchInterceptor,
            CommitHandler commitHandler) {
        return ModifiableAggregateRoot.getIfActive(aggregateId).orElseGet(
                () -> new ModifiableAggregateRoot<>(loader.get(), commitInBatch, eventPublication,
                                                    entityHelper, serializer, dispatchInterceptor, commitHandler));
    }

    private Entity lastCommitted;
    private Entity lastStable;
    private final boolean commitInBatch;

    private final EventPublication eventPublication;
    private final EntityHelper entityHelper;
    private final Serializer serializer;
    private final DispatchInterceptor dispatchInterceptor;
    private final CommitHandler commitHandler;

    private final AtomicBoolean waitingForHandlerEnd = new AtomicBoolean(), waitingForBatchEnd = new AtomicBoolean();
    private final List applied = new ArrayList<>(), uncommitted = new ArrayList<>();
    private final List>> queued = new ArrayList<>();
    private volatile boolean updating, committing, commitPending;

    protected ModifiableAggregateRoot(Entity delegate, boolean commitInBatch,
                                      EventPublication eventPublication, EntityHelper entityHelper,
                                      Serializer serializer, DispatchInterceptor dispatchInterceptor,
                                      CommitHandler commitHandler) {
        super(delegate);
        this.entityHelper = entityHelper;
        this.lastCommitted = delegate;
        this.lastStable = delegate;
        this.commitInBatch = commitInBatch;
        this.eventPublication = eventPublication;
        this.serializer = serializer;
        this.dispatchInterceptor = dispatchInterceptor;
        this.commitHandler = commitHandler;
    }

    @Override
    public  Entity assertLegal(Object command) throws E {
        entityHelper.intercept(command, this).forEach(c -> entityHelper.assertLegal(c, this));
        return this;
    }

    @Override
    public Entity assertAndApply(Object payloadOrMessage) {
        entityHelper.intercept(payloadOrMessage, this).forEach(m -> apply(Message.asMessage(m), true));
        return this;
    }

    @Override
    public Entity update(UnaryOperator function) {
        return handleUpdate(a -> a.update(function));
    }

    @Override
    public Entity apply(Message message) {
        entityHelper.intercept(message, this).forEach(m -> apply(Message.asMessage(m), false));
        return this;
    }

    protected Entity apply(Message message, boolean assertLegal) {
        return handleUpdate(a -> {
            if (assertLegal) {
                entityHelper.assertLegal(message, a);
            }
            var eventPublication =
                    entityHelper.applyInvoker(new DeserializingMessage(message, EVENT, serializer), a)
                            .map(HandlerInvoker::getMethodAnnotation).map(Apply::eventPublication)
                            .filter(ep -> ep != DEFAULT).orElse(this.eventPublication);

            int hashCodeBefore = eventPublication == IF_MODIFIED ? a.get() == null ? -1 : a.get().hashCode() : -1;

            Entity result = a.apply(message);
            if (switch (eventPublication) {
                case ALWAYS, DEFAULT -> true;
                case IF_MODIFIED -> !Objects.equals(a.get(), result.get())
                                    || (result.get() != null && result.get().hashCode() != hashCodeBefore);
                case NEVER -> false;
            }) {
                Message intercepted = dispatchInterceptor.interceptDispatch(message, EVENT);
                if (intercepted == null) {
                    return a;
                }
                Message m = intercepted.addMetadata(Entity.AGGREGATE_ID_METADATA_KEY, id().toString(),
                                                    Entity.AGGREGATE_TYPE_METADATA_KEY, type().getName(),
                                                    Entity.AGGREGATE_SN_METADATA_KEY,
                                                    String.valueOf(getDelegate().sequenceNumber() + 1L));
                var serializedEvent =
                        dispatchInterceptor.modifySerializedMessage(m.serialize(serializer), m, EVENT);
                if (serializedEvent == null) {
                    return a;
                }
                applied.add(new DeserializingMessage(
                        serializedEvent, type -> serializer.convert(m.getPayload(), type), EVENT));
            }
            return result;
        });
    }

    protected Entity handleUpdate(UnaryOperator> update) {
        if (updating) {
            queued.add(update);
            return this;
        }
        try {
            updating = true;
            boolean firstUpdate = waitingForHandlerEnd.compareAndSet(false, true);
            if (firstUpdate) {
                activeAggregates.get().putIfAbsent(id(), this);
            }
            try {
                delegate = update.apply(getDelegate());
            } finally {
                if (firstUpdate) {
                    Invocation.whenHandlerCompletes((r, e) -> whenHandlerCompletes(e));
                }
            }
        } finally {
            updating = false;
        }
        while (!queued.isEmpty()) {
            queued.removeFirst().apply(this);
        }
        return this;
    }

    protected void whenHandlerCompletes(Throwable error) {
        waitingForHandlerEnd.set(false);
        if (error == null) {
            uncommitted.addAll(applied);
            lastStable = delegate;
        } else {
            delegate = lastStable;
        }
        applied.clear();
        if (!commitInBatch) {
            commit();
            activeAggregates.get().remove(id(), this);
        } else if (waitingForBatchEnd.compareAndSet(false, true)) {
            DeserializingMessage.whenBatchCompletes(this::whenBatchCompletes);
        }
    }

    protected void whenBatchCompletes(Throwable error) {
        waitingForBatchEnd.set(false);
        commit();
        activeAggregates.get().remove(id(), this);
    }

    @Override
    public Entity commit() {
        if (committing) {
            commitPending = true;
            return this;
        }
        try {
            committing = true;
            commitPending = false;
            uncommitted.addAll(applied);
            applied.clear();
            lastStable = delegate;
            List events = new ArrayList<>(uncommitted);
            uncommitted.clear();
            var before = lastCommitted;
            lastCommitted = lastStable;
            commitHandler.handle(lastStable, events, before);
        } finally {
            committing = false;
        }
        while (commitPending) {
            commit();
        }
        return this;
    }

    @Override
    public Collection> entities() {
        return super.entities().stream().map(e -> new ModifiableEntity<>(e, this)).collect(Collectors.toList());
    }

    @Override
    public Entity previous() {
        Entity previous = getDelegate().previous();
        return previous == null ? null : new ModifiableEntity<>(previous, this);
    }

    @FunctionalInterface
    public interface CommitHandler {
        void handle(Entity model, List unpublished, Entity beforeUpdate);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy