/** * Creates the handler for messages in disconnected state. * Overwrite and extend by additional matchers. * * @return an FSM function builder */ protected FSMStateFunctionBuilder<BaseClientState, BaseClientData> inDisconnectingState() { return matchEventEquals(StateTimeout(), BaseClientData.class, this::connectionTimedOut) .event(ConnectionFailure.class, BaseClientData.class, this::connectionFailure) .event(ClientDisconnected.class, BaseClientData.class, this::clientDisconnected); }
private State<BaseClientState, BaseClientData> clientConnected(final ClientConnected clientConnected, final BaseClientData data) { return ifEventUpToDate(clientConnected, () -> { LogUtil.enhanceLogWithCustomField(log, BaseClientData.MDC_CONNECTION_ID, connectionId()); startMessageMappingProcessor(data.getConnection().getMappingContext().orElse(null)); allocateResourcesOnConnection(clientConnected); data.getSessionSender().ifPresent(origin -> origin.tell(new Status.Success(CONNECTED), getSelf())); return goTo(CONNECTED).using(data.resetSession() .setConnectionStatus(ConnectionStatus.OPEN) .setConnectionStatusDetails("Connected at " + Instant.now())); }); }
private FSM.State<BaseClientState, BaseClientData> openConnection(final OpenConnection openConnection, final BaseClientData data) { final ActorRef sender = getSender(); final Connection connection = data.getConnection(); final DittoHeaders dittoHeaders = openConnection.getDittoHeaders(); if (canConnectViaSocket(connection)) { doConnectClient(connection, sender); return goTo(CONNECTING).using(setSession(data, sender, dittoHeaders)); } else { cleanupResourcesForConnection(); final DittoRuntimeException error = newConnectionFailedException(data.getConnection(), dittoHeaders); sender.tell(new Status.Failure(error), getSelf()); return goTo(UNKNOWN).using(data.resetSession()); } }
private CompletionStage<List<TargetMetrics>> getCurrentTargetsMetrics() { final long publishedMessages = publishedMessageCounter; return collectAsList(getTargetsOrEmptySet() .stream() .map(this::getTargetConnectionStatus) .map(future -> future.thenApply(status -> ConnectivityModelFactory.newTargetMetrics(status, publishedMessages) ))); }
private CompletionStage<List<SourceMetrics>> getCurrentSourcesMetrics() { final long consumedMessages = consumedMessageCounter; return collectAsList(getSourcesOrEmptySet() .stream() .map(this::getSourceConnectionStatus) .map(future -> future.thenApply(status -> ConnectivityModelFactory.newSourceMetrics(status, consumedMessages) )) ); }
private FSM.State<BaseClientState, BaseClientData> retrieveConnectionMetrics( final RetrieveConnectionMetrics command, final BaseClientData data) { final ActorRef sender = getSender(); final ActorRef self = getSelf(); final DittoHeaders dittoHeaders = command.getDittoHeaders().toBuilder() .source(org.eclipse.ditto.services.utils.config.ConfigUtil.instanceIdentifier()) .build(); final CompletionStage<List<SourceMetrics>> sourceMetricsFuture = getCurrentSourcesMetrics(); final CompletionStage<List<TargetMetrics>> targetMetricsFuture = getCurrentTargetsMetrics(); final CompletionStage<ConnectionMetrics> metricsFuture = sourceMetricsFuture.thenCompose(sourceMetrics -> targetMetricsFuture.thenApply(targetMetrics -> ConnectivityModelFactory.newConnectionMetrics( getCurrentConnectionStatus(), getCurrentConnectionStatusDetails().orElse(null), getInConnectionStatusSince(), stateName().name(), sourceMetrics, targetMetrics) ) ); metricsFuture.thenAccept(connectionMetrics -> { final Object response = RetrieveConnectionMetricsResponse.of(connectionId(), connectionMetrics, dittoHeaders); sender.tell(response, self); }); return stay(); }
private FSM.State<BaseClientState, BaseClientData> testConnection(final TestConnection testConnection, final BaseClientData data) { final ActorRef self = getSelf(); final ActorRef sender = getSender(); final Connection connection = testConnection.getConnection(); if (!canConnectViaSocket(connection)) { final ConnectionFailedException connectionFailedException = newConnectionFailedException(connection, testConnection.getDittoHeaders()); final Status.Status failure = new Status.Failure(connectionFailedException); getSelf().tell(failure, self); } else { final CompletionStage<Status.Status> connectionStatusStage = doTestConnection(connection); final CompletionStage<Status.Status> mappingStatusStage = testMessageMappingProcessor(connection.getMappingContext().orElse(null)); return goTo(TESTING) .using(setSession(data, sender, testConnection.getDittoHeaders()) .setConnection(connection) .setConnectionStatusDetails("Testing connection since " + Instant.now()));
private State<BaseClientState, BaseClientData> connectionFailure(final ConnectionFailure event, final BaseClientData data) { return ifEventUpToDate(event, () -> { LogUtil.enhanceLogWithCustomField(log, BaseClientData.MDC_CONNECTION_ID, connectionId()); cleanupResourcesForConnection(); data.getSessionSender().ifPresent(sender -> sender.tell(getStatusToReport(event.getFailure()), getSelf())); return goTo(UNKNOWN).using(data.resetSession() .setConnectionStatus(ConnectionStatus.FAILED) .setConnectionStatusDetails(event.getFailureDescription()) .setSessionSender(getSender()) ); }); }
/** * Creates the handler for messages in testing state. * Overwrite and extend by additional matchers. * * @return an FSM function builder */ protected FSMStateFunctionBuilder<BaseClientState, BaseClientData> inTestingState() { return matchEvent(Status.Status.class, (e, d) -> Objects.equals(getSender(), getSelf()), (status, data) -> { final Status.Status answerToPublish = getStatusToReport(status); data.getSessionSender().ifPresent(sender -> sender.tell(answerToPublish, getSelf())); return stop(); }) .eventEquals(StateTimeout(), BaseClientData.class, (stats, data) -> { log.info("test timed out."); data.getSessionSender().ifPresent(sender -> { final DittoRuntimeException error = ConnectionFailedException.newBuilder(connectionId()) .description(String.format("Failed to open requested connection within <%d> seconds!", TEST_CONNECTION_TIMEOUT)) .dittoHeaders(data.getSessionHeaders()) .build(); sender.tell(new Status.Failure(error), getSelf()); }); return stop(); }); }
private State<BaseClientState, BaseClientData> clientDisconnected(final ClientDisconnected event, final BaseClientData data) { return ifEventUpToDate(event, () -> { LogUtil.enhanceLogWithCustomField(log, BaseClientData.MDC_CONNECTION_ID, connectionId()); stopMessageMappingProcessorActor(); cleanupResourcesForConnection(); data.getSessionSender().ifPresent(sender -> sender.tell(new Status.Success(DISCONNECTED), getSelf())); return goTo(DISCONNECTED).using(data.resetSession() .setConnectionStatus(ConnectionStatus.CLOSED) .setConnectionStatusDetails("Disconnected at " + Instant.now())); }); }
private FSM.State<BaseClientState, BaseClientData> connectionTimedOut(final Object event, final BaseClientData data) { data.getSessionSender().ifPresent(sender -> { final DittoRuntimeException error = ConnectionFailedException.newBuilder(connectionId()) .dittoHeaders(data.getSessionHeaders()) .build(); sender.tell(new Status.Failure(error), getSelf()); }); cleanupResourcesForConnection(); cleanupFurtherResourcesOnConnectionTimeout(stateName()); return goTo(UNKNOWN).using(data.resetSession() .setConnectionStatus(ConnectionStatus.FAILED) .setConnectionStatusDetails("Connection timed out at " + Instant.now() + " while " + stateName())); }
private FSMStateFunctionBuilder<BaseClientState, BaseClientData> inUnknownState() { return matchEvent(OpenConnection.class, BaseClientData.class, this::openConnection) .event(CloseConnection.class, BaseClientData.class, this::closeConnection) .event(TestConnection.class, BaseClientData.class, this::testConnection) .eventEquals(StateTimeout(), BaseClientData.class, (state, data) -> { if (ConnectionStatus.OPEN == data.getDesiredConnectionStatus()) { log.info("Did not receive connect command within init-timeout, connecting"); final OpenConnection openConnection = OpenConnection.of(connectionId(), DittoHeaders.empty()); getSelf().tell(openConnection, getSelf()); } else if (ConnectionStatus.CLOSED == data.getDesiredConnectionStatus()) { log.info( "Did not receive connect command within init-timeout, desired state is closed, going to disconnected state."); return goTo(DISCONNECTED); } else { log.info( "Did not receive connect command within init-timeout, desired state is {}, do nothing.", data.getDesiredConnectionStatus()); } return stay(); // handle self-told commands later }); }
if (!getMessageMappingProcessorActor().isPresent()) { final Connection connection = connection(); processor = MessageMappingProcessor.of(connectionId(), mappingContext, getContext().getSystem(), log); } catch (final DittoRuntimeException dre) { log.info( connection.getProcessorPoolSize()); final Props props = MessageMappingProcessorActor.props(getSelf(), conciergeForwarder, processor, connectionId()); messageMappingProcessorActor = getContext().actorOf(new RoundRobinPool(1) .withDispatcher("message-mapping-processor-dispatcher") .withResizer(resizer) .props(props), nextChildActorName(MessageMappingProcessorActor.ACTOR_NAME)); } else { log.info("MessageMappingProcessor already instantiated: not initializing again.");
private CompletionStage<Status.Status> testMessageMappingProcessor(@Nullable final MappingContext mappingContext) { try { // this one throws DittoRuntimeExceptions when the mapper could not be configured MessageMappingProcessor.of(connectionId(), mappingContext, getContext().getSystem(), log); return CompletableFuture.completedFuture(new Status.Success("mapping")); } catch (final DittoRuntimeException dre) { log.info("Got DittoRuntimeException during initialization of MessageMappingProcessor: {} {} - desc: {}", dre.getClass().getSimpleName(), dre.getMessage(), dre.getDescription().orElse("")); getSender().tell(dre, getSelf()); return CompletableFuture.completedFuture(new Status.Failure(dre)); } }
/** * Add meaningful message to status for reporting. * * @param status status to report. * @return status with meaningful message. */ private Status.Status getStatusToReport(final Status.Status status) { final Status.Status answerToPublish; if (status instanceof Status.Failure) { final Status.Failure failure = (Status.Failure) status; log.info("test failed: <{}>", failure.cause()); if (!(failure.cause() instanceof DittoRuntimeException)) { final DittoRuntimeException error = ConnectionFailedException.newBuilder(connectionId()) .description(describeEventualCause(failure.cause())) .dittoHeaders(stateData().getSessionHeaders()) .build(); answerToPublish = new Status.Failure(error); } else { answerToPublish = status; } } else { answerToPublish = status; } return answerToPublish; }
/** * @return whether this client is consuming at all */ protected final boolean isConsuming() { return !connection().getSources().isEmpty(); }
private boolean checkHostAndPortForAvailability(final String host, final int port) { try (final Socket socket = new Socket()) { socket.connect(new InetSocketAddress(host, port), SOCKET_CHECK_TIMEOUT_MS); return true; } catch (final IOException ex) { LogUtil.enhanceLogWithCustomField(log, BaseClientData.MDC_CONNECTION_ID, connectionId()); log.warning("Socket could not be opened for <{}:{}> due to <{}:{}>", host, port, ex.getClass().getCanonicalName(), ex.getMessage()); } return false; }
private static String describeEventualCause(final Throwable throwable) { final Throwable cause = throwable.getCause(); if (cause == null || cause == throwable) { return "Cause: " + throwable.getMessage(); } else { return describeEventualCause(cause); } } }
private boolean canConnectViaSocket(final Connection connection) { return checkHostAndPortForAvailability(connection.getHostname(), connection.getPort()); }
@Override protected void cleanupFurtherResourcesOnConnectionTimeout(final BaseClientState currentState) { switch (currentState) { case CONNECTING: if (connectConnectionHandler != null) { stopChildActor(connectConnectionHandler); connectConnectionHandler = null; } break; case DISCONNECTING: if (disconnectConnectionHandler != null) { stopChildActor(disconnectConnectionHandler); disconnectConnectionHandler = null; } break; case TESTING: // no need to handle TESTING state because this actor stops after test timeout. break; } super.cleanupFurtherResourcesOnConnectionTimeout(currentState); }