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

io.axoniq.axonserver.connector.event.impl.EventChannelImpl Maven / Gradle / Ivy

/*
 * Copyright (c) 2020. AxonIQ
 *
 * 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.axoniq.axonserver.connector.event.impl;

import io.axoniq.axonserver.connector.AxonServerException;
import io.axoniq.axonserver.connector.ErrorCategory;
import io.axoniq.axonserver.connector.event.AggregateEventStream;
import io.axoniq.axonserver.connector.event.AppendEventsTransaction;
import io.axoniq.axonserver.connector.event.EventChannel;
import io.axoniq.axonserver.connector.event.EventStream;
import io.axoniq.axonserver.connector.impl.AbstractAxonServerChannel;
import io.axoniq.axonserver.connector.impl.AxonServerManagedChannel;
import io.axoniq.axonserver.connector.impl.FutureStreamObserver;
import io.axoniq.axonserver.grpc.InstructionAck;
import io.axoniq.axonserver.grpc.control.ClientIdentification;
import io.axoniq.axonserver.grpc.event.CancelScheduledEventRequest;
import io.axoniq.axonserver.grpc.event.Confirmation;
import io.axoniq.axonserver.grpc.event.Event;
import io.axoniq.axonserver.grpc.event.EventSchedulerGrpc;
import io.axoniq.axonserver.grpc.event.EventStoreGrpc;
import io.axoniq.axonserver.grpc.event.GetAggregateEventsRequest;
import io.axoniq.axonserver.grpc.event.GetAggregateSnapshotsRequest;
import io.axoniq.axonserver.grpc.event.GetFirstTokenRequest;
import io.axoniq.axonserver.grpc.event.GetLastTokenRequest;
import io.axoniq.axonserver.grpc.event.GetTokenAtRequest;
import io.axoniq.axonserver.grpc.event.ReadHighestSequenceNrRequest;
import io.axoniq.axonserver.grpc.event.ReadHighestSequenceNrResponse;
import io.axoniq.axonserver.grpc.event.RescheduleEventRequest;
import io.axoniq.axonserver.grpc.event.ScheduleEventRequest;
import io.axoniq.axonserver.grpc.event.ScheduleToken;
import io.axoniq.axonserver.grpc.event.TrackingToken;
import io.grpc.stub.StreamObserver;

import java.time.Instant;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;

/**
 * {@link EventChannel} implementation, serving as the event connection between AxonServer and a client application.
 */
public class EventChannelImpl extends AbstractAxonServerChannel implements EventChannel {

    private static final ReadHighestSequenceNrResponse UNKNOWN_HIGHEST_SEQ =
            ReadHighestSequenceNrResponse.newBuilder().setToSequenceNr(-1).build();
    private static final TrackingToken NO_TOKEN_AVAILABLE = TrackingToken.newBuilder().setToken(-1).build();

    private final EventStoreGrpc.EventStoreStub eventStore;
    private final EventSchedulerGrpc.EventSchedulerStub eventScheduler;
    private final Set buffers = ConcurrentHashMap.newKeySet();
    // guarded by -this-

    /**
     * Constructs a {@link EventChannelImpl}.
     *
     * @param executor a {@link ScheduledExecutorService} used to schedule reconnects of this channel
     * @param channel  the {@link AxonServerManagedChannel} used to form the connection with AxonServer
     */
    public EventChannelImpl(ClientIdentification clientIdentification, ScheduledExecutorService executor, AxonServerManagedChannel channel) {
        super(clientIdentification, executor, channel);
        eventStore = EventStoreGrpc.newStub(channel);
        eventScheduler = EventSchedulerGrpc.newStub(channel);
    }

    @Override
    public synchronized void connect() {
        // there is no instruction stream for the events channel
    }

    @Override
    public void reconnect() {
        closeEventStreams();
    }

    @Override
    public void disconnect() {
        closeEventStreams();
    }

    private void closeEventStreams() {
        buffers.forEach(BufferedEventStream::close);
        buffers.clear();
    }

    @Override
    public boolean isReady() {
        return true;
    }

    @Override
    public AppendEventsTransaction startAppendEventsTransaction() {
        FutureStreamObserver result = new FutureStreamObserver<>(null);
        StreamObserver clientStream = eventStore.appendEvent(result);
        return new AppendEventsTransactionImpl(clientStream, result);
    }

    @Override
    public CompletableFuture scheduleEvent(Instant scheduleTime, Event event) {
        FutureStreamObserver responseObserver = new FutureStreamObserver<>(new AxonServerException(
                ErrorCategory.OTHER,
                "An unknown error occurred while scheduling an Event. No response received from Server.",
                ""
        ));

        eventScheduler.scheduleEvent(ScheduleEventRequest.newBuilder()
                                                         .setEvent(event)
                                                         .setInstant(scheduleTime.toEpochMilli())
                                                         .build(), responseObserver);
        return responseObserver.thenApply(ScheduleToken::getToken);
    }

