diff --git a/memq-actor/pom.xml b/memq-actor/pom.xml index 5c6f49a..1e590a8 100644 --- a/memq-actor/pom.xml +++ b/memq-actor/pom.xml @@ -47,6 +47,12 @@ jackson-databind 2.16.1 + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.13.5 + test + io.dropwizard.metrics metrics-core diff --git a/memq-actor/src/main/java/io/appform/memq/ActorSystem.java b/memq-actor/src/main/java/io/appform/memq/ActorSystem.java index 05ad293..2eb8f31 100644 --- a/memq-actor/src/main/java/io/appform/memq/ActorSystem.java +++ b/memq-actor/src/main/java/io/appform/memq/ActorSystem.java @@ -1,12 +1,13 @@ package io.appform.memq; import com.codahale.metrics.MetricRegistry; -import io.appform.memq.actor.Actor; +import io.appform.memq.actor.IActor; import io.appform.memq.actor.Message; import io.appform.memq.exceptionhandler.config.DropConfig; import io.appform.memq.exceptionhandler.config.ExceptionHandlerConfigVisitor; import io.appform.memq.exceptionhandler.config.SidelineConfig; import io.appform.memq.actor.MessageMeta; +import io.appform.memq.hierarchical.IHierarchicalActor; import io.appform.memq.observer.ActorObserver; import io.appform.memq.retry.RetryStrategy; import io.appform.memq.stats.ActorMetricObserver; @@ -20,7 +21,8 @@ public interface ActorSystem extends AutoCloseable { - void register(Actor actor); + void register(IActor actor); + void register(IHierarchicalActor actor); ExecutorService createOrGetExecutorService(HighLevelActorConfig config); diff --git a/memq-actor/src/main/java/io/appform/memq/HighLevelActor.java b/memq-actor/src/main/java/io/appform/memq/HighLevelActor.java index c80ef5e..46b60bf 100644 --- a/memq-actor/src/main/java/io/appform/memq/HighLevelActor.java +++ b/memq-actor/src/main/java/io/appform/memq/HighLevelActor.java @@ -2,6 +2,7 @@ import io.appform.memq.actor.Actor; +import io.appform.memq.actor.IActor; import io.appform.memq.actor.Message; import io.appform.memq.actor.MessageMeta; import io.appform.memq.observer.ActorObserver; @@ -16,7 +17,7 @@ public abstract class HighLevelActor, M ex @Getter private final MessageType type; - private final Actor actor; + protected final IActor actor; @SuppressWarnings("unused") protected HighLevelActor( diff --git a/memq-actor/src/main/java/io/appform/memq/HighLevelActorConfig.java b/memq-actor/src/main/java/io/appform/memq/HighLevelActorConfig.java index 496f227..b3dfc4c 100644 --- a/memq-actor/src/main/java/io/appform/memq/HighLevelActorConfig.java +++ b/memq-actor/src/main/java/io/appform/memq/HighLevelActorConfig.java @@ -5,48 +5,48 @@ import io.appform.memq.retry.config.NoRetryConfig; import io.appform.memq.retry.config.RetryConfig; import lombok.*; -import lombok.extern.jackson.Jacksonized; import javax.validation.Valid; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; -@Value -@Builder -@Jacksonized +@Data +@EqualsAndHashCode +@ToString @AllArgsConstructor @NoArgsConstructor +@Builder public class HighLevelActorConfig { @Min(1) @Max(100) @Builder.Default - int partitions = 1; + private int partitions = 1; @Min(1) @Builder.Default - long maxSizePerPartition = Long.MAX_VALUE; + private long maxSizePerPartition = Long.MAX_VALUE; @Min(1) @Builder.Default - int maxConcurrencyPerPartition = Integer.MAX_VALUE; + private int maxConcurrencyPerPartition = Integer.MAX_VALUE; @Valid @NotNull @Builder.Default - RetryConfig retryConfig = new NoRetryConfig(); + private RetryConfig retryConfig = new NoRetryConfig(); @Valid @NotNull @Builder.Default - ExceptionHandlerConfig exceptionHandlerConfig = new DropConfig(); + private ExceptionHandlerConfig exceptionHandlerConfig = new DropConfig(); @NotNull @Builder.Default - String executorName = "default"; + private String executorName = "default"; @Builder.Default - boolean metricDisabled = false; + private boolean metricDisabled = false; } diff --git a/memq-actor/src/main/java/io/appform/memq/actor/Actor.java b/memq-actor/src/main/java/io/appform/memq/actor/Actor.java index 94faa7b..396ad7b 100644 --- a/memq-actor/src/main/java/io/appform/memq/actor/Actor.java +++ b/memq-actor/src/main/java/io/appform/memq/actor/Actor.java @@ -33,7 +33,7 @@ import java.util.stream.IntStream; @Slf4j -public class Actor implements AutoCloseable { +public class Actor implements IActor { private final String name; private final ExecutorService executorService; @@ -84,12 +84,14 @@ public Actor( this.rootObserver = setupObserver(observers); } + @Override public final boolean isEmpty() { return mailboxes.values() .stream() .allMatch(Mailbox::isEmpty); } + @Override public final long size() { return mailboxes.values() .stream() @@ -97,6 +99,7 @@ public final long size() { .sum(); } + @Override public final long inFlight() { return mailboxes.values() .stream() @@ -104,16 +107,19 @@ public final long inFlight() { .sum(); } + @Override public final boolean isRunning() { return mailboxes.values() .stream() .allMatch(Mailbox::isRunning); } + @Override public final void purge() { mailboxes.values().forEach(Mailbox::purge); } + @Override public final boolean publish(final M message) { return rootObserver.execute(ActorObserverContext.builder() .operation(ActorOperation.PUBLISH) @@ -124,6 +130,7 @@ public final boolean publish(final M message) { .publish(message)); } + @Override public final void start() { mailboxes.values().forEach(Mailbox::start); } diff --git a/memq-actor/src/main/java/io/appform/memq/actor/IActor.java b/memq-actor/src/main/java/io/appform/memq/actor/IActor.java new file mode 100644 index 0000000..ea3de31 --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/actor/IActor.java @@ -0,0 +1,15 @@ +package io.appform.memq.actor; + +public interface IActor extends AutoCloseable { + + void start(); + void close(); + + boolean isEmpty(); + long size(); + long inFlight(); + boolean isRunning(); + void purge(); + + boolean publish(final M message); +} \ No newline at end of file diff --git a/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchialHighLevelActorConfig.java b/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchialHighLevelActorConfig.java new file mode 100644 index 0000000..551563c --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchialHighLevelActorConfig.java @@ -0,0 +1,29 @@ +package io.appform.memq.hierarchical; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import io.appform.memq.HighLevelActorConfig; +import io.appform.memq.hierarchical.tree.HierarchicalDataStoreTreeNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@Data +@EqualsAndHashCode +@ToString +@NoArgsConstructor +public class HierarchialHighLevelActorConfig extends HighLevelActorConfig { + + /** + *

This param will reused all Parent Level ActorConfig while creating all child actors, + * if marked as false, every children will need tp provide Actor config specific to child

+ * + */ + private boolean useParentConfigInWorker = true; + + @JsonUnwrapped + private HierarchicalDataStoreTreeNode childrenData; + +} diff --git a/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalActor.java b/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalActor.java new file mode 100644 index 0000000..c67cd9b --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalActor.java @@ -0,0 +1,163 @@ +package io.appform.memq.hierarchical; + +import io.appform.memq.ActorSystem; +import io.appform.memq.actor.Message; +import io.appform.memq.actor.MessageMeta; +import io.appform.memq.hierarchical.tree.HierarchicalDataStoreSupplierTree; +import io.appform.memq.hierarchical.tree.HierarchicalTreeConfig; +import io.appform.memq.hierarchical.tree.key.HierarchicalRoutingKey; +import io.appform.memq.hierarchical.tree.key.RoutingKey; +import io.appform.memq.observer.ActorObserver; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import lombok.val; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.ToIntFunction; + +@Slf4j +public class HierarchicalActor, M extends Message> implements IHierarchicalActor { + + public static final RoutingKey EMPTY_ROUTING_KEY = RoutingKey.builder().build(); + + private final HierarchicalTreeConfig hierarchicalTreeConfig; + private final MessageType messageType; + private final ActorSystem actorSystem; + private final ToIntFunction partitioner; + private final List observers; + private final BiFunction messageHandler; + private final BiConsumer sidelineHandler; + + @Getter + private HierarchicalDataStoreSupplierTree< + HierarchicalOperationWorkerConfig, + HierarchialHighLevelActorConfig, + MessageType, + HierarchicalOperationWorker> worker; + + + public HierarchicalActor(MessageType messageType, + HierarchialHighLevelActorConfig hierarchicalActorConfig, + ActorSystem actorSystem, + BiFunction messageHandler, + BiConsumer sidelineHandler, + ToIntFunction partitioner, + List observers) { + this.messageType = messageType; + this.hierarchicalTreeConfig = new HierarchicalTreeConfig<>(hierarchicalActorConfig, hierarchicalActorConfig.getChildrenData()); + this.actorSystem = actorSystem; + this.messageHandler = messageHandler; + this.sidelineHandler = sidelineHandler; + this.partitioner = partitioner; + this.observers = observers; + } + + @Override + public void start() { + log.info("Starting all workers"); + this.initializeRouter(); + } + + @Override + public void close() { + log.info("Closing all workers"); + worker.traverse(hierarchicalOperationWorker -> { + log.info("Closing worker: {} {}", hierarchicalOperationWorker.getType(), hierarchicalOperationWorker.getRoutingKey().getRoutingKey()); + hierarchicalOperationWorker.close(); + }); + } + + @Override + public void purge() { + log.info("Purging all workers"); + worker.traverse(hierarchicalOperationWorker -> { + log.info("Purging worker: {} {}", hierarchicalOperationWorker.getType(), hierarchicalOperationWorker.getRoutingKey().getRoutingKey()); + hierarchicalOperationWorker.purge(); + }); + } + + @Override + public boolean publish(final M message) { + return publishActor(EMPTY_ROUTING_KEY).publish(message); + } + + @Override + public boolean publish(final HierarchicalRoutingKey routingKey, + final M message) { + return publishActor(routingKey).publish(message); + } + + @Override + public long size() { + log.info("Size of all workers"); + val atomicLong = new AtomicLong(); + worker.traverse(hierarchicalOperationWorker -> { + log.info("Size of worker: {} {}", hierarchicalOperationWorker.getType(), hierarchicalOperationWorker.getRoutingKey().getRoutingKey()); + atomicLong.getAndAdd(hierarchicalOperationWorker.size()); + }); + return atomicLong.get(); + } + + @Override + public long inFlight() { + log.info("inFlight Size of all workers"); + val atomicLong = new AtomicLong(); + worker.traverse(hierarchicalOperationWorker -> { + log.info("inFlight Size of worker: {} {}", hierarchicalOperationWorker.getType(), hierarchicalOperationWorker.getRoutingKey().getRoutingKey()); + atomicLong.getAndAdd(hierarchicalOperationWorker.inFlight()); + }); + return atomicLong.get(); + } + + @Override + public boolean isEmpty() { + log.info("isEmpty all workers"); + val atomicBoolean = new AtomicBoolean(); + worker.traverse(hierarchicalOperationWorker -> { + log.info("isEmpty worker: {} {}", hierarchicalOperationWorker.getType(), hierarchicalOperationWorker.getRoutingKey().getRoutingKey()); + atomicBoolean.set(atomicBoolean.get() && hierarchicalOperationWorker.isEmpty()); + }); + return atomicBoolean.get(); + } + + @Override + public boolean isRunning() { + log.info("isRunning all workers"); + val atomicBoolean = new AtomicBoolean(); + worker.traverse(hierarchicalOperationWorker -> { + log.info("isRunning worker: {} {} {}", hierarchicalOperationWorker.getType(), hierarchicalOperationWorker.getRoutingKey().getRoutingKey(), hierarchicalOperationWorker.isRunning()); + atomicBoolean.set(atomicBoolean.get() && hierarchicalOperationWorker.isRunning()); + }); + return atomicBoolean.get(); + } + + private HierarchicalOperationWorker publishActor(final HierarchicalRoutingKey routingKey) { + return (HierarchicalOperationWorker) this.worker.get(messageType, routingKey); + } + + private void initializeRouter() { + this.worker = new HierarchicalDataStoreSupplierTree<>( + messageType, + hierarchicalTreeConfig, + HierarchicalRouterUtils.actorConfigToWorkerConfigFunc, + (routingKey, messageTypeKey, workerConfig) -> { + log.info("{} -> {}", routingKey.getRoutingKey(), messageTypeKey); + return new HierarchicalOperationWorker<>( + messageType, + workerConfig, + hierarchicalTreeConfig.getDefaultData(), + routingKey, + actorSystem, + messageHandler, + sidelineHandler, + partitioner, + observers); + } + ); + } + +} diff --git a/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalHighLevelActor.java b/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalHighLevelActor.java new file mode 100644 index 0000000..1c55b28 --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalHighLevelActor.java @@ -0,0 +1,94 @@ +package io.appform.memq.hierarchical; + + +import io.appform.memq.ActorSystem; +import io.appform.memq.actor.Message; +import io.appform.memq.actor.MessageMeta; +import io.appform.memq.hierarchical.tree.key.HierarchicalRoutingKey; +import io.appform.memq.observer.ActorObserver; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.function.ToIntFunction; + +@Slf4j +public abstract class HierarchicalHighLevelActor, M extends Message> { + + + @Getter + private final MessageType type; + @Getter + private final HierarchicalActor actor; + + @SuppressWarnings("unused") + protected HierarchicalHighLevelActor( + MessageType type, + HierarchialHighLevelActorConfig highLevelActorConfig, + ActorSystem actorSystem) { + this(type, highLevelActorConfig, actorSystem, null, List.of()); + } + + protected HierarchicalHighLevelActor( + MessageType type, + HierarchialHighLevelActorConfig highLevelActorConfig, + ActorSystem actorSystem, + ToIntFunction partitioner) { + this(type, highLevelActorConfig, actorSystem, partitioner, List.of()); + } + + protected HierarchicalHighLevelActor( + MessageType type, + HierarchialHighLevelActorConfig highLevelActorConfig, + ActorSystem actorSystem, + List observers) { + this(type, highLevelActorConfig, actorSystem, null, observers); + } + + protected HierarchicalHighLevelActor( + MessageType type, + HierarchialHighLevelActorConfig highLevelActorConfig, + ActorSystem actorSystem, + ToIntFunction partitioner, + List observers) { + this.type = type; + this.actor = new HierarchicalActor<>(type, highLevelActorConfig, actorSystem, this::handle, this::sideline, partitioner, observers); + actorSystem.register(actor); + } + + protected abstract boolean handle(final M message, MessageMeta messageMeta); + + protected void sideline(final M message, MessageMeta messageMeta) { + log.warn("skipping sideline for actor:{} message:{}", type.name(), message); + } + + public final boolean publish(final M message) { + return actor.publish(message); + } + + public final boolean publish(final HierarchicalRoutingKey routingKey, final M message) { + return actor.publish(routingKey, message); + } + + + public final void purge() { + actor.purge(); + } + + public final long size() { + return actor.size(); + } + + public final long inFlight() { + return actor.inFlight(); + } + + public final boolean isEmpty() { + return actor.isEmpty(); + } + + public final boolean isRunning() { + return actor.isRunning(); + } + +} diff --git a/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalOperationWorker.java b/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalOperationWorker.java new file mode 100644 index 0000000..ec9c2a3 --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalOperationWorker.java @@ -0,0 +1,56 @@ +package io.appform.memq.hierarchical; + +import io.appform.memq.ActorSystem; +import io.appform.memq.HighLevelActor; +import io.appform.memq.actor.Message; +import io.appform.memq.actor.MessageMeta; +import io.appform.memq.hierarchical.tree.key.RoutingKey; +import io.appform.memq.observer.ActorObserver; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.ToIntFunction; + +@Getter +@EqualsAndHashCode +public class HierarchicalOperationWorker, M extends Message> + extends HighLevelActor { + + private final RoutingKey routingKey; + private final BiFunction messageHandler; + private final BiConsumer sidelineHandler; + + public HierarchicalOperationWorker(final MessageType messageType, + final HierarchicalOperationWorkerConfig workerConfig, + final HierarchialHighLevelActorConfig hierarchicalActorConfig, + final RoutingKey routingKey, + final ActorSystem actorSystem, + final BiFunction messageHandler, + final BiConsumer sidelineHandler, + final ToIntFunction partitioner, + final List observers) { + super(messageType, + HierarchicalRouterUtils.hierarchicalActorConfig(messageType, routingKey, workerConfig, hierarchicalActorConfig), + actorSystem, partitioner, observers); + this.routingKey = routingKey; + this.messageHandler = messageHandler; + this.sidelineHandler = sidelineHandler; + } + + @Override + protected boolean handle(M message, MessageMeta messageMeta) { + return messageHandler.apply(message, messageMeta); + } + + @Override + protected void sideline(M message, MessageMeta messageMeta) { + sidelineHandler.accept(message, messageMeta); + } + + public final void close() { + actor.close(); + } +} diff --git a/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalOperationWorkerConfig.java b/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalOperationWorkerConfig.java new file mode 100644 index 0000000..d9830e5 --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalOperationWorkerConfig.java @@ -0,0 +1,52 @@ +package io.appform.memq.hierarchical; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.appform.memq.exceptionhandler.config.DropConfig; +import io.appform.memq.exceptionhandler.config.ExceptionHandlerConfig; +import io.appform.memq.retry.config.NoRetryConfig; +import io.appform.memq.retry.config.RetryConfig; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import javax.validation.Valid; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@Data +@EqualsAndHashCode +@ToString +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class HierarchicalOperationWorkerConfig { + + @Min(1) + @Max(100) + @Builder.Default + private int partitions = 1; + + @Min(1) + @Builder.Default + private long maxSizePerPartition = Long.MAX_VALUE; + + @Min(1) + @Builder.Default + private int maxConcurrencyPerPartition = Integer.MAX_VALUE; + + @Valid + @NotNull + @Builder.Default + private RetryConfig retryConfig = new NoRetryConfig(); + + @Valid + @NotNull + @Builder.Default + private ExceptionHandlerConfig exceptionHandlerConfig = new DropConfig(); + +} diff --git a/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalRouterUtils.java b/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalRouterUtils.java new file mode 100644 index 0000000..cf6b381 --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/hierarchical/HierarchicalRouterUtils.java @@ -0,0 +1,68 @@ +package io.appform.memq.hierarchical; + +import io.appform.memq.HighLevelActorConfig; +import io.appform.memq.hierarchical.tree.key.RoutingKey; +import lombok.experimental.UtilityClass; +import lombok.val; +import org.apache.commons.lang3.StringUtils; + +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@UtilityClass +public class HierarchicalRouterUtils { + + private static final String EXECUTORS = "executors"; + private static final BiFunction, String, String> beautifierFunction = (stream, delimiter) -> stream + .filter(e -> !StringUtils.isEmpty(e)) + .collect(Collectors.joining(delimiter)); + + + static final Function actorConfigToWorkerConfigFunc = + actorConfig -> HierarchicalOperationWorkerConfig.builder() + .partitions(actorConfig.getPartitions()) + .maxSizePerPartition(actorConfig.getMaxSizePerPartition()) + .maxConcurrencyPerPartition(actorConfig.getMaxConcurrencyPerPartition()) + .retryConfig(actorConfig.getRetryConfig()) + .exceptionHandlerConfig(actorConfig.getExceptionHandlerConfig()) + .build(); + + + static > HighLevelActorConfig hierarchicalActorConfig( + MessageType messageType, + RoutingKey routingKeyData, + HierarchicalOperationWorkerConfig workerConfig, + HierarchialHighLevelActorConfig mainActorConfig) { + val useParentConfigInWorker = mainActorConfig.isUseParentConfigInWorker(); + return HighLevelActorConfig.builder() + // Custom fields + .executorName(executorName(mainActorConfig.getExecutorName(), messageType, routingKeyData)) + + .partitions(useParentConfigInWorker ? mainActorConfig.getPartitions() : workerConfig.getPartitions()) + .maxSizePerPartition(useParentConfigInWorker ? mainActorConfig.getMaxSizePerPartition() : workerConfig.getMaxSizePerPartition()) + .maxConcurrencyPerPartition(useParentConfigInWorker ? mainActorConfig.getMaxConcurrencyPerPartition() : workerConfig.getMaxConcurrencyPerPartition()) + .retryConfig(useParentConfigInWorker ? mainActorConfig.getRetryConfig() : workerConfig.getRetryConfig()) + .exceptionHandlerConfig(useParentConfigInWorker ? mainActorConfig.getExceptionHandlerConfig() : workerConfig.getExceptionHandlerConfig()) + .metricDisabled(mainActorConfig.isMetricDisabled()) + .build(); + } + + private static > String executorName(final String parentExchangeName, + final MessageType messageType, + final RoutingKey routingKeyData) { + val routingKey = routingKeyData.getRoutingKey(); + + if (!StringUtils.isEmpty(parentExchangeName)) { + // For backward compatibility + if(routingKey.isEmpty()) { + return parentExchangeName; + } + + return beautifierFunction.apply(Stream.of(parentExchangeName, String.join(".", routingKey)), "."); + } + + return beautifierFunction.apply(Stream.of(EXECUTORS, String.join(".", routingKey), messageType.name()), "."); + } +} \ No newline at end of file diff --git a/memq-actor/src/main/java/io/appform/memq/hierarchical/IHierarchicalActor.java b/memq-actor/src/main/java/io/appform/memq/hierarchical/IHierarchicalActor.java new file mode 100644 index 0000000..6d3f240 --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/hierarchical/IHierarchicalActor.java @@ -0,0 +1,10 @@ +package io.appform.memq.hierarchical; + +import io.appform.memq.actor.IActor; +import io.appform.memq.actor.Message; +import io.appform.memq.hierarchical.tree.key.HierarchicalRoutingKey; + +public interface IHierarchicalActor extends IActor { + + boolean publish(final HierarchicalRoutingKey routingKey, final M message); +} diff --git a/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/HierarchicalDataStoreSupplierTree.java b/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/HierarchicalDataStoreSupplierTree.java new file mode 100644 index 0000000..0298ac9 --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/HierarchicalDataStoreSupplierTree.java @@ -0,0 +1,63 @@ +package io.appform.memq.hierarchical.tree; + +import com.google.common.collect.Lists; +import io.appform.memq.hierarchical.tree.key.RoutingKey; +import lombok.val; + +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +@SuppressWarnings("java:S119") +public class HierarchicalDataStoreSupplierTree extends HierarchicalDataStoreTree { + + private static final Function, RoutingKey> routingKeyGenerator = (list) -> RoutingKey.builder() + .list(list) + .build(); + + public HierarchicalDataStoreSupplierTree(final NODE_KEY_TYPE key, + final HierarchicalTreeConfig treeConfig, + final Function rootNodeConverterSupplier, + final TriConsumerSupplier supplier) { + super(supplier.get( + routingKeyGenerator.apply(List.of()), + key, + rootNodeConverterSupplier.apply(treeConfig.getDefaultData()) + )); + buildTree(key, treeConfig.getChildrenData(), supplier); + } + + private void buildTree(final NODE_KEY_TYPE key, + final HierarchicalDataStoreTreeNode childrenList, + final TriConsumerSupplier supplier) { + val tokenList = Lists.newArrayList(); + buildTreeHelper(key, childrenList, tokenList, supplier); + } + + private void buildTreeHelper(final NODE_KEY_TYPE key, + final HierarchicalDataStoreTreeNode rootChildrenData, + final List tokenList, + final TriConsumerSupplier supplier) { + val childrenList = rootChildrenData.getChildren(); + if (childrenList.isEmpty()) { + add(routingKeyGenerator.apply(tokenList), key, null); + return; + } + + for (String childrenToken : childrenList.keySet()) { + val currentChildrenData = childrenList.get(childrenToken); + + tokenList.add(childrenToken); + + val routingKey = routingKeyGenerator.apply(tokenList.stream().map(String::valueOf).toList()); + val currentChildrenDefaultData = Objects.nonNull(currentChildrenData.getNodeData()) ? + currentChildrenData.getNodeData() : rootChildrenData.getNodeData(); + + add(routingKey, key, supplier.get(routingKey, key, currentChildrenDefaultData)); + buildTreeHelper(key, currentChildrenData, tokenList, supplier); + + tokenList.remove(childrenToken); + } + } + +} diff --git a/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/HierarchicalDataStoreTree.java b/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/HierarchicalDataStoreTree.java new file mode 100644 index 0000000..beaaeb4 --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/HierarchicalDataStoreTree.java @@ -0,0 +1,65 @@ +package io.appform.memq.hierarchical.tree; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.google.common.collect.Maps; +import io.appform.memq.hierarchical.tree.key.HierarchicalRoutingKey; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import lombok.val; + +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; + +@Slf4j +@ToString +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@SuppressWarnings("java:S119") +public class HierarchicalDataStoreTree { + + private final NODE_TYPE defaultData; + @JsonUnwrapped + private final Map> rootNodes = Maps.newConcurrentMap(); + + public HierarchicalDataStoreTree() { + this.defaultData = null; + } + + public HierarchicalDataStoreTree(NODE_TYPE defaultData) { + this.defaultData = defaultData; + } + + public void add(final HierarchicalRoutingKey routingKey, final NODE_KEY_TYPE key, final NODE_TYPE data) { + rootNodes.computeIfAbsent(key, t -> new HierarchicalDataStoreTreeNode<>(0, String.valueOf(key), defaultData)); + if (Objects.isNull(data)) { + return; + } + rootNodes.get(key) + .add(routingKey, data); + } + + public void traverse(final Consumer consumer) { + rootNodes.forEach((NODEKEYTYPE, vHierarchicalStoreNode) -> { + if (vHierarchicalStoreNode != null) { + vHierarchicalStoreNode.traverse(consumer); + } + }); + } + + public NODE_TYPE get(final NODE_KEY_TYPE key, final HierarchicalRoutingKey routingKey) { + if (!rootNodes.containsKey(key)) { + log.warn("Key {} not found in {} keys {}. Using default {}", key, rootNodes.keySet(), defaultData); + return defaultData; + } + + val routingKeyToken = routingKey.getRoutingKey(); + if (routingKeyToken== null || routingKeyToken.isEmpty()) { + log.warn("keys are empty {}. Using default {}", key, rootNodes.keySet(), defaultData); + return defaultData; + } + + return rootNodes.get(key) + .find(routingKey); + } +} \ No newline at end of file diff --git a/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/HierarchicalDataStoreTreeNode.java b/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/HierarchicalDataStoreTreeNode.java new file mode 100644 index 0000000..5d2c6bb --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/HierarchicalDataStoreTreeNode.java @@ -0,0 +1,107 @@ +package io.appform.memq.hierarchical.tree; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.google.common.collect.Maps; +import io.appform.memq.hierarchical.tree.key.HierarchicalRoutingKey; +import lombok.Builder; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +@Slf4j +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@Data +public class HierarchicalDataStoreTreeNode { + + @JsonIgnore + private final int depth; + @JsonIgnore + private final K token; + + private V nodeData; + private Map> children = Maps.newConcurrentMap(); + + + public HierarchicalDataStoreTreeNode() { + this.depth = 0; + this.token = null; + this.nodeData = null; + } + + public HierarchicalDataStoreTreeNode(K token) { + this.depth = 0; + this.token = token; + this.nodeData = null; + } + + + @Builder + public HierarchicalDataStoreTreeNode(final int depth, final K token, final V nodeData) { + this.depth = depth; + this.token = token; + this.nodeData = nodeData; + } + + void traverse(final Consumer consumer) { + if (nodeData != null) { + consumer.accept(nodeData); + } + children.forEach((k, kvHierarchicalStoreNode) -> { + if (kvHierarchicalStoreNode != null) { + kvHierarchicalStoreNode.traverse(consumer); + } + }); + } + + void addChild(final List tokens, final V defaultData) { + final K key = tokens.get(depth); + + log.debug("depth: {} name: {} key: {} tokens: {} defaultData: {}", depth, token, key, tokens, defaultData); + + if (tokens.size() > depth + 1) { + children.computeIfAbsent(key, k -> new HierarchicalDataStoreTreeNode<>(depth + 1, tokens.get(depth), null)); + children.get(key).addChild(tokens, defaultData); + } else { + if (!children.containsKey(key)) { + children.put(key, new HierarchicalDataStoreTreeNode(depth + 1, tokens.get(depth), defaultData)); + } else { + if (children.get(key) + .getNodeData() == null) { + children.get(key) + .setNodeData(defaultData); + } else { + log.error("Request to overwrite at {} existing defaultData: {} new defaultData {}", tokens, children.get(key) + .getNodeData(), defaultData); + } + } + } + } + + V findNode(final List tokens) { + if (tokens.size() == depth) { + return nodeData; + } + + if (!children.containsKey(tokens.get(depth))) { + return nodeData; + } + + V load = children.get(tokens.get(depth)) + .findNode(tokens); + return load == null + ? nodeData + : load; + } + + public void add(final HierarchicalRoutingKey routingKey, final V payload) { + addChild(routingKey.getRoutingKey(), payload); + } + + public V find(final HierarchicalRoutingKey routingKey) { + return findNode(routingKey.getRoutingKey()); + } +} diff --git a/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/HierarchicalTreeConfig.java b/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/HierarchicalTreeConfig.java new file mode 100644 index 0000000..7519d61 --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/HierarchicalTreeConfig.java @@ -0,0 +1,18 @@ +package io.appform.memq.hierarchical.tree; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@Data +@NoArgsConstructor +@AllArgsConstructor +@SuppressWarnings("java:S119") +public class HierarchicalTreeConfig { + private ROOT_TYPE defaultData; + @JsonUnwrapped + private HierarchicalDataStoreTreeNode childrenData; +} diff --git a/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/TriConsumerSupplier.java b/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/TriConsumerSupplier.java new file mode 100644 index 0000000..f2ec183 --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/TriConsumerSupplier.java @@ -0,0 +1,6 @@ +package io.appform.memq.hierarchical.tree; + +@FunctionalInterface +public interface TriConsumerSupplier { + public S get(R routingKey, K key, V value); +} \ No newline at end of file diff --git a/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/key/HierarchicalRoutingKey.java b/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/key/HierarchicalRoutingKey.java new file mode 100644 index 0000000..ee6248d --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/key/HierarchicalRoutingKey.java @@ -0,0 +1,7 @@ +package io.appform.memq.hierarchical.tree.key; + +import java.util.List; + +public interface HierarchicalRoutingKey { + List getRoutingKey(); +} diff --git a/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/key/RoutingKey.java b/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/key/RoutingKey.java new file mode 100644 index 0000000..221bd08 --- /dev/null +++ b/memq-actor/src/main/java/io/appform/memq/hierarchical/tree/key/RoutingKey.java @@ -0,0 +1,20 @@ +package io.appform.memq.hierarchical.tree.key; + + +import lombok.Builder; + +import java.util.List; + +public class RoutingKey implements HierarchicalRoutingKey { + private final List list; + + @Builder + public RoutingKey(final List list) { + this.list = list; + } + + @Override + public List getRoutingKey() { + return list; + } +} \ No newline at end of file diff --git a/memq-actor/src/main/java/io/appform/memq/observer/ActorObserver.java b/memq-actor/src/main/java/io/appform/memq/observer/ActorObserver.java index 9744e63..b63fd50 100644 --- a/memq-actor/src/main/java/io/appform/memq/observer/ActorObserver.java +++ b/memq-actor/src/main/java/io/appform/memq/observer/ActorObserver.java @@ -1,6 +1,6 @@ package io.appform.memq.observer; -import io.appform.memq.actor.Actor; +import io.appform.memq.actor.IActor; import io.appform.memq.actor.Message; import lombok.Getter; @@ -15,7 +15,7 @@ protected ActorObserver(ActorObserver next) { this.next = next; } - public abstract void initialize(Actor actor); + public abstract void initialize(IActor actor); public abstract boolean execute( final ActorObserverContext context, diff --git a/memq-actor/src/main/java/io/appform/memq/observer/TerminalActorObserver.java b/memq-actor/src/main/java/io/appform/memq/observer/TerminalActorObserver.java index e991a53..6be0b6f 100644 --- a/memq-actor/src/main/java/io/appform/memq/observer/TerminalActorObserver.java +++ b/memq-actor/src/main/java/io/appform/memq/observer/TerminalActorObserver.java @@ -1,7 +1,7 @@ package io.appform.memq.observer; -import io.appform.memq.actor.Actor; +import io.appform.memq.actor.IActor; import io.appform.memq.actor.Message; import java.util.function.BooleanSupplier; @@ -13,7 +13,7 @@ public TerminalActorObserver() { } @Override - public void initialize(Actor actor) { + public void initialize(IActor actor) { } @Override diff --git a/memq-actor/src/main/java/io/appform/memq/stats/ActorMetricObserver.java b/memq-actor/src/main/java/io/appform/memq/stats/ActorMetricObserver.java index 21ad7fd..3b8c498 100644 --- a/memq-actor/src/main/java/io/appform/memq/stats/ActorMetricObserver.java +++ b/memq-actor/src/main/java/io/appform/memq/stats/ActorMetricObserver.java @@ -2,7 +2,7 @@ import com.codahale.metrics.*; -import io.appform.memq.actor.Actor; +import io.appform.memq.actor.IActor; import io.appform.memq.actor.Message; import io.appform.memq.observer.ActorObserver; import io.appform.memq.observer.ActorObserverContext; @@ -40,7 +40,7 @@ private static String normalizeString(final String name) { } @Override - public void initialize(Actor actor) { + public void initialize(IActor actor) { this.metricRegistry.gauge(MetricRegistry.name(getMetricPrefix(actorName), "size"), (MetricRegistry.MetricSupplier>) () -> new CachedGauge<>(5, TimeUnit.SECONDS) { diff --git a/memq-actor/src/test/java/io/appform/memq/helper/TestUtil.java b/memq-actor/src/test/java/io/appform/memq/helper/TestUtil.java index 154029f..f272f4d 100644 --- a/memq-actor/src/test/java/io/appform/memq/helper/TestUtil.java +++ b/memq-actor/src/test/java/io/appform/memq/helper/TestUtil.java @@ -4,12 +4,13 @@ import com.google.common.collect.Lists; import io.appform.memq.ActorSystem; import io.appform.memq.HighLevelActor; -import io.appform.memq.actor.Actor; +import io.appform.memq.actor.IActor; import io.appform.memq.HighLevelActorConfig; import io.appform.memq.exceptionhandler.config.ExceptionHandlerConfig; import io.appform.memq.exceptionhandler.config.SidelineConfig; import io.appform.memq.helper.message.TestIntMessage; import io.appform.memq.actor.MessageMeta; +import io.appform.memq.hierarchical.IHierarchicalActor; import io.appform.memq.observer.ActorObserver; import io.appform.memq.retry.RetryStrategy; import io.appform.memq.retry.RetryStrategyFactory; @@ -38,10 +39,16 @@ public static ActorSystem actorSystem(ExecutorService tp) { val metricRegistry = new MetricRegistry(); return new ActorSystem() { private final RetryStrategyFactory retryStrategyFactory = new RetryStrategyFactory(); - private final List> registeredActors = Lists.newArrayList(); + private final List> registeredActors = Lists.newArrayList(); @Override - public void register(Actor actor) { + public void register(IActor actor) { + registeredActors.add(actor); + actor.start(); + } + + @Override + public void register(IHierarchicalActor actor) { registeredActors.add(actor); actor.start(); } @@ -68,12 +75,12 @@ public List registeredObservers() { @Override public boolean isRunning() { - return !registeredActors.isEmpty() && registeredActors.stream().allMatch(Actor::isRunning); + return !registeredActors.isEmpty() && registeredActors.stream().allMatch(IActor::isRunning); } @Override public void close() { - registeredActors.forEach(Actor::close); + registeredActors.forEach(IActor::close); } }; } diff --git a/memq-actor/src/test/java/io/appform/memq/hierarchical/FlowHierarchicalMemqActorConfig.java b/memq-actor/src/test/java/io/appform/memq/hierarchical/FlowHierarchicalMemqActorConfig.java new file mode 100644 index 0000000..8e48cf8 --- /dev/null +++ b/memq-actor/src/test/java/io/appform/memq/hierarchical/FlowHierarchicalMemqActorConfig.java @@ -0,0 +1,16 @@ +package io.appform.memq.hierarchical; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FlowHierarchicalMemqActorConfig> { + private Map workers; +} diff --git a/memq-actor/src/test/java/io/appform/memq/hierarchical/HierarchicalHighLevelActorTest.java b/memq-actor/src/test/java/io/appform/memq/hierarchical/HierarchicalHighLevelActorTest.java new file mode 100644 index 0000000..d712e34 --- /dev/null +++ b/memq-actor/src/test/java/io/appform/memq/hierarchical/HierarchicalHighLevelActorTest.java @@ -0,0 +1,110 @@ +package io.appform.memq.hierarchical; + +import com.fasterxml.jackson.core.type.TypeReference; +import io.appform.memq.ActorSystem; +import io.appform.memq.MemQTestExtension; +import io.appform.memq.hierarchical.actor.FlowTypeHierarchicalActorBuilder; +import io.appform.memq.hierarchical.data.ActionMessage; +import io.appform.memq.hierarchical.data.C2CDataActionMessage; +import io.appform.memq.hierarchical.data.C2MDataActionMessage; +import io.appform.memq.hierarchical.data.FlowType; +import io.appform.memq.hierarchical.tree.key.RoutingKey; +import io.appform.memq.util.YamlReader; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@ExtendWith(MemQTestExtension.class) +public class HierarchicalHighLevelActorTest { + + private final static FlowHierarchicalMemqActorConfig RMQ_CONFIG = YamlReader.loadConfig("rmqHierarchicalMemq.yaml", new TypeReference<>() { + }); + private Map> actorActors; + + enum HierarchicalHighLevelActorType { + C2M_AUTH_FLOW, + C2C_AUTH_FLOW; + } + + static final int THREAD_POOL_SIZE = 10; + + @SneakyThrows + public void createActors(ActorSystem actorSystem) { + actorActors = RMQ_CONFIG.getWorkers() + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getKey().accept(new FlowTypeHierarchicalActorBuilder(e.getValue(), actorSystem)))); + } + + @Test + @SneakyThrows + void testSuccessSinglePartition(ActorSystem actorSystem) { + createActors(actorSystem); + val messages = Map.of( + RoutingKey.builder().list(List.of("")).build(), + C2MDataActionMessage.builder() + .data("C2M") + .build(), + + RoutingKey.builder().list(List.of("REGULAR", "JAR")).build(), + C2MDataActionMessage.builder() + .data("C2M-REGULAR-JAR-SOME") + .build(), + + RoutingKey.builder().list(List.of("REGULAR")).build(), + C2CDataActionMessage.builder() + .data("C2C-REGULAR") + .build(), + + RoutingKey.builder().list(List.of("C2C_AUTH_FLOW")).build(), + C2CDataActionMessage.builder() + .data("C2C") + .build(), + + RoutingKey.builder().list(List.of("FULL_AUTH", "JAR")).build(), + C2MDataActionMessage.builder() + .data("C2M-FULL_AUTH-JAR-SOME") + .build() + ); + + messages.forEach((routingKey, message) -> { + val flowType = message.getType(); + + if (actorActors.containsKey(flowType)) { + val router = actorActors.get(flowType); + Assertions.assertNotNull(router); + + val flowLevelPrefix = Arrays.asList(RMQ_CONFIG.getWorkers().get(flowType).getExecutorName().split("\\.")); + System.out.println("flowLevelPrefix" + flowLevelPrefix); + + val worker = router.getActor().getWorker().get(flowType, routingKey); + Assertions.assertNotNull(worker); + + val routingKeyWorker = worker.getRoutingKey(); + if(!worker.getRoutingKey().getRoutingKey().isEmpty()) { + val routingKeyWorkerStr = String.join(",",routingKeyWorker.getRoutingKey()); + val routingKeyStr = String.join(",", routingKey.getRoutingKey()); + Assertions.assertEquals(routingKeyWorkerStr, routingKeyStr); + } + message.setExecutorName(String.join("-", routingKeyWorker.getRoutingKey())); + try { + router.publish(routingKey, message); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + + } + + +} diff --git a/memq-actor/src/test/java/io/appform/memq/hierarchical/actor/C2CDataActionMessageHierarchicalActor.java b/memq-actor/src/test/java/io/appform/memq/hierarchical/actor/C2CDataActionMessageHierarchicalActor.java new file mode 100644 index 0000000..d2a060f --- /dev/null +++ b/memq-actor/src/test/java/io/appform/memq/hierarchical/actor/C2CDataActionMessageHierarchicalActor.java @@ -0,0 +1,24 @@ +package io.appform.memq.hierarchical.actor; + +import io.appform.memq.ActorSystem; +import io.appform.memq.actor.MessageMeta; +import io.appform.memq.hierarchical.HierarchialHighLevelActorConfig; +import io.appform.memq.hierarchical.HierarchicalHighLevelActor; +import io.appform.memq.hierarchical.data.ActionMessage; +import io.appform.memq.hierarchical.data.FlowType; + + +public class C2CDataActionMessageHierarchicalActor extends HierarchicalHighLevelActor { + + + public C2CDataActionMessageHierarchicalActor(final HierarchialHighLevelActorConfig hierarchicalTreeConfig, + final ActorSystem actorSystem) { + super(FlowType.C2C_AUTH_FLOW, hierarchicalTreeConfig, actorSystem); + } + + @Override + protected boolean handle(ActionMessage actionMessage, MessageMeta messageMetadata) { + System.out.println("C2C : " + actionMessage); + return true; + } +} \ No newline at end of file diff --git a/memq-actor/src/test/java/io/appform/memq/hierarchical/actor/C2MDataActionMessageHierarchicalActor.java b/memq-actor/src/test/java/io/appform/memq/hierarchical/actor/C2MDataActionMessageHierarchicalActor.java new file mode 100644 index 0000000..34ed4e8 --- /dev/null +++ b/memq-actor/src/test/java/io/appform/memq/hierarchical/actor/C2MDataActionMessageHierarchicalActor.java @@ -0,0 +1,25 @@ +package io.appform.memq.hierarchical.actor; + + +import io.appform.memq.ActorSystem; +import io.appform.memq.actor.MessageMeta; +import io.appform.memq.hierarchical.HierarchialHighLevelActorConfig; +import io.appform.memq.hierarchical.HierarchicalHighLevelActor; +import io.appform.memq.hierarchical.data.ActionMessage; +import io.appform.memq.hierarchical.data.FlowType; + +public class C2MDataActionMessageHierarchicalActor extends HierarchicalHighLevelActor { + + + public C2MDataActionMessageHierarchicalActor(final HierarchialHighLevelActorConfig hierarchicalTreeConfig, + final ActorSystem actorSystem) { + super(FlowType.C2M_AUTH_FLOW, hierarchicalTreeConfig, actorSystem); + } + + @Override + protected boolean handle(ActionMessage actionMessage, MessageMeta messageMeta) { + System.out.println("C2M : " + actionMessage); + return true; + } + +} \ No newline at end of file diff --git a/memq-actor/src/test/java/io/appform/memq/hierarchical/actor/FlowTypeHierarchicalActorBuilder.java b/memq-actor/src/test/java/io/appform/memq/hierarchical/actor/FlowTypeHierarchicalActorBuilder.java new file mode 100644 index 0000000..60857cf --- /dev/null +++ b/memq-actor/src/test/java/io/appform/memq/hierarchical/actor/FlowTypeHierarchicalActorBuilder.java @@ -0,0 +1,30 @@ +package io.appform.memq.hierarchical.actor; + + +import io.appform.memq.ActorSystem; +import io.appform.memq.hierarchical.HierarchialHighLevelActorConfig; +import io.appform.memq.hierarchical.HierarchicalHighLevelActor; +import io.appform.memq.hierarchical.data.ActionMessage; +import io.appform.memq.hierarchical.data.FlowType; + +public class FlowTypeHierarchicalActorBuilder implements FlowType.FlowTypeVisitor> { + + private final HierarchialHighLevelActorConfig hierarchicalTreeConfig; + private final ActorSystem actorSystem; + + public FlowTypeHierarchicalActorBuilder(final HierarchialHighLevelActorConfig hierarchicalTreeConfig, + final ActorSystem actorSystem) { + this.hierarchicalTreeConfig = hierarchicalTreeConfig; + this.actorSystem = actorSystem; + } + + @Override + public HierarchicalHighLevelActor visitC2M() { + return new C2MDataActionMessageHierarchicalActor(hierarchicalTreeConfig, actorSystem); + } + + @Override + public HierarchicalHighLevelActor visitC2C() { + return new C2CDataActionMessageHierarchicalActor(hierarchicalTreeConfig, actorSystem); + } +} diff --git a/memq-actor/src/test/java/io/appform/memq/hierarchical/data/ActionMessage.java b/memq-actor/src/test/java/io/appform/memq/hierarchical/data/ActionMessage.java new file mode 100644 index 0000000..9b14a44 --- /dev/null +++ b/memq-actor/src/test/java/io/appform/memq/hierarchical/data/ActionMessage.java @@ -0,0 +1,33 @@ +package io.appform.memq.hierarchical.data; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.appform.memq.actor.Message; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Setter; +import lombok.ToString; + +import javax.validation.constraints.NotNull; + +@Data +@EqualsAndHashCode +@ToString +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(name = FlowType.C2M_AUTH_FLOW_TEXT, value = C2MDataActionMessage.class), + @JsonSubTypes.Type(name = FlowType.C2C_AUTH_FLOW_TEXT, value = C2CDataActionMessage.class) +}) +public abstract class ActionMessage implements Message { + + @NotNull + private final FlowType type; + + @Setter + private String executorName; + + protected ActionMessage(FlowType type) { + this.type = type; + } + +} \ No newline at end of file diff --git a/memq-actor/src/test/java/io/appform/memq/hierarchical/data/C2CDataActionMessage.java b/memq-actor/src/test/java/io/appform/memq/hierarchical/data/C2CDataActionMessage.java new file mode 100644 index 0000000..1e5b31e --- /dev/null +++ b/memq-actor/src/test/java/io/appform/memq/hierarchical/data/C2CDataActionMessage.java @@ -0,0 +1,28 @@ +package io.appform.memq.hierarchical.data; + +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class C2CDataActionMessage extends ActionMessage { + private String data; + + public C2CDataActionMessage() { + super(FlowType.C2C_AUTH_FLOW); + } + + @Builder + public C2CDataActionMessage(String data) { + this(); + this.data = data; + } + + @Override + public String id() { + return data; + } +} \ No newline at end of file diff --git a/memq-actor/src/test/java/io/appform/memq/hierarchical/data/C2MDataActionMessage.java b/memq-actor/src/test/java/io/appform/memq/hierarchical/data/C2MDataActionMessage.java new file mode 100644 index 0000000..87d6844 --- /dev/null +++ b/memq-actor/src/test/java/io/appform/memq/hierarchical/data/C2MDataActionMessage.java @@ -0,0 +1,28 @@ +package io.appform.memq.hierarchical.data; + +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class C2MDataActionMessage extends ActionMessage { + private String data; + + public C2MDataActionMessage() { + super(FlowType.C2M_AUTH_FLOW); + } + + @Builder + public C2MDataActionMessage(String data) { + this(); + this.data = data; + } + + @Override + public String id() { + return data; + } +} \ No newline at end of file diff --git a/memq-actor/src/test/java/io/appform/memq/hierarchical/data/FlowType.java b/memq-actor/src/test/java/io/appform/memq/hierarchical/data/FlowType.java new file mode 100644 index 0000000..2adb690 --- /dev/null +++ b/memq-actor/src/test/java/io/appform/memq/hierarchical/data/FlowType.java @@ -0,0 +1,25 @@ +package io.appform.memq.hierarchical.data; + +public enum FlowType { + C2M_AUTH_FLOW { + @Override + public T accept(FlowTypeVisitor visitor) { + return visitor.visitC2M(); + } + }, + C2C_AUTH_FLOW { + @Override + public T accept(FlowTypeVisitor visitor) { + return visitor.visitC2C(); + } + }; + + public static final String C2M_AUTH_FLOW_TEXT = "C2M_AUTH_FLOW"; + public static final String C2C_AUTH_FLOW_TEXT = "C2C_AUTH_FLOW"; + + public abstract T accept(FlowTypeVisitor visitor); + public interface FlowTypeVisitor { + T visitC2M(); + T visitC2C(); + } +} diff --git a/memq-actor/src/test/java/io/appform/memq/util/YamlReader.java b/memq-actor/src/test/java/io/appform/memq/util/YamlReader.java new file mode 100644 index 0000000..1885c63 --- /dev/null +++ b/memq-actor/src/test/java/io/appform/memq/util/YamlReader.java @@ -0,0 +1,48 @@ +package io.appform.memq.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.google.common.io.Resources; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +@UtilityClass +public class YamlReader { + + // Config reader + private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); + + @SneakyThrows + public T readYAML(final String data, final TypeReference typeReference) { + return yamlMapper.readValue(data, typeReference); + } + + @SneakyThrows + public T loadConfig(final String filePath, final TypeReference typeReference) { + String data = fixture(filePath); + return readYAML(data, typeReference); + } + + public static String fixture(String filename) { + return fixture(filename, StandardCharsets.UTF_8); + } + + private static String fixture(String filename, Charset charset) { + URL resource = Resources.getResource(filename); + + try { + return Resources.toString(resource, charset).trim(); + } catch (IOException var4) { + IOException e = var4; + throw new IllegalArgumentException(e); + } + } + + +} diff --git a/memq-actor/src/test/resources/rmqHierarchicalMemq.yaml b/memq-actor/src/test/resources/rmqHierarchicalMemq.yaml new file mode 100644 index 0000000..21899f1 --- /dev/null +++ b/memq-actor/src/test/resources/rmqHierarchicalMemq.yaml @@ -0,0 +1,23 @@ +workers: + C2M_AUTH_FLOW: + executorName: prod.mandate.actors.c2m + partitions: 1 + children: + REGULAR: + nodeData: + partitions: 1 + children: + HOTSTAR: + nodeData: + partitions: 2 + JAR: + nodeData: + partitions: 1 + C2C_AUTH_FLOW: + executorName: prod.mandate.actors.c2c + partitions: 1 + children: + REGULAR: + nodeData: + partitions: 1 + diff --git a/memq-dw-bundle/src/main/java/io/appform/MemqActorSystem.java b/memq-dw-bundle/src/main/java/io/appform/MemqActorSystem.java index 8a752ac..caea4ed 100644 --- a/memq-dw-bundle/src/main/java/io/appform/MemqActorSystem.java +++ b/memq-dw-bundle/src/main/java/io/appform/MemqActorSystem.java @@ -4,8 +4,9 @@ import io.appform.config.ExecutorConfig; import io.appform.config.MemqConfig; import io.appform.memq.ActorSystem; -import io.appform.memq.actor.Actor; +import io.appform.memq.actor.IActor; import io.appform.memq.HighLevelActorConfig; +import io.appform.memq.hierarchical.IHierarchicalActor; import io.appform.memq.observer.ActorObserver; import io.appform.memq.retry.RetryStrategy; import io.appform.memq.retry.RetryStrategyFactory; @@ -28,7 +29,7 @@ public class MemqActorSystem implements ActorSystem, Managed { private final ConcurrentHashMap executors; private final ExecutorServiceProvider executorServiceProvider; private final Map executorConfigMap; - private final List> registeredActors; + private final List> registeredActors; private final RetryStrategyFactory retryStrategyFactory; private final MetricRegistry metricRegistry; private final List actorObservers; @@ -55,12 +56,18 @@ public MemqActorSystem( //System shutdown @Override public void close() { - registeredActors.forEach(Actor::close); + registeredActors.forEach(IActor::close); executors.values().forEach(ExecutorService::shutdown); } @Override - public final void register(Actor actor) { + public final void register(IActor actor) { + registeredActors.add(actor); + actor.start(); //Starting actor during registration + } + + @Override + public final void register(IHierarchicalActor actor) { registeredActors.add(actor); actor.start(); //Starting actor during registration } @@ -89,7 +96,7 @@ public List registeredObservers() { @Override public boolean isRunning() { - return !registeredActors.isEmpty() && registeredActors.stream().allMatch(Actor::isRunning); + return !registeredActors.isEmpty() && registeredActors.stream().allMatch(IActor::isRunning); } @Override