@Override public CommandSubscriber create() { return new CommandSubscriber(delegateActor, backpressureQueueSize, eventStream); } });
@Override public StreamingSessionActor create() throws Exception { return new StreamingSessionActor(connectionCorrelationId, type, pubSubMediator, eventAndResponsePublisher); } });
@Override public StreamingActor create() { return new StreamingActor(pubSubMediator, commandRouter); } });
@Override public Receive createReceive() { // Initially, this Actor can only receive the Connect message: return ReceiveBuilder.create() .match(Connect.class, connect -> { final String connectionCorrelationId = connect.getConnectionCorrelationId(); LogUtil.enhanceLogWithCorrelationId(logger, connectionCorrelationId); logger.debug("Established new connection: {}", connectionCorrelationId); getContext().become(connected(connectionCorrelationId)); }) .matchAny(any -> { logger.info("Got unknown message during init phase '{}' - stashing..", any); stash(); }).build(); }
private void handleSignal(final Signal<?> signal) { LogUtil.enhanceLogWithCorrelationId(logger, signal); final DittoHeaders dittoHeaders = signal.getDittoHeaders(); if (connectionCorrelationId.equals(dittoHeaders.getOrigin().orElse(null))) { logger.debug("Got Signal <{}> in <{}> session, " + "but this was issued by this connection itself, not telling " + "eventAndResponsePublisher about it", signal.getType(), type); } else { // check if this session is "allowed" to receive the LiveSignal if (authorizationSubjects != null && !Collections.disjoint(dittoHeaders.getReadSubjects(), authorizationSubjects)) { if (matchesNamespaces(signal)) { if (matchesFilter(signal)) { logger.debug("Got Signal <{}> in <{}> session, " + "telling eventAndResponsePublisher about it: {}", signal.getType(), type, signal); eventAndResponsePublisher.tell(signal, getSelf()); } else { logger.debug("Signal does not match filter"); } } else { logger.debug("Signal does not match namespaces"); } } } }
eventAndResponsePublisher.forward(connect, getContext()); final String connectionCorrelationId = connect.getConnectionCorrelationId(); getContext().actorOf( StreamingSessionActor.props(connectionCorrelationId, connect.getType(), pubSubMediator, eventAndResponsePublisher), connectionCorrelationId); }) .match(StartStreaming.class, startStreaming -> forwardToSessionActor(startStreaming.getConnectionCorrelationId(), startStreaming) stopStreaming -> forwardToSessionActor(stopStreaming.getConnectionCorrelationId(), stopStreaming) if (originOpt.isPresent()) { final String origin = originOpt.get(); final ActorRef sessionActor = getContext().getChild(origin); if (sessionActor != null) { commandRouter.tell(signal, sessionActor); final Optional<String> originOpt = cre.getDittoHeaders().getOrigin(); if (originOpt.isPresent()) { forwardToSessionActor(originOpt.get(), cre); } else { logger.warning("Unhandled DittoRuntimeException: <{}: {}>", cre.getClass().getSimpleName(),
private boolean matchesFilter(final Signal<?> signal) { if (signal instanceof ThingEvent) { final StreamingType streamingType = determineStreamingType(signal); // currently only ThingEvents may be filtered with RQL return ThingEventToThingConverter.thingEventToThing((ThingEvent) signal) .filter(thing -> doMatchFilter(streamingType, thing)) .isPresent(); } else { return true; } }
private boolean matchesNamespaces(final Signal<?> signal) { final StreamingType streamingType = determineStreamingType(signal); final List<String> namespaces = Optional.ofNullable(namespacesForStreamingTypes.get(streamingType)) .orElse(Collections.emptyList()); return namespaces.isEmpty() || namespaces.contains(namespaceFromId(signal)); }
private void handleBackpressureFor(final Jsonifiable.WithPredicate<JsonObject, JsonField> jsonifiable) { if (currentlyInMessageConsumedCheck.compareAndSet(false, true)) { logger.warning("Backpressure - buffer of '{}' outstanding Events/CommandResponses is full, dropping '{}'", backpressureBufferSize, jsonifiable); final long bufSize = buffer.size(); final ActorContext context = getContext(); context.system().scheduler().scheduleOnce(FiniteDuration.apply(MESSAGE_CONSUMPTION_CHECK_SECONDS, TimeUnit.SECONDS), () -> { if (bufSize == buffer.size()) { logger.warning( "Terminating Publisher - did not to consume anything in the last '{}' seconds, buffer " + "is still at '{}' outstanding messages", MESSAGE_CONSUMPTION_CHECK_SECONDS, bufSize); context.stop(getSelf()); } else { currentlyInMessageConsumedCheck.set(false); logger.info("Outstanding messages were consumed, Publisher is not terminated"); } }, context.system().dispatcher()); } }
@Override public EventAndResponsePublisher create() { return new EventAndResponsePublisher(backpressureBufferSize); } });
private CommandSubscriber(final ActorRef delegateActor, final int backpressureQueueSize, final EventStream eventStream) { this.delegateActor = delegateActor; this.backpressureQueueSize = backpressureQueueSize; eventStream.subscribe(getSelf(), ResponsePublished.class); }
private StreamingSessionActor(final String connectionCorrelationId, final String type, final ActorRef pubSubMediator, final ActorRef eventAndResponsePublisher) { this.connectionCorrelationId = connectionCorrelationId; this.type = type; this.pubSubMediator = pubSubMediator; this.eventAndResponsePublisher = eventAndResponsePublisher; outstandingSubscriptionAcks = new HashSet<>(); namespacesForStreamingTypes = new EnumMap<>(StreamingType.class); eventFilterCriteriaForStreamingTypes = new EnumMap<>(StreamingType.class); getContext().watch(eventAndResponsePublisher); }
private void deliverBuf() { LogUtil.enhanceLogWithCorrelationId(logger, connectionCorrelationId); while (totalDemand() > 0) { /* * totalDemand is a Long and could be larger than * what buffer.splitAt can accept */ if (totalDemand() <= Integer.MAX_VALUE) { final List<Jsonifiable.WithPredicate<JsonObject, JsonField>> took = buffer.subList(0, Math.min(buffer.size(), (int) totalDemand())); took.forEach(this::onNext); buffer.removeAll(took); break; } else { final List<Jsonifiable.WithPredicate<JsonObject, JsonField>> took = buffer.subList(0, Math.min(buffer.size(), Integer.MAX_VALUE)); took.forEach(this::onNext); buffer.removeAll(took); } } }
private void forwardToSessionActor(final String connectionCorrelationId, final Object object) { if (object instanceof WithDittoHeaders) { LogUtil.enhanceLogWithCorrelationId(logger, (WithDittoHeaders<?>) object); } else { LogUtil.enhanceLogWithCorrelationId(logger, (String) null); } logger.debug("Forwarding to session actor '{}': {}", connectionCorrelationId, object); getContext().actorSelection(connectionCorrelationId).forward(object, getContext()); } }
private void handleDittoRuntimeException(final ActorRef delegateActor, final DittoRuntimeException cre) { LogUtil.enhanceLogWithCorrelationId(logger, cre.getDittoHeaders().getCorrelationId()); logger.info("Got 'DittoRuntimeException': {} {}", cre.getClass().getName(), cre.getMessage()); cre.getDittoHeaders().getCorrelationId().ifPresent(outstandingCommandCorrelationIds::remove); if (cre.getDittoHeaders().isResponseRequired()) { delegateActor.forward(cre, getContext()); } else { logger.debug("Requester did not require response (via DittoHeader '{}') - not sending one", DittoHeaderDefinition.RESPONSE_REQUIRED); } }
private Source<Message, NotUsed> createSource(final String connectionCorrelationId, final ProtocolAdapter adapter) { return Source.<Jsonifiable.WithPredicate<JsonObject, JsonField>>actorPublisher( EventAndResponsePublisher.props(publisherBackpressureBufferSize)) .mapMaterializedValue(actorRef -> { streamingActor.tell(new Connect(actorRef, connectionCorrelationId, STREAMING_TYPE_WS), null); return NotUsed.getInstance(); }) .map(this::publishResponsePublishedEvent) .map(jsonifiableToString(adapter)) .via(Flow.fromFunction(result -> { LogUtil.logWithCorrelationId(LOGGER, connectionCorrelationId, logger -> logger.debug("Sending outgoing WebSocket message: {}", result)); return result; })) .map(TextMessage::create); }
/** * Creates Akka configuration object Props for this CommandSubscriber. * * @param delegateActor the ActorRef of the Actor to which to forward {@link Command}s. * @param backpressureQueueSize the max queue size of how many inflight commands a single producer can have. * @param eventStream used to subscribe to {@link ResponsePublished} events * @return the Akka configuration Props object. */ public static Props props(final ActorRef delegateActor, final int backpressureQueueSize, final EventStream eventStream) { return Props.create(CommandSubscriber.class, new Creator<CommandSubscriber>() { private static final long serialVersionUID = 1L; @Override public CommandSubscriber create() { return new CommandSubscriber(delegateActor, backpressureQueueSize, eventStream); } }); }
/** * Creates Akka configuration object Props for this StreamingSessionActor. * * @param pubSubMediator the PubSub mediator actor * @param eventAndResponsePublisher the {@link EventAndResponsePublisher} actor. * @return the Akka configuration Props object. */ static Props props(final String connectionCorrelationId, final String type, final ActorRef pubSubMediator, final ActorRef eventAndResponsePublisher) { return Props.create(StreamingSessionActor.class, new Creator<StreamingSessionActor>() { private static final long serialVersionUID = 1L; @Override public StreamingSessionActor create() throws Exception { return new StreamingSessionActor(connectionCorrelationId, type, pubSubMediator, eventAndResponsePublisher); } }); }
/** * Creates Akka configuration object Props for this StreamingActor. * * @param pubSubMediator the PubSub mediator actor * @param commandRouter the command router used to send signals into the cluster * @return the Akka configuration Props object. */ public static Props props(final ActorRef pubSubMediator, final ActorRef commandRouter) { return Props.create(StreamingActor.class, new Creator<StreamingActor>() { private static final long serialVersionUID = 1L; @Override public StreamingActor create() { return new StreamingActor(pubSubMediator, commandRouter); } }); }
/** * Creates Akka configuration object Props for this EventAndResponsePublisher. * * @param backpressureBufferSize the max buffer size of how many outstanding CommandResponses and Events a single * consumer may have - additionally incoming CommandResponses and Events are dropped if this size is reached. * @return the Akka configuration Props object. */ public static Props props(final int backpressureBufferSize) { return Props.create(EventAndResponsePublisher.class, new Creator<EventAndResponsePublisher>() { private static final long serialVersionUID = 1L; @Override public EventAndResponsePublisher create() { return new EventAndResponsePublisher(backpressureBufferSize); } }); }