org.eclipse.ditto.internal.utils.pubsubthings.DittoProtocolSubImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ditto-internal-utils-pubsub-things Show documentation
Show all versions of ditto-internal-utils-pubsub-things Show documentation
Eclipse Ditto is a framework for creating and managing digital twins in the IoT.
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.internal.utils.pubsubthings;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.apache.pekko.actor.AbstractExtensionId;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.ExtendedActorSystem;
import org.eclipse.ditto.base.model.acks.AcknowledgementLabel;
import org.eclipse.ditto.base.model.acks.AcknowledgementLabelInvalidException;
import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException;
import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.internal.utils.pubsub.DistributedAcks;
import org.eclipse.ditto.internal.utils.pubsub.DistributedSub;
import org.eclipse.ditto.internal.utils.pubsub.StreamingType;
import org.eclipse.ditto.internal.utils.pubsub.api.SubAck;
import org.eclipse.ditto.internal.utils.pubsubpolicies.PolicyAnnouncementPubSubFactory;
/**
* Default implementation of {@link DittoProtocolSub}.
*/
final class DittoProtocolSubImpl implements DittoProtocolSub {
private final DistributedSub liveSignalSub;
private final DistributedSub twinEventSub;
private final DistributedSub policyAnnouncementSub;
private final DistributedAcks distributedAcks;
private DittoProtocolSubImpl(final DistributedSub liveSignalSub,
final DistributedSub twinEventSub,
final DistributedSub policyAnnouncementSub,
final DistributedAcks distributedAcks) {
this.liveSignalSub = liveSignalSub;
this.twinEventSub = twinEventSub;
this.policyAnnouncementSub = policyAnnouncementSub;
this.distributedAcks = distributedAcks;
}
static DittoProtocolSubImpl of(final ActorSystem system, final DistributedAcks distributedAcks) {
final DistributedSub liveSignalSub =
LiveSignalPubSubFactory.of(system, distributedAcks).startDistributedSub();
final DistributedSub twinEventSub =
ThingEventPubSubFactory.readSubjectsOnly(system, distributedAcks).startDistributedSub();
final DistributedSub policyAnnouncementSub =
PolicyAnnouncementPubSubFactory.of(system, system).startDistributedSub();
return new DittoProtocolSubImpl(liveSignalSub, twinEventSub, policyAnnouncementSub, distributedAcks);
}
@Override
public CompletionStage subscribe(final Collection types,
final Collection topics,
final ActorRef subscriber,
@Nullable final String group,
final boolean resubscribe) {
final CompletionStage nop = CompletableFuture.completedFuture(null);
return partitionByStreamingTypes(types,
liveTypes -> !liveTypes.isEmpty()
? liveSignalSub.subscribeWithFilterAndGroup(topics, subscriber, toFilter(liveTypes), group,
resubscribe)
: nop,
hasTwinEvents -> hasTwinEvents
? twinEventSub.subscribeWithFilterAndGroup(topics, subscriber, null, group, resubscribe)
: nop,
hasPolicyAnnouncements -> hasPolicyAnnouncements
? policyAnnouncementSub.subscribeWithFilterAndGroup(topics, subscriber, null, group,
resubscribe)
: nop
);
}
@Override
public void removeSubscriber(final ActorRef subscriber) {
liveSignalSub.removeSubscriber(subscriber);
twinEventSub.removeSubscriber(subscriber);
policyAnnouncementSub.removeSubscriber(subscriber);
distributedAcks.removeSubscriber(subscriber);
}
@Override
public CompletionStage updateLiveSubscriptions(final Collection types,
final Collection topics,
final ActorRef subscriber) {
return partitionByStreamingTypes(types,
liveTypes -> !liveTypes.isEmpty()
? liveSignalSub.subscribeWithFilterAndGroup(topics, subscriber, toFilter(liveTypes), null,
false)
: liveSignalSub.unsubscribeWithAck(topics, subscriber),
hasTwinEvents -> CompletableFuture.completedFuture(null),
hasPolicyAnnouncements -> CompletableFuture.completedFuture(null)
).thenApply(consistent -> null);
}
@Override
public CompletionStage removeTwinSubscriber(final ActorRef subscriber, final Collection topics) {
return twinEventSub.unsubscribeWithAck(topics, subscriber).thenApply(ack -> null);
}
@Override
public CompletionStage removePolicyAnnouncementSubscriber(final ActorRef subscriber,
final Collection topics) {
return policyAnnouncementSub.unsubscribeWithAck(topics, subscriber).thenApply(ack -> null);
}
@Override
public CompletionStage declareAcknowledgementLabels(
final Collection acknowledgementLabels,
final ActorRef subscriber,
@Nullable final String group) {
if (acknowledgementLabels.isEmpty()) {
return CompletableFuture.completedFuture(null);
}
try {
ensureAcknowledgementLabelsAreFullyResolved(acknowledgementLabels);
} catch (final DittoRuntimeException dre) {
return CompletableFuture.failedStage(dre);
}
return distributedAcks.declareAcknowledgementLabels(acknowledgementLabels, subscriber, group)
.thenApply(ack -> null);
}
private static void ensureAcknowledgementLabelsAreFullyResolved(final Collection ackLabels) {
ackLabels.stream()
.filter(Predicate.not(AcknowledgementLabel::isFullyResolved))
.findFirst()
.ifPresent(ackLabel -> {
throw AcknowledgementLabelInvalidException.of(ackLabel,
"AcknowledgementLabel was not fully resolved while trying to declare it",
null,
DittoHeaders.empty());
});
}
@Override
public void removeAcknowledgementLabelDeclaration(final ActorRef subscriber) {
distributedAcks.removeAcknowledgementLabelDeclaration(subscriber);
}
private CompletionStage partitionByStreamingTypes(final Collection types,
final Function, CompletionStage> onLiveSignals,
final Function> onTwinEvents,
final Function> onPolicyAnnouncement) {
final Set liveTypes;
final boolean hasTwinEvents;
final boolean hasPolicyAnnouncements;
if (types.isEmpty()) {
liveTypes = Collections.emptySet();
hasTwinEvents = false;
hasPolicyAnnouncements = false;
} else {
liveTypes = EnumSet.copyOf(types);
hasTwinEvents = liveTypes.remove(StreamingType.EVENTS);
hasPolicyAnnouncements = liveTypes.remove(StreamingType.POLICY_ANNOUNCEMENTS);
}
final var liveStage = onLiveSignals.apply(liveTypes).thenApply(this::isConsistent);
final var twinStage = onTwinEvents.apply(hasTwinEvents).thenApply(this::isConsistent);
final var policyAnnouncementStage =
onPolicyAnnouncement.apply(hasPolicyAnnouncements).thenApply(this::isConsistent);
return liveStage.thenCompose(liveConsistent ->
twinStage.thenCompose(twinConsistent ->
policyAnnouncementStage.thenApply(policyConsistent ->
liveConsistent && twinConsistent && policyConsistent)));
}
private boolean isConsistent(@Nullable final SubAck ack) {
return ack == null || ack.isConsistent();
}
private static Predicate> toFilter(final Collection streamingTypes) {
final Set streamingTypeTopics =
streamingTypes.stream().map(StreamingType::getDistributedPubSubTopic).collect(Collectors.toSet());
return topics -> topics.stream().anyMatch(streamingTypeTopics::contains);
}
static final class ExtensionId extends AbstractExtensionId {
static final ExtensionId INSTANCE = new ExtensionId();
private ExtensionId() {}
@Override
public DittoProtocolSub createExtension(final ExtendedActorSystem system) {
final DistributedAcks distributedAcks = DistributedAcks.create(system);
return of(system, distributedAcks);
}
}
}