@Override public ConnectionActor create() { return new ConnectionActor(connectionId, pubSubMediator, conciergeForwarder, propsFactory, commandValidator); } });
private void askClientActorIfStarted(final Command<?> cmd, final Consumer<Object> onSuccess, final Consumer<Throwable> onError, final Runnable onClientActorNotStarted) { if (clientActorRouter != null && connection != null) { askClientActor(cmd, onSuccess, onError); } else { onClientActorNotStarted.run(); } }
private void retrieveConnectionMetrics(final RetrieveConnectionMetrics command) { checkConnectionNotNull(); final ActorRef origin = getSender(); askClientActorIfStarted(command, response -> origin.tell(response, getSelf()), error -> handleException("retrieve-metrics", origin, error), () -> respondWithEmptyMetrics(command, origin)); }
private void unsubscribeFromEvents() { forEachPubSubTopicDo(pubSubTopic -> { log.debug("Unsubscribing from pub-sub topic <{}> for connection <{}>.", pubSubTopic, connectionId); final DistributedPubSubMediator.Unsubscribe unsubscribe = new DistributedPubSubMediator.Unsubscribe(pubSubTopic, PUB_SUB_GROUP_PREFIX + connectionId, getSelf()); pubSubMediator.tell(unsubscribe, getSelf()); }); }
private void retrieveConnection(final RetrieveConnection command) { checkConnectionNotNull(); getSender().tell(RetrieveConnectionResponse.of(connection, command.getDittoHeaders()), getSelf()); }
private void closeConnection(final CloseConnection command) { checkConnectionNotNull(); final ActorRef origin = getSender(); final ActorRef self = getSelf(); persistEvent(connectionClosed, persistedEvent -> { if (connection != null) { restoreConnection(connection.toBuilder().connectionStatus(ConnectionStatus.CLOSED).build()); askClientActorIfStarted(command, response -> { final PerformTask performTask = "unsubscribe from events on connection closed, stop client actor and schdeule response", connectionActor -> { connectionActor.unsubscribeFromEvents(); connectionActor.stopClientActor(); origin.tell(closeConnectionResponse, getSelf()); }); self.tell(performTask, ActorRef.noSender()); }, error -> handleException("disconnect", origin, error), () -> { log.debug("Client actor was not started, responding directly."); origin.tell(closeConnectionResponse, getSelf());
private void createConnection(final CreateConnection command) { final ActorRef origin = getSender(); final ActorRef self = getSelf(); final ActorRef parent = getContext().getParent(); persistEvent(ConnectionCreated.of(command.getConnection(), command.getDittoHeaders()), persistedEvent -> { restoreConnection(persistedEvent.getConnection()); getContext().become(connectionCreatedBehaviour); connection.getConnectionStatus().getName()); final OpenConnection openConnection = OpenConnection.of(connectionId, command.getDittoHeaders()); askClientActor(openConnection, response -> { final ConnectivityCommandResponse commandResponse = final PerformTask performTask = new PerformTask("subscribe for events and schedule CreateConnectionResponse", subscribeForEventsAndScheduleResponse(commandResponse, origin)); parent.tell(ConnectionSupervisorActor.ManualReset.getInstance(), self); self.tell(performTask, ActorRef.noSender()); error -> { handleException("connect", origin, error, false); respondWithCreateConnectionResponse(connection, command, origin); connection.getId(), connection.getConnectionStatus().getName()); respondWithCreateConnectionResponse(connection, command, origin);
private void openConnection(final OpenConnection command) { checkConnectionNotNull(); final ConnectionOpened connectionOpened = ConnectionOpened.of(command.getConnectionId(), command.getDittoHeaders()); final ActorRef origin = getSender(); final ActorRef self = getSelf(); persistEvent(connectionOpened, persistedEvent -> { restoreConnection(connection.toBuilder().connectionStatus(ConnectionStatus.OPEN).build()); askClientActor(command, response -> { final ConnectivityCommandResponse commandResponse = OpenConnectionResponse.of(connectionId, command.getDittoHeaders()); final PerformTask performTask = new PerformTask("open connection", subscribeForEventsAndScheduleResponse(commandResponse, origin)); self.tell(performTask, ActorRef.noSender()); }, error -> handleException("open-connection", origin, error) ); }); }
private void modifyConnection(final ModifyConnection command) { final ActorRef origin = getSender(); final ActorRef self = getSelf(); if (connection != null && !connection.getConnectionType().equals(command.getConnection().getConnectionType())) { handleException("modify", origin, ConnectionConfigurationInvalidException .newBuilder("ConnectionType <" + connection.getConnectionType().getName() + "> of existing connection <" + connectionId + "> cannot be changed!") .dittoHeaders(command.getDittoHeaders()) .build() ); return; } persistEvent(ConnectionModified.of(command.getConnection(), command.getDittoHeaders()), persistedEvent -> { restoreConnection(persistedEvent.getConnection()); getContext().become(connectionCreatedBehaviour); // if client actor is started: send an artificial CloseConnection command to gracefully disconnect and stop the child actors askClientActorIfStarted(CloseConnection.of(connectionId, DittoHeaders.empty()), onSuccess -> { final PerformTask modifyTask = createModifyConnectionTask(true, command, origin); self.tell(modifyTask, ActorRef.noSender()); }, error -> handleException("connect-after-modify", origin, error), () -> { final PerformTask modifyTask = createModifyConnectionTask(false, command, origin); self.tell(modifyTask, ActorRef.noSender()); }); }); }
log.info("Received SnapshotOffer containing connection: <{}>", fromSnapshotStore); if (fromSnapshotStore != null) { restoreConnection(fromSnapshotStore); .match(ConnectionCreated.class, event -> restoreConnection(event.getConnection())) .match(ConnectionModified.class, event -> restoreConnection(event.getConnection())) .match(ConnectionOpened.class, event -> restoreConnection(connection != null ? connection.toBuilder() .connectionStatus(ConnectionStatus.OPEN).build() : null)) .match(ConnectionClosed.class, event -> restoreConnection(connection != null ? connection.toBuilder() .connectionStatus(ConnectionStatus.CLOSED).build() : null)) .match(ConnectionDeleted.class, event -> restoreConnection(null)) .match(RecoveryCompleted.class, rc -> { log.info("Connection <{}> was recovered: {}", connectionId, connection); final ActorRef origin = getSender(); askClientActor(connect, response -> log.info("OpenConnection result: {}", response), error -> handleException("recovery-connect", origin, error) ); subscribeForEvents(); getContext().become(connectionCreatedBehaviour); } else { stopSelfIfDeletedAfterDelay(); getContext().getParent().tell(ConnectionSupervisorActor.ManualReset.getInstance(), getSelf()); }) .matchAny(m -> { if (m == null) {
private Receive createConnectionCreatedBehaviour() { return ReceiveBuilder.create() .match(TestConnection.class, testConnection -> getSender().tell(TestConnectionResponse.alreadyCreated(testConnection.getConnectionId(), testConnection.getDittoHeaders()), getSelf())) .match(CreateConnection.class, createConnection -> { enhanceLogUtil(createConnection); log.info("Connection <{}> already exists! Responding with conflict.", createConnection.getId()); final ConnectionConflictException conflictException = .dittoHeaders(createConnection.getDittoHeaders()) .build(); getSender().tell(conflictException, getSelf()); }) .match(ModifyConnection.class, modifyConnection -> validateAndForward(modifyConnection, this::modifyConnection)) .match(OpenConnection.class, this::openConnection) .match(CloseConnection.class, this::closeConnection) .matchEquals(STOP_SELF_IF_DELETED, msg -> { cancelStopSelfIfDeletedTrigger(); }) .matchAny(m -> { log.warning("Unknown message: {}", m); unhandled(m); }).build();
private void testConnection(final TestConnection command) { final ActorRef origin = getSender(); final ActorRef self = getSelf(); restoreConnection(command.getConnection()); final PerformTask stopSelfTask = new PerformTask("stop self after test", ConnectionActor::stopSelf); askClientActor(command, response -> { origin.tell(TestConnectionResponse.success(command.getConnectionId(), response.toString(), command.getDittoHeaders()), self); // terminate this actor's supervisor after a connection test again: self.tell(stopSelfTask, ActorRef.noSender()); }, error -> { handleException("test", origin, error); // terminate this actor's supervisor after a connection test again: self.tell(stopSelfTask, ActorRef.noSender()); }); }
private void handleModifyConnection(final ModifyConnection command, final ActorRef origin) { checkNotNull(connection, "Connection"); final ActorRef self = getSelf(); final ActorRef parent = getContext().getParent(); final DittoHeaders dittoHeaders = command.getDittoHeaders(); final ConnectivityCommandResponse commandResponse = ModifyConnectionResponse.modified(connectionId, dittoHeaders); if (ConnectionStatus.OPEN.equals(connection.getConnectionStatus())) { final OpenConnection openConnectionAfterModification = OpenConnection.of(connectionId, dittoHeaders); log.debug("Desired connection state is {}, forwarding {} to client actor.", connection.getConnectionStatus(), openConnectionAfterModification); askClientActor(openConnectionAfterModification, response -> { final PerformTask performTask = new PerformTask("subscribe for events and schedule ModifyConnectionResponse", subscribeForEventsAndScheduleResponse(commandResponse, origin)); parent.tell(ConnectionSupervisorActor.ManualReset.getInstance(), self); self.tell(performTask, ActorRef.noSender()); }, error -> handleException("connect-after-modify", origin, error) ); } else { log.debug("Desired connection state is {}, do not open connection.", connection.getConnectionStatus()); origin.tell(commandResponse, getSelf()); } }
private <E extends Event> void persistEvent(final E event, final Consumer<E> consumer) { persist(event, persistedEvent -> { log.debug("Successfully persisted Event <{}>.", persistedEvent.getType()); consumer.accept(persistedEvent); pubSubMediator.tell(new DistributedPubSubMediator.Publish(event.getType(), event, true), getSelf()); // save a snapshot if there were too many changes since the last snapshot if ((lastSequenceNr() - lastSnapshotSequenceNr) > snapshotThreshold) { doSaveSnapshot(); } }); }
private void subscribeForEvents() { checkConnectionNotNull(); // unsubscribe to previously subscribed topics unsubscribeFromEvents(); uniqueTopics = connection.getTargets().stream() .flatMap(target -> target.getTopics().stream().map(FilteredTopic::getTopic)) .collect(Collectors.toSet()); forEachPubSubTopicDo(pubSubTopic -> { final DistributedPubSubMediator.Subscribe subscribe = new DistributedPubSubMediator.Subscribe(pubSubTopic, PUB_SUB_GROUP_PREFIX + connectionId, getSelf()); log.debug("Subscribing to pub-sub topic <{}> for connection <{}>.", pubSubTopic, connectionId); pubSubMediator.tell(subscribe, getSelf()); }); }
private void stopSelfIfDeletedAfterDelay() { final ExecutionContextExecutor dispatcher = getContext().dispatcher(); cancelStopSelfIfDeletedTrigger(); stopSelfIfDeletedTrigger = getContext().system() .scheduler() .scheduleOnce(DELETED_ACTOR_LIFETIME, getSelf(), STOP_SELF_IF_DELETED, dispatcher, ActorRef.noSender()); }
private void startClientActorIfRequired() { checkNotNull(connectionId, "connectionId"); checkConnectionNotNull(); if (clientActorRouter == null) { final int clientCount = connection.getClientCount(); log.info("Starting ClientActor for connection <{}> with <{}> clients.", connectionId, clientCount); final Props props = propsFactory.getActorPropsForType(connection, conciergeForwarder); final ClusterRouterPoolSettings clusterRouterPoolSettings = new ClusterRouterPoolSettings(clientCount, 1, true, Collections.singleton(CLUSTER_ROLE)); final RoundRobinPool roundRobinPool = new RoundRobinPool(clientCount); final Props clusterRouterPoolProps = new ClusterRouterPool(roundRobinPool, clusterRouterPoolSettings).props(props); // start client actor without name so it does not conflict with its previous incarnation clientActorRouter = getContext().actorOf(clusterRouterPoolProps); } else { log.debug("ClientActor already started."); } }
private ConnectionActor(final String connectionId, final ActorRef pubSubMediator, final ActorRef conciergeForwarder, final ClientActorPropsFactory propsFactory, @Nullable final Consumer<ConnectivityCommand<?>> customCommandValidator) { this.connectionId = connectionId; this.pubSubMediator = pubSubMediator; this.conciergeForwarder = conciergeForwarder; this.propsFactory = propsFactory; final DittoConnectivityCommandValidator dittoCommandValidator = new DittoConnectivityCommandValidator(propsFactory, conciergeForwarder, CONNECTION_VALIDATOR); if (customCommandValidator != null) { this.commandValidator = new CompoundConnectivityCommandInterceptor(dittoCommandValidator, customCommandValidator); } else { this.commandValidator = dittoCommandValidator; } final ConnectionConfigReader configReader = ConnectionConfigReader.fromRawConfig(getContext().system().settings().config()); snapshotThreshold = configReader.snapshotThreshold(); snapshotAdapter = new ConnectionMongoSnapshotAdapter(); connectionCreatedBehaviour = createConnectionCreatedBehaviour(); final java.time.Duration javaFlushTimeout = configReader.flushPendingResponsesTimeout(); flushPendingResponsesTimeout = Duration.create(javaFlushTimeout.toMillis(), TimeUnit.MILLISECONDS); }
private void handleSignal(final Signal<?> signal) { enhanceLogUtil(signal); if (clientActorRouter == null) { log.debug("Signal dropped: Client actor not ready."); clientActorRouter.tell(outbound, getSender());
@Override public void postStop() { log.info("stopped connection <{}>", connectionId); cancelStopSelfIfDeletedTrigger(); super.postStop(); }