    @Override
    public CompletableFuture cancelSchedule(String token) {
        FutureStreamObserver responseObserver = new FutureStreamObserver<>(new AxonServerException(
                ErrorCategory.OTHER,
                "An unknown error occurred while cancelling a scheduled Event. No response received from Server.",
                ""
        ));

        eventScheduler.cancelScheduledEvent(CancelScheduledEventRequest.newBuilder().setToken(token).build(),
                                            responseObserver);
        return responseObserver;
    }

    @Override
    public CompletableFuture reschedule(String scheduleToken, Instant scheduleTime, Event event) {
        FutureStreamObserver responseObserver = new FutureStreamObserver<>(new AxonServerException(
                ErrorCategory.OTHER,
                "An unknown error occurred while rescheduling Event. No response received from Server.",
                ""
        ));

        eventScheduler.rescheduleEvent(RescheduleEventRequest.newBuilder()
                                                             .setToken(scheduleToken)
                                                             .setEvent(event)
                                                             .setInstant(scheduleTime.toEpochMilli())
                                                             .build(),
                                       responseObserver);
        return responseObserver.thenApply(ScheduleToken::getToken);
    }

    @Override
    public CompletableFuture findHighestSequence(String aggregateId) {
        FutureStreamObserver result = new FutureStreamObserver<>(UNKNOWN_HIGHEST_SEQ);
        eventStore.readHighestSequenceNr(ReadHighestSequenceNrRequest.newBuilder()
                                                                     .setAggregateId(aggregateId)
                                                                     .build(),
                                         result);
        return result.thenApply(ReadHighestSequenceNrResponse::getToSequenceNr);
    }

    @Override
    public EventStream openStream(long token, int bufferSize, int refillBatch, boolean forceReadFromLeader) {
        BufferedEventStream buffer = new BufferedEventStream(token,
                                                             Math.max(64, bufferSize),
                                                             Math.max(16, Math.min(bufferSize, refillBatch)),
                                                             forceReadFromLeader);
        buffers.add(buffer);
        buffer.onCloseRequested(() -> buffers.remove(buffer));
        try {
            //noinspection ResultOfMethodCallIgnored
            eventStore.listEvents(buffer);
        } catch (Exception e) {
            buffers.remove(buffer);
            throw e;
        }
        buffer.enableFlowControl();
        return buffer;
    }

    @Override
    public AggregateEventStream openAggregateStream(String aggregateIdentifier, boolean allowSnapshots) {
        return doGetAggregateStream(GetAggregateEventsRequest.newBuilder()
                                                             .setAggregateId(aggregateIdentifier)
                                                             .setAllowSnapshots(allowSnapshots)
                                                             .build());
    }

    @Override
    public AggregateEventStream openAggregateStream(String aggregateIdentifier,
                                                    long initialSequence,
                                                    long maxSequence) {
        return doGetAggregateStream(GetAggregateEventsRequest.newBuilder()
                                                             .setAggregateId(aggregateIdentifier)
                                                             .setInitialSequence(initialSequence)
                                                             .setMaxSequence(maxSequence)
                                                             .build());
    }

    @Override
    public CompletableFuture appendSnapshot(Event snapshotEvent) {
        FutureStreamObserver result =
                new FutureStreamObserver<>(Confirmation.newBuilder().setSuccess(false).build());
        eventStore.appendSnapshot(snapshotEvent, result);
        return result;
    }

    @Override
    public AggregateEventStream loadSnapshots(String aggregateIdentifier,
                                              long initialSequence,
                                              long maxSequence,
                                              int maxResults) {
        BufferedAggregateEventStream buffer = new BufferedAggregateEventStream(maxResults);
        eventStore.listAggregateSnapshots(GetAggregateSnapshotsRequest.newBuilder()
                                                                      .setInitialSequence(initialSequence)
                                                                      .setMaxResults(maxResults)
                                                                      .setMaxSequence(maxSequence)
                                                                      .setAggregateId(aggregateIdentifier)
                                                                      .build(),
                                          buffer);
        return buffer;
    }

    @Override
    public CompletableFuture getLastToken() {
        FutureStreamObserver result = new FutureStreamObserver<>(NO_TOKEN_AVAILABLE);
        eventStore.getLastToken(GetLastTokenRequest.newBuilder().build(), result);
        return result.thenApply(TrackingToken::getToken);
    }

    @Override
    public CompletableFuture getFirstToken() {
        FutureStreamObserver result = new FutureStreamObserver<>(NO_TOKEN_AVAILABLE);
        eventStore.getFirstToken(GetFirstTokenRequest.newBuilder().build(), result);
        // older versions of AxonServer erroneously return -1 in certain cases. We assume 0 in such case.
        return result.thenApply(trackingToken -> Math.max(trackingToken.getToken(), 0) - 1);
    }

    @Override
    public CompletableFuture getTokenAt(long instant) {
        FutureStreamObserver result = new FutureStreamObserver<>(NO_TOKEN_AVAILABLE);
        eventStore.getTokenAt(GetTokenAtRequest.newBuilder().setInstant(instant).build(), result);
        return result.thenApply(TrackingToken::getToken);
    }

    private AggregateEventStream doGetAggregateStream(GetAggregateEventsRequest request) {
        BufferedAggregateEventStream buffer = new BufferedAggregateEventStream();
        eventStore.listAggregateEvents(request, buffer);
        return buffer;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy