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

org.axonframework.test.deadline.StubDeadlineManager Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010-2022. Axon Framework
 *
 * 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 org.axonframework.test.deadline;

import org.axonframework.common.Registration;
import org.axonframework.deadline.DeadlineManager;
import org.axonframework.deadline.DeadlineMessage;
import org.axonframework.messaging.DefaultInterceptorChain;
import org.axonframework.messaging.InterceptorChain;
import org.axonframework.messaging.MessageDispatchInterceptor;
import org.axonframework.messaging.MessageHandlerInterceptor;
import org.axonframework.messaging.ResultMessage;
import org.axonframework.messaging.ScopeDescriptor;
import org.axonframework.messaging.unitofwork.DefaultUnitOfWork;
import org.axonframework.test.FixtureExecutionException;

import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.NavigableSet;
import java.util.NoSuchElementException;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nonnull;

import static org.axonframework.deadline.GenericDeadlineMessage.asDeadlineMessage;

/**
 * Stub implementation of {@link DeadlineManager}. Records all scheduled, canceled and met deadlines.
 *
 * @author Milan Savic
 * @author Steven van Beelen
 * @since 3.3
 */
public class StubDeadlineManager implements DeadlineManager {

    private final NavigableSet scheduledDeadlines = new TreeSet<>();
    private final List triggeredDeadlines = new CopyOnWriteArrayList<>();
    private final AtomicInteger deadlineCounter = new AtomicInteger(0);
    private final List>> dispatchInterceptors =
            new CopyOnWriteArrayList<>();
    private final List>> handlerInterceptors =
            new CopyOnWriteArrayList<>();
    private Instant currentDateTime;

    /**
     * Initializes the manager with {@link ZonedDateTime#now()} as current time.
     */
    public StubDeadlineManager() {
        this(ZonedDateTime.now());
    }

    /**
     * Initializes the manager with provided {@code currentDateTime} as current time.
     *
     * @param currentDateTime The instance to use as current date and time
     */
    public StubDeadlineManager(TemporalAccessor currentDateTime) {
        this.currentDateTime = Instant.from(currentDateTime);
    }

    /**
     * Resets the initial "current time" of this manager. Must be called before any deadlines are scheduled.
     *
     * @param currentDateTime The instant to use as the current date and time
     * @throws IllegalStateException when calling this method after deadlines are scheduled
     */
    public void initializeAt(TemporalAccessor currentDateTime) throws IllegalStateException {
        if (!scheduledDeadlines.isEmpty()) {
            throw new IllegalStateException("Initializing the deadline manager at a specific dateTime must take place "
                                                    + "before any deadlines are scheduled");
        }
        this.currentDateTime = Instant.from(currentDateTime);
    }

    @Nonnull
    @Override
    public String schedule(@Nonnull Instant triggerDateTime,
                           @Nonnull String deadlineName,
                           Object payloadOrMessage,
                           @Nonnull ScopeDescriptor deadlineScope) {
        DeadlineMessage scheduledMessage =
                processDispatchInterceptors(asDeadlineMessage(deadlineName, payloadOrMessage, triggerDateTime));

        scheduledDeadlines.add(new ScheduledDeadlineInfo(triggerDateTime,
                                                         deadlineName,
                                                         scheduledMessage.getIdentifier(),
                                                         deadlineCounter.getAndIncrement(),
                                                         scheduledMessage,
                                                         deadlineScope));
        return scheduledMessage.getIdentifier();
    }

    @Nonnull
    @Override
    public String schedule(@Nonnull Duration triggerDuration,
                           @Nonnull String deadlineName,
                           Object payloadOrMessage,
                           @Nonnull ScopeDescriptor deadlineScope) {
        return schedule(currentDateTime.plus(triggerDuration), deadlineName, payloadOrMessage, deadlineScope);
    }

    @Override
    public void cancelSchedule(@Nonnull String deadlineName, @Nonnull String scheduleId) {
        scheduledDeadlines.removeIf(
                scheduledDeadline -> scheduledDeadline.getDeadlineName().equals(deadlineName)
                        && scheduledDeadline.getScheduleId().equals(scheduleId)
        );
    }

    @Override
    public void cancelAll(@Nonnull String deadlineName) {
        scheduledDeadlines.removeIf(scheduledDeadline -> scheduledDeadline.getDeadlineName().equals(deadlineName));
    }

    @Override
    public void cancelAllWithinScope(@Nonnull String deadlineName, @Nonnull ScopeDescriptor scope) {
        scheduledDeadlines.removeIf(
                scheduledDeadline -> scheduledDeadline.getDeadlineName().equals(deadlineName)
                        && scheduledDeadline.getDeadlineScope().equals(scope)
        );
    }

    /**
     * Return all scheduled deadlines which have not been met (yet).
     *
     * @return all scheduled deadlines which have not been met (yet)
     */
    public List getScheduledDeadlines() {
        return new ArrayList<>(scheduledDeadlines);
    }

    /**
     * Return all deadlines which have been met.
     *
     * @return all deadlines which have been met
     * @deprecated in favor of {@link #getTriggeredDeadlines()}
     */
    @Deprecated
    public List getDeadlinesMet() {
        return Collections.unmodifiableList(triggeredDeadlines);
    }

    /**
     * Return all triggered deadlines.
     *
     * @return all triggered deadlines
     */
    public List getTriggeredDeadlines() {
        return Collections.unmodifiableList(triggeredDeadlines);
    }

    /**
     * Return the current date and time as an {@link Instant} as is being used by this {@link DeadlineManager}.
     *
     * @return the current date and time used by the manager
     */
    public Instant getCurrentDateTime() {
        return currentDateTime;
    }

    /**
     * Advances the "current time" of the manager to the next scheduled deadline, and returns that deadline. In theory,
     * this may cause "current time" to move backwards.
     *
     * @return {@link ScheduledDeadlineInfo} of the first scheduled deadline
     */
    public ScheduledDeadlineInfo advanceToNextTrigger() {
        ScheduledDeadlineInfo nextItem = scheduledDeadlines.pollFirst();
        if (nextItem == null) {
            throw new NoSuchElementException("There are no scheduled deadlines");
        }
        if (nextItem.getScheduleTime().isAfter(currentDateTime)) {
            currentDateTime = nextItem.getScheduleTime();
        }
        triggeredDeadlines.add(nextItem);
        return nextItem;
    }

    /**
     * Advances time to the given {@code newDateTime} and invokes the given {@code deadlineConsumer} for each deadline
     * scheduled until that time.
     *
     * @param newDateTime      the time to advance the "current time" of the manager to
     * @param deadlineConsumer the consumer to invoke for each deadline to trigger
     */
    public void advanceTimeTo(Instant newDateTime, DeadlineConsumer deadlineConsumer) {
        while (!scheduledDeadlines.isEmpty() && !scheduledDeadlines.first().getScheduleTime().isAfter(newDateTime)) {
            ScheduledDeadlineInfo scheduledDeadlineInfo = advanceToNextTrigger();
            DeadlineMessage consumedMessage = consumeDeadline(deadlineConsumer, scheduledDeadlineInfo);
            triggeredDeadlines.remove(scheduledDeadlineInfo);
            triggeredDeadlines.add(scheduledDeadlineInfo.recreateWithNewMessage(consumedMessage));
        }
        if (newDateTime.isAfter(currentDateTime)) {
            currentDateTime = newDateTime;
        }
    }

    /**
     * Advances time by the given {@code duration} and invokes the given {@code deadlineConsumer} for each deadline
     * scheduled until that time.
     *
     * @param duration         the amount of time to advance the "current time" of the manager with
     * @param deadlineConsumer the consumer to invoke for each deadline to trigger
     */
    public void advanceTimeBy(Duration duration, DeadlineConsumer deadlineConsumer) {
        advanceTimeTo(currentDateTime.plus(duration), deadlineConsumer);
    }

    @Override
    public @Nonnull
    Registration registerDispatchInterceptor(
            @Nonnull MessageDispatchInterceptor> dispatchInterceptor) {
        dispatchInterceptors.add(dispatchInterceptor);
        return () -> dispatchInterceptors.remove(dispatchInterceptor);
    }

    @Nonnull
    @Override
    public Registration registerHandlerInterceptor(
            @Nonnull MessageHandlerInterceptor> handlerInterceptor) {
        handlerInterceptors.add(handlerInterceptor);
        return () -> handlerInterceptors.remove(handlerInterceptor);
    }

    @SuppressWarnings("unchecked")
    private  DeadlineMessage processDispatchInterceptors(DeadlineMessage message) {
        DeadlineMessage intercepted = message;
        for (MessageDispatchInterceptor> interceptor : dispatchInterceptors) {
            intercepted = (DeadlineMessage) interceptor.handle(intercepted);
        }
        return intercepted;
    }

    private DeadlineMessage consumeDeadline(DeadlineConsumer deadlineConsumer,
                                               ScheduledDeadlineInfo scheduledDeadlineInfo) {
        DefaultUnitOfWork> uow =
                DefaultUnitOfWork.startAndGet(scheduledDeadlineInfo.deadlineMessage());
        InterceptorChain chain = new DefaultInterceptorChain<>(uow, handlerInterceptors, deadlineMessage -> {
            deadlineConsumer.consume(scheduledDeadlineInfo.getDeadlineScope(), deadlineMessage);
            return deadlineMessage;
        });
        ResultMessage resultMessage = uow.executeWithResult(chain::proceed);
        if (resultMessage.isExceptional()) {
            Throwable e = resultMessage.exceptionResult();
            throw new FixtureExecutionException("Exception occurred while handling the deadline", e);
        }
        return (DeadlineMessage) resultMessage.getPayload();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy