aboutsummaryrefslogtreecommitdiffstats
path: root/message-bus/src
diff options
context:
space:
mode:
Diffstat (limited to 'message-bus/src')
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/Topic.java90
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicEndpoint.java238
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicEndpointManager.java36
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicEndpointProxy.java485
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicListener.java37
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicRegisterable.java41
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicSink.java38
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicSource.java36
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/ApiKeyEnabled.java39
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusConsumer.java278
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusPublisher.java43
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusTopicBase.java109
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusTopicSink.java42
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusTopicSource.java57
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/InlineBusTopicSink.java165
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/SingleThreadedBusTopicSource.java288
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/TopicBase.java243
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/TopicBaseFactory.java87
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/TopicBaseHashedFactory.java207
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/BidirectionalTopicClient.java222
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/BidirectionalTopicClientException.java53
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/TopicSinkClient.java114
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/TopicSinkClientException.java54
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/IndexedKafkaTopicSinkFactory.java201
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/IndexedKafkaTopicSourceFactory.java211
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/InlineKafkaTopicSink.java83
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaPublisherWrapper.java121
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicFactories.java39
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSink.java28
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSinkFactory.java89
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSource.java28
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSourceFactory.java88
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/SingleThreadedKafkaTopicSource.java80
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicEndpoint.java141
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicFactories.java42
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicFactory.java117
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSink.java56
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSinkFactory.java58
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSource.java56
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSourceFactory.java56
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/features/NetLoggerFeatureApi.java54
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/features/NetLoggerFeatureProviders.java41
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/properties/MessageBusProperties.java78
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/utils/KafkaPropertyUtils.java80
-rw-r--r--message-bus/src/main/java/org/onap/policy/common/message/bus/utils/NetLoggerUtil.java134
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/CommonTestData.java111
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/TopicEndpointProxyTest.java400
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/TopicParameterGroupTest.java147
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/BusConsumerTest.java282
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/BusTopicBaseTest.java139
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/BusTopicFactoryTestBase.java238
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/InlineBusTopicSinkTest.java230
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/SingleThreadedBusTopicSourceTest.java375
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicBaseTest.java355
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicFactoryTestBase.java225
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicPropertyBuilder.java121
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicTestBase.java159
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/client/BidirectionalTopicClientTest.java454
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/client/TopicClientExceptionTest.java36
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/client/TopicSinkClientTest.java147
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/IndexedKafkaTopicSourceFactoryTest.java63
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/InlineKafkaTopicSinkTest.java68
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaPublisherWrapperTest.java98
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicFactoryTestBase.java47
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicPropertyBuilder.java94
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSinkFactoryTest.java200
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSinkTest.java34
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSourceFactoryTest.java168
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSourceTest.java34
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/SingleThreadedKafkaTopicSourceTest.java61
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicEndpointTest.java121
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicFactoryTest.java257
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicPropertyBuilder.java77
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSinkFactoryTest.java39
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSinkTest.java58
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSourceFactoryTest.java39
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSourceTest.java56
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/features/NetLoggerFeatureApiTest.java86
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/utils/KafkaPropertyUtilsTest.java54
-rw-r--r--message-bus/src/test/java/org/onap/policy/common/message/bus/utils/NetLoggerUtilTest.java269
-rw-r--r--message-bus/src/test/resources/META-INF/services/org.onap.policy.common.message.bus.features.NetLoggerFeatureApi1
-rw-r--r--message-bus/src/test/resources/logback-test.xml42
-rw-r--r--message-bus/src/test/resources/org/onap/policy/common/message/bus/event/TopicEndpointProxyTest.json30
-rw-r--r--message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/BusTopicBaseTest.json14
-rw-r--r--message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/InlineBusTopicSinkTest.json15
-rw-r--r--message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/SingleThreadedBusTopicSourceTest.json18
-rw-r--r--message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/TopicBaseTest.json9
-rw-r--r--message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_all_params.json62
-rw-r--r--message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_invalid.json6
-rw-r--r--message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_missing_mandatory.json12
-rw-r--r--message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_valid.json30
91 files changed, 10464 insertions, 0 deletions
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/Topic.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/Topic.java
new file mode 100644
index 00000000..48dbb715
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/Topic.java
@@ -0,0 +1,90 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2017-2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2019 Samsung Electronics Co., Ltd.
+ * Copyright (C) 2022,2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event;
+
+import java.util.List;
+import org.onap.policy.common.capabilities.Lockable;
+import org.onap.policy.common.capabilities.Startable;
+
+
+/**
+ * Essential Topic Data.
+ */
+public interface Topic extends TopicRegisterable, Startable, Lockable {
+
+ /**
+ * Underlying Communication infrastructure Types.
+ */
+ enum CommInfrastructure {
+ /**
+ * KAFKA Communication Infrastructure.
+ */
+ KAFKA,
+ /**
+ * NOOP for internal use only.
+ */
+ NOOP,
+ /**
+ * REST Communication Infrastructure.
+ */
+ REST
+ }
+
+ /**
+ * Gets the canonical topic name.
+ *
+ * @return topic name
+ */
+ String getTopic();
+
+ /**
+ * Gets the effective topic that is used in
+ * the network communication. This name is usually
+ * the topic name.
+ *
+ * @return topic name alias
+ */
+ String getEffectiveTopic();
+
+ /**
+ * Gets the communication infrastructure type.
+ *
+ * @return CommInfrastructure object
+ */
+ CommInfrastructure getTopicCommInfrastructure();
+
+ /**
+ * Return list of servers.
+ *
+ * @return bus servers
+ */
+ List<String> getServers();
+
+ /**
+ * Get the more recent events in this topic entity.
+ *
+ * @return array of most recent events
+ */
+ String[] getRecentEvents();
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicEndpoint.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicEndpoint.java
new file mode 100644
index 00000000..5511a82c
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicEndpoint.java
@@ -0,0 +1,238 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2022,2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event;
+
+import java.util.List;
+import java.util.Properties;
+import org.onap.policy.common.capabilities.Lockable;
+import org.onap.policy.common.capabilities.Startable;
+import org.onap.policy.common.message.bus.event.kafka.KafkaTopicSink;
+import org.onap.policy.common.message.bus.event.kafka.KafkaTopicSource;
+import org.onap.policy.common.message.bus.event.noop.NoopTopicSink;
+import org.onap.policy.common.message.bus.event.noop.NoopTopicSource;
+import org.onap.policy.common.parameters.topic.TopicParameterGroup;
+import org.onap.policy.common.parameters.topic.TopicParameters;
+
+/**
+ * Abstraction to manage the system's Networked Topic Endpoints, sources of all events input into
+ * the System.
+ */
+public interface TopicEndpoint extends Startable, Lockable {
+
+ /**
+ * Add topics configuration (sources and sinks) into a single list.
+ *
+ * @param properties topic configuration
+ * @return topic list
+ * @throws IllegalArgumentException when invalid arguments are provided
+ */
+ List<Topic> addTopics(Properties properties);
+
+ /**
+ * Add topics configuration (sources and sinks) into a single list.
+ *
+ * @param params parameters to configure topic
+ * @return topic list
+ * @throws IllegalArgumentException when invalid arguments are provided
+ */
+ List<Topic> addTopics(TopicParameterGroup params);
+
+ /**
+ * Add Topic Sources to the communication infrastructure initialized per properties.
+ *
+ * @param properties properties for Topic Source construction
+ * @return a list of generic Topic Sources
+ * @throws IllegalArgumentException when invalid arguments are provided
+ */
+ List<TopicSource> addTopicSources(Properties properties);
+
+
+ /**
+ * Add Topic Sources to the communication infrastructure initialized per properties.
+ *
+ * @param paramList parameters for Topic Source construction
+ * @return a list of generic Topic Sources
+ * @throws IllegalArgumentException when invalid arguments are provided
+ */
+ List<TopicSource> addTopicSources(List<TopicParameters> paramList);
+
+ /**
+ * Add Topic Sinks to the communication infrastructure initialized per properties.
+ *
+ * @param properties properties for Topic Sink construction
+ * @return a list of generic Topic Sinks
+ * @throws IllegalArgumentException when invalid arguments are provided
+ */
+ List<TopicSink> addTopicSinks(Properties properties);
+
+ /**
+ * Add Topic Sinks to the communication infrastructure initialized per properties.
+ *
+ * @param paramList parameters for Topic Sink construction
+ * @return a list of generic Topic Sinks
+ * @throws IllegalArgumentException when invalid arguments are provided
+ */
+ List<TopicSink> addTopicSinks(List<TopicParameters> paramList);
+
+ /**
+ * Gets all Topic Sources.
+ *
+ * @return the Topic Source List
+ */
+ List<TopicSource> getTopicSources();
+
+ /**
+ * Get the Topic Sources for the given topic name.
+ *
+ * @param topicNames the topic name
+ *
+ * @return the Topic Source List
+ * @throws IllegalStateException if the entity is in an invalid state
+ * @throws IllegalArgumentException if invalid parameters are present
+ */
+ List<TopicSource> getTopicSources(List<String> topicNames);
+
+ /**
+ * Gets the Topic Source for the given topic name and underlying communication infrastructure
+ * type.
+ *
+ * @param commType communication infrastructure type
+ * @param topicName the topic name
+ *
+ * @return the Topic Source
+ * @throws IllegalStateException if the entity is in an invalid state, for example multiple
+ * TopicReaders for a topic name and communication infrastructure
+ * @throws IllegalArgumentException if invalid parameters are present
+ * @throws UnsupportedOperationException if the operation is not supported.
+ */
+ TopicSource getTopicSource(Topic.CommInfrastructure commType, String topicName);
+
+ /**
+ * Get the Noop Source for the given topic name.
+ *
+ * @param topicName the topic name.
+ * @return the Noop Source.
+ */
+ NoopTopicSource getNoopTopicSource(String topicName);
+
+ /**
+ * Get the Kafka Source for the given topic name.
+ *
+ * @param topicName the topic name.
+ * @return the Kafka Source.
+ */
+ KafkaTopicSource getKafkaTopicSource(String topicName);
+
+ /**
+ * Get the Topic Sinks for the given topic name.
+ *
+ * @param topicNames the topic names
+ * @return the Topic Sink List
+ */
+ List<TopicSink> getTopicSinks(List<String> topicNames);
+
+ /**
+ * Get the Topic Sinks for the given topic name and all the underlying communication
+ * infrastructure type.
+ *
+ * @param topicName the topic name
+ *
+ * @return the Topic Sink List
+ * @throws IllegalStateException if the entity is in an invalid state, for example multiple
+ * TopicWriters for a topic name and communication infrastructure
+ * @throws IllegalArgumentException if invalid parameters are present
+ */
+ List<TopicSink> getTopicSinks(String topicName);
+
+ /**
+ * Gets all Topic Sinks.
+ *
+ * @return the Topic Sink List
+ */
+ List<TopicSink> getTopicSinks();
+
+ /**
+ * Get the Topic Sinks for the given topic name and underlying communication infrastructure type.
+ *
+ * @param topicName the topic name
+ * @param commType communication infrastructure type
+ *
+ * @return the Topic Sink List
+ * @throws IllegalStateException if the entity is in an invalid state, for example multiple
+ * TopicWriters for a topic name and communication infrastructure
+ * @throws IllegalArgumentException if invalid parameters are present
+ */
+ TopicSink getTopicSink(Topic.CommInfrastructure commType, String topicName);
+
+ /**
+ * Get the no-op Topic Sink for the given topic name.
+ *
+ * @param topicName the topic name
+ *
+ * @return the Topic Source
+ * @throws IllegalStateException if the entity is in an invalid state, for example multiple
+ * TopicReaders for a topic name and communication infrastructure
+ * @throws IllegalArgumentException if invalid parameters are present
+ */
+ NoopTopicSink getNoopTopicSink(String topicName);
+
+ /**
+ * Get the KAFKA Topic Source for the given topic name.
+ *
+ * @param topicName the topic name
+ *
+ * @return the Topic Source
+ * @throws IllegalStateException if the entity is in an invalid state, for example multiple
+ * TopicReaders for a topic name and communication infrastructure
+ * @throws IllegalArgumentException if invalid parameters are present
+ */
+ KafkaTopicSink getKafkaTopicSink(String topicName);
+
+ /**
+ * Gets only the KAFKA Topic Sources.
+ *
+ * @return the KAFKA Topic Source List
+ */
+ List<KafkaTopicSource> getKafkaTopicSources();
+
+ /**
+ * Gets only the NOOP Topic Sources.
+ *
+ * @return the NOOP Topic Source List
+ */
+ List<NoopTopicSource> getNoopTopicSources();
+
+ /**
+ * Gets only the KAFKA Topic Sinks.
+ *
+ * @return the KAFKA Topic Sinks List
+ */
+ List<KafkaTopicSink> getKafkaTopicSinks();
+
+ /**
+ * Gets only the NOOP Topic Sinks.
+ *
+ * @return the NOOP Topic Sinks List
+ */
+ List<NoopTopicSink> getNoopTopicSinks();
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicEndpointManager.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicEndpointManager.java
new file mode 100644
index 00000000..40b9c235
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicEndpointManager.java
@@ -0,0 +1,36 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class TopicEndpointManager {
+
+ /**
+ * Topic endpoint manager.
+ */
+ @Getter
+ static TopicEndpoint manager = new TopicEndpointProxy();
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicEndpointProxy.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicEndpointProxy.java
new file mode 100644
index 00000000..9dbf5418
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicEndpointProxy.java
@@ -0,0 +1,485 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2017-2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2022-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Properties;
+import lombok.Getter;
+import org.onap.policy.common.capabilities.Startable;
+import org.onap.policy.common.gson.annotation.GsonJsonIgnore;
+import org.onap.policy.common.message.bus.event.kafka.KafkaTopicFactories;
+import org.onap.policy.common.message.bus.event.kafka.KafkaTopicSink;
+import org.onap.policy.common.message.bus.event.kafka.KafkaTopicSource;
+import org.onap.policy.common.message.bus.event.noop.NoopTopicFactories;
+import org.onap.policy.common.message.bus.event.noop.NoopTopicSink;
+import org.onap.policy.common.message.bus.event.noop.NoopTopicSource;
+import org.onap.policy.common.parameters.topic.TopicParameterGroup;
+import org.onap.policy.common.parameters.topic.TopicParameters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This implementation of the Topic Endpoint Manager, proxies operations to the appropriate
+ * implementation(s).
+ */
+@Getter
+public class TopicEndpointProxy implements TopicEndpoint {
+ /**
+ * Logger.
+ */
+ private static final Logger logger = LoggerFactory.getLogger(TopicEndpointProxy.class);
+
+ /**
+ * Is this element locked boolean.
+ */
+ private volatile boolean locked = false;
+
+ /**
+ * Is this element alive boolean.
+ */
+ private volatile boolean alive = false;
+
+ @Override
+ public List<Topic> addTopics(Properties properties) {
+ List<Topic> topics = new ArrayList<>(addTopicSources(properties));
+ topics.addAll(addTopicSinks(properties));
+ return topics;
+ }
+
+ @Override
+ public List<Topic> addTopics(TopicParameterGroup params) {
+ List<TopicParameters> sinks =
+ (params.getTopicSinks() != null ? params.getTopicSinks() : Collections.emptyList());
+ List<TopicParameters> sources =
+ (params.getTopicSources() != null ? params.getTopicSources() : Collections.emptyList());
+
+ List<Topic> topics = new ArrayList<>(sinks.size() + sources.size());
+ topics.addAll(addTopicSources(sources));
+ topics.addAll(addTopicSinks(sinks));
+ return topics;
+ }
+
+ @Override
+ public List<TopicSource> addTopicSources(List<TopicParameters> paramList) {
+ List<TopicSource> sources = new ArrayList<>(paramList.size());
+
+ for (TopicParameters param : paramList) {
+ switch (Topic.CommInfrastructure.valueOf(param.getTopicCommInfrastructure().toUpperCase())) {
+ case KAFKA:
+ sources.add(KafkaTopicFactories.getSourceFactory().build(param));
+ break;
+ case NOOP:
+ sources.add(NoopTopicFactories.getSourceFactory().build(param));
+ break;
+ default:
+ logger.debug("Unknown source type {} for topic: {}", param.getTopicCommInfrastructure(),
+ param.getTopic());
+ break;
+ }
+ }
+
+ lockSources(sources);
+
+ return sources;
+ }
+
+ @Override
+ public List<TopicSource> addTopicSources(Properties properties) {
+
+ // 1. Create KAFKA Sources
+ // 2. Create NOOP Sources
+
+ List<TopicSource> sources = new ArrayList<>();
+
+ sources.addAll(KafkaTopicFactories.getSourceFactory().build(properties));
+ sources.addAll(NoopTopicFactories.getSourceFactory().build(properties));
+
+ lockSources(sources);
+
+ return sources;
+ }
+
+ private void lockSources(List<TopicSource> sources) {
+ if (this.isLocked()) {
+ sources.forEach(TopicSource::lock);
+ }
+ }
+
+ @Override
+ public List<TopicSink> addTopicSinks(List<TopicParameters> paramList) {
+ List<TopicSink> sinks = new ArrayList<>(paramList.size());
+
+ for (TopicParameters param : paramList) {
+ switch (Topic.CommInfrastructure.valueOf(param.getTopicCommInfrastructure().toUpperCase())) {
+ case KAFKA:
+ sinks.add(KafkaTopicFactories.getSinkFactory().build(param));
+ break;
+ case NOOP:
+ sinks.add(NoopTopicFactories.getSinkFactory().build(param));
+ break;
+ default:
+ logger.debug("Unknown sink type {} for topic: {}", param.getTopicCommInfrastructure(),
+ param.getTopic());
+ break;
+ }
+ }
+
+ lockSinks(sinks);
+
+ return sinks;
+ }
+
+ @Override
+ public List<TopicSink> addTopicSinks(Properties properties) {
+ // 1. Create KAFKA Sinks
+ // 2. Create NOOP Sinks
+
+ final List<TopicSink> sinks = new ArrayList<>();
+
+ sinks.addAll(KafkaTopicFactories.getSinkFactory().build(properties));
+ sinks.addAll(NoopTopicFactories.getSinkFactory().build(properties));
+
+ lockSinks(sinks);
+
+ return sinks;
+ }
+
+ private void lockSinks(List<TopicSink> sinks) {
+ if (this.isLocked()) {
+ sinks.forEach(TopicSink::lock);
+ }
+ }
+
+ @Override
+ public List<TopicSource> getTopicSources() {
+
+ final List<TopicSource> sources = new ArrayList<>();
+
+ sources.addAll(KafkaTopicFactories.getSourceFactory().inventory());
+ sources.addAll(NoopTopicFactories.getSourceFactory().inventory());
+
+ return sources;
+ }
+
+ @Override
+ public List<TopicSource> getTopicSources(List<String> topicNames) {
+
+ if (topicNames == null) {
+ throw new IllegalArgumentException("must provide a list of topics");
+ }
+
+ final List<TopicSource> sources = new ArrayList<>();
+
+ topicNames.forEach(topic -> {
+ try {
+ sources.add(Objects.requireNonNull(this.getKafkaTopicSource(topic)));
+ } catch (final Exception e) {
+ logger.debug("No KAFKA source for topic: {}", topic, e);
+ }
+
+ try {
+ sources.add(Objects.requireNonNull(this.getNoopTopicSource(topic)));
+ } catch (final Exception e) {
+ logger.debug("No NOOP source for topic: {}", topic, e);
+ }
+ });
+
+ return sources;
+ }
+
+ @Override
+ public List<TopicSink> getTopicSinks() {
+
+ final List<TopicSink> sinks = new ArrayList<>();
+
+ sinks.addAll(KafkaTopicFactories.getSinkFactory().inventory());
+ sinks.addAll(NoopTopicFactories.getSinkFactory().inventory());
+
+ return sinks;
+ }
+
+ @Override
+ public List<TopicSink> getTopicSinks(List<String> topicNames) {
+
+ if (topicNames == null) {
+ throw new IllegalArgumentException("must provide a list of topics");
+ }
+
+ final List<TopicSink> sinks = new ArrayList<>();
+ for (final String topic : topicNames) {
+ try {
+ sinks.add(Objects.requireNonNull(this.getKafkaTopicSink(topic)));
+ } catch (final Exception e) {
+ logger.debug("No KAFKA sink for topic: {}", topic, e);
+ }
+
+ try {
+ sinks.add(Objects.requireNonNull(this.getNoopTopicSink(topic)));
+ } catch (final Exception e) {
+ logger.debug("No NOOP sink for topic: {}", topic, e);
+ }
+ }
+ return sinks;
+ }
+
+ @Override
+ public List<TopicSink> getTopicSinks(String topicName) {
+ if (topicName == null) {
+ throw paramException(null);
+ }
+
+ final List<TopicSink> sinks = new ArrayList<>();
+
+ try {
+ sinks.add(this.getKafkaTopicSink(topicName));
+ } catch (final Exception e) {
+ logNoSink(topicName, e);
+ }
+
+ try {
+ sinks.add(this.getNoopTopicSink(topicName));
+ } catch (final Exception e) {
+ logNoSink(topicName, e);
+ }
+
+ return sinks;
+ }
+
+ @GsonJsonIgnore
+ @Override
+ public List<KafkaTopicSource> getKafkaTopicSources() {
+ return KafkaTopicFactories.getSourceFactory().inventory();
+ }
+
+ @GsonJsonIgnore
+ @Override
+ public List<NoopTopicSource> getNoopTopicSources() {
+ return NoopTopicFactories.getSourceFactory().inventory();
+ }
+
+ @Override
+ @GsonJsonIgnore
+ public List<KafkaTopicSink> getKafkaTopicSinks() {
+ return KafkaTopicFactories.getSinkFactory().inventory();
+ }
+
+ @GsonJsonIgnore
+ @Override
+ public List<NoopTopicSink> getNoopTopicSinks() {
+ return NoopTopicFactories.getSinkFactory().inventory();
+ }
+
+ @Override
+ public boolean start() {
+
+ synchronized (this) {
+ if (this.locked) {
+ throw new IllegalStateException(this + " is locked");
+ }
+
+ if (this.alive) {
+ return true;
+ }
+
+ this.alive = true;
+ }
+
+ final List<Startable> endpoints = this.getEndpoints();
+
+ var success = true;
+ for (final Startable endpoint : endpoints) {
+ try {
+ success = endpoint.start() && success;
+ } catch (final Exception e) {
+ success = false;
+ logger.error("Problem starting endpoint: {}", endpoint, e);
+ }
+ }
+
+ return success;
+ }
+
+ @Override
+ public boolean stop() {
+
+ /*
+ * stop regardless if it is locked, in other words, stop operation has precedence over
+ * locks.
+ */
+ synchronized (this) {
+ this.alive = false;
+ }
+
+ final List<Startable> endpoints = this.getEndpoints();
+
+ var success = true;
+ for (final Startable endpoint : endpoints) {
+ try {
+ success = endpoint.stop() && success;
+ } catch (final Exception e) {
+ success = false;
+ logger.error("Problem stopping endpoint: {}", endpoint, e);
+ }
+ }
+
+ return success;
+ }
+
+ /**
+ * Gets the endpoints.
+ *
+ * @return list of managed endpoints
+ */
+ @GsonJsonIgnore
+ protected List<Startable> getEndpoints() {
+ final List<Startable> endpoints = new ArrayList<>();
+
+ endpoints.addAll(this.getTopicSources());
+ endpoints.addAll(this.getTopicSinks());
+
+ return endpoints;
+ }
+
+ @Override
+ public void shutdown() {
+ this.stop();
+
+ KafkaTopicFactories.getSourceFactory().destroy();
+ KafkaTopicFactories.getSinkFactory().destroy();
+
+ NoopTopicFactories.getSinkFactory().destroy();
+ NoopTopicFactories.getSourceFactory().destroy();
+
+ }
+
+ @Override
+ public boolean lock() {
+ boolean shouldLock;
+
+ synchronized (this) {
+ shouldLock = !this.locked;
+ this.locked = true;
+ }
+
+ if (shouldLock) {
+ for (final TopicSource source : this.getTopicSources()) {
+ source.lock();
+ }
+
+ for (final TopicSink sink : this.getTopicSinks()) {
+ sink.lock();
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean unlock() {
+ boolean shouldUnlock;
+
+ synchronized (this) {
+ shouldUnlock = this.locked;
+ this.locked = false;
+ }
+
+ if (shouldUnlock) {
+ for (final TopicSource source : this.getTopicSources()) {
+ source.unlock();
+ }
+
+ for (final TopicSink sink : this.getTopicSinks()) {
+ sink.unlock();
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public TopicSource getTopicSource(Topic.CommInfrastructure commType, String topicName) {
+
+ if (commType == null) {
+ throw paramException(topicName);
+ }
+
+ if (topicName == null) {
+ throw paramException(null);
+ }
+
+ return switch (commType) {
+ case KAFKA -> this.getKafkaTopicSource(topicName);
+ case NOOP -> this.getNoopTopicSource(topicName);
+ default -> throw new UnsupportedOperationException("Unsupported " + commType.name());
+ };
+ }
+
+ @Override
+ public TopicSink getTopicSink(Topic.CommInfrastructure commType, String topicName) {
+ if (commType == null) {
+ throw paramException(topicName);
+ }
+
+ if (topicName == null) {
+ throw paramException(null);
+ }
+
+ return switch (commType) {
+ case KAFKA -> this.getKafkaTopicSink(topicName);
+ case NOOP -> this.getNoopTopicSink(topicName);
+ default -> throw new UnsupportedOperationException("Unsupported " + commType.name());
+ };
+ }
+
+ @Override
+ public KafkaTopicSource getKafkaTopicSource(String topicName) {
+ return KafkaTopicFactories.getSourceFactory().get(topicName);
+ }
+
+ @Override
+ public NoopTopicSource getNoopTopicSource(String topicName) {
+ return NoopTopicFactories.getSourceFactory().get(topicName);
+ }
+
+ @Override
+ public KafkaTopicSink getKafkaTopicSink(String topicName) {
+ return KafkaTopicFactories.getSinkFactory().get(topicName);
+ }
+
+ @Override
+ public NoopTopicSink getNoopTopicSink(String topicName) {
+ return NoopTopicFactories.getSinkFactory().get(topicName);
+ }
+
+ private IllegalArgumentException paramException(String topicName) {
+ return new IllegalArgumentException(
+ "Invalid parameter: a communication infrastructure required to fetch " + topicName);
+ }
+
+ private void logNoSink(String topicName, Exception ex) {
+ logger.debug("No sink for topic: {}", topicName, ex);
+ }
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicListener.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicListener.java
new file mode 100644
index 00000000..f9896243
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicListener.java
@@ -0,0 +1,37 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event;
+
+/**
+ * Listener for event messages entering the Policy Engine.
+ */
+@FunctionalInterface
+public interface TopicListener {
+
+ /**
+ * Notification of a new Event over a given Topic.
+ *
+ * @param commType communication infrastructure type
+ * @param topic topic name
+ * @param event event message as a string
+ */
+ void onTopicEvent(Topic.CommInfrastructure commType, String topic, String event);
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicRegisterable.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicRegisterable.java
new file mode 100644
index 00000000..1d6873e9
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicRegisterable.java
@@ -0,0 +1,41 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event;
+
+/**
+ * Marks a Topic entity as registerable.
+ */
+public interface TopicRegisterable {
+
+ /**
+ * Register for notification of events with this Topic Entity.
+ *
+ * @param topicListener the listener of events
+ */
+ void register(TopicListener topicListener);
+
+ /**
+ * Unregisters for notification of events with this Topic Entity.
+ *
+ * @param topicListener the listener of events
+ */
+ void unregister(TopicListener topicListener);
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicSink.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicSink.java
new file mode 100644
index 00000000..d5269b9a
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicSink.java
@@ -0,0 +1,38 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2023-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event;
+
+/**
+ * Marks a given Topic Endpoint as able to send messages over a topic.
+ */
+public interface TopicSink extends Topic {
+
+ /**
+ * Sends a string message over this Topic Endpoint.
+ *
+ * @param message message to send
+ * @return true if the send operation succeeded, false otherwise
+ * @throws IllegalArgumentException an invalid message has been provided
+ * @throws IllegalStateException the entity is in a state that prevents
+ * it from sending messages, for example, locked or stopped.
+ */
+ boolean send(String message);
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicSource.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicSource.java
new file mode 100644
index 00000000..f0dc3b74
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/TopicSource.java
@@ -0,0 +1,36 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event;
+
+/**
+ * Marker for a Topic Entity, indicating that the entity is able to read
+ * over a topic.
+ */
+public interface TopicSource extends Topic {
+
+ /**
+ * Pushes an event into the source programmatically.
+ *
+ * @param event the event in json format
+ * @return true if it can be processed correctly, false otherwise
+ */
+ boolean offer(String event);
+
+} \ No newline at end of file
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/ApiKeyEnabled.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/ApiKeyEnabled.java
new file mode 100644
index 00000000..360a88a2
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/ApiKeyEnabled.java
@@ -0,0 +1,39 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+/**
+ * API.
+ */
+public interface ApiKeyEnabled {
+ /**
+ * Get API key.
+ *
+ * @return api key
+ */
+ String getApiKey();
+
+ /**
+ * Get API secret.
+ *
+ * @return api secret
+ */
+ String getApiSecret();
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusConsumer.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusConsumer.java
new file mode 100644
index 00000000..925949aa
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusConsumer.java
@@ -0,0 +1,278 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2017-2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ * Modifications Copyright (C) 2020,2023 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2022-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.DEFAULT_TIMEOUT_MS_FETCH;
+
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanContext;
+import io.opentelemetry.api.trace.TraceFlags;
+import io.opentelemetry.api.trace.TraceState;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingConsumerInterceptor;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.apache.kafka.clients.consumer.ConsumerRecords;
+import org.apache.kafka.clients.consumer.KafkaConsumer;
+import org.apache.kafka.clients.consumer.OffsetAndMetadata;
+import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.header.Headers;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Wrapper around libraries to consume from message bus.
+ */
+public interface BusConsumer {
+
+ /**
+ * fetch messages.
+ *
+ * @return list of messages
+ * @throws IOException when error encountered by underlying libraries
+ */
+ Iterable<String> fetch() throws IOException;
+
+ /**
+ * close underlying library consumer.
+ */
+ void close();
+
+ /**
+ * Consumer that handles fetch() failures by sleeping.
+ */
+ abstract class FetchingBusConsumer implements BusConsumer {
+ private static final Logger logger = LoggerFactory.getLogger(FetchingBusConsumer.class);
+
+ /**
+ * Fetch timeout.
+ */
+ protected int fetchTimeout;
+
+ /**
+ * Time to sleep on a fetch failure.
+ */
+ @Getter
+ private final int sleepTime;
+
+ /**
+ * Counted down when {@link #close()} is invoked.
+ */
+ private final CountDownLatch closeCondition = new CountDownLatch(1);
+
+
+ /**
+ * Constructs the object.
+ *
+ * @param busTopicParams parameters for the bus topic
+ */
+ protected FetchingBusConsumer(BusTopicParams busTopicParams) {
+ this.fetchTimeout = busTopicParams.getFetchTimeout();
+
+ if (this.fetchTimeout <= 0) {
+ this.sleepTime = DEFAULT_TIMEOUT_MS_FETCH;
+ } else {
+ // don't sleep too long, even if fetch timeout is large
+ this.sleepTime = Math.min(this.fetchTimeout, DEFAULT_TIMEOUT_MS_FETCH);
+ }
+ }
+
+ /**
+ * Causes the thread to sleep; invoked after fetch() fails. If the consumer is closed,
+ * or the thread is interrupted, then this will return immediately.
+ */
+ protected void sleepAfterFetchFailure() {
+ try {
+ logger.info("{}: backoff for {}ms", this, sleepTime);
+ if (this.closeCondition.await(this.sleepTime, TimeUnit.MILLISECONDS)) {
+ logger.info("{}: closed while handling fetch error", this);
+ }
+
+ } catch (InterruptedException e) {
+ logger.warn("{}: interrupted while handling fetch error", this, e);
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ @Override
+ public void close() {
+ this.closeCondition.countDown();
+ }
+ }
+
+ /**
+ * Kafka based consumer.
+ */
+ class KafkaConsumerWrapper extends FetchingBusConsumer {
+
+ /**
+ * logger.
+ */
+ private static final Logger logger = LoggerFactory.getLogger(KafkaConsumerWrapper.class);
+
+ private static final String KEY_DESERIALIZER = "org.apache.kafka.common.serialization.StringDeserializer";
+
+ /**
+ * Kafka consumer.
+ */
+ protected KafkaConsumer<String, String> consumer;
+ protected Properties kafkaProps;
+
+ protected boolean allowTracing;
+
+ /**
+ * Kafka Consumer Wrapper.
+ * BusTopicParam - object contains the following parameters
+ * servers - messaging bus hosts.
+ * topic - topic
+ *
+ * @param busTopicParams - The parameters for the bus topic
+ */
+ public KafkaConsumerWrapper(BusTopicParams busTopicParams) {
+ super(busTopicParams);
+
+ if (busTopicParams.isTopicInvalid()) {
+ throw new IllegalArgumentException("No topic for Kafka");
+ }
+
+ //Setup Properties for consumer
+ kafkaProps = new Properties();
+ kafkaProps.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
+ busTopicParams.getServers().get(0));
+
+ if (busTopicParams.isAdditionalPropsValid()) {
+ kafkaProps.putAll(busTopicParams.getAdditionalProps());
+ }
+
+ if (kafkaProps.get(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG) == null) {
+ kafkaProps.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, KEY_DESERIALIZER);
+ }
+ if (kafkaProps.get(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG) == null) {
+ kafkaProps.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, KEY_DESERIALIZER);
+ }
+ if (kafkaProps.get(ConsumerConfig.GROUP_ID_CONFIG) == null) {
+ kafkaProps.setProperty(ConsumerConfig.GROUP_ID_CONFIG, busTopicParams.getConsumerGroup());
+ }
+ if (busTopicParams.isAllowTracing()) {
+ this.allowTracing = true;
+ kafkaProps.setProperty(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG,
+ TracingConsumerInterceptor.class.getName());
+ }
+
+ consumer = new KafkaConsumer<>(kafkaProps);
+ //Subscribe to the topic
+ consumer.subscribe(List.of(busTopicParams.getTopic()));
+ }
+
+ @Override
+ public Iterable<String> fetch() {
+ ConsumerRecords<String, String> records = this.consumer.poll(Duration.ofMillis(fetchTimeout));
+ if (records == null || records.count() <= 0) {
+ return Collections.emptyList();
+ }
+ List<String> messages = new ArrayList<>(records.count());
+ try {
+ if (allowTracing) {
+ createParentTraceContext(records);
+ }
+
+ for (TopicPartition partition : records.partitions()) {
+ List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);
+ for (ConsumerRecord<String, String> partitionRecord : partitionRecords) {
+ messages.add(partitionRecord.value());
+ }
+ long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset();
+ consumer.commitSync(Collections.singletonMap(partition, new OffsetAndMetadata(lastOffset + 1)));
+ }
+ } catch (Exception e) {
+ logger.error("{}: cannot fetch, throwing exception after sleep...", this);
+ sleepAfterFetchFailure();
+ throw e;
+ }
+ return messages;
+ }
+
+ private void createParentTraceContext(ConsumerRecords<String, String> records) {
+ TraceParentInfo traceParentInfo = new TraceParentInfo();
+ for (ConsumerRecord<String, String> consumerRecord : records) {
+
+ Headers consumerRecordHeaders = consumerRecord.headers();
+ traceParentInfo = processTraceParentHeader(consumerRecordHeaders);
+ }
+
+ SpanContext spanContext = SpanContext.createFromRemoteParent(
+ traceParentInfo.getTraceId(), traceParentInfo.getSpanId(),
+ TraceFlags.getSampled(), TraceState.builder().build());
+
+ Context.current().with(Span.wrap(spanContext)).makeCurrent();
+ }
+
+ private TraceParentInfo processTraceParentHeader(Headers headers) {
+ TraceParentInfo traceParentInfo = new TraceParentInfo();
+ if (headers.lastHeader("traceparent") != null) {
+ traceParentInfo.setParentTraceId(new String(headers.lastHeader(
+ "traceparent").value(), StandardCharsets.UTF_8));
+
+ String[] parts = traceParentInfo.getParentTraceId().split("-");
+ traceParentInfo.setTraceId(parts[1]);
+ traceParentInfo.setSpanId(parts[2]);
+ }
+
+ return traceParentInfo;
+ }
+
+ @Data
+ @NoArgsConstructor
+ private static class TraceParentInfo {
+ private String parentTraceId;
+ private String traceId;
+ private String spanId;
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ this.consumer.close();
+ logger.info("Kafka Consumer exited {}", this);
+ }
+
+ @Override
+ public String toString() {
+ return "KafkaConsumerWrapper [fetchTimeout=" + fetchTimeout + "]";
+ }
+ }
+}
+
+
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusPublisher.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusPublisher.java
new file mode 100644
index 00000000..10c7db2d
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusPublisher.java
@@ -0,0 +1,43 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2017-2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2018 Samsung Electronics Co., Ltd.
+ * Modifications Copyright (C) 2020,2023 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2022-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+public interface BusPublisher {
+
+ String NO_MESSAGE_PROVIDED = "No message provided";
+ String LOG_CLOSE = "{}: CLOSE";
+
+ /**
+ * sends a message.
+ *
+ * @param partitionId id
+ * @param message the message
+ * @return true if success, false otherwise
+ * @throws IllegalArgumentException if no message provided
+ */
+ boolean send(String partitionId, String message);
+
+ /**
+ * closes the publisher.
+ */
+ void close();
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusTopicBase.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusTopicBase.java
new file mode 100644
index 00000000..6516945a
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusTopicBase.java
@@ -0,0 +1,109 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2017-2019, 2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2020 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import lombok.Getter;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+
+/**
+ * Bus Topic Base.
+ */
+@Getter
+public abstract class BusTopicBase extends TopicBase implements ApiKeyEnabled {
+
+ /**
+ * API Key.
+ */
+ protected String apiKey;
+
+ /**
+ * API Secret.
+ */
+ protected String apiSecret;
+
+ /**
+ * Use https.
+ */
+ protected boolean useHttps;
+
+ /**
+ * Allow tracing.
+ */
+ protected boolean allowTracing;
+
+ /**
+ * allow self-signed certificates.
+ */
+ protected boolean allowSelfSignedCerts;
+
+ /**
+ * Instantiates a new Bus Topic Base.
+ *
+ * <p>servers list of servers
+ * topic: the topic name
+ * apiKey: API Key
+ * apiSecret: API Secret
+ * useHttps: does connection use HTTPS?
+ * allowTracing: Is tracing allowed?
+ * allowSelfSignedCerts: are self-signed certificates allow
+ *
+ * @param busTopicParams holds all our parameters
+ * @throws IllegalArgumentException if invalid parameters are present
+ */
+ protected BusTopicBase(BusTopicParams busTopicParams) {
+ super(busTopicParams.getServers(), busTopicParams.getTopic(), busTopicParams.getEffectiveTopic());
+ this.apiKey = busTopicParams.getApiKey();
+ this.apiSecret = busTopicParams.getApiSecret();
+ this.useHttps = busTopicParams.isUseHttps();
+ this.allowTracing = busTopicParams.isAllowTracing();
+ this.allowSelfSignedCerts = busTopicParams.isAllowSelfSignedCerts();
+ }
+
+ protected boolean anyNullOrEmpty(String... args) {
+ for (String arg : args) {
+ if (arg == null || arg.isEmpty()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected boolean allNullOrEmpty(String... args) {
+ for (String arg : args) {
+ if (!(arg == null || arg.isEmpty())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+ @Override
+ public String toString() {
+ return "BusTopicBase [apiKey=" + apiKey + ", apiSecret=" + apiSecret + ", useHttps=" + useHttps
+ + ", allowSelfSignedCerts=" + allowSelfSignedCerts + ", toString()=" + super.toString() + "]";
+ }
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusTopicSink.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusTopicSink.java
new file mode 100644
index 00000000..54b08619
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusTopicSink.java
@@ -0,0 +1,42 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2017, 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2023-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import org.onap.policy.common.message.bus.event.TopicSink;
+
+/**
+ * Topic Sink over Bus Infrastructure (KAFKA).
+ */
+public interface BusTopicSink extends ApiKeyEnabled, TopicSink {
+
+ /**
+ * Sets the partition key for published messages.
+ *
+ * @param partitionKey the partition key
+ */
+ void setPartitionKey(String partitionKey);
+
+ /**
+ * Return the partition key in used by the system to publish messages.
+ *
+ * @return the partition key
+ */
+ String getPartitionKey();
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusTopicSource.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusTopicSource.java
new file mode 100644
index 00000000..974c02bb
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/BusTopicSource.java
@@ -0,0 +1,57 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import org.onap.policy.common.message.bus.event.TopicSource;
+
+/**
+ * Generic Topic Source for Bus Communication Infrastructure.
+ *
+ */
+public interface BusTopicSource extends ApiKeyEnabled, TopicSource {
+
+ /**
+ * Gets the consumer group.
+ *
+ * @return consumer group
+ */
+ public String getConsumerGroup();
+
+ /**
+ * Gets the consumer instance.
+ *
+ * @return consumer instance
+ */
+ public String getConsumerInstance();
+
+ /**
+ * Gets the fetch timeout.
+ *
+ * @return fetch timeout
+ */
+ public int getFetchTimeout();
+
+ /**
+ * Gets the fetch limit.
+ *
+ * @return fetch limit
+ */
+ public int getFetchLimit();
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/InlineBusTopicSink.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/InlineBusTopicSink.java
new file mode 100644
index 00000000..6a30f00f
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/InlineBusTopicSink.java
@@ -0,0 +1,165 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2017-2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2018-2019 Samsung Electronics Co., Ltd.
+ * Modifications Copyright (C) 2020 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2023-2024 Nordix Foundation.
+* ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import java.util.UUID;
+import lombok.Getter;
+import lombok.Setter;
+import org.onap.policy.common.message.bus.utils.NetLoggerUtil;
+import org.onap.policy.common.message.bus.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Transport Agnostic Bus Topic Sink to carry out the core functionality to interact with a sink.
+ *
+ */
+public abstract class InlineBusTopicSink extends BusTopicBase implements BusTopicSink {
+
+ /**
+ * Loggers.
+ */
+ private static final Logger logger = LoggerFactory.getLogger(InlineBusTopicSink.class);
+
+ /**
+ * The partition key to publish to.
+ */
+ @Getter
+ @Setter
+ protected String partitionKey;
+
+ /**
+ * Message bus publisher.
+ */
+ protected BusPublisher publisher;
+
+ /**
+ * Constructor for abstract sink.
+ * @param busTopicParams contains below listed attributes
+ * servers: servers
+ * topic: topic
+ * apiKey: api secret
+ * apiSecret: api secret
+ * partitionId: partition id
+ * useHttps: does connection use HTTPS?
+ * allowTracing: is tracing allowed?
+ * allowSelfSignedCerts: are self-signed certificates allow *
+ * @throws IllegalArgumentException if invalid parameters are passed in
+ */
+ protected InlineBusTopicSink(BusTopicParams busTopicParams) {
+
+ super(busTopicParams);
+
+ if (busTopicParams.isPartitionIdInvalid()) {
+ this.partitionKey = UUID.randomUUID().toString();
+ } else {
+ this.partitionKey = busTopicParams.getPartitionId();
+ }
+ }
+
+ /**
+ * Initialize the Bus publisher.
+ */
+ public abstract void init();
+
+ @Override
+ public boolean start() {
+ logger.info("{}: starting", this);
+
+ synchronized (this) {
+ if (!this.alive) {
+ if (locked) {
+ throw new IllegalStateException(this + " is locked.");
+ }
+
+ this.init();
+ this.alive = true;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean stop() {
+
+ BusPublisher publisherCopy;
+ synchronized (this) {
+ this.alive = false;
+ publisherCopy = this.publisher;
+ this.publisher = null;
+ }
+
+ if (publisherCopy != null) {
+ try {
+ publisherCopy.close();
+ } catch (Exception e) {
+ logger.warn("{}: cannot stop publisher because of {}", this, e.getMessage(), e);
+ }
+ } else {
+ logger.warn("{}: there is no publisher", this);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean send(String message) {
+
+ if (message == null || message.isEmpty()) {
+ throw new IllegalArgumentException("Message to send is empty");
+ }
+
+ if (!this.alive) {
+ throw new IllegalStateException(this + " is stopped");
+ }
+
+ try {
+ synchronized (this) {
+ this.recentEvents.add(message);
+ }
+
+ NetLoggerUtil.log(EventType.OUT, this.getTopicCommInfrastructure(), this.topic, message);
+
+ publisher.send(this.partitionKey, message);
+ broadcast(message);
+ } catch (Exception e) {
+ logger.warn("{}: cannot send because of {}", this, e.getMessage(), e);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void shutdown() {
+ this.stop();
+ }
+
+ @Override
+ public String toString() {
+ return "InlineBusTopicSink [partitionId=" + partitionKey + ", alive=" + alive + ", publisher=" + publisher
+ + "]";
+ }
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/SingleThreadedBusTopicSource.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/SingleThreadedBusTopicSource.java
new file mode 100644
index 00000000..912b698c
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/SingleThreadedBusTopicSource.java
@@ -0,0 +1,288 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2017-2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2018-2019 Samsung Electronics Co., Ltd.
+ * Modifications Copyright (C) 2020 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.NO_LIMIT_FETCH;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.NO_TIMEOUT_MS_FETCH;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.UUID;
+import lombok.Getter;
+import org.onap.policy.common.message.bus.event.TopicListener;
+import org.onap.policy.common.message.bus.utils.NetLoggerUtil;
+import org.onap.policy.common.message.bus.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+import org.onap.policy.common.utils.network.NetworkUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This topic source implementation specializes in reading messages over a bus topic source and
+ * notifying its listeners.
+ */
+public abstract class SingleThreadedBusTopicSource extends BusTopicBase
+ implements Runnable, BusTopicSource {
+
+ /**
+ * Not to be converted to PolicyLogger. This will contain all in/out traffic and only
+ * that in a single file in a concise format.
+ */
+ private static final Logger logger = LoggerFactory.getLogger(SingleThreadedBusTopicSource.class);
+
+ /**
+ * Bus consumer group.
+ */
+ @Getter
+ protected final String consumerGroup;
+
+ /**
+ * Bus consumer instance.
+ */
+ @Getter
+ protected final String consumerInstance;
+
+ /**
+ * Bus fetch timeout.
+ */
+ @Getter
+ protected final int fetchTimeout;
+
+ /**
+ * Bus fetch limit.
+ */
+ @Getter
+ protected final int fetchLimit;
+
+ /**
+ * Message Bus Consumer.
+ */
+ protected BusConsumer consumer;
+
+ /**
+ * Independent thread reading message over my topic.
+ */
+ protected Thread busPollerThread;
+
+
+ /**
+ * Constructor.
+ *
+ * @param busTopicParams topic parameters
+ *
+ * @throws IllegalArgumentException An invalid parameter passed in
+ */
+ protected SingleThreadedBusTopicSource(BusTopicParams busTopicParams) {
+
+ super(busTopicParams);
+
+ if (busTopicParams.isConsumerGroupInvalid() && busTopicParams.isConsumerInstanceInvalid()) {
+ this.consumerGroup = UUID.randomUUID().toString();
+ this.consumerInstance = NetworkUtil.getHostname();
+
+ } else if (busTopicParams.isConsumerGroupInvalid()) {
+ this.consumerGroup = UUID.randomUUID().toString();
+ this.consumerInstance = busTopicParams.getConsumerInstance();
+
+ } else if (busTopicParams.isConsumerInstanceInvalid()) {
+ this.consumerGroup = busTopicParams.getConsumerGroup();
+ this.consumerInstance = UUID.randomUUID().toString();
+
+ } else {
+ this.consumerGroup = busTopicParams.getConsumerGroup();
+ this.consumerInstance = busTopicParams.getConsumerInstance();
+ }
+
+ if (busTopicParams.getFetchTimeout() <= 0) {
+ this.fetchTimeout = NO_TIMEOUT_MS_FETCH;
+ } else {
+ this.fetchTimeout = busTopicParams.getFetchTimeout();
+ }
+
+ if (busTopicParams.getFetchLimit() <= 0) {
+ this.fetchLimit = NO_LIMIT_FETCH;
+ } else {
+ this.fetchLimit = busTopicParams.getFetchLimit();
+ }
+
+ }
+
+ /**
+ * Initialize the Bus client.
+ */
+ public abstract void init() throws MalformedURLException;
+
+ @Override
+ public void register(TopicListener topicListener) {
+
+ super.register(topicListener);
+
+ try {
+ if (!alive && !locked) {
+ this.start();
+ } else {
+ logger.info("{}: register: start not attempted", this);
+ }
+ } catch (Exception e) {
+ logger.warn("{}: cannot start after registration of because of: {}", this, topicListener, e);
+ }
+ }
+
+ @Override
+ public void unregister(TopicListener topicListener) {
+ boolean stop;
+ synchronized (this) {
+ super.unregister(topicListener);
+ stop = this.topicListeners.isEmpty();
+ }
+
+ if (stop) {
+ this.stop();
+ }
+ }
+
+ @Override
+ public boolean start() {
+ logger.info("{}: starting", this);
+
+ synchronized (this) {
+
+ if (alive) {
+ return true;
+ }
+
+ if (locked) {
+ throw new IllegalStateException(this + " is locked.");
+ }
+
+ if (this.busPollerThread == null || !this.busPollerThread.isAlive() || this.consumer == null) {
+
+ try {
+ this.init();
+ this.alive = true;
+ this.busPollerThread = makePollerThread();
+ this.busPollerThread.setName(this.getTopicCommInfrastructure() + "-source-" + this.getTopic());
+ busPollerThread.start();
+ return true;
+ } catch (Exception e) {
+ throw new IllegalStateException(this + ": cannot start", e);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Makes a new thread to be used for polling.
+ *
+ * @return a new Thread
+ */
+ protected Thread makePollerThread() {
+ return new Thread(this);
+ }
+
+ @Override
+ public boolean stop() {
+ logger.info("{}: stopping", this);
+
+ synchronized (this) {
+ BusConsumer consumerCopy = this.consumer;
+
+ this.alive = false;
+ this.consumer = null;
+
+ if (consumerCopy != null) {
+ try {
+ consumerCopy.close();
+ } catch (Exception e) {
+ logger.warn("{}: stop failed because of {}", this, e.getMessage(), e);
+ }
+ }
+ }
+
+ Thread.yield();
+
+ return true;
+ }
+
+ /**
+ * Run thread method for the Bus Reader.
+ */
+ @Override
+ public void run() {
+ while (this.alive) {
+ try {
+ fetchAllMessages();
+ } catch (IOException | RuntimeException e) {
+ logger.error("{}: cannot fetch", this, e);
+ }
+ }
+
+ logger.info("{}: exiting thread", this);
+ }
+
+ private void fetchAllMessages() throws IOException {
+ for (String event : this.consumer.fetch()) {
+ synchronized (this) {
+ this.recentEvents.add(event);
+ }
+
+ NetLoggerUtil.log(EventType.IN, this.getTopicCommInfrastructure(), this.topic, event);
+
+ broadcast(event);
+
+ if (!this.alive) {
+ return;
+ }
+ }
+ }
+
+ @Override
+ public boolean offer(String event) {
+ if (!this.alive) {
+ throw new IllegalStateException(this + " is not alive.");
+ }
+
+ synchronized (this) {
+ this.recentEvents.add(event);
+ }
+
+ NetLoggerUtil.log(EventType.IN, this.getTopicCommInfrastructure(), this.topic, event);
+
+ return broadcast(event);
+ }
+
+ @Override
+ public String toString() {
+ return "SingleThreadedBusTopicSource [consumerGroup=" + consumerGroup + ", consumerInstance=" + consumerInstance
+ + ", fetchTimeout=" + fetchTimeout + ", fetchLimit=" + fetchLimit + ", consumer=" + this.consumer
+ + ", alive=" + alive + ", locked=" + locked + ", uebThread=" + busPollerThread + ", topicListeners="
+ + topicListeners.size() + ", toString()=" + super.toString() + "]";
+ }
+
+ @Override
+ public void shutdown() {
+ this.stop();
+ this.topicListeners.clear();
+ }
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/TopicBase.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/TopicBase.java
new file mode 100644
index 00000000..4d1fbc9e
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/TopicBase.java
@@ -0,0 +1,243 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2017-2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2020 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2023-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AccessLevel;
+import lombok.Getter;
+import org.apache.commons.collections4.queue.CircularFifoQueue;
+import org.onap.policy.common.message.bus.event.Topic;
+import org.onap.policy.common.message.bus.event.TopicListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Getter
+public abstract class TopicBase implements Topic {
+
+ /**
+ * Logger.
+ */
+ private static final Logger logger = LoggerFactory.getLogger(TopicBase.class);
+
+ /**
+ * List of servers.
+ */
+ protected List<String> servers;
+
+ /**
+ * Topic.
+ */
+ protected final String topic;
+
+ /**
+ * Topic Alias.
+ */
+ protected final String effectiveTopic;
+
+ /**
+ * Event cache.
+ */
+ protected CircularFifoQueue<String> recentEvents = new CircularFifoQueue<>(10);
+
+ /**
+ * Am I running? reflects invocation of start()/stop() !locked & start() => alive stop() =>
+ * !alive.
+ */
+ protected volatile boolean alive = false;
+
+ /**
+ * Am I locked? reflects invocation of lock()/unlock() operations locked => !alive (but not in
+ * the other direction necessarily) locked => !offer, !run, !start, !stop (but this last one is
+ * obvious since locked => !alive).
+ */
+ protected volatile boolean locked = false;
+
+ /**
+ * All my subscribers for new message notifications.
+ */
+ @Getter(AccessLevel.NONE)
+ protected final ArrayList<TopicListener> topicListeners = new ArrayList<>();
+
+ /**
+ * Instantiates a new Topic Base.
+ *
+ * @param servers list of servers
+ * @param topic topic name
+ *
+ * @throws IllegalArgumentException if invalid parameters are present
+ */
+ protected TopicBase(List<String> servers, String topic) {
+ this(servers, topic, topic);
+ }
+
+ /**
+ * Instantiates a new Topic Base.
+ *
+ * @param servers list of servers
+ * @param topic topic name
+ *
+ * @throws IllegalArgumentException if invalid parameters are present
+ */
+ protected TopicBase(List<String> servers, String topic, String effectiveTopic) {
+
+ if (servers == null || servers.isEmpty()) {
+ throw new IllegalArgumentException("Server(s) must be provided");
+ }
+
+ if (topic == null || topic.isEmpty()) {
+ throw new IllegalArgumentException("A Topic must be provided");
+ }
+
+ String effectiveTopicCopy;
+ if (effectiveTopic == null || effectiveTopic.isEmpty()) {
+ effectiveTopicCopy = topic;
+ } else {
+ effectiveTopicCopy = effectiveTopic;
+ }
+
+ this.servers = servers;
+ this.topic = topic.toLowerCase();
+ this.effectiveTopic = effectiveTopicCopy.toLowerCase();
+ }
+
+ @Override
+ public void register(TopicListener topicListener) {
+
+ logger.info("{}: registering {}", this, topicListener);
+
+ synchronized (this) {
+ if (topicListener == null) {
+ throw new IllegalArgumentException("TopicListener must be provided");
+ }
+
+ for (TopicListener listener : this.topicListeners) {
+ if (listener == topicListener) {
+ return;
+ }
+ }
+
+ this.topicListeners.add(topicListener);
+ }
+ }
+
+ @Override
+ public void unregister(TopicListener topicListener) {
+
+ logger.info("{}: unregistering {}", this, topicListener);
+
+ synchronized (this) {
+ if (topicListener == null) {
+ throw new IllegalArgumentException("TopicListener must be provided");
+ }
+
+ this.topicListeners.remove(topicListener);
+ }
+ }
+
+ /**
+ * Broadcast event to all listeners.
+ *
+ * @param message the event
+ * @return true if all notifications are performed with no error, false otherwise
+ */
+ protected boolean broadcast(String message) {
+ List<TopicListener> snapshotListeners = this.snapshotTopicListeners();
+
+ var success = true;
+ for (TopicListener topicListener : snapshotListeners) {
+ try {
+ topicListener.onTopicEvent(this.getTopicCommInfrastructure(), this.topic, message);
+ } catch (Exception e) {
+ logger.warn("{}: notification error @ {} because of {}", this, topicListener, e.getMessage(), e);
+ success = false;
+ }
+ }
+ return success;
+ }
+
+ /**
+ * Take a snapshot of current topic listeners.
+ *
+ * @return the topic listeners
+ */
+ protected synchronized List<TopicListener> snapshotTopicListeners() {
+ @SuppressWarnings("unchecked")
+ List<TopicListener> listeners = (List<TopicListener>) topicListeners.clone();
+ return listeners;
+ }
+
+ @Override
+ public boolean lock() {
+
+ logger.info("{}: locking", this);
+
+ synchronized (this) {
+ if (this.locked) {
+ return true;
+ }
+
+ this.locked = true;
+ }
+
+ return this.stop();
+ }
+
+ @Override
+ public boolean unlock() {
+ logger.info("{}: unlocking", this);
+
+ synchronized (this) {
+ if (!this.locked) {
+ return true;
+ }
+
+ this.locked = false;
+ }
+
+ try {
+ return this.start();
+ } catch (Exception e) {
+ logger.warn("{}: cannot after unlocking because of {}", this, e.getMessage(), e);
+ return false;
+ }
+ }
+
+ @Override
+ public synchronized String[] getRecentEvents() {
+ var events = new String[recentEvents.size()];
+ return recentEvents.toArray(events);
+ }
+
+
+ @Override
+ public String toString() {
+ return "TopicBase [servers=" + servers
+ + ", topic=" + topic
+ + ", effectiveTopic=" + effectiveTopic
+ + ", #recentEvents=" + recentEvents.size()
+ + ", locked=" + locked
+ + ", #topicListeners=" + topicListeners.size()
+ + "]";
+ }
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/TopicBaseFactory.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/TopicBaseFactory.java
new file mode 100644
index 00000000..d98de653
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/TopicBaseFactory.java
@@ -0,0 +1,87 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import java.util.List;
+import java.util.Properties;
+import org.onap.policy.common.message.bus.event.Topic;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+
+/**
+ * Topic Base Factory.
+ *
+ * @param <T> Type.
+ */
+public interface TopicBaseFactory<T extends Topic> {
+
+ /**
+ * build a TopicBase instance.
+ *
+ * @param properties properties.
+ * @return T instance.
+ */
+ List<T> build(Properties properties);
+
+ /**
+ * build a TopicBase instance.
+ *
+ * @param servers servers.
+ * @param topic topic.
+ * @param managed managed.
+ * @return T instance.
+ */
+ T build(List<String> servers, String topic, boolean managed);
+
+ /**
+ * Construct an instance of an endpoint.
+ *
+ * @param param parameters
+ * @return an instance of T.
+ */
+ T build(BusTopicParams param);
+
+ /**
+ * destroy TopicBase instance.
+ * @param topic topic.
+ */
+ void destroy(String topic);
+
+ /**
+ * destroy.
+ */
+ void destroy();
+
+ /**
+ * get T instance.
+ *
+ * @param topic topic.
+ * @return T instance.
+ */
+ T get(String topic);
+
+ /**
+ * inventory of T instances.
+ *
+ * @return T instance list.
+ */
+ List<T> inventory();
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/TopicBaseHashedFactory.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/TopicBaseHashedFactory.java
new file mode 100644
index 00000000..70ff04e4
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/base/TopicBaseHashedFactory.java
@@ -0,0 +1,207 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Properties;
+import org.onap.policy.common.message.bus.event.Topic;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+
+/**
+ * Topic Factory implementation that indexes T instances in a hash table.
+ */
+public abstract class TopicBaseHashedFactory<T extends Topic> implements TopicBaseFactory<T> {
+
+ protected static final String MISSING_TOPIC_MESSAGE = "A topic must be provided";
+ protected static final String MISSING_SERVERS_MESSAGE = "Servers must be provided";
+
+ /**
+ * endpoints.
+ */
+ protected final HashMap<String, T> endpoints = new HashMap<>();
+
+ /**
+ * get the topic names.
+ *
+ * @param properties properties.
+ * @return list of topic names.
+ */
+ protected abstract List<String> getTopicNames(Properties properties);
+
+ /**
+ * get the servers that this topic uses.
+ *
+ * @param topicName name.
+ * @param properties properties.
+ * @return list of servers.
+ */
+ protected abstract List<String> getServers(String topicName, Properties properties);
+
+ /**
+ * Determines if this topic is managed.
+ *
+ * @param topicName name.
+ * @param properties properties.
+ * @return if managed.
+ */
+ protected abstract boolean isManaged(String topicName, Properties properties);
+
+ /**
+ * construct an instance of an endpoint.
+ *
+ * @param servers servers,
+ * @param topic topic.
+ * @return an instance of T.
+ */
+ public abstract T build(List<String> servers, String topic);
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public List<T> build(Properties properties) {
+ List<String> topicNames = getTopicNames(properties);
+ if (topicNames == null || topicNames.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<T> newEndpoints = new ArrayList<>();
+ synchronized (this) {
+ for (String name : topicNames) {
+ if (this.endpoints.containsKey(name)) {
+ newEndpoints.add(this.endpoints.get(name));
+ continue;
+ }
+
+ newEndpoints.add(this.build(getServers(name, properties), name, isManaged(name, properties)));
+ }
+ }
+ return newEndpoints;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public T build(BusTopicParams param) {
+ return this.build(param.getServers(), param.getTopic(), param.isManaged());
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public T build(List<String> servers, String topic, boolean managed) {
+ if (servers == null || servers.isEmpty()) {
+ throw new IllegalArgumentException(MISSING_SERVERS_MESSAGE);
+ }
+
+ if (topic == null || topic.isEmpty()) {
+ throw new IllegalArgumentException(MISSING_TOPIC_MESSAGE);
+ }
+
+ synchronized (this) {
+ if (this.endpoints.containsKey(topic)) {
+ return this.endpoints.get(topic);
+ }
+
+ var endpoint = build(servers, topic);
+ if (managed) {
+ this.endpoints.put(topic, endpoint);
+ }
+
+ return endpoint;
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void destroy(String topic) {
+ if (topic == null || topic.isEmpty()) {
+ throw new IllegalArgumentException(MISSING_TOPIC_MESSAGE);
+ }
+
+ T endpoint;
+ synchronized (this) {
+ if (!this.endpoints.containsKey(topic)) {
+ return;
+ }
+
+ endpoint = this.endpoints.remove(topic);
+ }
+ endpoint.shutdown();
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void destroy() {
+ final List<T> snapshotEndpoints = this.inventory();
+ for (final T snapshot : snapshotEndpoints) {
+ snapshot.shutdown();
+ }
+
+ synchronized (this) {
+ this.endpoints.clear();
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public T get(String topic) {
+ if (topic == null || topic.isEmpty()) {
+ throw new IllegalArgumentException(MISSING_TOPIC_MESSAGE);
+ }
+
+ synchronized (this) {
+ if (this.endpoints.containsKey(topic)) {
+ return this.endpoints.get(topic);
+ } else {
+ throw new IllegalStateException(topic + " not found");
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public List<T> inventory() {
+ return new ArrayList<>(this.endpoints.values());
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public String toString() {
+ return "TopicBaseHashedFactory[ " + super.toString() + " ]";
+ }
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/BidirectionalTopicClient.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/BidirectionalTopicClient.java
new file mode 100644
index 00000000..6ffc188d
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/BidirectionalTopicClient.java
@@ -0,0 +1,222 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2023-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.client;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.BlockingDeque;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.TimeUnit;
+import lombok.Getter;
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.event.TopicEndpoint;
+import org.onap.policy.common.message.bus.event.TopicEndpointManager;
+import org.onap.policy.common.message.bus.event.TopicListener;
+import org.onap.policy.common.message.bus.event.TopicSink;
+import org.onap.policy.common.message.bus.event.TopicSource;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A "bidirectional" topic, which is a pair of topics, one of which is used to publish
+ * requests and the other to receive responses.
+ */
+@Getter
+public class BidirectionalTopicClient {
+ private static final Logger logger = LoggerFactory.getLogger(BidirectionalTopicClient.class);
+ private static final Coder coder = new StandardCoder();
+
+ private final String sinkTopic;
+ private final String sourceTopic;
+ private final TopicSink sink;
+ private final TopicSource source;
+ private final CommInfrastructure sinkTopicCommInfrastructure;
+ private final CommInfrastructure sourceTopicCommInfrastructure;
+
+ /**
+ * Used when checking whether a message sent on the sink topic can be received
+ * on the source topic. When a matching message is received on the incoming topic,
+ * {@code true} is placed on the queue. If {@link #stopWaiting()} is called or the waiting
+ * thread is interrupted, then {@code false} is placed on the queue. Whenever a value
+ * is pulled from the queue, it is immediately placed back on the queue.
+ */
+ private final BlockingDeque<Boolean> checkerQueue = new LinkedBlockingDeque<>();
+
+
+ /**
+ * Constructs the object.
+ *
+ * @param sinkTopic sink topic name
+ * @param sourceTopic source topic name
+ * @throws BidirectionalTopicClientException if either topic does not exist
+ */
+ public BidirectionalTopicClient(String sinkTopic, String sourceTopic) throws BidirectionalTopicClientException {
+ this.sinkTopic = sinkTopic.toLowerCase();
+ this.sourceTopic = sourceTopic.toLowerCase();
+
+ // init sinkClient
+ List<TopicSink> sinks = getTopicEndpointManager().getTopicSinks(sinkTopic);
+ if (sinks.isEmpty()) {
+ throw new BidirectionalTopicClientException("no sinks for topic: " + sinkTopic);
+ } else if (sinks.size() > 1) {
+ throw new BidirectionalTopicClientException("too many sinks for topic: " + sinkTopic);
+ }
+
+ this.sink = sinks.get(0);
+
+ // init source
+ List<TopicSource> sources = getTopicEndpointManager().getTopicSources(Collections.singletonList(sourceTopic));
+ if (sources.isEmpty()) {
+ throw new BidirectionalTopicClientException("no sources for topic: " + sourceTopic);
+ } else if (sources.size() > 1) {
+ throw new BidirectionalTopicClientException("too many sources for topic: " + sourceTopic);
+ }
+
+ this.source = sources.get(0);
+
+ this.sinkTopicCommInfrastructure = sink.getTopicCommInfrastructure();
+ this.sourceTopicCommInfrastructure = source.getTopicCommInfrastructure();
+ }
+
+ public boolean send(String message) {
+ return sink.send(message);
+ }
+
+ public void register(TopicListener topicListener) {
+ source.register(topicListener);
+ }
+
+ public boolean offer(String event) {
+ return source.offer(event);
+ }
+
+ public void unregister(TopicListener topicListener) {
+ source.unregister(topicListener);
+ }
+
+ /**
+ * Determines whether the topic is ready (i.e., {@link #awaitReady(Object, long)} has
+ * previously returned {@code true}).
+ *
+ * @return {@code true}, if the topic is ready to send and receive
+ */
+ public boolean isReady() {
+ return Boolean.TRUE.equals(checkerQueue.peek());
+ }
+
+ /**
+ * Waits for the bidirectional topic to become "ready" by publishing a message on the
+ * sink topic and awaiting receipt of the message on the source topic. If the message
+ * is not received within a few seconds, then it tries again. This process is
+ * continued until the message is received, {@link #stopWaiting()} is called, or this thread
+ * is interrupted. Once this returns, subsequent calls will return immediately, always
+ * with the same value.
+ *
+ * @param message message to be sent to the sink topic. Note: the equals() method must
+ * return {@code true} if and only if two messages are the same
+ * @param waitMs time to wait, in milliseconds, before re-sending the message
+ * @return {@code true} if the message was received from the source topic,
+ * {@code false} if this method was stopped or interrupted before receipt of
+ * the message
+ * @throws CoderException if the message cannot be encoded
+ */
+ public synchronized <T> boolean awaitReady(T message, long waitMs) throws CoderException {
+ // see if we already know the answer
+ if (!checkerQueue.isEmpty()) {
+ return checkerQueue.peek();
+ }
+
+ final String messageText = coder.encode(message);
+
+ // class of message to be decoded
+ final TopicListener listener = getTopicListener(message);
+
+ source.register(listener);
+
+ // loop until the message is received
+ try {
+ Boolean result;
+ do {
+ send(messageText);
+ } while ((result = checkerQueue.poll(waitMs, TimeUnit.MILLISECONDS)) == null);
+
+ // put it back on the queue
+ checkerQueue.add(result);
+
+ } catch (InterruptedException e) {
+ logger.error("interrupted waiting for topic sink {} source {}", sink.getTopic(), source.getTopic(), e);
+ Thread.currentThread().interrupt();
+ checkerQueue.add(Boolean.FALSE);
+
+ } finally {
+ source.unregister(listener);
+ }
+
+ return checkerQueue.peek();
+ }
+
+ private <T> TopicListener getTopicListener(T message) {
+ @SuppressWarnings("unchecked")
+ final Class<? extends T> clazz = (Class<? extends T>) message.getClass();
+
+ // create a listener to detect when a matching message is received
+ return (infra, topic, msg) -> {
+ try {
+ T incoming = decode(msg, clazz);
+
+ if (message.equals(incoming)) {
+ logger.info("topic {} is ready; found matching message {}", topic, incoming);
+ checkerQueue.add(Boolean.TRUE);
+ }
+
+ } catch (CoderException e) {
+ logger.warn("cannot decode message from topic {}", topic, e);
+ decodeFailed();
+ }
+ };
+ }
+
+ /**
+ * Stops any listeners that are currently stuck in {@link #awaitReady(Object, long)} by
+ * adding {@code false} to the queue.
+ */
+ public void stopWaiting() {
+ checkerQueue.add(Boolean.FALSE);
+ }
+
+ // these may be overridden by junit tests
+
+ protected TopicEndpoint getTopicEndpointManager() {
+ return TopicEndpointManager.getManager();
+ }
+
+ protected <T> T decode(String msg, Class<? extends T> clazz) throws CoderException {
+ return coder.decode(msg, clazz);
+ }
+
+ protected void decodeFailed() {
+ // already logged - nothing else to do
+ }
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/BidirectionalTopicClientException.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/BidirectionalTopicClientException.java
new file mode 100644
index 00000000..442c5243
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/BidirectionalTopicClientException.java
@@ -0,0 +1,53 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2023-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.client;
+
+import java.io.Serial;
+
+/**
+ * Exception thrown by BidirectionalTopicClient class.
+ */
+public class BidirectionalTopicClientException extends Exception {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public BidirectionalTopicClientException() {
+ super();
+ }
+
+ public BidirectionalTopicClientException(String message) {
+ super(message);
+ }
+
+ public BidirectionalTopicClientException(Throwable cause) {
+ super(cause);
+ }
+
+ public BidirectionalTopicClientException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public BidirectionalTopicClientException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/TopicSinkClient.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/TopicSinkClient.java
new file mode 100644
index 00000000..131bf2d7
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/TopicSinkClient.java
@@ -0,0 +1,114 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2019, 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.client;
+
+import java.util.List;
+import lombok.Getter;
+import lombok.NonNull;
+import org.onap.policy.common.message.bus.event.TopicEndpointManager;
+import org.onap.policy.common.message.bus.event.TopicSink;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Client for sending messages to a Topic using TopicSink.
+ */
+@Getter
+public class TopicSinkClient {
+ private static final Logger logger = LoggerFactory.getLogger(TopicSinkClient.class);
+
+ /**
+ * Coder used to encode messages being sent to the topic.
+ */
+ private static final Coder CODER = new StandardCoder();
+
+ /**
+ * Where messages are published.
+ */
+ private final TopicSink sink;
+
+ /**
+ * Constructs the object.
+ *
+ * @param topic topic to which messages should be published
+ * @throws TopicSinkClientException if the topic does not exist
+ */
+ public TopicSinkClient(final String topic) throws TopicSinkClientException {
+ final List<TopicSink> lst = getTopicSinks(topic.toLowerCase());
+ if (lst.isEmpty()) {
+ throw new TopicSinkClientException("no sinks for topic: " + topic.toLowerCase());
+ }
+
+ this.sink = lst.get(0);
+ }
+
+ /**
+ * Constructs the client from a sink object.
+ *
+ * @param sink topic sink publisher
+ */
+ public TopicSinkClient(@NonNull TopicSink sink) {
+ this.sink = sink;
+ }
+
+
+ /**
+ * Gets the canonical topic name.
+ *
+ * @return topic name
+ */
+ public String getTopic() {
+ return this.sink.getTopic();
+ }
+
+ /**
+ * Sends a message to the topic, after encoding the message as json.
+ *
+ * @param message message to be encoded and sent
+ * @return {@code true} if the message was successfully sent/enqueued, {@code false} otherwise
+ */
+ public boolean send(final Object message) {
+ try {
+ final String json = CODER.encode(message);
+ return sink.send(json);
+
+ } catch (RuntimeException | CoderException e) {
+ logger.warn("send to {} failed because of {}", sink.getTopic(), e.getMessage(), e);
+ return false;
+ }
+ }
+
+ // the remaining methods are wrappers that can be overridden by junit tests
+
+ /**
+ * Gets the sinks for a given topic.
+ *
+ * @param topic the topic of interest
+ * @return the sinks for the topic
+ */
+ protected List<TopicSink> getTopicSinks(final String topic) {
+ return TopicEndpointManager.getManager().getTopicSinks(topic);
+ }
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/TopicSinkClientException.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/TopicSinkClientException.java
new file mode 100644
index 00000000..fad5e119
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/client/TopicSinkClientException.java
@@ -0,0 +1,54 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2019, 2023-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.client;
+
+import java.io.Serial;
+
+/**
+ * Exception thrown by TopicSink client classes.
+ */
+public class TopicSinkClientException extends Exception {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ public TopicSinkClientException() {
+ super();
+ }
+
+ public TopicSinkClientException(final String message) {
+ super(message);
+ }
+
+ public TopicSinkClientException(final Throwable cause) {
+ super(cause);
+ }
+
+ public TopicSinkClientException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ public TopicSinkClientException(final String message, final Throwable cause, final boolean enableSuppression,
+ final boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/IndexedKafkaTopicSinkFactory.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/IndexedKafkaTopicSinkFactory.java
new file mode 100644
index 00000000..0497f1f5
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/IndexedKafkaTopicSinkFactory.java
@@ -0,0 +1,201 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_KAFKA_SINK_TOPICS;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_SERVERS_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_SINK_PARTITION_KEY_SUFFIX;
+
+import com.google.re2j.Pattern;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Properties;
+import org.apache.commons.lang3.StringUtils;
+import org.onap.policy.common.message.bus.utils.KafkaPropertyUtils;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+import org.onap.policy.common.utils.properties.PropertyUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory of KAFKA Reader Topics indexed by topic name.
+ */
+class IndexedKafkaTopicSinkFactory implements KafkaTopicSinkFactory {
+ private static final Pattern COMMA_SPACE_PAT = Pattern.compile("\\s*,\\s*");
+ private static final String MISSING_TOPIC = "A topic must be provided";
+
+ /**
+ * Logger.
+ */
+ private static final Logger logger = LoggerFactory.getLogger(IndexedKafkaTopicSinkFactory.class);
+
+ /**
+ * KAFKA Topic Name Index.
+ */
+ protected HashMap<String, KafkaTopicSink> kafkaTopicSinks = new HashMap<>();
+
+ @Override
+ public KafkaTopicSink build(BusTopicParams busTopicParams) {
+
+ if (busTopicParams.getServers() == null || busTopicParams.getServers().isEmpty()) {
+ throw new IllegalArgumentException("KAFKA Server(s) must be provided");
+ }
+
+ if (StringUtils.isBlank(busTopicParams.getTopic())) {
+ throw new IllegalArgumentException(MISSING_TOPIC);
+ }
+
+ synchronized (this) {
+ if (kafkaTopicSinks.containsKey(busTopicParams.getTopic())) {
+ return kafkaTopicSinks.get(busTopicParams.getTopic());
+ }
+
+ KafkaTopicSink kafkaTopicWriter = makeSink(busTopicParams);
+ if (busTopicParams.isManaged()) {
+ kafkaTopicSinks.put(busTopicParams.getTopic(), kafkaTopicWriter);
+ }
+
+ return kafkaTopicWriter;
+ }
+ }
+
+
+ @Override
+ public KafkaTopicSink build(List<String> servers, String topic) {
+ return this.build(BusTopicParams.builder()
+ .servers(servers)
+ .topic(topic)
+ .managed(true)
+ .useHttps(false)
+ .build());
+ }
+
+
+ @Override
+ public List<KafkaTopicSink> build(Properties properties) {
+
+ String writeTopics = properties.getProperty(PROPERTY_KAFKA_SINK_TOPICS);
+ if (StringUtils.isBlank(writeTopics)) {
+ logger.info("{}: no topic for KAFKA Sink", this);
+ return new ArrayList<>();
+ }
+
+ List<KafkaTopicSink> newKafkaTopicSinks = new ArrayList<>();
+ synchronized (this) {
+ for (String topic : COMMA_SPACE_PAT.split(writeTopics)) {
+ addTopic(newKafkaTopicSinks, topic.toLowerCase(), properties);
+ }
+ return newKafkaTopicSinks;
+ }
+ }
+
+ private void addTopic(List<KafkaTopicSink> newKafkaTopicSinks, String topic, Properties properties) {
+ if (this.kafkaTopicSinks.containsKey(topic)) {
+ newKafkaTopicSinks.add(this.kafkaTopicSinks.get(topic));
+ return;
+ }
+
+ String topicPrefix = PROPERTY_KAFKA_SINK_TOPICS + "." + topic;
+
+ var props = new PropertyUtils(properties, topicPrefix,
+ (name, value, ex) -> logger.warn("{}: {} {} is in invalid format for topic sink {} ",
+ this, name, value, topic));
+
+ String servers = properties.getProperty(topicPrefix + PROPERTY_TOPIC_SERVERS_SUFFIX);
+ if (StringUtils.isBlank(servers)) {
+ logger.error("{}: no KAFKA servers configured for sink {}", this, topic);
+ return;
+ }
+
+ KafkaTopicSink kafkaTopicWriter = this.build(KafkaPropertyUtils.makeBuilder(props, topic, servers)
+ .partitionId(props.getString(PROPERTY_TOPIC_SINK_PARTITION_KEY_SUFFIX, null))
+ .build());
+ newKafkaTopicSinks.add(kafkaTopicWriter);
+ }
+
+ @Override
+ public void destroy(String topic) {
+
+ if (topic == null || topic.isEmpty()) {
+ throw new IllegalArgumentException(MISSING_TOPIC);
+ }
+
+ KafkaTopicSink kafkaTopicWriter;
+ synchronized (this) {
+ if (!kafkaTopicSinks.containsKey(topic)) {
+ return;
+ }
+
+ kafkaTopicWriter = kafkaTopicSinks.remove(topic);
+ }
+
+ kafkaTopicWriter.shutdown();
+ }
+
+ @Override
+ public void destroy() {
+ List<KafkaTopicSink> writers = this.inventory();
+ for (KafkaTopicSink writer : writers) {
+ writer.shutdown();
+ }
+
+ synchronized (this) {
+ this.kafkaTopicSinks.clear();
+ }
+ }
+
+ @Override
+ public KafkaTopicSink get(String topic) {
+
+ if (topic == null || topic.isEmpty()) {
+ throw new IllegalArgumentException(MISSING_TOPIC);
+ }
+
+ synchronized (this) {
+ if (kafkaTopicSinks.containsKey(topic)) {
+ return kafkaTopicSinks.get(topic);
+ } else {
+ throw new IllegalStateException("KafkaTopicSink for " + topic + " not found");
+ }
+ }
+ }
+
+ @Override
+ public synchronized List<KafkaTopicSink> inventory() {
+ return new ArrayList<>(this.kafkaTopicSinks.values());
+ }
+
+ /**
+ * Makes a new sink.
+ *
+ * @param busTopicParams parameters to use to configure the sink
+ * @return a new sink
+ */
+ protected KafkaTopicSink makeSink(BusTopicParams busTopicParams) {
+ return new InlineKafkaTopicSink(busTopicParams);
+ }
+
+
+ @Override
+ public String toString() {
+ return "IndexedKafkaTopicSinkFactory " + kafkaTopicSinks.keySet();
+ }
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/IndexedKafkaTopicSourceFactory.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/IndexedKafkaTopicSourceFactory.java
new file mode 100644
index 00000000..1aac89ce
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/IndexedKafkaTopicSourceFactory.java
@@ -0,0 +1,211 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.DEFAULT_LIMIT_FETCH;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.DEFAULT_TIMEOUT_MS_FETCH;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_KAFKA_SOURCE_TOPICS;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_SERVERS_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_SOURCE_CONSUMER_GROUP_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_SOURCE_CONSUMER_INSTANCE_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_SOURCE_FETCH_LIMIT_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_SOURCE_FETCH_TIMEOUT_SUFFIX;
+
+import com.google.re2j.Pattern;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Properties;
+import org.apache.commons.lang3.StringUtils;
+import org.onap.policy.common.message.bus.utils.KafkaPropertyUtils;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+import org.onap.policy.common.utils.properties.PropertyUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory of KAFKA Source Topics indexed by topic name.
+ */
+class IndexedKafkaTopicSourceFactory implements KafkaTopicSourceFactory {
+ private static final Pattern COMMA_SPACE_PAT = Pattern.compile("\\s*,\\s*");
+ private static final String MISSING_TOPIC = "A topic must be provided";
+
+ /**
+ * Logger.
+ */
+ private static final Logger logger = LoggerFactory.getLogger(IndexedKafkaTopicSourceFactory.class);
+
+ /**
+ * KAFKA Topic Name Index.
+ */
+ protected HashMap<String, KafkaTopicSource> kafkaTopicSources = new HashMap<>();
+
+ @Override
+ public KafkaTopicSource build(BusTopicParams busTopicParams) {
+ if (busTopicParams.getServers() == null || busTopicParams.getServers().isEmpty()) {
+ throw new IllegalArgumentException("KAFKA Server(s) must be provided");
+ }
+
+ if (busTopicParams.getTopic() == null || busTopicParams.getTopic().isEmpty()) {
+ throw new IllegalArgumentException(MISSING_TOPIC);
+ }
+
+ synchronized (this) {
+ if (kafkaTopicSources.containsKey(busTopicParams.getTopic())) {
+ return kafkaTopicSources.get(busTopicParams.getTopic());
+ }
+
+ var kafkaTopicSource = makeSource(busTopicParams);
+
+ kafkaTopicSources.put(busTopicParams.getTopic(), kafkaTopicSource);
+
+ return kafkaTopicSource;
+ }
+ }
+
+ @Override
+ public List<KafkaTopicSource> build(Properties properties) {
+
+ String readTopics = properties.getProperty(PROPERTY_KAFKA_SOURCE_TOPICS);
+ if (StringUtils.isBlank(readTopics)) {
+ logger.info("{}: no topic for KAFKA Source", this);
+ return new ArrayList<>();
+ }
+
+ List<KafkaTopicSource> newKafkaTopicSources = new ArrayList<>();
+ synchronized (this) {
+ for (String topic : COMMA_SPACE_PAT.split(readTopics)) {
+ addTopic(newKafkaTopicSources, topic.toLowerCase(), properties);
+ }
+ }
+ return newKafkaTopicSources;
+ }
+
+ @Override
+ public KafkaTopicSource build(List<String> servers, String topic) {
+ return this.build(BusTopicParams.builder()
+ .servers(servers)
+ .topic(topic)
+ .managed(true)
+ .fetchTimeout(DEFAULT_TIMEOUT_MS_FETCH)
+ .fetchLimit(DEFAULT_LIMIT_FETCH)
+ .useHttps(false).build());
+ }
+
+ private void addTopic(List<KafkaTopicSource> newKafkaTopicSources, String topic, Properties properties) {
+ if (this.kafkaTopicSources.containsKey(topic)) {
+ newKafkaTopicSources.add(this.kafkaTopicSources.get(topic));
+ return;
+ }
+
+ String topicPrefix = PROPERTY_KAFKA_SOURCE_TOPICS + "." + topic;
+
+ var props = new PropertyUtils(properties, topicPrefix,
+ (name, value, ex) -> logger.warn("{}: {} {} is in invalid format for topic source {} ",
+ this, name, value, topic));
+
+ String servers = properties.getProperty(topicPrefix + PROPERTY_TOPIC_SERVERS_SUFFIX);
+ if (StringUtils.isBlank(servers)) {
+ logger.error("{}: no KAFKA servers configured for source {}", this, topic);
+ return;
+ }
+
+ var kafkaTopicSource = this.build(KafkaPropertyUtils.makeBuilder(props, topic, servers)
+ .consumerGroup(props.getString(
+ PROPERTY_TOPIC_SOURCE_CONSUMER_GROUP_SUFFIX, null))
+ .consumerInstance(props.getString(
+ PROPERTY_TOPIC_SOURCE_CONSUMER_INSTANCE_SUFFIX, null))
+ .fetchTimeout(props.getInteger(
+ PROPERTY_TOPIC_SOURCE_FETCH_TIMEOUT_SUFFIX,
+ DEFAULT_TIMEOUT_MS_FETCH))
+ .fetchLimit(props.getInteger(PROPERTY_TOPIC_SOURCE_FETCH_LIMIT_SUFFIX,
+ DEFAULT_LIMIT_FETCH))
+ .build());
+
+ newKafkaTopicSources.add(kafkaTopicSource);
+ }
+
+ /**
+ * Makes a new source.
+ *
+ * @param busTopicParams parameters to use to configure the source
+ * @return a new source
+ */
+ protected KafkaTopicSource makeSource(BusTopicParams busTopicParams) {
+ return new SingleThreadedKafkaTopicSource(busTopicParams);
+ }
+
+ @Override
+ public void destroy(String topic) {
+
+ if (topic == null || topic.isEmpty()) {
+ throw new IllegalArgumentException(MISSING_TOPIC);
+ }
+
+ KafkaTopicSource kafkaTopicSource;
+
+ synchronized (this) {
+ if (!kafkaTopicSources.containsKey(topic)) {
+ return;
+ }
+
+ kafkaTopicSource = kafkaTopicSources.remove(topic);
+ }
+
+ kafkaTopicSource.shutdown();
+ }
+
+ @Override
+ public void destroy() {
+ List<KafkaTopicSource> readers = this.inventory();
+ for (KafkaTopicSource reader : readers) {
+ reader.shutdown();
+ }
+
+ synchronized (this) {
+ this.kafkaTopicSources.clear();
+ }
+ }
+
+ @Override
+ public KafkaTopicSource get(String topic) {
+
+ if (topic == null || topic.isEmpty()) {
+ throw new IllegalArgumentException(MISSING_TOPIC);
+ }
+
+ synchronized (this) {
+ if (kafkaTopicSources.containsKey(topic)) {
+ return kafkaTopicSources.get(topic);
+ } else {
+ throw new IllegalStateException("KafkaTopiceSource for " + topic + " not found");
+ }
+ }
+ }
+
+ @Override
+ public synchronized List<KafkaTopicSource> inventory() {
+ return new ArrayList<>(this.kafkaTopicSources.values());
+ }
+
+ @Override
+ public String toString() {
+ return "IndexedKafkaTopicSourceFactory " + kafkaTopicSources.keySet();
+ }
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/InlineKafkaTopicSink.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/InlineKafkaTopicSink.java
new file mode 100644
index 00000000..4bdd2b0f
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/InlineKafkaTopicSink.java
@@ -0,0 +1,83 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import java.util.Map;
+import org.onap.policy.common.message.bus.event.Topic;
+import org.onap.policy.common.message.bus.event.base.InlineBusTopicSink;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This implementation publishes events for the associated KAFKA topic, inline with the calling
+ * thread.
+ */
+public class InlineKafkaTopicSink extends InlineBusTopicSink implements KafkaTopicSink {
+
+ /**
+ * Logger.
+ */
+ private static final Logger logger = LoggerFactory.getLogger(InlineKafkaTopicSink.class);
+
+ protected Map<String, String> additionalProps;
+
+ /**
+ * Argument-based KAFKA Topic Writer instantiation. BusTopicParams contains the below
+ * attributes.
+ *
+ * <p>servers list of KAFKA servers available for publishing
+ * topic the topic to publish to
+ * partitionId the partition key (optional, autogenerated if not provided)
+ * useHttps does connection use HTTPS?
+ * @param busTopicParams contains attributes needed
+ * @throws IllegalArgumentException if invalid arguments are detected
+ */
+ public InlineKafkaTopicSink(BusTopicParams busTopicParams) {
+ super(busTopicParams);
+ this.additionalProps = busTopicParams.getAdditionalProps();
+ }
+
+ /**
+ * Instantiation of internal resources.
+ */
+ @Override
+ public void init() {
+
+ this.publisher = new KafkaPublisherWrapper(BusTopicParams.builder()
+ .servers(this.servers)
+ .topic(this.effectiveTopic)
+ .useHttps(this.useHttps)
+ .allowTracing(this.allowTracing)
+ .additionalProps(this.additionalProps)
+ .build());
+ logger.info("{}: KAFKA SINK created", this);
+ }
+
+ @Override
+ public String toString() {
+ return "InlineKafkaTopicSink [getTopicCommInfrastructure()=" + getTopicCommInfrastructure() + ", toString()="
+ + super.toString() + "]";
+ }
+
+ @Override
+ public CommInfrastructure getTopicCommInfrastructure() {
+ return Topic.CommInfrastructure.KAFKA;
+ }
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaPublisherWrapper.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaPublisherWrapper.java
new file mode 100644
index 00000000..86b9e936
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaPublisherWrapper.java
@@ -0,0 +1,121 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import io.opentelemetry.instrumentation.kafkaclients.v2_6.TracingProducerInterceptor;
+import java.util.Properties;
+import java.util.UUID;
+import org.apache.kafka.clients.producer.KafkaProducer;
+import org.apache.kafka.clients.producer.Producer;
+import org.apache.kafka.clients.producer.ProducerConfig;
+import org.apache.kafka.clients.producer.ProducerRecord;
+import org.onap.policy.common.message.bus.event.base.BusPublisher;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Kafka based library publisher.
+ */
+public class KafkaPublisherWrapper implements BusPublisher {
+
+ private static final Logger logger = LoggerFactory.getLogger(KafkaPublisherWrapper.class);
+ private static final String KEY_SERIALIZER = "org.apache.kafka.common.serialization.StringSerializer";
+
+ private final String topic;
+
+ /**
+ * Kafka publisher.
+ */
+ private final Producer<String, String> producer;
+ protected Properties kafkaProps;
+
+ /**
+ * Kafka Publisher Wrapper.
+ *
+ * @param busTopicParams topic parameters
+ */
+ public KafkaPublisherWrapper(BusTopicParams busTopicParams) {
+
+ if (busTopicParams.isTopicInvalid()) {
+ throw new IllegalArgumentException("No topic for Kafka");
+ }
+
+ this.topic = busTopicParams.getTopic();
+
+ // Setup Properties for consumer
+ kafkaProps = new Properties();
+ kafkaProps.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, busTopicParams.getServers().get(0));
+ if (busTopicParams.isAdditionalPropsValid()) {
+ kafkaProps.putAll(busTopicParams.getAdditionalProps());
+ }
+
+ if (kafkaProps.get(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG) == null) {
+ kafkaProps.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, KEY_SERIALIZER);
+ }
+
+ if (kafkaProps.get(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG) == null) {
+ kafkaProps.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KEY_SERIALIZER);
+ }
+
+ if (busTopicParams.isAllowTracing()) {
+ kafkaProps.setProperty(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,
+ TracingProducerInterceptor.class.getName());
+ }
+
+ producer = new KafkaProducer<>(kafkaProps);
+ }
+
+ @Override
+ public boolean send(String partitionId, String message) {
+ if (message == null) {
+ throw new IllegalArgumentException(NO_MESSAGE_PROVIDED);
+ }
+
+ try {
+ // Create the record
+ ProducerRecord<String, String> producerRecord =
+ new ProducerRecord<>(topic, UUID.randomUUID().toString(), message);
+
+ this.producer.send(producerRecord);
+ producer.flush();
+ } catch (Exception e) {
+ logger.warn("{}: SEND of {} cannot be performed because of {}", this, message, e.getMessage(), e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void close() {
+ logger.info(LOG_CLOSE, this);
+
+ try {
+ this.producer.close();
+ } catch (Exception e) {
+ logger.warn("{}: CLOSE FAILED because of {}", this, e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "KafkaPublisherWrapper []";
+ }
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicFactories.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicFactories.java
new file mode 100644
index 00000000..c10285fc
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicFactories.java
@@ -0,0 +1,39 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022, 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class KafkaTopicFactories {
+
+ /**
+ * Factory for instantiation and management of sinks.
+ */
+ @Getter
+ private static final KafkaTopicSinkFactory sinkFactory = new IndexedKafkaTopicSinkFactory();
+
+ /**
+ * Factory for instantiation and management of sources.
+ */
+ @Getter
+ private static final KafkaTopicSourceFactory sourceFactory = new IndexedKafkaTopicSourceFactory();
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSink.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSink.java
new file mode 100644
index 00000000..f784a987
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSink.java
@@ -0,0 +1,28 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022, 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import org.onap.policy.common.message.bus.event.base.BusTopicSink;
+
+/**
+ * Topic Writer over KAFKA Infrastructure.
+ */
+public interface KafkaTopicSink extends BusTopicSink {
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSinkFactory.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSinkFactory.java
new file mode 100644
index 00000000..8feecbe0
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSinkFactory.java
@@ -0,0 +1,89 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022, 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import java.util.List;
+import java.util.Properties;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+
+/**
+ * KAFKA Topic Sink Factory.
+ */
+public interface KafkaTopicSinkFactory {
+
+ /**
+ * Instantiates a new KAFKA Topic Writer.
+ *
+ * @param busTopicParams parameters object
+ * @return an KAFKA Topic Sink
+ */
+ KafkaTopicSink build(BusTopicParams busTopicParams);
+
+ /**
+ * Creates an KAFKA Topic Writer based on properties files.
+ *
+ * @param properties Properties containing initialization values
+ *
+ * @return an KAFKA Topic Writer
+ * @throws IllegalArgumentException if invalid parameters are present
+ */
+ List<KafkaTopicSink> build(Properties properties);
+
+ /**
+ * Instantiates a new KAFKA Topic Writer.
+ *
+ * @param servers list of servers
+ * @param topic topic name
+ *
+ * @return an KAFKA Topic Writer
+ * @throws IllegalArgumentException if invalid parameters are present
+ */
+ KafkaTopicSink build(List<String> servers, String topic);
+
+ /**
+ * Destroys an KAFKA Topic Writer based on a topic.
+ *
+ * @param topic topic name
+ * @throws IllegalArgumentException if invalid parameters are present
+ */
+ void destroy(String topic);
+
+ /**
+ * Destroys all KAFKA Topic Writers.
+ */
+ void destroy();
+
+ /**
+ * gets an KAFKA Topic Writer based on topic name.
+ *
+ * @param topic the topic name
+ *
+ * @return an KAFKA Topic Writer with topic name
+ * @throws IllegalArgumentException if an invalid topic is provided
+ * @throws IllegalStateException if the KAFKA Topic Reader is an incorrect state
+ */
+ KafkaTopicSink get(String topic);
+
+ /**
+ * Provides a snapshot of the KAFKA Topic Writers.
+ *
+ * @return a list of the KAFKA Topic Writers
+ */
+ List<KafkaTopicSink> inventory();
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSource.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSource.java
new file mode 100644
index 00000000..bddced7a
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSource.java
@@ -0,0 +1,28 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022, 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import org.onap.policy.common.message.bus.event.base.BusTopicSource;
+
+/**
+ * Kafka Topic Source.
+ */
+public interface KafkaTopicSource extends BusTopicSource {
+
+} \ No newline at end of file
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSourceFactory.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSourceFactory.java
new file mode 100644
index 00000000..06f4412c
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSourceFactory.java
@@ -0,0 +1,88 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import java.util.List;
+import java.util.Properties;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+
+/**
+ * Kafka Topic Source Factory.
+ */
+public interface KafkaTopicSourceFactory {
+
+ /**
+ * Creates a Kafka Topic Source based on properties files.
+ *
+ * @param properties Properties containing initialization values
+ *
+ * @return a Kafka Topic Source
+ * @throws IllegalArgumentException if invalid parameters are present
+ */
+ List<KafkaTopicSource> build(Properties properties);
+
+ /**
+ * Instantiates a new Kafka Topic Source.
+ *
+ * @param busTopicParams parameters object
+ * @return a Kafka Topic Source
+ */
+ KafkaTopicSource build(BusTopicParams busTopicParams);
+
+ /**
+ * Instantiates a new Kafka Topic Source.
+ *
+ * @param servers list of servers
+ * @param topic topic name
+ *
+ * @return a Kafka Topic Source
+ * @throws IllegalArgumentException if invalid parameters are present
+ */
+ KafkaTopicSource build(List<String> servers, String topic);
+
+ /**
+ * Destroys a Kafka Topic Source based on a topic.
+ *
+ * @param topic topic name
+ * @throws IllegalArgumentException if invalid parameters are present
+ */
+ void destroy(String topic);
+
+ /**
+ * Destroys all Kafka Topic Sources.
+ */
+ void destroy();
+
+ /**
+ * Gets a Kafka Topic Source based on topic name.
+ *
+ * @param topic the topic name
+ * @return a Kafka Topic Source with topic name
+ * @throws IllegalArgumentException if an invalid topic is provided
+ * @throws IllegalStateException if the Kafka Topic Source is an incorrect state
+ */
+ KafkaTopicSource get(String topic);
+
+ /**
+ * Provides a snapshot of the Kafka Topic Sources.
+ *
+ * @return a list of the Kafka Topic Sources
+ */
+ List<KafkaTopicSource> inventory();
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/SingleThreadedKafkaTopicSource.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/SingleThreadedKafkaTopicSource.java
new file mode 100644
index 00000000..5691cb12
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/kafka/SingleThreadedKafkaTopicSource.java
@@ -0,0 +1,80 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import java.util.Map;
+import org.onap.policy.common.message.bus.event.Topic;
+import org.onap.policy.common.message.bus.event.base.BusConsumer;
+import org.onap.policy.common.message.bus.event.base.SingleThreadedBusTopicSource;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+
+/**
+ * This topic source implementation specializes in reading messages over a Kafka Bus topic source and
+ * notifying its listeners.
+ */
+public class SingleThreadedKafkaTopicSource extends SingleThreadedBusTopicSource implements KafkaTopicSource {
+
+ protected Map<String, String> additionalProps = null;
+
+ /**
+ * Constructor.
+ *
+ * @param busTopicParams Parameters object containing all the required inputs
+ * @throws IllegalArgumentException An invalid parameter passed in
+ */
+ public SingleThreadedKafkaTopicSource(BusTopicParams busTopicParams) {
+ super(busTopicParams);
+ this.additionalProps = busTopicParams.getAdditionalProps();
+ try {
+ this.init();
+ } catch (Exception e) {
+ throw new IllegalArgumentException("ERROR during init in kafka-source: cannot create topic " + topic, e);
+ }
+ }
+
+ /**
+ * Initialize the client.
+ */
+ @Override
+ public void init() {
+ BusTopicParams.TopicParamsBuilder builder = BusTopicParams.builder()
+ .servers(this.servers)
+ .topic(this.effectiveTopic)
+ .fetchTimeout(this.fetchTimeout)
+ .consumerGroup(this.consumerGroup)
+ .useHttps(this.useHttps)
+ .allowTracing(this.allowTracing);
+
+ this.consumer = new BusConsumer.KafkaConsumerWrapper(builder
+ .additionalProps(this.additionalProps)
+ .build());
+ }
+
+ @Override
+ public CommInfrastructure getTopicCommInfrastructure() {
+ return Topic.CommInfrastructure.KAFKA;
+ }
+
+ @Override
+ public String toString() {
+ return "SingleThreadedKafkaTopicSource [getTopicCommInfrastructure()=" + getTopicCommInfrastructure()
+ + ", toString()=" + super.toString() + "]";
+ }
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicEndpoint.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicEndpoint.java
new file mode 100644
index 00000000..e25aca54
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicEndpoint.java
@@ -0,0 +1,141 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2019 Samsung Electronics Co., Ltd.
+ * Modifications Copyright (C) 2020 Bell Canada. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.noop;
+
+import java.util.List;
+import org.onap.policy.common.message.bus.event.base.TopicBase;
+import org.onap.policy.common.message.bus.utils.NetLoggerUtil;
+import org.onap.policy.common.message.bus.utils.NetLoggerUtil.EventType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * No Operation topic endpoint.
+ */
+public abstract class NoopTopicEndpoint extends TopicBase {
+
+ /**
+ * Logger.
+ */
+ private static final Logger logger = LoggerFactory.getLogger(NoopTopicEndpoint.class);
+
+ /**
+ * Constructs the object.
+ */
+ protected NoopTopicEndpoint(List<String> servers, String topic) {
+ super(servers, topic);
+ }
+
+ /**
+ * I/O.
+ *
+ * @param type "IN" or "OUT".
+ * @param message message.
+ * @return true if successful.
+ */
+ protected boolean io(EventType type, String message) {
+
+ if (message == null || message.isEmpty()) {
+ throw new IllegalArgumentException("Message is empty");
+ }
+
+ if (!this.alive) {
+ throw new IllegalStateException(this + " is stopped");
+ }
+
+ try {
+ synchronized (this) {
+ this.recentEvents.add(message);
+ }
+
+ NetLoggerUtil.log(type, this.getTopicCommInfrastructure(), this.topic, message);
+
+ broadcast(message);
+ } catch (Exception e) {
+ logger.warn("{}: cannot send because of {}", this, e.getMessage(), e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public CommInfrastructure getTopicCommInfrastructure() {
+ return CommInfrastructure.NOOP;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public boolean start() {
+ logger.info("{}: starting", this);
+
+ synchronized (this) {
+ if (!this.alive) {
+ if (locked) {
+ throw new IllegalStateException(this + " is locked.");
+ }
+
+ this.alive = true;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public boolean stop() {
+ logger.info("{}: stopping", this);
+
+ synchronized (this) {
+ this.alive = false;
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public void shutdown() {
+ logger.info("{}: shutdown", this);
+
+ this.stop();
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public String toString() {
+ return "NoopTopicEndpoint[" + super.toString() + "]";
+ }
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicFactories.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicFactories.java
new file mode 100644
index 00000000..506cc9f9
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicFactories.java
@@ -0,0 +1,42 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.noop;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class NoopTopicFactories {
+
+ /**
+ * Factory for instantiation and management of sinks.
+ */
+ @Getter
+ private static final NoopTopicSinkFactory sinkFactory = new NoopTopicSinkFactory();
+
+ /**
+ * Factory for instantiation and management of sources.
+ */
+ @Getter
+ private static final NoopTopicSourceFactory sourceFactory = new NoopTopicSourceFactory();
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicFactory.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicFactory.java
new file mode 100644
index 00000000..e81dfaea
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicFactory.java
@@ -0,0 +1,117 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.noop;
+
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_MANAGED_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_SERVERS_SUFFIX;
+
+import com.google.re2j.Pattern;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.event.base.TopicBaseHashedFactory;
+
+/**
+ * Noop Topic Factory.
+ */
+public abstract class NoopTopicFactory<T extends NoopTopicEndpoint> extends TopicBaseHashedFactory<T> {
+ private static final Pattern COMMA_SPACE_PAT = Pattern.compile("\\s*,\\s*");
+
+ /**
+ * Get Topics Property Name.
+ *
+ * @return property name.
+ */
+ protected abstract String getTopicsPropertyName();
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ protected List<String> getTopicNames(Properties properties) {
+ String topics = properties.getProperty(getTopicsPropertyName());
+ if (topics == null || topics.isEmpty()) {
+ return new ArrayList<>();
+ }
+
+ return Arrays.asList(COMMA_SPACE_PAT.split(topics));
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ protected List<String> getServers(String topicName, Properties properties) {
+ String servers =
+ properties.getProperty(getTopicsPropertyName() + "." + topicName
+ + PROPERTY_TOPIC_SERVERS_SUFFIX);
+
+ if (servers == null || servers.isEmpty()) {
+ servers = CommInfrastructure.NOOP.toString();
+ }
+
+ return new ArrayList<>(Arrays.asList(COMMA_SPACE_PAT.split(servers)));
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ protected boolean isManaged(String topicName, Properties properties) {
+ var managedString =
+ properties.getProperty(getTopicsPropertyName() + "." + topicName + PROPERTY_MANAGED_SUFFIX);
+
+ var managed = true;
+ if (managedString != null && !managedString.isEmpty()) {
+ managed = Boolean.parseBoolean(managedString);
+ }
+
+ return managed;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public T build(List<String> serverList, String topic, boolean managed) {
+ List<String> servers;
+ if (serverList == null || serverList.isEmpty()) {
+ servers = Collections.singletonList(CommInfrastructure.NOOP.toString());
+ } else {
+ servers = serverList;
+ }
+
+ return super.build(servers, topic, managed);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public String toString() {
+ return "NoopTopicFactory[ " + super.toString() + " ]";
+ }
+}
+
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSink.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSink.java
new file mode 100644
index 00000000..b7d78ff4
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSink.java
@@ -0,0 +1,56 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.noop;
+
+import java.util.List;
+import org.onap.policy.common.message.bus.event.TopicSink;
+import org.onap.policy.common.message.bus.utils.NetLoggerUtil.EventType;
+
+/**
+ * No Operation Topic Sink.
+ */
+public class NoopTopicSink extends NoopTopicEndpoint implements TopicSink {
+
+ /**
+ * Constructs the object.
+ */
+ public NoopTopicSink(List<String> servers, String topic) {
+ super(servers, topic);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public boolean send(String message) {
+ return super.io(EventType.OUT, message);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public String toString() {
+ return "NoopTopicSink[" + super.toString() + "]";
+ }
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSinkFactory.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSinkFactory.java
new file mode 100644
index 00000000..cdf4a17b
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSinkFactory.java
@@ -0,0 +1,58 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.noop;
+
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_NOOP_SINK_TOPICS;
+
+import java.util.List;
+
+/**
+ * Noop Topic Sink Factory.
+ */
+public class NoopTopicSinkFactory extends NoopTopicFactory<NoopTopicSink> {
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ protected String getTopicsPropertyName() {
+ return PROPERTY_NOOP_SINK_TOPICS;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public NoopTopicSink build(List<String> servers, String topic) {
+ return new NoopTopicSink(servers, topic);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public String toString() {
+ return "NoopTopicSinkFactory [" + super.toString() + "]";
+ }
+
+}
+
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSource.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSource.java
new file mode 100644
index 00000000..6e7d0216
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSource.java
@@ -0,0 +1,56 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.noop;
+
+import java.util.List;
+import org.onap.policy.common.message.bus.event.TopicSource;
+import org.onap.policy.common.message.bus.utils.NetLoggerUtil.EventType;
+
+/**
+ * No Operation Topic Source.
+ */
+public class NoopTopicSource extends NoopTopicEndpoint implements TopicSource {
+
+ /**
+ * Constructs the object.
+ */
+ public NoopTopicSource(List<String> servers, String topic) {
+ super(servers, topic);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public boolean offer(String event) {
+ return super.io(EventType.IN, event);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public String toString() {
+ return "NoopTopicSource[" + super.toString() + "]";
+ }
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSourceFactory.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSourceFactory.java
new file mode 100644
index 00000000..5e3a365a
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSourceFactory.java
@@ -0,0 +1,56 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.noop;
+
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_NOOP_SOURCE_TOPICS;
+
+import java.util.List;
+
+/**
+ * No Operation Topic Source Factory.
+ */
+public class NoopTopicSourceFactory extends NoopTopicFactory<NoopTopicSource> {
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ protected String getTopicsPropertyName() {
+ return PROPERTY_NOOP_SOURCE_TOPICS;
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public NoopTopicSource build(List<String> servers, String topic) {
+ return new NoopTopicSource(servers, topic);
+ }
+
+ /**
+ * {@inheritDoc}.
+ */
+ @Override
+ public String toString() {
+ return "NoopTopicSourceFactory [" + super.toString() + "]";
+ }
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/features/NetLoggerFeatureApi.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/features/NetLoggerFeatureApi.java
new file mode 100644
index 00000000..6e3b8301
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/features/NetLoggerFeatureApi.java
@@ -0,0 +1,54 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.features;
+
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.utils.services.OrderedService;
+import org.slf4j.Logger;
+
+/**
+ * Logging Feature API. Provides interception points before and after logging a message.
+ */
+public interface NetLoggerFeatureApi extends OrderedService {
+
+ /**
+ * Intercepts a message before it is logged.
+ *
+ * @return true if this feature intercepts and takes ownership of the operation
+ * preventing the invocation of lower priority features. False, otherwise.
+ */
+ default boolean beforeLog(Logger eventLogger, EventType type, CommInfrastructure protocol, String topic,
+ String message) {
+ return false;
+ }
+
+ /**
+ * Intercepts a message after it is logged.
+ *
+ * @return true if this feature intercepts and takes ownership of the operation
+ * preventing the invocation of lower priority features. False, otherwise.
+ */
+ default boolean afterLog(Logger eventLogger, EventType type, CommInfrastructure protocol, String topic,
+ String message) {
+ return false;
+ }
+
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/features/NetLoggerFeatureProviders.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/features/NetLoggerFeatureProviders.java
new file mode 100644
index 00000000..4f57ab2a
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/features/NetLoggerFeatureProviders.java
@@ -0,0 +1,41 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.features;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.onap.policy.common.utils.services.OrderedServiceImpl;
+
+/**
+ * Providers for network logging feature.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class NetLoggerFeatureProviders {
+
+ /**
+ * Feature providers implementing this interface.
+ */
+ @Getter
+ private static final OrderedServiceImpl<NetLoggerFeatureApi> providers =
+ new OrderedServiceImpl<>(NetLoggerFeatureApi.class);
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/properties/MessageBusProperties.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/properties/MessageBusProperties.java
new file mode 100644
index 00000000..9aa529f3
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/properties/MessageBusProperties.java
@@ -0,0 +1,78 @@
+/*-
+ * ============LICENSE_START===============================================
+ * Copyright (C) 2024 Nordix Foundation.
+ * ========================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=================================================
+ */
+
+package org.onap.policy.common.message.bus.properties;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class MessageBusProperties {
+
+ /* Generic property suffixes */
+
+ public static final String PROPERTY_TOPIC_SERVERS_SUFFIX = ".servers";
+ public static final String PROPERTY_TOPIC_EFFECTIVE_TOPIC_SUFFIX = ".effectiveTopic";
+
+ public static final String PROPERTY_TOPIC_SOURCE_CONSUMER_GROUP_SUFFIX = ".consumerGroup";
+ public static final String PROPERTY_TOPIC_SOURCE_CONSUMER_INSTANCE_SUFFIX = ".consumerInstance";
+ public static final String PROPERTY_TOPIC_SOURCE_FETCH_TIMEOUT_SUFFIX = ".fetchTimeout";
+ public static final String PROPERTY_TOPIC_SOURCE_FETCH_LIMIT_SUFFIX = ".fetchLimit";
+ public static final String PROPERTY_MANAGED_SUFFIX = ".managed";
+ public static final String PROPERTY_ADDITIONAL_PROPS_SUFFIX = ".additionalProps";
+
+ public static final String PROPERTY_TOPIC_SINK_PARTITION_KEY_SUFFIX = ".partitionKey";
+
+ public static final String PROPERTY_ALLOW_SELF_SIGNED_CERTIFICATES_SUFFIX = ".selfSignedCertificates";
+
+ public static final String PROPERTY_NOOP_SOURCE_TOPICS = "noop.source.topics";
+ public static final String PROPERTY_NOOP_SINK_TOPICS = "noop.sink.topics";
+
+ /* KAFKA Properties */
+
+ public static final String PROPERTY_KAFKA_SOURCE_TOPICS = "kafka.source.topics";
+ public static final String PROPERTY_KAFKA_SINK_TOPICS = "kafka.sink.topics";
+
+ /* HTTP Server Properties */
+
+ public static final String PROPERTY_HTTP_HTTPS_SUFFIX = ".https";
+
+ /* Topic Sink Values */
+
+ /* Topic Source values */
+
+ /**
+ * Default Timeout fetching in milliseconds.
+ */
+ public static final int DEFAULT_TIMEOUT_MS_FETCH = 15000;
+
+ /**
+ * Default maximum number of messages fetch at the time.
+ */
+ public static final int DEFAULT_LIMIT_FETCH = 100;
+
+ /**
+ * Definition of No Timeout fetching.
+ */
+ public static final int NO_TIMEOUT_MS_FETCH = -1;
+
+ /**
+ * Definition of No limit fetching.
+ */
+ public static final int NO_LIMIT_FETCH = -1;
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/utils/KafkaPropertyUtils.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/utils/KafkaPropertyUtils.java
new file mode 100644
index 00000000..cfe62208
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/utils/KafkaPropertyUtils.java
@@ -0,0 +1,80 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2022-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.utils;
+
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_ADDITIONAL_PROPS_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_MANAGED_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_EFFECTIVE_TOPIC_SUFFIX;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.re2j.Pattern;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+import org.onap.policy.common.parameters.topic.BusTopicParams.TopicParamsBuilder;
+import org.onap.policy.common.utils.properties.PropertyUtils;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class KafkaPropertyUtils {
+ private static final Pattern COMMA_SPACE_PAT = Pattern.compile("\\s*,\\s*");
+
+ /**
+ * Makes a topic builder, configuring it with properties that are common to both
+ * sources and sinks.
+ *
+ * @param props properties to be used to configure the builder
+ * @param topic topic being configured
+ * @param servers target servers
+ * @return a topic builder
+ */
+ public static TopicParamsBuilder makeBuilder(PropertyUtils props, String topic, String servers) {
+
+ final List<String> serverList = new ArrayList<>(Arrays.asList(COMMA_SPACE_PAT.split(servers)));
+ return BusTopicParams.builder()
+ .servers(serverList)
+ .topic(topic)
+ .effectiveTopic(props.getString(PROPERTY_TOPIC_EFFECTIVE_TOPIC_SUFFIX, topic))
+ .managed(props.getBoolean(PROPERTY_MANAGED_SUFFIX, true))
+ .additionalProps(getAdditionalProps(props.getString(PROPERTY_ADDITIONAL_PROPS_SUFFIX, "")));
+ }
+
+ private static Map<String, String> getAdditionalProps(String additionalPropsString) {
+ try {
+ Map<String, String> additionalProps = new HashMap<>();
+ var converted = new ObjectMapper().readValue(additionalPropsString, Map.class);
+ converted.forEach((k, v) -> {
+ if (k instanceof String key && v instanceof String value) {
+ additionalProps.put(key, value);
+ }
+ });
+ return additionalProps;
+ } catch (Exception e) {
+ return Collections.emptyMap();
+ }
+
+ }
+}
diff --git a/message-bus/src/main/java/org/onap/policy/common/message/bus/utils/NetLoggerUtil.java b/message-bus/src/main/java/org/onap/policy/common/message/bus/utils/NetLoggerUtil.java
new file mode 100644
index 00000000..b5454e5c
--- /dev/null
+++ b/message-bus/src/main/java/org/onap/policy/common/message/bus/utils/NetLoggerUtil.java
@@ -0,0 +1,134 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.utils;
+
+import lombok.Getter;
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.features.NetLoggerFeatureProviders;
+import org.onap.policy.common.utils.services.FeatureApiUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A network logging utility class that allows drools applications code to access the
+ * network log (or other specified loggers) and logging features.
+ *
+ */
+public class NetLoggerUtil {
+
+ /**
+ * Loggers.
+ */
+ private static final Logger logger = LoggerFactory.getLogger(NetLoggerUtil.class);
+ @Getter
+ private static final Logger networkLogger = LoggerFactory.getLogger("network");
+
+ /**
+ * Constant for the system line separator.
+ */
+ public static final String SYSTEM_LS = System.lineSeparator();
+
+ /**
+ * Specifies if the message is coming in or going out.
+ */
+ public enum EventType {
+ IN, OUT
+ }
+
+ /**
+ * Logs a message to the network logger.
+ *
+ * @param type can either be IN or OUT
+ * @param protocol the protocol used to receive/send the message
+ * @param topic the topic the message came from or null if the type is REST
+ * @param message message to be logged
+ */
+ public static void log(EventType type, CommInfrastructure protocol, String topic, String message) {
+ log(networkLogger, type, protocol, topic, message);
+ }
+
+ /**
+ * Logs a message to the specified logger (i.e. a controller logger).
+ *
+ * @param eventLogger the logger that will have the message appended
+ * @param type can either be IN or OUT
+ * @param protocol the protocol used to receive/send the message
+ * @param topic the topic the message came from or null if the type is REST
+ * @param message message to be logged
+ */
+ public static void log(Logger eventLogger, EventType type, CommInfrastructure protocol, String topic,
+ String message) {
+ if (eventLogger == null) {
+ logger.debug("the logger is null, defaulting to network logger");
+ eventLogger = networkLogger;
+ }
+
+ if (featureBeforeLog(eventLogger, type, protocol, topic, message)) {
+ return;
+ }
+
+ eventLogger.info("[{}|{}|{}]{}{}", type, protocol, topic, SYSTEM_LS, message);
+
+ featureAfterLog(eventLogger, type, protocol, topic, message);
+ }
+
+ /**
+ * Executes features that pre-process a message before it is logged.
+ *
+ * @param eventLogger the logger that will have the message appended
+ * @param type can either be IN or OUT
+ * @param protocol the protocol used to receive/send the message
+ * @param topic the topic the message came from or null if the type is REST
+ * @param message message to be logged
+ *
+ * @return true if this feature intercepts and takes ownership of the operation
+ * preventing the invocation of lower priority features. False, otherwise
+ */
+ private static boolean featureBeforeLog(Logger eventLogger, EventType type, CommInfrastructure protocol,
+ String topic, String message) {
+
+ return FeatureApiUtils.apply(NetLoggerFeatureProviders.getProviders().getList(),
+ feature -> feature.beforeLog(eventLogger, type, protocol, topic, message),
+ (feature, ex) -> logger.error("feature {} before-log failure because of {}",
+ feature.getClass().getName(), ex.getMessage(), ex));
+ }
+
+ /**
+ * Executes features that post-process a message after it is logged.
+ *
+ * @param eventLogger the logger that will have the message appended
+ * @param type can either be IN or OUT
+ * @param protocol the protocol used to receive/send the message
+ * @param topic the topic the message came from or null if the type is rest
+ * @param message message to be logged
+ *
+ * @return true if this feature intercepts and takes ownership of the operation
+ * preventing the invocation of lower priority features. False, otherwise
+ */
+ private static boolean featureAfterLog(Logger eventLogger, EventType type, CommInfrastructure protocol,
+ String topic, String message) {
+
+ return FeatureApiUtils.apply(NetLoggerFeatureProviders.getProviders().getList(),
+ feature -> feature.afterLog(eventLogger, type, protocol, topic, message),
+ (feature, ex) -> logger.error("feature {} after-log failure because of {}",
+ feature.getClass().getName(), ex.getMessage(), ex));
+ }
+
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/CommonTestData.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/CommonTestData.java
new file mode 100644
index 00000000..ecd2f20c
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/CommonTestData.java
@@ -0,0 +1,111 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019, 2024 Nordix Foundation.
+ * Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import org.onap.policy.common.parameters.ParameterGroup;
+import org.onap.policy.common.parameters.topic.TopicParameters;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+
+/**
+ * Class to hold/create all parameters for test cases.
+ *
+ * @author Ajith Sreekumar (ajith.sreekumar@est.tech)
+ */
+public class CommonTestData {
+
+ public static final String TOPIC_NAME = "policy-pdp-pap";
+ public static final String TOPIC_INFRA = "kafka";
+ public static final String TOPIC_SERVER = "kafka:9092";
+
+ public static final List<TopicParameters> TOPIC_PARAMS =
+ List.of(getTopicParameters(TOPIC_NAME, TOPIC_INFRA, TOPIC_SERVER));
+
+ protected static final Coder coder = new StandardCoder();
+
+ /**
+ * Create topic parameters for test cases.
+ *
+ * @param topicName name of topic
+ * @param topicInfra topicCommInfrastructure
+ * @param topicServer topic server
+ * @return topic parameters
+ */
+ public static TopicParameters getTopicParameters(String topicName, String topicInfra, String topicServer) {
+ final TopicParameters topicParams = new TopicParameters();
+ topicParams.setTopic(topicName);
+ topicParams.setTopicCommInfrastructure(topicInfra);
+ topicParams.setServers(List.of(topicServer));
+ return topicParams;
+ }
+
+ /**
+ * Converts the contents of a map to a parameter class.
+ *
+ * @param source property map
+ * @param clazz class of object to be created from the map
+ * @return a new object represented by the map
+ */
+ public <T extends ParameterGroup> T toObject(final Map<String, Object> source, final Class<T> clazz) {
+ try {
+ return coder.decode(coder.encode(source), clazz);
+
+ } catch (final CoderException e) {
+ throw new RuntimeException("cannot create " + clazz.getName() + " from map", e);
+ }
+ }
+
+ /**
+ * Returns a property map for a TopicParameters map for test cases.
+ *
+ * @param isEmpty boolean value to represent that object created should be empty or not
+ * @return a property map suitable for constructing an object
+ */
+ public Map<String, Object> getTopicParameterGroupMap(final boolean isEmpty) {
+ final Map<String, Object> map = new TreeMap<>();
+ if (!isEmpty) {
+ map.put("topicSources", TOPIC_PARAMS);
+ map.put("topicSinks", TOPIC_PARAMS);
+ }
+
+ return map;
+ }
+
+ /**
+ * Gets the standard parameter group as a String.
+ *
+ * @param filePath path of the file
+ * @return the standard parameters
+ * @throws IOException when file read operation fails
+ */
+ public String getParameterGroupAsString(String filePath) throws IOException {
+ File file = new File(filePath);
+ return Files.readString(file.toPath());
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/TopicEndpointProxyTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/TopicEndpointProxyTest.java
new file mode 100644
index 00000000..6f0e38dd
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/TopicEndpointProxyTest.java
@@ -0,0 +1,400 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.event.kafka.KafkaTopicFactories;
+import org.onap.policy.common.message.bus.event.kafka.KafkaTopicPropertyBuilder;
+import org.onap.policy.common.message.bus.event.noop.NoopTopicFactories;
+import org.onap.policy.common.message.bus.event.noop.NoopTopicPropertyBuilder;
+import org.onap.policy.common.message.bus.properties.MessageBusProperties;
+import org.onap.policy.common.parameters.topic.TopicParameterGroup;
+import org.onap.policy.common.parameters.topic.TopicParameters;
+import org.onap.policy.common.utils.gson.GsonTestUtils;
+
+class TopicEndpointProxyTest {
+
+ private static final String NOOP_SOURCE_TOPIC = "noop-source";
+ private static final String NOOP_SINK_TOPIC = "noop-sink";
+
+ private static final String KAFKA_SOURCE_TOPIC = "kafka-source";
+ private static final String KAFKA_SINK_TOPIC = "kafka-sink";
+
+ private final Properties configuration = new Properties();
+ private final TopicParameterGroup group = new TopicParameterGroup();
+
+ /**
+ * Constructor.
+ */
+ public TopicEndpointProxyTest() {
+ group.setTopicSinks(new LinkedList<>());
+ group.setTopicSources(new LinkedList<>());
+
+ NoopTopicPropertyBuilder noopSourceBuilder =
+ new NoopTopicPropertyBuilder(MessageBusProperties.PROPERTY_NOOP_SOURCE_TOPICS)
+ .makeTopic(NOOP_SOURCE_TOPIC);
+ configuration.putAll(noopSourceBuilder.build());
+ group.getTopicSources().add(noopSourceBuilder.getParams());
+
+ NoopTopicPropertyBuilder noopSinkBuilder =
+ new NoopTopicPropertyBuilder(MessageBusProperties.PROPERTY_NOOP_SINK_TOPICS)
+ .makeTopic(NOOP_SINK_TOPIC);
+ configuration.putAll(noopSinkBuilder.build());
+ group.getTopicSinks().add(noopSinkBuilder.getParams());
+
+ TopicParameters invalidCommInfraParams =
+ new NoopTopicPropertyBuilder(MessageBusProperties.PROPERTY_NOOP_SOURCE_TOPICS)
+ .makeTopic(NOOP_SOURCE_TOPIC).getParams();
+ invalidCommInfraParams.setTopicCommInfrastructure(Topic.CommInfrastructure.REST.name());
+ group.getTopicSources().add(invalidCommInfraParams);
+ group.getTopicSinks().add(invalidCommInfraParams);
+ }
+
+ private <T extends Topic> boolean exists(List<T> topics, String topicName) {
+ return topics.stream().map(Topic::getTopic).anyMatch(topicName::equals);
+ }
+
+ private <T extends Topic> boolean allSources(List<T> topics) {
+ return exists(topics, NOOP_SOURCE_TOPIC);
+ }
+
+ private <T extends Topic> boolean allSinks(List<T> topics) {
+ return exists(topics, NOOP_SINK_TOPIC);
+ }
+
+ private <T extends Topic> boolean anySource(List<T> topics) {
+ return exists(topics, NOOP_SOURCE_TOPIC);
+ }
+
+ private <T extends Topic> boolean anySink(List<T> topics) {
+ return exists(topics, NOOP_SINK_TOPIC);
+ }
+
+ /**
+ * Destroys all managed topics.
+ */
+ @AfterEach
+ public void tearDown() {
+ NoopTopicFactories.getSinkFactory().destroy();
+ NoopTopicFactories.getSourceFactory().destroy();
+ KafkaTopicFactories.getSinkFactory().destroy();
+ KafkaTopicFactories.getSourceFactory().destroy();
+ }
+
+ @Test
+ void testSerialize() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ manager.addTopicSources(configuration);
+ manager.addTopicSinks(configuration);
+
+ assertThatCode(() -> new GsonTestUtils().compareGson(manager, TopicEndpointProxyTest.class))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void testAddTopicSourcesListOfTopicParameters() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ List<TopicSource> sources = manager.addTopicSources(group.getTopicSources());
+ assertSame(1, sources.size());
+
+ assertTrue(allSources(sources));
+ assertFalse(anySink(sources));
+
+ sources = manager.addTopicSources(group.getTopicSources());
+ assertSame(1, sources.size());
+ assertTrue(allSources(sources));
+ }
+
+ @Test
+ void testAddTopicSourcesKafka() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ KafkaTopicPropertyBuilder kafkaTopicPropertyBuilder =
+ new KafkaTopicPropertyBuilder(MessageBusProperties.PROPERTY_KAFKA_SOURCE_TOPICS)
+ .makeTopic(KAFKA_SOURCE_TOPIC);
+
+ configuration.putAll(kafkaTopicPropertyBuilder.build());
+ group.getTopicSources().add(kafkaTopicPropertyBuilder.getParams());
+ List<TopicSource> sources = manager.addTopicSources(group.getTopicSources());
+ assertSame(2, sources.size());
+
+ configuration.remove(KAFKA_SOURCE_TOPIC);
+ group.setTopicSources(new LinkedList<>());
+ sources = manager.addTopicSources(group.getTopicSources());
+ assertSame(0, sources.size());
+ }
+
+ @Test
+ void testAddTopicSourcesProperties() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ List<TopicSource> sources = manager.addTopicSources(configuration);
+ assertSame(1, sources.size());
+
+ assertTrue(allSources(sources));
+ assertFalse(anySink(sources));
+ }
+
+ @Test
+ void testAddTopicSinksListOfTopicParameters() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ List<TopicSink> sinks = manager.addTopicSinks(group.getTopicSinks());
+ assertSame(1, sinks.size());
+
+ assertFalse(anySource(sinks));
+ assertTrue(allSinks(sinks));
+ }
+
+ @Test
+ void testAddTopicSinksListOfTopicParametersKafka() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ List<TopicSink> sinks = manager.addTopicSinks(group.getTopicSinks());
+ assertSame(1, sinks.size());
+
+ KafkaTopicPropertyBuilder kafkaTopicPropertyBuilder =
+ new KafkaTopicPropertyBuilder(MessageBusProperties.PROPERTY_KAFKA_SINK_TOPICS)
+ .makeTopic(KAFKA_SINK_TOPIC);
+
+ configuration.putAll(kafkaTopicPropertyBuilder.build());
+ group.getTopicSources().add(kafkaTopicPropertyBuilder.getParams());
+ sinks = manager.addTopicSinks(group.getTopicSources());
+ assertSame(2, sinks.size());
+
+ configuration.remove(KAFKA_SOURCE_TOPIC);
+ group.setTopicSources(new LinkedList<>());
+ sinks = manager.addTopicSinks(group.getTopicSources());
+ assertSame(0, sinks.size());
+ }
+
+ @Test
+ void testAddTopicSinksProperties() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ List<TopicSink> sinks = manager.addTopicSinks(configuration);
+ assertSame(1, sinks.size());
+
+ assertFalse(anySource(sinks));
+ assertTrue(allSinks(sinks));
+ }
+
+ @Test
+ void testAddTopicsProperties() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ List<Topic> topics = manager.addTopics(configuration);
+ assertSame(2, topics.size());
+
+ assertTrue(allSources(topics));
+ assertTrue(allSinks(topics));
+ }
+
+ @Test
+ void testAddTopicsTopicParameterGroup() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ List<Topic> topics = manager.addTopics(group);
+ assertSame(2, topics.size());
+
+ assertTrue(allSources(topics));
+ assertTrue(allSinks(topics));
+ }
+
+ @Test
+ void testAddTopicsTopicParameterGroupNull() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ List<Topic> topics = manager.addTopics(new TopicParameterGroup());
+ assertEquals(0, topics.size());
+ }
+
+ @Test
+ void testLockSinks_lockSources_locked() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+ manager.lock();
+ for (Topic topic : manager.addTopics(group)) {
+ assertTrue(topic.isLocked());
+ }
+ }
+
+ @Test
+ void testLockSinks_lockSources_unlocked() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+ for (Topic topic : manager.addTopics(group)) {
+ assertFalse(topic.isLocked());
+ }
+ }
+
+ @Test
+ void testGetTopicSources() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ manager.addTopicSources(configuration);
+ manager.addTopicSinks(configuration);
+
+ List<TopicSource> sources = manager.getTopicSources();
+ assertSame(1, sources.size());
+
+ assertTrue(allSources(sources));
+ assertFalse(anySink(sources));
+
+ assertThatThrownBy(() -> manager.getKafkaTopicSource("testTopic"))
+ .hasMessageContaining("KafkaTopiceSource for testTopic not found");
+
+ List<String> topicName = null;
+ assertThatThrownBy(() -> manager.getTopicSources(topicName))
+ .hasMessageContaining("must provide a list of topics");
+ }
+
+ @Test
+ void testGetTopicSinks() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ manager.addTopicSources(configuration);
+ manager.addTopicSinks(configuration);
+
+ List<TopicSink> sinks = manager.getTopicSinks();
+ assertSame(1, sinks.size());
+
+ assertFalse(anySource(sinks));
+ assertTrue(allSinks(sinks));
+
+ final List<String> sinks2 = null;
+ assertThatThrownBy(() -> manager.getTopicSinks(sinks2)).hasMessageContaining("must provide a list of topics");
+
+ List<String> sinks3 = List.of(NOOP_SINK_TOPIC);
+ assertThatCode(() -> manager.getTopicSinks(sinks3)).doesNotThrowAnyException();
+
+ String sinkTest = null;
+ assertThatThrownBy(() -> manager.getTopicSinks(sinkTest))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Invalid parameter");
+
+ assertThatThrownBy(() -> manager.getKafkaTopicSink("testTopic"))
+ .hasMessageContaining("KafkaTopicSink for testTopic not found");
+ }
+
+ @Test
+ void testGetNoopTopicSources() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ manager.addTopicSources(configuration);
+ assertSame(1, manager.getNoopTopicSources().size());
+ }
+
+ @Test
+ void testGetNoopTopicSinks() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ manager.addTopicSinks(configuration);
+ assertSame(1, manager.getNoopTopicSinks().size());
+ }
+
+ @Test
+ void testLifecycle() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ assertTrue(manager.start());
+ assertTrue(manager.isAlive());
+
+ assertTrue(manager.stop());
+ assertFalse(manager.isAlive());
+
+ assertTrue(manager.start());
+ assertTrue(manager.isAlive());
+
+ manager.shutdown();
+ assertFalse(manager.isAlive());
+ }
+
+ @Test
+ void testLock() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+
+ manager.lock();
+ assertTrue(manager.isLocked());
+
+ manager.unlock();
+ assertFalse(manager.isLocked());
+ }
+
+ @Test
+ void testGetTopicSource() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+ manager.addTopicSources(configuration);
+
+ assertSame(NOOP_SOURCE_TOPIC, manager.getTopicSource(CommInfrastructure.NOOP, NOOP_SOURCE_TOPIC).getTopic());
+
+ assertThatIllegalStateException()
+ .isThrownBy(() -> manager.getTopicSource(CommInfrastructure.NOOP, NOOP_SINK_TOPIC));
+ }
+
+ @Test
+ void testGetTopicSink() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+ manager.addTopicSinks(configuration);
+
+ assertSame(NOOP_SINK_TOPIC, manager.getTopicSink(CommInfrastructure.NOOP, NOOP_SINK_TOPIC).getTopic());
+
+ assertThatIllegalStateException()
+ .isThrownBy(() -> manager.getTopicSink(CommInfrastructure.NOOP, NOOP_SOURCE_TOPIC));
+ }
+
+ @Test
+ void testGetNoopTopicSource() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+ manager.addTopicSources(configuration);
+
+ assertSame(NOOP_SOURCE_TOPIC, manager.getNoopTopicSource(NOOP_SOURCE_TOPIC).getTopic());
+
+ assertThatIllegalArgumentException().isThrownBy(() -> manager.getNoopTopicSource(null));
+ assertThatIllegalArgumentException().isThrownBy(() -> manager.getNoopTopicSource(""));
+ }
+
+ @Test
+ void testGetNoopTopicSink() {
+ TopicEndpoint manager = new TopicEndpointProxy();
+ manager.addTopicSinks(configuration);
+
+ assertSame(NOOP_SINK_TOPIC, manager.getNoopTopicSink(NOOP_SINK_TOPIC).getTopic());
+
+ assertThatIllegalArgumentException().isThrownBy(() -> manager.getNoopTopicSink(null));
+ assertThatIllegalArgumentException().isThrownBy(() -> manager.getNoopTopicSink(""));
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/TopicParameterGroupTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/TopicParameterGroupTest.java
new file mode 100644
index 00000000..db28892e
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/TopicParameterGroupTest.java
@@ -0,0 +1,147 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019-2024 Nordix Foundation.
+ * Modifications Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+import org.onap.policy.common.parameters.topic.TopicParameterGroup;
+import org.onap.policy.common.parameters.topic.TopicParameters;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+
+/**
+ * Class to perform unit test of {@link TopicParameterGroup}.
+ *
+ * @author Ajith Sreekumar (ajith.sreekumar@est.tech)
+ */
+class TopicParameterGroupTest {
+ private static final CommonTestData testData = new CommonTestData();
+ private static final Coder coder = new StandardCoder();
+ private final String packageDir = "src/test/resources/org/onap/policy/common/message/bus/parameters/";
+
+ @Test
+ void test() throws CoderException {
+ final TopicParameterGroup topicParameterGroup =
+ testData.toObject(testData.getTopicParameterGroupMap(false), TopicParameterGroup.class);
+ final ValidationResult validationResult = topicParameterGroup.validate();
+ assertTrue(validationResult.isValid());
+ assertEquals(CommonTestData.TOPIC_PARAMS, topicParameterGroup.getTopicSinks());
+ assertEquals(CommonTestData.TOPIC_PARAMS, topicParameterGroup.getTopicSources());
+
+ // these should default to true
+ assertTrue(new TopicParameters().isManaged());
+ assertTrue(coder.decode("{}", TopicParameters.class).isManaged());
+
+ // but can be overridden
+ assertFalse(coder.decode("{'managed':false}".replace('\'', '"'), TopicParameters.class).isManaged());
+ }
+
+ @Test
+ void testValidate() {
+ final TopicParameterGroup topicParameterGroup =
+ testData.toObject(testData.getTopicParameterGroupMap(false), TopicParameterGroup.class);
+ final ValidationResult result = topicParameterGroup.validate();
+ assertNull(result.getResult());
+ assertTrue(result.isValid());
+ }
+
+ @Test
+ void test_valid() throws Exception {
+ String json = testData.getParameterGroupAsString(
+ packageDir + "TopicParameters_valid.json");
+ TopicParameterGroup topicParameterGroup = coder.decode(json, TopicParameterGroup.class);
+ final ValidationResult result = topicParameterGroup.validate();
+ assertNull(result.getResult());
+ assertTrue(result.isValid());
+ }
+
+ @Test
+ void test_invalid() throws Exception {
+ String json = testData.getParameterGroupAsString(
+ packageDir + "TopicParameters_invalid.json");
+ TopicParameterGroup topicParameterGroup = coder.decode(json, TopicParameterGroup.class);
+ final ValidationResult result = topicParameterGroup.validate();
+ assertFalse(result.isValid());
+ assertTrue(result.getResult().contains("INVALID"));
+ }
+
+ @Test
+ void test_missing_mandatory_params() throws Exception {
+ String json = testData.getParameterGroupAsString(
+ packageDir + "TopicParameters_missing_mandatory.json");
+ TopicParameterGroup topicParameterGroup = coder.decode(json, TopicParameterGroup.class);
+ final ValidationResult result = topicParameterGroup.validate();
+ assertTrue(result.getResult().contains("Mandatory parameters are missing"));
+ assertFalse(result.isValid());
+ }
+
+ @Test
+ void test_allParams() throws Exception {
+ String json = testData.getParameterGroupAsString(
+ packageDir + "TopicParameters_all_params.json");
+ TopicParameterGroup topicParameterGroup = coder.decode(json, TopicParameterGroup.class);
+ final ValidationResult result = topicParameterGroup.validate();
+ assertNull(result.getResult());
+ assertTrue(result.isValid());
+ assertTrue(checkIfAllParamsNotEmpty(topicParameterGroup.getTopicSinks()));
+ assertTrue(checkIfAllParamsNotEmpty(topicParameterGroup.getTopicSources()));
+ }
+
+ /**
+ * Method to check if all parameters in TopicParameters are set.
+ * Any parameters added to @link TopicParameters or @link BusTopicParams must be added to
+ * TopicParameters_all_params.json.
+ *
+ * @param topicParametersList list of topic parameters
+ * @return true if all parameters are not empty (if string) or true (if boolean)
+ * @throws Exception the exception
+ */
+ private boolean checkIfAllParamsNotEmpty(List<TopicParameters> topicParametersList) throws Exception {
+ for (TopicParameters topicParameters : topicParametersList) {
+ Field[] fields = BusTopicParams.class.getDeclaredFields();
+ for (Field field : fields) {
+ if (!field.isSynthetic() && !Modifier.isStatic(field.getModifiers())) {
+ Object parameter = new PropertyDescriptor(field.getName(), TopicParameters.class).getReadMethod()
+ .invoke(topicParameters);
+ if ((parameter instanceof String && StringUtils.isBlank(parameter.toString()))
+ || (parameter instanceof Boolean && !(Boolean) parameter)
+ || (parameter instanceof Number && ((Number) parameter).longValue() == 0)) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/BusConsumerTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/BusConsumerTest.java
new file mode 100644
index 00000000..207023e5
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/BusConsumerTest.java
@@ -0,0 +1,282 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2018-2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2023-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.apache.kafka.clients.consumer.ConsumerRecords;
+import org.apache.kafka.clients.consumer.KafkaConsumer;
+import org.apache.kafka.common.TopicPartition;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.message.bus.event.base.BusConsumer.FetchingBusConsumer;
+import org.onap.policy.common.message.bus.event.base.BusConsumer.KafkaConsumerWrapper;
+import org.onap.policy.common.message.bus.properties.MessageBusProperties;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+
+class BusConsumerTest extends TopicTestBase {
+
+ private static final int SHORT_TIMEOUT_MILLIS = 10;
+ private static final int LONG_TIMEOUT_MILLIS = 3000;
+
+ @Mock
+ KafkaConsumer<String, String> mockedKafkaConsumer;
+
+ AutoCloseable closeable;
+
+ @BeforeEach
+ @Override
+ public void setUp() {
+ super.setUp();
+ closeable = MockitoAnnotations.openMocks(this);
+ }
+
+ @AfterEach
+ public void tearDown() throws Exception {
+ closeable.close();
+ }
+
+
+ @Test
+ void testFetchingBusConsumer() {
+ // should not be negative
+ var cons = new FetchingBusConsumerImpl(makeBuilder().fetchTimeout(-1).build());
+ assertThat(cons.getSleepTime()).isEqualTo(MessageBusProperties.DEFAULT_TIMEOUT_MS_FETCH);
+
+ // should not be zero
+ cons = new FetchingBusConsumerImpl(makeBuilder().fetchTimeout(0).build());
+ assertThat(cons.getSleepTime()).isEqualTo(MessageBusProperties.DEFAULT_TIMEOUT_MS_FETCH);
+
+ // should not be too large
+ cons = new FetchingBusConsumerImpl(
+ makeBuilder().fetchTimeout(MessageBusProperties.DEFAULT_TIMEOUT_MS_FETCH + 100).build());
+ assertThat(cons.getSleepTime()).isEqualTo(MessageBusProperties.DEFAULT_TIMEOUT_MS_FETCH);
+
+ // should not be what was specified
+ cons = new FetchingBusConsumerImpl(makeBuilder().fetchTimeout(100).build());
+ assertThat(cons.getSleepTime()).isEqualTo(100);
+ }
+
+ @Test
+ void testFetchingBusConsumerSleepAfterFetchFailure() throws InterruptedException {
+
+ var cons = new FetchingBusConsumerImpl(makeBuilder().fetchTimeout(SHORT_TIMEOUT_MILLIS).build()) {
+
+ private CountDownLatch started = new CountDownLatch(1);
+
+ @Override
+ protected void sleepAfterFetchFailure() {
+ started.countDown();
+ super.sleepAfterFetchFailure();
+ }
+ };
+
+ // full sleep
+ long tstart = System.currentTimeMillis();
+ cons.sleepAfterFetchFailure();
+ assertThat(System.currentTimeMillis() - tstart).isGreaterThanOrEqualTo(SHORT_TIMEOUT_MILLIS);
+
+ // close while sleeping - sleep should halt prematurely
+ cons.fetchTimeout = LONG_TIMEOUT_MILLIS;
+ cons.started = new CountDownLatch(1);
+ Thread thread = new Thread(cons::sleepAfterFetchFailure);
+ tstart = System.currentTimeMillis();
+ thread.start();
+ cons.started.await();
+ cons.close();
+ thread.join();
+ assertThat(System.currentTimeMillis() - tstart).isLessThan(LONG_TIMEOUT_MILLIS);
+
+ // interrupt while sleeping - sleep should halt prematurely
+ cons.fetchTimeout = LONG_TIMEOUT_MILLIS;
+ cons.started = new CountDownLatch(1);
+ thread = new Thread(cons::sleepAfterFetchFailure);
+ tstart = System.currentTimeMillis();
+ thread.start();
+ cons.started.await();
+ thread.interrupt();
+ thread.join();
+ assertThat(System.currentTimeMillis() - tstart).isLessThan(LONG_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ void testKafkaConsumerWrapper() {
+ // verify that different wrappers can be built
+ assertThatCode(() -> new KafkaConsumerWrapper(makeKafkaBuilder().build())).doesNotThrowAnyException();
+ }
+
+ @Test
+ void testKafkaConsumerWrapper_InvalidTopic() {
+ BusTopicParams params = makeBuilder().topic(null).build();
+ assertThatThrownBy(() -> new KafkaConsumerWrapper(params))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void testKafkaConsumerWrapperFetch() {
+
+ //Setup Properties for consumer
+ Properties kafkaProps = new Properties();
+ kafkaProps.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
+ kafkaProps.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "test");
+ kafkaProps.setProperty("enable.auto.commit", "true");
+ kafkaProps.setProperty("auto.commit.interval.ms", "1000");
+ kafkaProps.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
+ "org.apache.kafka.common.serialization.StringDeserializer");
+ kafkaProps.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
+ "org.apache.kafka.common.serialization.StringDeserializer");
+ kafkaProps.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
+ kafkaProps.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
+
+ KafkaConsumerWrapper kafka = new KafkaConsumerWrapper(makeKafkaBuilder().build());
+ KafkaConsumer<String, String> consumer = new KafkaConsumer<>(kafkaProps);
+ kafka.consumer = consumer;
+
+ assertThrows(java.lang.IllegalStateException.class, () -> kafka.fetch().iterator().hasNext());
+ consumer.close();
+ }
+
+ @Test
+ void testFetchNoMessages() {
+ KafkaConsumerWrapper kafkaConsumerWrapper = new KafkaConsumerWrapper(makeKafkaBuilder().build());
+ kafkaConsumerWrapper.consumer = mockedKafkaConsumer;
+
+ when(mockedKafkaConsumer.poll(any())).thenReturn(new ConsumerRecords<>(Collections.emptyMap()));
+
+ Iterable<String> result = kafkaConsumerWrapper.fetch();
+
+ verify(mockedKafkaConsumer).poll(any());
+
+ assertNotNull(result);
+
+ assertFalse(result.iterator().hasNext());
+
+ mockedKafkaConsumer.close();
+ }
+
+ @Test
+ void testFetchWithMessages() {
+ // Setup
+ KafkaConsumerWrapper kafkaConsumerWrapper = new KafkaConsumerWrapper(makeKafkaBuilder().build());
+ kafkaConsumerWrapper.consumer = mockedKafkaConsumer;
+
+ ConsumerRecord<String, String> customerRecord =
+ new ConsumerRecord<>("my-effective-topic", 0, 0, "key", "value");
+ Map<TopicPartition, List<ConsumerRecord<String, String>>> recordsMap = new HashMap<>();
+ recordsMap.put(new TopicPartition("my-effective-topic", 0), Collections.singletonList(customerRecord));
+ ConsumerRecords<String, String> consumerRecords = new ConsumerRecords<>(recordsMap);
+
+ when(mockedKafkaConsumer.poll(any())).thenReturn(consumerRecords);
+
+ Iterable<String> result = kafkaConsumerWrapper.fetch();
+
+ verify(mockedKafkaConsumer, times(1)).poll(any());
+
+ verify(mockedKafkaConsumer, times(1)).commitSync(any(Map.class));
+
+ assertNotNull(result);
+
+ assertTrue(result.iterator().hasNext());
+
+ assertEquals("value", result.iterator().next());
+
+ mockedKafkaConsumer.close();
+ }
+
+ @Test
+ void testFetchWithMessagesAndTraceParent() {
+ // Setup
+ KafkaConsumerWrapper kafkaConsumerWrapper = new KafkaConsumerWrapper(makeKafkaBuilder().build());
+ kafkaConsumerWrapper.consumer = mockedKafkaConsumer;
+
+ ConsumerRecord<String, String> customerRecord =
+ new ConsumerRecord<>("my-effective-topic", 0, 0, "key", "value");
+ customerRecord.headers().add(
+ "traceparent",
+ "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01".getBytes(StandardCharsets.UTF_8)
+ );
+
+ Map<TopicPartition, List<ConsumerRecord<String, String>>> recordsMap = new HashMap<>();
+ recordsMap.put(new TopicPartition("my-effective-topic", 0), Collections.singletonList(customerRecord));
+ ConsumerRecords<String, String> consumerRecords = new ConsumerRecords<>(recordsMap);
+
+ when(mockedKafkaConsumer.poll(any())).thenReturn(consumerRecords);
+
+ Iterable<String> result = kafkaConsumerWrapper.fetch();
+
+ verify(mockedKafkaConsumer, times(1)).poll(any());
+
+ verify(mockedKafkaConsumer, times(1)).commitSync(any(Map.class));
+
+ assertNotNull(result);
+
+ assertTrue(result.iterator().hasNext());
+
+ assertEquals("value", result.iterator().next());
+
+ mockedKafkaConsumer.close();
+ }
+
+
+ @Test
+ void testKafkaConsumerWrapperClose() {
+ assertThatCode(() -> new KafkaConsumerWrapper(makeKafkaBuilder().build()).close()).doesNotThrowAnyException();
+ }
+
+ @Test
+ void testKafkaConsumerWrapperToString() {
+ assertNotNull(new KafkaConsumerWrapper(makeKafkaBuilder().build()) {}.toString());
+ }
+
+ private static class FetchingBusConsumerImpl extends FetchingBusConsumer {
+
+ protected FetchingBusConsumerImpl(BusTopicParams busTopicParams) {
+ super(busTopicParams);
+ }
+
+ @Override
+ public Iterable<String> fetch() {
+ return null;
+ }
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/BusTopicBaseTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/BusTopicBaseTest.java
new file mode 100644
index 00000000..343a56a8
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/BusTopicBaseTest.java
@@ -0,0 +1,139 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2018-2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+import org.onap.policy.common.utils.gson.GsonTestUtils;
+
+class BusTopicBaseTest extends TopicTestBase {
+
+ private BusTopicBaseImpl base;
+
+ /**
+ * Initializes the object to be tested.
+ */
+ @BeforeEach
+ @Override
+ public void setUp() {
+ super.setUp();
+
+ base = new BusTopicBaseImpl(builder.build());
+ }
+
+ @Test
+ void testToString() {
+ assertNotNull(base.toString());
+ }
+
+ @Test
+ void testSerialize() {
+ assertThatCode(() -> new GsonTestUtils().compareGson(base, BusTopicBaseTest.class)).doesNotThrowAnyException();
+ }
+
+ @Test
+ void testGetApiKey() {
+ assertEquals(MY_API_KEY, base.getApiKey());
+ }
+
+ @Test
+ void testGetApiSecret() {
+ assertEquals(MY_API_SECRET, base.getApiSecret());
+ }
+
+ @Test
+ void testIsUseHttps() {
+ assertTrue(base.isUseHttps());
+ assertFalse(new BusTopicBaseImpl(builder.useHttps(false).build()).isUseHttps());
+ }
+
+ @Test
+ void testIsAllowSelfSignedCerts() {
+ assertTrue(base.isAllowSelfSignedCerts());
+ assertFalse(new BusTopicBaseImpl(builder.allowSelfSignedCerts(false).build()).isAllowSelfSignedCerts());
+ }
+
+ @Test
+ void testTopic() {
+ assertEquals(MY_TOPIC, base.getTopic());
+ assertEquals(MY_EFFECTIVE_TOPIC, base.getEffectiveTopic());
+ assertNotEquals(base.getTopic(), base.getEffectiveTopic());
+ }
+
+ @Test
+ void testAnyNullOrEmpty() {
+ assertFalse(base.anyNullOrEmpty());
+ assertFalse(base.anyNullOrEmpty("any-none-null", "any-none-null-B"));
+
+ assertTrue(base.anyNullOrEmpty(null, "any-first-null"));
+ assertTrue(base.anyNullOrEmpty("any-middle-null", null, "any-middle-null-B"));
+ assertTrue(base.anyNullOrEmpty("any-last-null", null));
+ assertTrue(base.anyNullOrEmpty("any-empty", ""));
+ }
+
+ @Test
+ void testAllNullOrEmpty() {
+ assertTrue(base.allNullOrEmpty());
+ assertTrue(base.allNullOrEmpty(""));
+ assertTrue(base.allNullOrEmpty(null, ""));
+
+ assertFalse(base.allNullOrEmpty("all-ok-only-one"));
+ assertFalse(base.allNullOrEmpty("all-ok-one", "all-ok-two"));
+ assertFalse(base.allNullOrEmpty("all-ok-null", null));
+ assertFalse(base.allNullOrEmpty("", "all-ok-empty"));
+ assertFalse(base.allNullOrEmpty("", "all-one-ok", null));
+ }
+
+ private static class BusTopicBaseImpl extends BusTopicBase {
+
+ public BusTopicBaseImpl(BusTopicParams busTopicParams) {
+ super(busTopicParams);
+ }
+
+ @Override
+ public CommInfrastructure getTopicCommInfrastructure() {
+ return CommInfrastructure.NOOP;
+ }
+
+ @Override
+ public boolean start() {
+ return true;
+ }
+
+ @Override
+ public boolean stop() {
+ return true;
+ }
+
+ @Override
+ public void shutdown() {
+ // do nothing
+ }
+
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/BusTopicFactoryTestBase.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/BusTopicFactoryTestBase.java
new file mode 100644
index 00000000..bd531114
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/BusTopicFactoryTestBase.java
@@ -0,0 +1,238 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2018-2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_ALLOW_SELF_SIGNED_CERTIFICATES_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_HTTP_HTTPS_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_MANAGED_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_EFFECTIVE_TOPIC_SUFFIX;
+
+import java.util.List;
+import java.util.Properties;
+import java.util.function.Predicate;
+import org.onap.policy.common.message.bus.event.Topic;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+
+/**
+ * Base class for Topic Factory tests that use BusTopicParams.
+ *
+ * @param <T> type of topic managed by the factory
+ */
+public abstract class BusTopicFactoryTestBase<T extends Topic> extends TopicFactoryTestBase<T> {
+
+ /**
+ * Builds a topic.
+ *
+ * @param params the parameters used to configure the topic
+ * @return a new topic
+ */
+ protected abstract T buildTopic(BusTopicParams params);
+
+ /**
+ * Builds a topic.
+ *
+ * @param servers list of servers
+ * @param topic the topic name
+ * @return a new topic
+ */
+ protected abstract T buildTopic(List<String> servers, String topic);
+
+ /**
+ * Gets the parameters used to build the most recent topic.
+ *
+ * @return the most recent topic's parameters
+ */
+ protected abstract BusTopicParams getLastParams();
+
+ /**
+ * Tests building a topic using BusTopicParams.
+ */
+ public void testBuildBusTopicParams() {
+ initFactory();
+
+ // two unmanaged topics
+ T item = buildTopic(makeBuilder().managed(false).effectiveTopic(null).build());
+ T item2 = buildTopic(makeBuilder().managed(false).topic(TOPIC2).build());
+ assertNotNull(item);
+ assertNotNull(item2);
+ assertEquals(item.getTopic(), item.getEffectiveTopic());
+ assertNotEquals(item2.getTopic(), item2.getEffectiveTopic());
+ assertNotSame(item, item2);
+
+ // duplicate topics, but since they aren't managed, they should be different
+ T item3 = buildTopic(makeBuilder().managed(false).build());
+ T item4 = buildTopic(makeBuilder().managed(false).effectiveTopic(TOPIC2).build());
+ assertNotNull(item3);
+ assertNotNull(item4);
+ assertEquals(MY_TOPIC, item4.getTopic());
+ assertEquals(TOPIC2, item4.getEffectiveTopic());
+ assertNotSame(item, item3);
+ assertNotSame(item, item4);
+ assertNotSame(item3, item4);
+
+ // two managed topics
+ T item5 = buildTopic(makeBuilder().build());
+ T item6 = buildTopic(makeBuilder().topic(TOPIC2).build());
+ assertNotNull(item5);
+ assertNotNull(item6);
+
+ // re-build same managed topics - should get exact same objects
+ assertSame(item5, buildTopic(makeBuilder().topic(MY_TOPIC).build()));
+ assertSame(item6, buildTopic(makeBuilder().topic(TOPIC2).build()));
+ }
+
+ /**
+ * Tests exception cases when building a topic using BusTopicParams.
+ */
+ public void testBuildBusTopicParams_Ex() {
+ // null topic
+ assertThatIllegalArgumentException().isThrownBy(() -> buildTopic(makeBuilder().topic(null).build()));
+
+ // empty topic
+ assertThatIllegalArgumentException().isThrownBy(() -> buildTopic(makeBuilder().topic("").build()));
+ }
+
+ /**
+ * Tests building a topic using a list of servers and a topic.
+ */
+ public void testBuildListOfStringString() {
+ initFactory();
+
+ T item1 = buildTopic(servers, MY_TOPIC);
+ assertNotNull(item1);
+
+ // check parameters that were used
+ BusTopicParams params = getLastParams();
+ assertEquals(servers, params.getServers());
+ assertEquals(MY_TOPIC, params.getTopic());
+ assertTrue(params.isManaged());
+ assertFalse(params.isUseHttps());
+
+ T item2 = buildTopic(servers, TOPIC2);
+ assertNotNull(item2);
+ assertNotSame(item1, item2);
+
+ // duplicate - should be the same, as these topics are managed
+ T item3 = buildTopic(servers, TOPIC2);
+ assertSame(item2, item3);
+ }
+
+ /**
+ * Tests building a topic using Properties. Verifies parameters specific to Bus
+ * topics.
+ */
+ public void testBuildProperties() {
+ initFactory();
+
+ List<T> topics = buildTopics(makePropBuilder().makeTopic(MY_TOPIC).build());
+ assertEquals(1, topics.size());
+ assertEquals(MY_TOPIC, topics.get(0).getTopic());
+ assertEquals(MY_EFFECTIVE_TOPIC, topics.get(0).getEffectiveTopic());
+
+ BusTopicParams params = getLastParams();
+ assertTrue(params.isManaged());
+ assertTrue(params.isUseHttps());
+ assertTrue(params.isAllowSelfSignedCerts());
+ assertEquals(MY_API_KEY, params.getApiKey());
+ assertEquals(MY_API_SECRET, params.getApiSecret());
+ assertEquals(List.of(SERVER), params.getServers());
+ assertEquals(MY_TOPIC, params.getTopic());
+ assertEquals(MY_EFFECTIVE_TOPIC, params.getEffectiveTopic());
+
+ List<T> topics2 = buildTopics(makePropBuilder().makeTopic(TOPIC3)
+ .removeTopicProperty(PROPERTY_TOPIC_EFFECTIVE_TOPIC_SUFFIX).build());
+ assertEquals(1, topics2.size());
+ assertEquals(TOPIC3, topics2.get(0).getTopic());
+ assertEquals(topics2.get(0).getTopic(), topics2.get(0).getEffectiveTopic());
+ }
+
+ @Override
+ void testBuildProperties_Variations() {
+ super.testBuildProperties_Variations();
+
+ // check boolean properties that default to true
+ checkDefault(PROPERTY_MANAGED_SUFFIX, BusTopicParams::isManaged);
+
+ // check boolean properties that default to false
+ checkDefault(PROPERTY_HTTP_HTTPS_SUFFIX, params -> !params.isUseHttps());
+ checkDefault(PROPERTY_ALLOW_SELF_SIGNED_CERTIFICATES_SUFFIX, params -> !params.isAllowSelfSignedCerts());
+ }
+
+ /**
+ * Verifies that a parameter has the correct default, if the original builder property
+ * is not provided.
+ *
+ * @param builderName name of the builder property
+ * @param validate function to test the validity of the property
+ * @param values the values to which the property should be set, defaults to
+ * {@code null} and ""
+ */
+ protected void checkDefault(String builderName, Predicate<BusTopicParams> validate, Object... values) {
+ Object[] values2 = (values.length > 0 ? values : new Object[] {null, ""});
+
+ for (Object value : values2) {
+ // always start with a fresh factory
+ initFactory();
+
+ TopicPropertyBuilder builder = makePropBuilder().makeTopic(MY_TOPIC);
+
+ if (value == null) {
+ builder.removeTopicProperty(builderName);
+
+ } else {
+ builder.setTopicProperty(builderName, value.toString());
+ }
+
+ assertEquals(1, buildTopics(builder.build()).size(), "size for default " + value);
+ assertTrue(validate.test(getLastParams()), "default for " + value);
+ }
+ }
+
+ /**
+ * Verifies that an "additional" property does not exist, if the original builder
+ * property is not provided.
+ *
+ * @param builderName name of the builder property
+ * @param addName name of the "additional" property
+ */
+ public void expectNullAddProp(String builderName, String addName) {
+
+ // remove the property
+ initFactory();
+ Properties props = makePropBuilder().makeTopic(MY_TOPIC).removeTopicProperty(builderName).build();
+ assertEquals(1, buildTopics(props).size());
+ assertFalse(getLastParams().getAdditionalProps().containsKey(addName));
+
+
+ // repeat, this time using an empty string instead of null
+ initFactory();
+ props = makePropBuilder().makeTopic(MY_TOPIC).setTopicProperty(builderName, "").build();
+ assertEquals(1, buildTopics(props).size());
+ assertFalse(getLastParams().getAdditionalProps().containsKey(addName));
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/InlineBusTopicSinkTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/InlineBusTopicSinkTest.java
new file mode 100644
index 00000000..820fc2c3
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/InlineBusTopicSinkTest.java
@@ -0,0 +1,230 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2018-2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.event.TopicListener;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+import org.onap.policy.common.utils.gson.GsonTestUtils;
+
+class InlineBusTopicSinkTest extends TopicTestBase {
+
+ private InlineBusTopicSinkImpl sink;
+
+ /**
+ * Creates the object to be tested.
+ */
+ @BeforeEach
+ @Override
+ public void setUp() {
+ super.setUp();
+
+ sink = new InlineBusTopicSinkImpl(makeBuilder().build());
+ }
+
+ @AfterEach
+ public void tearDown() {
+ sink.shutdown();
+ }
+
+ @Test
+ void testSerialize() {
+ assertThatCode(() -> new GsonTestUtils().compareGson(sink, InlineBusTopicSinkTest.class))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void testInlineBusTopicSinkImpl() {
+ // verify that different wrappers can be built
+ sink = new InlineBusTopicSinkImpl(makeBuilder().build());
+ assertEquals(MY_PARTITION, sink.getPartitionKey());
+
+ sink = new InlineBusTopicSinkImpl(makeBuilder().partitionId(null).build());
+ assertNotNull(sink.getPartitionKey());
+ }
+
+ @Test
+ void testStart() {
+ assertTrue(sink.start());
+ assertEquals(1, sink.initCount);
+
+ // re-start, init() should not be invoked again
+ assertTrue(sink.start());
+ assertEquals(1, sink.initCount);
+ }
+
+ @Test
+ void testStart_Locked() {
+ sink.lock();
+ assertThatThrownBy(() -> sink.start()).isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ void testStop() {
+ BusPublisher pub = mock(BusPublisher.class);
+ sink.publisher = pub;
+
+ assertTrue(sink.stop());
+ verify(pub).close();
+
+ // stop again, shouldn't not invoke close() again
+ assertFalse(sink.stop());
+ verify(pub).close();
+
+ // publisher throws exception
+ sink = new InlineBusTopicSinkImpl(makeBuilder().build());
+ sink.publisher = pub;
+ doThrow(new RuntimeException(EXPECTED)).when(pub).close();
+ assertTrue(sink.stop());
+ }
+
+ @Test
+ void testSend() {
+ sink.start();
+ BusPublisher pub = mock(BusPublisher.class);
+ sink.publisher = pub;
+
+ TopicListener listener = mock(TopicListener.class);
+ sink.register(listener);
+
+ assertTrue(sink.send(MY_MESSAGE));
+
+ verify(pub).send(MY_PARTITION, MY_MESSAGE);
+ verify(listener).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, MY_MESSAGE);
+ assertEquals(List.of(MY_MESSAGE), Arrays.asList(sink.getRecentEvents()));
+
+ // arrange for send to throw an exception
+ when(pub.send(anyString(), anyString())).thenThrow(new RuntimeException(EXPECTED));
+
+ assertFalse(sink.send(MY_MESSAGE));
+
+ // no more event deliveries
+ verify(listener).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, MY_MESSAGE);
+ }
+
+ @Test
+ void testSend_NullMessage() {
+ sink.start();
+ sink.publisher = mock(BusPublisher.class);
+
+ assertThatThrownBy(() -> sink.send(null)).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void testSend_EmptyMessage() {
+ sink.start();
+ sink.publisher = mock(BusPublisher.class);
+
+ assertThatThrownBy(() -> sink.send("")).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void testSend_NotStarted() {
+ sink.publisher = mock(BusPublisher.class);
+ assertThatThrownBy(() -> sink.send(MY_MESSAGE)).isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ void testSetPartitionKey_getPartitionKey() {
+ assertEquals(MY_PARTITION, sink.getPartitionKey());
+
+ sink.setPartitionKey("part-B");
+ assertEquals("part-B", sink.getPartitionKey());
+ }
+
+ @Test
+ void testShutdown() {
+ BusPublisher pub = mock(BusPublisher.class);
+ sink.publisher = pub;
+
+ sink.shutdown();
+ verify(pub).close();
+ }
+
+ @Test
+ void testAnyNullOrEmpty() {
+ assertFalse(sink.anyNullOrEmpty());
+ assertFalse(sink.anyNullOrEmpty("any-none-null", "any-none-null-B"));
+
+ assertTrue(sink.anyNullOrEmpty(null, "any-first-null"));
+ assertTrue(sink.anyNullOrEmpty("any-middle-null", null, "any-middle-null-B"));
+ assertTrue(sink.anyNullOrEmpty("any-last-null", null));
+ assertTrue(sink.anyNullOrEmpty("any-empty", ""));
+ }
+
+ @Test
+ void testAllNullOrEmpty() {
+ assertTrue(sink.allNullOrEmpty());
+ assertTrue(sink.allNullOrEmpty(""));
+ assertTrue(sink.allNullOrEmpty(null, ""));
+
+ assertFalse(sink.allNullOrEmpty("all-ok-only-one"));
+ assertFalse(sink.allNullOrEmpty("all-ok-one", "all-ok-two"));
+ assertFalse(sink.allNullOrEmpty("all-ok-null", null));
+ assertFalse(sink.allNullOrEmpty("", "all-ok-empty"));
+ assertFalse(sink.allNullOrEmpty("", "all-one-ok", null));
+ }
+
+ @Test
+ void testToString() {
+ assertTrue(sink.toString().startsWith("InlineBusTopicSink ["));
+ }
+
+ /**
+ * Implementation of InlineBusTopicSink that tracks the number of times that init() is
+ * invoked.
+ */
+ private static class InlineBusTopicSinkImpl extends InlineBusTopicSink {
+
+ private int initCount = 0;
+
+ public InlineBusTopicSinkImpl(BusTopicParams busTopicParams) {
+ super(busTopicParams);
+ }
+
+ @Override
+ public CommInfrastructure getTopicCommInfrastructure() {
+ return CommInfrastructure.NOOP;
+ }
+
+ @Override
+ public void init() {
+ ++initCount;
+ }
+
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/SingleThreadedBusTopicSourceTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/SingleThreadedBusTopicSourceTest.java
new file mode 100644
index 00000000..8ad8e8fd
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/SingleThreadedBusTopicSourceTest.java
@@ -0,0 +1,375 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2018-2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.event.TopicListener;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+import org.onap.policy.common.utils.gson.GsonTestUtils;
+import org.onap.policy.common.utils.network.NetworkUtil;
+
+class SingleThreadedBusTopicSourceTest extends TopicTestBase {
+ private Thread thread;
+ private BusConsumer cons;
+ private TopicListener listener;
+ private SingleThreadedBusTopicSourceImpl source;
+
+ /**
+ * Creates the object to be tested, as well as various mocks.
+ */
+ @BeforeEach
+ @Override
+ public void setUp() {
+ super.setUp();
+
+ thread = mock(Thread.class);
+ cons = mock(BusConsumer.class);
+ listener = mock(TopicListener.class);
+ source = new SingleThreadedBusTopicSourceImpl(makeBuilder().build());
+ }
+
+ @AfterEach
+ public void tearDown() {
+ source.shutdown();
+ }
+
+ @Test
+ void testSerialize() {
+ assertThatCode(() -> new GsonTestUtils().compareGson(source, SingleThreadedBusTopicSourceTest.class))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void testRegister() {
+ source.register(listener);
+ assertEquals(1, source.initCount);
+ source.offer(MY_MESSAGE);
+ verify(listener).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, MY_MESSAGE);
+
+ // register another - should not re-init
+ TopicListener listener2 = mock(TopicListener.class);
+ source.register(listener2);
+ assertEquals(1, source.initCount);
+ source.offer(MY_MESSAGE + "z");
+ verify(listener).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, MY_MESSAGE + "z");
+ verify(listener2).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, MY_MESSAGE + "z");
+
+ // re-register - should not re-init
+ source.register(listener);
+ assertEquals(1, source.initCount);
+ source.offer(MY_MESSAGE2);
+ verify(listener).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, MY_MESSAGE2);
+
+ // lock & register - should not init
+ source = new SingleThreadedBusTopicSourceImpl(makeBuilder().build());
+ source.lock();
+ source.register(listener);
+ assertEquals(0, source.initCount);
+
+ // exception during init
+ source = new SingleThreadedBusTopicSourceImpl(makeBuilder().build());
+ source.initEx = true;
+ source.register(listener);
+ }
+
+ @Test
+ void testUnregister() {
+ TopicListener listener2 = mock(TopicListener.class);
+ source.register(listener);
+ source.register(listener2);
+
+ // unregister first listener - should NOT invoke close
+ source.unregister(listener);
+ verify(cons, never()).close();
+ assertEquals(Arrays.asList(listener2), source.snapshotTopicListeners());
+
+ // unregister same listener - should not invoke close
+ source.unregister(listener);
+ verify(cons, never()).close();
+ assertEquals(Arrays.asList(listener2), source.snapshotTopicListeners());
+
+ // unregister second listener - SHOULD invoke close
+ source.unregister(listener2);
+ verify(cons).close();
+ assertTrue(source.snapshotTopicListeners().isEmpty());
+
+ // unregister same listener - should not invoke close again
+ source.unregister(listener2);
+ verify(cons).close();
+ assertTrue(source.snapshotTopicListeners().isEmpty());
+ }
+
+ @Test
+ void testToString() {
+ assertTrue(source.toString().startsWith("SingleThreadedBusTopicSource ["));
+ }
+
+ @Test
+ void testMakePollerThread() {
+ SingleThreadedBusTopicSource source2 = new SingleThreadedBusTopicSource(makeBuilder().build()) {
+ @Override
+ public CommInfrastructure getTopicCommInfrastructure() {
+ return CommInfrastructure.NOOP;
+ }
+
+ @Override
+ public void init() throws MalformedURLException {
+ // do nothing
+ }
+ };
+
+ assertNotNull(source2.makePollerThread());
+ }
+
+ @Test
+ void testSingleThreadedBusTopicSource() {
+ // Note: if the value contains "-", it's probably a UUID
+
+ // verify that different wrappers can be built
+ source = new SingleThreadedBusTopicSourceImpl(makeBuilder().build());
+ assertThat(source.getConsumerGroup()).isEqualTo(MY_CONS_GROUP);
+ assertThat(source.getConsumerInstance()).isEqualTo(MY_CONS_INST);
+
+ // group is null => group is UUID, instance is as provided
+ source = new SingleThreadedBusTopicSourceImpl(makeBuilder().consumerGroup(null).build());
+ assertThat(source.getConsumerGroup()).contains("-").isNotEqualTo(NetworkUtil.getHostname());
+ assertThat(source.getConsumerInstance()).isEqualTo(MY_CONS_INST);
+
+ // instance is null => group is as provided, instance is UUID
+ source = new SingleThreadedBusTopicSourceImpl(makeBuilder().consumerInstance(null).build());
+ assertThat(source.getConsumerGroup()).isEqualTo(MY_CONS_GROUP);
+ assertThat(source.getConsumerInstance()).contains("-").isNotEqualTo(NetworkUtil.getHostname());
+
+ // group & instance are null => group is UUID, instance is hostname
+ source = new SingleThreadedBusTopicSourceImpl(makeBuilder().consumerGroup(null).consumerInstance(null).build());
+ assertThat(source.getConsumerGroup()).contains("-").isNotEqualTo(NetworkUtil.getHostname());
+ assertThat(source.getConsumerInstance()).isEqualTo(NetworkUtil.getHostname());
+
+ assertThatCode(() -> new SingleThreadedBusTopicSourceImpl(
+ makeBuilder().fetchLimit(-1).fetchTimeout(-1).build())).doesNotThrowAnyException();
+ }
+
+ @Test
+ void testStart() {
+ source.start();
+ assertTrue(source.isAlive());
+ assertEquals(1, source.initCount);
+ verify(thread).start();
+
+ // attempt to start again - nothing should be invoked again
+ source.start();
+ assertTrue(source.isAlive());
+ assertEquals(1, source.initCount);
+ verify(thread).start();
+
+ // stop & re-start
+ source.stop();
+ source.start();
+ assertTrue(source.isAlive());
+ assertEquals(2, source.initCount);
+ verify(thread, times(2)).start();
+ }
+
+ @Test
+ void testStart_Locked() {
+ source.lock();
+ assertThatThrownBy(() -> source.start()).isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ void testStart_InitEx() {
+ assertThatThrownBy(() -> {
+ source.initEx = true;
+
+ source.start();
+ }).isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ void testStop() {
+ source.start();
+ source.stop();
+ verify(cons).close();
+
+ // stop it again - not re-closed
+ source.stop();
+ verify(cons).close();
+
+ // start & stop again, but with an exception
+ doThrow(new RuntimeException(EXPECTED)).when(cons).close();
+ source.start();
+ source.stop();
+ }
+
+ @Test
+ void testRun() throws Exception {
+ source.register(listener);
+
+ /*
+ * Die in the middle of fetching messages. Also, throw an exception during the
+ * first fetch attempt.
+ */
+ when(cons.fetch()).thenAnswer(new Answer<Iterable<String>>() {
+ int count = 0;
+
+ @Override
+ public Iterable<String> answer(InvocationOnMock invocation) throws Throwable {
+ if (++count > 1) {
+ source.alive = false;
+ return Arrays.asList(MY_MESSAGE, MY_MESSAGE2);
+
+ } else {
+ throw new IOException(EXPECTED);
+ }
+ }
+ });
+ source.alive = true;
+ source.run();
+ assertEquals(Arrays.asList(MY_MESSAGE), Arrays.asList(source.getRecentEvents()));
+ verify(listener).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, MY_MESSAGE);
+ verify(listener, never()).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, MY_MESSAGE2);
+
+ /*
+ * Die AFTER fetching messages.
+ */
+ final String msga = "message-A";
+ final String msgb = "message-B";
+ when(cons.fetch()).thenAnswer(new Answer<Iterable<String>>() {
+ int count = 0;
+
+ @Override
+ public Iterable<String> answer(InvocationOnMock invocation) throws Throwable {
+ if (++count > 1) {
+ source.alive = false;
+ return Collections.emptyList();
+
+ } else {
+ return Arrays.asList(msga, msgb);
+ }
+ }
+ });
+ source.alive = true;
+ source.run();
+ verify(listener).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, msga);
+ verify(listener).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, msgb);
+
+ assertEquals(Arrays.asList(MY_MESSAGE, msga, msgb), Arrays.asList(source.getRecentEvents()));
+ }
+
+ @Test
+ void testOffer() {
+ source.register(listener);
+ source.offer(MY_MESSAGE);
+ verify(listener).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, MY_MESSAGE);
+ assertEquals(Arrays.asList(MY_MESSAGE), Arrays.asList(source.getRecentEvents()));
+ }
+
+ @Test
+ void testOffer_NotStarted() {
+ assertThatThrownBy(() -> source.offer(MY_MESSAGE)).isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ void testGetConsumerGroup() {
+ assertEquals(MY_CONS_GROUP, source.getConsumerGroup());
+ }
+
+ @Test
+ void testGetConsumerInstance() {
+ assertEquals(MY_CONS_INST, source.getConsumerInstance());
+ }
+
+ @Test
+ void testShutdown() {
+ source.register(listener);
+
+ source.shutdown();
+ verify(cons).close();
+ assertTrue(source.snapshotTopicListeners().isEmpty());
+ }
+
+ @Test
+ void testGetFetchTimeout() {
+ assertEquals(MY_FETCH_TIMEOUT, source.getFetchTimeout());
+ }
+
+ @Test
+ void testGetFetchLimit() {
+ assertEquals(MY_FETCH_LIMIT, source.getFetchLimit());
+ }
+
+ /**
+ * Implementation of SingleThreadedBusTopicSource that counts the number of times
+ * init() is invoked.
+ */
+ private class SingleThreadedBusTopicSourceImpl extends SingleThreadedBusTopicSource {
+
+ private int initCount = 0;
+ private boolean initEx = false;
+
+ public SingleThreadedBusTopicSourceImpl(BusTopicParams busTopicParams) {
+ super(busTopicParams);
+ }
+
+ @Override
+ public CommInfrastructure getTopicCommInfrastructure() {
+ return CommInfrastructure.NOOP;
+ }
+
+ @Override
+ public void init() throws MalformedURLException {
+ ++initCount;
+
+ if (initEx) {
+ throw new MalformedURLException(EXPECTED);
+ }
+
+ consumer = cons;
+ }
+
+ @Override
+ protected Thread makePollerThread() {
+ return thread;
+ }
+
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicBaseTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicBaseTest.java
new file mode 100644
index 00000000..5ecde258
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicBaseTest.java
@@ -0,0 +1,355 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2018-2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.util.Collections;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.event.TopicListener;
+import org.onap.policy.common.utils.gson.GsonTestUtils;
+
+class TopicBaseTest extends TopicTestBase {
+
+ private TopicBaseImpl base;
+
+ /**
+ * Creates the object to be tested.
+ */
+ @BeforeEach
+ @Override
+ public void setUp() {
+ super.setUp();
+
+ base = new TopicBaseImpl(servers, MY_TOPIC);
+ }
+
+ @Test
+ void testTopicBase_NullServers() {
+ assertThatThrownBy(() -> new TopicBaseImpl(null, MY_TOPIC)).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void testTopicBase_EmptyServers() {
+ List<String> testList = Collections.emptyList();
+ assertThatThrownBy(() -> new TopicBaseImpl(testList, MY_TOPIC))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void testTopicBase_NullTopic() {
+ assertThatThrownBy(() -> new TopicBaseImpl(servers, null)).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void testTopicBase_EmptyTopic() {
+ assertThatThrownBy(() -> new TopicBaseImpl(servers, "")).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void testTopicBase_EffectiveTopic() {
+ TopicBase baseEf = new TopicBaseImpl(servers, MY_TOPIC, MY_EFFECTIVE_TOPIC);
+ assertEquals(MY_TOPIC, baseEf.getTopic());
+ assertEquals(MY_EFFECTIVE_TOPIC, baseEf.getEffectiveTopic());
+ }
+
+ @Test
+ void testTopicBase_NullEffectiveTopic() {
+ TopicBase baseEf = new TopicBaseImpl(servers, MY_TOPIC, null);
+ assertEquals(MY_TOPIC, baseEf.getTopic());
+ assertEquals(MY_TOPIC, baseEf.getEffectiveTopic());
+ }
+
+ @Test
+ void testTopicBase_EmptyEffectiveTopic() {
+ TopicBase baseEf = new TopicBaseImpl(servers, MY_TOPIC, "");
+ assertEquals(MY_TOPIC, baseEf.getTopic());
+ assertEquals(MY_TOPIC, baseEf.getEffectiveTopic());
+ }
+
+ @Test
+ void testSerialize() {
+ assertThatCode(() -> new GsonTestUtils().compareGson(base, TopicBaseTest.class)).doesNotThrowAnyException();
+ }
+
+ @Test
+ void testRegister() {
+ TopicListener listener = mock(TopicListener.class);
+ base.register(listener);
+ assertEquals(List.of(listener), base.snapshotTopicListeners());
+
+ // re-register - list should be unchanged
+ base.register(listener);
+ assertEquals(List.of(listener), base.snapshotTopicListeners());
+
+ // register a new listener
+ TopicListener listener2 = mock(TopicListener.class);
+ base.register(listener2);
+ assertEquals(List.of(listener, listener2), base.snapshotTopicListeners());
+ }
+
+ @Test
+ void testRegister_NullListener() {
+ assertThatThrownBy(() -> base.register(null)).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void testUnregister() {
+ // register two listeners
+ TopicListener listener = mock(TopicListener.class);
+ TopicListener listener2 = mock(TopicListener.class);
+ base.register(listener);
+ base.register(listener2);
+
+ // unregister one
+ base.unregister(listener);
+ assertEquals(List.of(listener2), base.snapshotTopicListeners());
+
+ // unregister the other
+ base.unregister(listener2);
+ assertTrue(base.snapshotTopicListeners().isEmpty());
+
+ // unregister again
+ base.unregister(listener2);
+ assertTrue(base.snapshotTopicListeners().isEmpty());
+ }
+
+ @Test
+ void testUnregister_NullListener() {
+ base.register(mock(TopicListener.class));
+ assertThatThrownBy(() -> base.unregister(null)).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void testBroadcast() {
+ // register two listeners
+ TopicListener listener = mock(TopicListener.class);
+ TopicListener listener2 = mock(TopicListener.class);
+ base.register(listener);
+ base.register(listener2);
+
+ // broadcast a message
+ final String msg1 = "message-A";
+ base.broadcast(msg1);
+ verify(listener).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, msg1);
+ verify(listener2).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, msg1);
+
+ // broadcast another message, with an exception
+ final String msg2 = "message-B";
+ doThrow(new RuntimeException(EXPECTED)).when(listener).onTopicEvent(any(), any(), any());
+ base.broadcast(msg2);
+ verify(listener).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, msg2);
+ verify(listener2).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, msg2);
+ }
+
+ @Test
+ void testLock_testUnlock() {
+ assertFalse(base.isLocked());
+ assertTrue(base.lock());
+ assertEquals(0, base.startCount);
+ assertEquals(1, base.stopCount);
+
+ // lock again - should not stop again
+ assertTrue(base.isLocked());
+ assertTrue(base.lock());
+ assertEquals(0, base.startCount);
+ assertEquals(1, base.stopCount);
+
+ assertTrue(base.isLocked());
+ assertTrue(base.unlock());
+ assertEquals(1, base.startCount);
+ assertEquals(1, base.stopCount);
+
+ // unlock again - should not start again
+ assertFalse(base.isLocked());
+ assertTrue(base.unlock());
+ assertEquals(1, base.startCount);
+ assertEquals(1, base.stopCount);
+ }
+
+ /**
+ * Tests lock/unlock when the stop/start methods return false.
+ */
+ @Test
+ void testLock_testUnlock_FalseReturns() {
+
+ // lock, but stop returns false
+ base.stopReturn = false;
+ assertFalse(base.lock());
+ assertTrue(base.isLocked());
+ assertTrue(base.lock());
+
+ // unlock, but start returns false
+ base.startReturn = false;
+ assertFalse(base.unlock());
+ assertFalse(base.isLocked());
+ assertTrue(base.unlock());
+ }
+
+ /**
+ * Tests lock/unlock when the start method throws an exception.
+ */
+ @Test
+ void testLock_testUnlock_Exception() {
+
+ // lock & re-lock, but start throws an exception
+ base.startEx = true;
+ assertTrue(base.lock());
+ assertFalse(base.unlock());
+ assertFalse(base.isLocked());
+ assertTrue(base.unlock());
+ }
+
+ @Test
+ void testIsLocked() {
+ assertFalse(base.isLocked());
+ base.lock();
+ assertTrue(base.isLocked());
+ base.unlock();
+ assertFalse(base.isLocked());
+ }
+
+ @Test
+ void testGetTopic() {
+ assertEquals(MY_TOPIC, base.getTopic());
+ }
+
+ @Test
+ void testGetEffectiveTopic() {
+ assertEquals(MY_TOPIC, base.getTopic());
+ assertEquals(MY_TOPIC, base.getEffectiveTopic());
+ }
+
+ @Test
+ void testIsAlive() {
+ assertFalse(base.isAlive());
+ base.start();
+ assertTrue(base.isAlive());
+ base.stop();
+ assertFalse(base.isAlive());
+ }
+
+ @Test
+ void testGetServers() {
+ assertEquals(servers, base.getServers());
+ }
+
+ @Test
+ void testGetRecentEvents() {
+ assertEquals(0, base.getRecentEvents().length);
+
+ base.addEvent("recent-A");
+ base.addEvent("recent-B");
+
+ String[] recent = base.getRecentEvents();
+ assertEquals(2, recent.length);
+ assertEquals("recent-A", recent[0]);
+ assertEquals("recent-B", recent[1]);
+ }
+
+ @Test
+ void testToString() {
+ assertNotNull(base.toString());
+ }
+
+ /**
+ * Implementation of TopicBase.
+ */
+ private static class TopicBaseImpl extends TopicBase {
+ private int startCount = 0;
+ private int stopCount = 0;
+ private boolean startReturn = true;
+ private boolean stopReturn = true;
+ private boolean startEx = false;
+
+ /**
+ * Constructor.
+ *
+ * @param servers list of servers
+ * @param topic topic name
+ */
+ public TopicBaseImpl(List<String> servers, String topic) {
+ super(servers, topic);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param servers list of servers
+ * @param topic topic name
+ * @param effectiveTopic effective topic name for network communication
+ */
+ public TopicBaseImpl(List<String> servers, String topic, String effectiveTopic) {
+ super(servers, topic, effectiveTopic);
+ }
+
+ @Override
+ public CommInfrastructure getTopicCommInfrastructure() {
+ return CommInfrastructure.NOOP;
+ }
+
+ @Override
+ public boolean start() {
+ ++startCount;
+
+ if (startEx) {
+ throw new RuntimeException(EXPECTED);
+ }
+
+ alive = true;
+ return startReturn;
+ }
+
+ @Override
+ public boolean stop() {
+ ++stopCount;
+ alive = false;
+ return stopReturn;
+ }
+
+ @Override
+ public void shutdown() {
+ // do nothing
+ }
+
+ /**
+ * Adds an event to the list of recent events.
+ *
+ * @param event event to be added
+ */
+ public void addEvent(String event) {
+ recentEvents.add(event);
+ }
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicFactoryTestBase.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicFactoryTestBase.java
new file mode 100644
index 00000000..8444b482
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicFactoryTestBase.java
@@ -0,0 +1,225 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2018-2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_SERVERS_SUFFIX;
+
+import java.util.List;
+import java.util.Properties;
+import org.onap.policy.common.message.bus.event.Topic;
+
+/**
+ * Base class for XxxTopicFactory tests.
+ *
+ * @param <T> type of topic managed by the factory
+ */
+public abstract class TopicFactoryTestBase<T extends Topic> extends TopicTestBase {
+
+ public static final String SERVER = "my-server";
+ public static final String TOPIC2 = "my-topic-2";
+ public static final String TOPIC3 = "my-topic-3";
+
+ /**
+ * Initializes a new factory.
+ */
+ protected abstract void initFactory();
+
+ /**
+ * Makes a property builder.
+ *
+ * @return a new property builder
+ */
+ protected abstract TopicPropertyBuilder makePropBuilder();
+
+ /**
+ * Builds a set of topics.
+ *
+ * @param properties the properties used to configure the topics
+ * @return a list of new topics
+ */
+ protected abstract List<T> buildTopics(Properties properties);
+
+ /**
+ * Destroys the factory.
+ */
+ protected abstract void destroyFactory();
+
+ /**
+ * Destroys a topic within the factory.
+ *
+ * @param topic the topic to destroy
+ */
+ protected abstract void destroyTopic(String topic);
+
+ /**
+ * Gets the list of topics from the factory.
+ *
+ * @return the topic inventory
+ */
+ protected abstract List<T> getInventory();
+
+ /**
+ * Gets a topic from the factory.
+ *
+ * @param topic the topic name
+ * @return the topic
+ */
+ protected abstract T getTopic(String topic);
+
+
+ /**
+ * Tests building a topic using varied Properties.
+ */
+ void testBuildProperties_Variations() {
+ initFactory();
+
+ // null topic list
+ assertTrue(buildTopics(makePropBuilder().build()).isEmpty());
+
+ // empty topic list
+ assertTrue(buildTopics(makePropBuilder().addTopic("").build()).isEmpty());
+
+ // null servers
+ assertTrue(buildTopics(makePropBuilder().makeTopic(MY_TOPIC).removeTopicProperty(PROPERTY_TOPIC_SERVERS_SUFFIX)
+ .build()).isEmpty());
+
+ // empty servers
+ assertTrue(buildTopics(makePropBuilder().makeTopic(MY_TOPIC).setTopicProperty(PROPERTY_TOPIC_SERVERS_SUFFIX, "")
+ .build()).isEmpty());
+ }
+
+ /**
+ * Tests building multiple topics using Properties.
+ */
+ public void testBuildProperties_Multiple() {
+ initFactory();
+
+ // make two fully-defined topics, and add two duplicate topic names to the list
+ TopicPropertyBuilder builder =
+ makePropBuilder().makeTopic(MY_TOPIC).makeTopic(TOPIC2).addTopic(MY_TOPIC).addTopic(MY_TOPIC);
+
+ List<T> lst = buildTopics(builder.build());
+ assertEquals(4, lst.size());
+
+ int index = 0;
+ T item = lst.get(index++);
+ assertNotSame(item, lst.get(index++));
+ assertSame(item, lst.get(index++));
+ assertSame(item, lst.get(index++));
+ }
+
+ /**
+ * Tests destroy(topic), get(topic), and inventory() methods.
+ */
+ public void testDestroyString_testGet_testInventory() {
+ initFactory();
+
+ List<T> lst = buildTopics(makePropBuilder().makeTopic(MY_TOPIC).makeTopic(TOPIC2).build());
+
+ int index = 0;
+ T item1 = lst.get(index++);
+ T item2 = lst.get(index++);
+
+ assertEquals(2, getInventory().size());
+ assertTrue(getInventory().contains(item1));
+ assertTrue(getInventory().contains(item2));
+
+ item1.start();
+ item2.start();
+
+ assertEquals(item1, getTopic(MY_TOPIC));
+ assertEquals(item2, getTopic(TOPIC2));
+
+ destroyTopic(MY_TOPIC);
+ assertFalse(item1.isAlive());
+ assertTrue(item2.isAlive());
+ assertEquals(item2, getTopic(TOPIC2));
+ assertEquals(1, getInventory().size());
+ assertTrue(getInventory().contains(item2));
+
+ // repeat
+ destroyTopic(MY_TOPIC);
+ assertFalse(item1.isAlive());
+ assertTrue(item2.isAlive());
+
+ // with other topic
+ destroyTopic(TOPIC2);
+ assertFalse(item1.isAlive());
+ assertFalse(item2.isAlive());
+ assertEquals(0, getInventory().size());
+ }
+
+ /**
+ * Tests exception cases with destroy(topic).
+ */
+ public void testDestroyString_Ex() {
+ // null topic
+ assertThatIllegalArgumentException().as("null topic").isThrownBy(() -> destroyTopic(null));
+
+ // empty topic
+ assertThatIllegalArgumentException().as("empty topic").isThrownBy(() -> destroyTopic(""));
+ }
+
+ /**
+ * Tests the destroy() method.
+ */
+ public void testDestroy() {
+ initFactory();
+
+ List<T> lst = buildTopics(makePropBuilder().makeTopic(MY_TOPIC).makeTopic(TOPIC2).build());
+
+ int index = 0;
+ T item1 = lst.get(index++);
+ T item2 = lst.get(index++);
+
+ item1.start();
+ item2.start();
+
+ destroyFactory();
+
+ assertFalse(item1.isAlive());
+ assertFalse(item2.isAlive());
+ assertEquals(0, getInventory().size());
+ }
+
+ /**
+ * Tests exception cases with get(topic).
+ */
+ public void testGet_Ex() {
+ // null topic
+ assertThatIllegalArgumentException().as("null topic").isThrownBy(() -> getTopic(null));
+
+ // empty topic
+ assertThatIllegalArgumentException().as("empty topic").isThrownBy(() -> getTopic(""));
+
+ // unknown topic
+ initFactory();
+ buildTopics(makePropBuilder().makeTopic(MY_TOPIC).build());
+
+ assertThatIllegalStateException().as("unknown topic").isThrownBy(() -> getTopic(TOPIC2));
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicPropertyBuilder.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicPropertyBuilder.java
new file mode 100644
index 00000000..29c5306b
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicPropertyBuilder.java
@@ -0,0 +1,121 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP Policy Engine - Common Modules
+ * ================================================================================
+ * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import java.util.Properties;
+
+/**
+ * Builder of properties used when configuring topics.
+ */
+public abstract class TopicPropertyBuilder {
+ private final Properties properties = new Properties();
+ private final String prefix;
+ private String topicPrefix;
+
+ /**
+ * Constructs the object.
+ *
+ * @param prefix the prefix for the properties to be built
+ */
+ public TopicPropertyBuilder(String prefix) {
+ this.prefix = prefix;
+ }
+
+ /**
+ * Constructs the properties from the builder.
+ *
+ * @return a copy of the properties
+ */
+ public Properties build() {
+ Properties props = new Properties();
+ props.putAll(properties);
+
+ return props;
+ }
+
+ /**
+ * Adds a topic to the list of topics, configuring all of its properties with default
+ * values.
+ *
+ * @param topic the topic to be added
+ * @return this builder
+ */
+ public abstract TopicPropertyBuilder makeTopic(String topic);
+
+ /**
+ * Adds a topic to the list of topics. Also sets the current topic so that subsequent
+ * invocations of property methods will manipulate the topic's properties.
+ *
+ * @param topic the topic to be added
+ * @return this builder
+ */
+ public TopicPropertyBuilder addTopic(String topic) {
+ // add topic to the list of topics
+ String topicList = properties.getProperty(prefix);
+ if (topicList == null || topicList.isEmpty()) {
+ topicList = topic;
+ } else {
+ topicList += "," + topic;
+ }
+
+ properties.setProperty(prefix, topicList);
+
+ setTopic(topic);
+
+ return this;
+ }
+
+ /**
+ * Sets the topic for which subsequent properties will be managed.
+ *
+ * @param topic the topic
+ * @return this builder
+ */
+ public TopicPropertyBuilder setTopic(String topic) {
+ this.topicPrefix = prefix + "." + topic;
+ return this;
+ }
+
+ /**
+ * Sets a topic's property.
+ *
+ * @param name name of the property
+ * @param value value to which the property should be set
+ * @return this builder
+ */
+ public TopicPropertyBuilder setTopicProperty(String name, Object value) {
+ properties.setProperty(topicPrefix + name, value.toString());
+ return this;
+ }
+
+ /**
+ * Removes a topic's property.
+ *
+ * @param name name of the property
+ * @return this builder
+ */
+ public TopicPropertyBuilder removeTopicProperty(String name) {
+ properties.remove(topicPrefix + name);
+ return this;
+ }
+}
+
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicTestBase.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicTestBase.java
new file mode 100644
index 00000000..8d5c3535
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/base/TopicTestBase.java
@@ -0,0 +1,159 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2018-2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.base;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+import org.onap.policy.common.parameters.topic.BusTopicParams.TopicParamsBuilder;
+
+/**
+ * Base class for Topic Test classes.
+ */
+public class TopicTestBase {
+
+ public static final String MY_AFT_ENV = "my-aft-env";
+ public static final String MY_API_KEY = "my-api-key";
+ public static final String MY_API_SECRET = "my-api-secret";
+ public static final String MY_BASE_PATH = "my-base";
+ public static final String MY_CLIENT_NAME = "my-client";
+ public static final String MY_CONS_GROUP = "my-cons-group";
+ public static final String MY_CONS_INST = "my-cons-inst";
+ public static final String MY_ENV = "my-env";
+ public static final int MY_FETCH_LIMIT = 100;
+ public static final int MY_FETCH_TIMEOUT = 101;
+ public static final String MY_HOST = "my-host";
+ public static final String MY_LAT = "my-lat";
+ public static final String MY_LONG = "my-long";
+ public static final String MY_PARTNER = "my-partner";
+ public static final String MY_PASS = "my-pass";
+ public static final int MY_PORT = 102;
+ public static final String MY_TOPIC = "my-topic";
+ public static final String MY_EFFECTIVE_TOPIC = "my-effective-topic";
+ public static final String MY_USERNAME = "my-user";
+
+ public static final String MY_MESSAGE = "my-message";
+ public static final String MY_PARTITION = "my-partition";
+ public static final String MY_MESSAGE2 = "my-message-2";
+
+ public static final String MY_SERIALIZER = "org.apache.kafka.common.serialization.StringSerializer";
+ public static final int KAFKA_PORT = 9092;
+
+ /**
+ * Message used within exceptions that are expected.
+ */
+ public static final String EXPECTED = "expected exception";
+
+ /**
+ * Additional properties to be added to the parameter builder.
+ */
+ protected Map<String, String> addProps;
+
+ /**
+ * Servers to be added to the parameter builder.
+ */
+ protected List<String> servers;
+
+ /**
+ * Servers to be added to the parameter builder.
+ */
+ protected List<String> kafkaServers;
+
+ /**
+ * Parameter builder used to build topic parameters.
+ */
+ protected TopicParamsBuilder builder;
+
+ /**
+ * Initializes {@link #addProps}, {@link #servers}, and {@link #builder}.
+ */
+ public void setUp() {
+ addProps = new TreeMap<>();
+ addProps.put("my-key-A", "my-value-A");
+ addProps.put("my-key-B", "my-value-B");
+
+ servers = Arrays.asList("svra", "svrb");
+ kafkaServers = Arrays.asList("localhost:9092", "10.1.2.3:9092");
+
+ builder = makeBuilder();
+ }
+
+ /**
+ * Makes a fully populated parameter builder.
+ *
+ * @return a new parameter builder
+ */
+ public TopicParamsBuilder makeBuilder() {
+ return makeBuilder(addProps, servers);
+ }
+
+ /**
+ * Makes a fully populated parameter builder.
+ *
+ * @param addProps additional properties to be added to the builder
+ * @param servers servers to be added to the builder
+ * @return a new parameter builder
+ */
+ public TopicParamsBuilder makeBuilder(Map<String, String> addProps, List<String> servers) {
+
+ return BusTopicParams.builder().additionalProps(addProps).aftEnvironment(MY_AFT_ENV).allowSelfSignedCerts(true)
+ .apiKey(MY_API_KEY).apiSecret(MY_API_SECRET).basePath(MY_BASE_PATH).clientName(MY_CLIENT_NAME)
+ .consumerGroup(MY_CONS_GROUP).consumerInstance(MY_CONS_INST).environment(MY_ENV)
+ .fetchLimit(MY_FETCH_LIMIT).fetchTimeout(MY_FETCH_TIMEOUT).hostname(MY_HOST).latitude(MY_LAT)
+ .longitude(MY_LONG).managed(true).partitionId(MY_PARTITION).partner(MY_PARTNER)
+ .password(MY_PASS).port(MY_PORT).servers(servers).topic(MY_TOPIC)
+ .effectiveTopic(MY_EFFECTIVE_TOPIC).useHttps(true).allowTracing(true).userName(MY_USERNAME)
+ .serializationProvider(MY_SERIALIZER);
+ }
+
+ /**
+ * Makes a fully populated parameter builder.
+ *
+ * @return a new parameter builder
+ */
+ public TopicParamsBuilder makeKafkaBuilder() {
+ addProps.clear();
+ String jaas = "org.apache.kafka.common.security.plain.PlainLoginModule "
+ + "required username=abc password=abc serviceName=kafka;";
+ addProps.put("sasl.jaas.config", jaas);
+ addProps.put("sasl.mechanism", "SCRAM-SHA-512");
+ addProps.put("security.protocol", "SASL_PLAINTEXT");
+
+ return makeKafkaBuilder(addProps, kafkaServers);
+ }
+
+ /**
+ * Makes a fully populated parameter builder.
+ *
+ * @param addProps additional properties to be added to the builder
+ * @param servers servers to be added to the builder
+ * @return a new parameter builder
+ */
+ public TopicParamsBuilder makeKafkaBuilder(Map<String, String> addProps, List<String> servers) {
+
+ return BusTopicParams.builder().additionalProps(addProps).basePath(MY_BASE_PATH).clientName(MY_CLIENT_NAME)
+ .consumerGroup(MY_CONS_GROUP).consumerInstance(MY_CONS_INST).environment(MY_ENV)
+ .hostname(MY_HOST).partitionId(MY_PARTITION).partner(MY_PARTNER).fetchTimeout(MY_FETCH_TIMEOUT)
+ .port(KAFKA_PORT).servers(servers).topic(MY_TOPIC)
+ .effectiveTopic(MY_EFFECTIVE_TOPIC).useHttps(false).allowTracing(true);
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/client/BidirectionalTopicClientTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/client/BidirectionalTopicClientTest.java
new file mode 100644
index 00000000..ba33ff9a
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/client/BidirectionalTopicClientTest.java
@@ -0,0 +1,454 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.client;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Properties;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.event.TopicEndpoint;
+import org.onap.policy.common.message.bus.event.TopicEndpointManager;
+import org.onap.policy.common.message.bus.event.TopicListener;
+import org.onap.policy.common.message.bus.event.TopicSink;
+import org.onap.policy.common.message.bus.event.TopicSource;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+
+@ExtendWith(MockitoExtension.class)
+class BidirectionalTopicClientTest {
+ private static final Coder coder = new StandardCoder();
+ private static final long MAX_WAIT_MS = 5000;
+ private static final long SHORT_WAIT_MS = 1;
+ private static final String SINK_TOPIC = "my-sink-topic";
+ private static final String SOURCE_TOPIC = "my-source-topic";
+ private static final String MY_TEXT = "my-text";
+
+ private static final CommInfrastructure SINK_INFRA = CommInfrastructure.NOOP;
+ private static final CommInfrastructure SOURCE_INFRA = CommInfrastructure.NOOP;
+
+ @Mock
+ private TopicSink sink;
+ @Mock
+ private TopicSource source;
+ @Mock
+ private TopicEndpoint endpoint;
+ @Mock
+ private TopicListener listener;
+
+ private MyMessage theMessage;
+
+ private BidirectionalTopicClient client;
+ private Context context;
+
+ /**
+ * Configures the endpoints.
+ */
+ @BeforeAll
+ public static void setUpBeforeClass() {
+ Properties props = new Properties();
+ props.setProperty("noop.sink.topics", SINK_TOPIC);
+ props.setProperty("noop.source.topics", SOURCE_TOPIC);
+
+ // clear all topics and then configure one sink and one source
+ TopicEndpointManager.getManager().shutdown();
+ TopicEndpointManager.getManager().addTopicSinks(props);
+ TopicEndpointManager.getManager().addTopicSources(props);
+ }
+
+ @AfterAll
+ public static void tearDownAfterClass() {
+ // clear all topics after the tests
+ TopicEndpointManager.getManager().shutdown();
+ }
+
+ /**
+ * Creates mocks and an initial client object.
+ */
+ @BeforeEach
+ public void setUp() throws Exception {
+ lenient().when(sink.send(anyString())).thenReturn(true);
+ lenient().when(sink.getTopicCommInfrastructure()).thenReturn(SINK_INFRA);
+
+ lenient().when(source.offer(anyString())).thenReturn(true);
+ lenient().when(source.getTopicCommInfrastructure()).thenReturn(SOURCE_INFRA);
+
+ lenient().when(endpoint.getTopicSinks(anyString())).thenReturn(Arrays.asList());
+ lenient().when(endpoint.getTopicSinks(SINK_TOPIC)).thenReturn(Arrays.asList(sink));
+
+ lenient().when(endpoint.getTopicSources(any())).thenReturn(Arrays.asList());
+ lenient().when(endpoint.getTopicSources(Arrays.asList(SOURCE_TOPIC))).thenReturn(Arrays.asList(source));
+
+ theMessage = new MyMessage(MY_TEXT);
+
+ client = new BidirectionalTopicClient2(SINK_TOPIC, SOURCE_TOPIC);
+
+ context = new Context();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ context.stop();
+ }
+
+ @Test
+ void testBidirectionalTopicClient_testGetters() {
+ assertSame(sink, client.getSink());
+ assertSame(source, client.getSource());
+ assertEquals(SINK_TOPIC, client.getSinkTopic());
+ assertEquals(SOURCE_TOPIC, client.getSourceTopic());
+ assertEquals(SINK_INFRA, client.getSinkTopicCommInfrastructure());
+ assertEquals(SOURCE_INFRA, client.getSourceTopicCommInfrastructure());
+ }
+
+ /**
+ * Tests the constructor when the sink or source cannot be found.
+ */
+ @Test
+ void testBidirectionalTopicClientExceptions() {
+ assertThatThrownBy(() -> new BidirectionalTopicClient2("unknown-sink", SOURCE_TOPIC))
+ .isInstanceOf(BidirectionalTopicClientException.class)
+ .hasMessage("no sinks for topic: unknown-sink");
+
+ assertThatThrownBy(() -> new BidirectionalTopicClient2(SINK_TOPIC, "unknown-source"))
+ .isInstanceOf(BidirectionalTopicClientException.class)
+ .hasMessage("no sources for topic: unknown-source");
+
+ // too many sources
+ when(endpoint.getTopicSources(Arrays.asList(SOURCE_TOPIC))).thenReturn(Arrays.asList(source, source));
+
+ assertThatThrownBy(() -> new BidirectionalTopicClient2(SINK_TOPIC, SOURCE_TOPIC))
+ .isInstanceOf(BidirectionalTopicClientException.class)
+ .hasMessage("too many sources for topic: my-source-topic");
+ }
+
+ /**
+ * Tests the "delegate" methods.
+ */
+ @Test
+ void testDelegates() {
+ assertTrue(client.send("hello"));
+ verify(sink).send("hello");
+
+ assertTrue(client.offer("incoming"));
+ verify(source).offer("incoming");
+
+ client.register(listener);
+ verify(source).register(listener);
+
+ client.unregister(listener);
+ verify(source).unregister(listener);
+ }
+
+ @Test
+ void testGetTopicEndpointManager() throws BidirectionalTopicClientException {
+ // use a real manager
+ client = new BidirectionalTopicClient(SINK_TOPIC, SOURCE_TOPIC);
+ assertNotNull(client.getTopicEndpointManager());
+
+ assertNotNull(client.getSink());
+ assertNotNull(client.getSource());
+
+ assertNotSame(sink, client.getSink());
+ assertNotSame(source, client.getSource());
+ }
+
+ @Test
+ void testAwaitReceipt() throws Exception {
+ context.start(theMessage);
+ assertThat(context.awaitSend(1)).isTrue();
+
+ verify(source).register(any());
+ verify(sink, atLeast(1)).send(any());
+ assertThat(context.checker.isReady()).isFalse();
+
+ inject(theMessage);
+
+ verifyReceipt();
+ }
+
+ @Test
+ void testAwaitReceipt_AlreadyDone() throws Exception {
+ context.start(theMessage);
+ assertThat(context.awaitSend(1)).isTrue();
+
+ inject(theMessage);
+
+ verifyReceipt();
+
+ // calling again should result in "true" again, without injecting message
+ context.start(theMessage);
+ verifyReceipt();
+ }
+
+ @Test
+ void testAwaitReceipt_MessageDoesNotMatch() throws Exception {
+ context.start(theMessage);
+ assertThat(context.awaitSend(1)).isTrue();
+
+ // non-matching message
+ inject("{}");
+
+ // wait for a few more calls to "send" and then inject a matching message
+ assertThat(context.awaitSend(3)).isTrue();
+ inject(theMessage);
+
+ verifyReceipt();
+ }
+
+ @Test
+ void testAwaitReceipt_DecodeFails() throws Exception {
+ context.start(theMessage);
+ assertThat(context.awaitSend(1)).isTrue();
+
+ // force a failure and inject the message
+ context.forceDecodeFailure = true;
+ inject(theMessage);
+
+ assertThat(context.awaitDecodeFailure()).isTrue();
+
+ // no more failures
+ context.forceDecodeFailure = false;
+ inject(theMessage);
+
+ verifyReceipt();
+ }
+
+ @Test
+ void testAwaitReceipt_Interrupted() throws InterruptedException {
+ context.start(theMessage);
+ assertThat(context.awaitSend(1)).isTrue();
+
+ context.interrupt();
+
+ verifyNoReceipt();
+ }
+
+ @Test
+ void testAwaitReceipt_MultipleLoops() throws Exception {
+ context.start(theMessage);
+
+ // wait for multiple "send" calls
+ assertThat(context.awaitSend(3)).isTrue();
+
+ inject(theMessage);
+
+ verifyReceipt();
+ }
+
+ @Test
+ void testStop() throws InterruptedException {
+ context.start(theMessage);
+ assertThat(context.awaitSend(1)).isTrue();
+
+ context.stop();
+
+ verifyNoReceipt();
+ }
+
+ /**
+ * Verifies that awaitReceipt() returns {@code true}.
+ *
+ * @throws InterruptedException if interrupted while waiting for the thread to
+ * terminate
+ */
+ private void verifyReceipt() throws InterruptedException {
+ assertThat(context.join()).isTrue();
+ assertThat(context.result).isTrue();
+ assertThat(context.exception).isNull();
+ assertThat(context.checker.isReady()).isTrue();
+
+ verify(source).unregister(any());
+ }
+
+ /**
+ * Verifies that awaitReceipt() returns {@code false}.
+ *
+ * @throws InterruptedException if interrupted while waiting for the thread to
+ * terminate
+ */
+ private void verifyNoReceipt() throws InterruptedException {
+ assertThat(context.join()).isTrue();
+ assertThat(context.result).isFalse();
+ assertThat(context.exception).isNull();
+ assertThat(context.checker.isReady()).isFalse();
+
+ verify(source).unregister(any());
+ }
+
+ /**
+ * Injects a message into the source topic.
+ *
+ * @param message message to be injected
+ * @throws CoderException if the message cannot be encoded
+ */
+ private void inject(MyMessage message) throws CoderException {
+ inject(coder.encode(message));
+ }
+
+ /**
+ * Injects a message into the source topic.
+ *
+ * @param message message to be injected
+ */
+ private void inject(String message) {
+ ArgumentCaptor<TopicListener> cap = ArgumentCaptor.forClass(TopicListener.class);
+ verify(source).register(cap.capture());
+
+ cap.getValue().onTopicEvent(SOURCE_INFRA, SOURCE_TOPIC, message);
+ }
+
+
+ /**
+ * BidirectionalTopicClient with some overrides.
+ */
+ private class BidirectionalTopicClient2 extends BidirectionalTopicClient {
+
+ public BidirectionalTopicClient2(String sinkTopic, String sourceTopic)
+ throws BidirectionalTopicClientException {
+ super(sinkTopic, sourceTopic);
+ }
+
+ @Override
+ protected TopicEndpoint getTopicEndpointManager() {
+ return endpoint;
+ }
+ }
+
+ private class Context {
+ private Thread thread;
+ private boolean result;
+ private Exception exception;
+ private boolean forceDecodeFailure;
+
+ // released every time the checker publishes a message
+ private final Semaphore sendSem = new Semaphore(0);
+
+ // released every time a message-decode fails
+ private final Semaphore decodeFailedSem = new Semaphore(0);
+
+ private final BidirectionalTopicClient2 checker;
+
+ public Context() throws BidirectionalTopicClientException {
+
+ checker = new BidirectionalTopicClient2(SINK_TOPIC, SOURCE_TOPIC) {
+
+ @Override
+ public boolean send(String messageText) {
+ boolean result = super.send(messageText);
+ sendSem.release();
+ return result;
+ }
+
+ @Override
+ protected <T> T decode(String msg, Class<? extends T> clazz) throws CoderException {
+ if (forceDecodeFailure) {
+ throw new CoderException("expected exception");
+ }
+
+ return super.decode(msg, clazz);
+ }
+
+ @Override
+ protected void decodeFailed() {
+ super.decodeFailed();
+ decodeFailedSem.release();
+ }
+ };
+ }
+
+ /**
+ * Starts the thread.
+ *
+ * @param message message to be sent to the sink topic
+ */
+ public void start(MyMessage message) {
+ thread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ result = checker.awaitReady(message, SHORT_WAIT_MS);
+ } catch (Exception e) {
+ exception = e;
+ }
+ }
+ };
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ public void stop() {
+ checker.stopWaiting();
+ }
+
+ public boolean join() throws InterruptedException {
+ thread.join(MAX_WAIT_MS);
+ return !thread.isAlive();
+ }
+
+ public void interrupt() {
+ thread.interrupt();
+ }
+
+ public boolean awaitSend(int npermits) throws InterruptedException {
+ return sendSem.tryAcquire(npermits, MAX_WAIT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ public boolean awaitDecodeFailure() throws InterruptedException {
+ return decodeFailedSem.tryAcquire(MAX_WAIT_MS, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class MyMessage {
+ private String text;
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/client/TopicClientExceptionTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/client/TopicClientExceptionTest.java
new file mode 100644
index 00000000..1ea2f1bc
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/client/TopicClientExceptionTest.java
@@ -0,0 +1,36 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP PAP
+ * ================================================================================
+ * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2019, 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.client;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.utils.test.ExceptionsTester;
+
+class TopicClientExceptionTest {
+
+ @Test
+ void test() {
+ assertEquals(5, new ExceptionsTester().test(TopicSinkClientException.class));
+ assertEquals(5, new ExceptionsTester().test(BidirectionalTopicClientException.class));
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/client/TopicSinkClientTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/client/TopicSinkClientTest.java
new file mode 100644
index 00000000..67b15ec9
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/client/TopicSinkClientTest.java
@@ -0,0 +1,147 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP PAP
+ * ================================================================================
+ * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2019, 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.client;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.message.bus.event.TopicEndpointManager;
+import org.onap.policy.common.message.bus.event.TopicSink;
+
+class TopicSinkClientTest {
+ private static final String TOPIC = "my-topic";
+
+ private TopicSinkClient client;
+ private TopicSink sink;
+ private List<TopicSink> sinks;
+
+ /**
+ * Creates mocks and an initial client object.
+ *
+ * @throws Exception if an error occurs
+ */
+ @BeforeEach
+ public void setUp() throws Exception {
+ sink = mock(TopicSink.class);
+ when(sink.send(anyString())).thenReturn(true);
+
+ sinks = Arrays.asList(sink, null);
+
+ client = new TopicSinkClient2(TOPIC);
+
+ Properties props = new Properties();
+ props.setProperty("noop.sink.topics", TOPIC);
+
+ // clear all topics and then configure one topic
+ TopicEndpointManager.getManager().shutdown();
+ TopicEndpointManager.getManager().addTopicSinks(props);
+ }
+
+ @AfterAll
+ public static void tearDown() {
+ // clear all topics after the tests
+ TopicEndpointManager.getManager().shutdown();
+ }
+
+ /**
+ * Uses a real NO-OP topic sink.
+ */
+ @Test
+ void testGetTopicSinks() throws Exception {
+
+ sink = TopicEndpointManager.getManager().getNoopTopicSink(TOPIC);
+ assertNotNull(sink);
+
+ final AtomicReference<String> evref = new AtomicReference<>(null);
+
+ sink.register((infra, topic, event) -> evref.set(event));
+ sink.start();
+
+ client = new TopicSinkClient(TOPIC);
+ client.send(100);
+
+ assertEquals("100", evref.get());
+ }
+
+ @Test
+ void testTopicSinkClient() {
+ // unknown topic -> should throw exception
+ sinks = new LinkedList<>();
+ assertThatThrownBy(() -> new TopicSinkClient2(TOPIC)).isInstanceOf(TopicSinkClientException.class)
+ .hasMessage("no sinks for topic: my-topic");
+ }
+
+ @Test
+ void testTopicSinkClient_GetTopic() throws TopicSinkClientException {
+ assertEquals(TOPIC, new TopicSinkClient(TopicEndpointManager.getManager().getNoopTopicSink(TOPIC)).getTopic());
+ assertEquals(TOPIC, new TopicSinkClient(TOPIC).getTopic());
+
+ assertThatThrownBy(() -> new TopicSinkClient((TopicSink) null))
+ .hasMessageContaining("sink is marked non-null but is null");
+ assertThatThrownBy(() -> new TopicSinkClient("blah")).isInstanceOf(TopicSinkClientException.class)
+ .hasMessage("no sinks for topic: blah");
+ }
+
+ @Test
+ void testSend() {
+ client.send(Arrays.asList("abc", "def"));
+ verify(sink).send("['abc','def']".replace('\'', '"'));
+
+ // sink send fails
+ when(sink.send(anyString())).thenReturn(false);
+ assertFalse(client.send("ghi"));
+
+ // sink send throws an exception
+ final RuntimeException ex = new RuntimeException("expected exception");
+ when(sink.send(anyString())).thenThrow(ex);
+ assertFalse(client.send("jkl"));
+ }
+
+ /**
+ * TopicSinkClient with some overrides.
+ */
+ private class TopicSinkClient2 extends TopicSinkClient {
+
+ public TopicSinkClient2(final String topic) throws TopicSinkClientException {
+ super(topic);
+ }
+
+ @Override
+ protected List<TopicSink> getTopicSinks(final String topic) {
+ return sinks;
+ }
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/IndexedKafkaTopicSourceFactoryTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/IndexedKafkaTopicSourceFactoryTest.java
new file mode 100644
index 00000000..a901b07b
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/IndexedKafkaTopicSourceFactoryTest.java
@@ -0,0 +1,63 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+
+class IndexedKafkaTopicSourceFactoryTest {
+
+ private IndexedKafkaTopicSourceFactory factory;
+
+ @Test
+ void testBuild() {
+ factory = new IndexedKafkaTopicSourceFactory();
+ BusTopicParams params = new BusTopicParams();
+
+ // set servers to null
+ params.setServers(null);
+ assertThatThrownBy(() -> factory.build(params))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("KAFKA Server(s) must be provided");
+
+ // set servers to empty
+ params.setServers(List.of());
+ assertThatThrownBy(() -> factory.build(params))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("KAFKA Server(s) must be provided");
+
+ List<String> servers = List.of("kafka:9092", "kafka:29092");
+ params.setServers(servers);
+
+ // set topic to null
+ params.setTopic(null);
+ assertThatThrownBy(() -> factory.build(params))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("A topic must be provided");
+
+ // set topic to empty
+ params.setTopic("");
+ assertThatThrownBy(() -> factory.build(params))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("A topic must be provided");
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/InlineKafkaTopicSinkTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/InlineKafkaTopicSinkTest.java
new file mode 100644
index 00000000..c8d6e21e
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/InlineKafkaTopicSinkTest.java
@@ -0,0 +1,68 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.event.base.TopicTestBase;
+
+class InlineKafkaTopicSinkTest extends TopicTestBase {
+ private InlineKafkaTopicSink sink;
+
+ /**
+ * Creates the object to be tested.
+ */
+ @BeforeEach
+ @Override
+ public void setUp() {
+ super.setUp();
+
+ sink = new InlineKafkaTopicSink(makeKafkaBuilder().build());
+ }
+
+ @AfterEach
+ public void tearDown() {
+ sink.shutdown();
+ }
+
+ @Test
+ void testToString() {
+ assertTrue(sink.toString().startsWith("InlineKafkaTopicSink ["));
+ }
+
+ @Test
+ void testInit() {
+ // nothing null
+ sink = new InlineKafkaTopicSink(makeKafkaBuilder().build());
+ sink.init();
+ assertThatCode(() -> sink.shutdown()).doesNotThrowAnyException();
+ }
+
+ @Test
+ void testGetTopicCommInfrastructure() {
+ assertEquals(CommInfrastructure.KAFKA, sink.getTopicCommInfrastructure());
+ }
+
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaPublisherWrapperTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaPublisherWrapperTest.java
new file mode 100644
index 00000000..8e13af23
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaPublisherWrapperTest.java
@@ -0,0 +1,98 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.Properties;
+import org.apache.kafka.clients.producer.KafkaProducer;
+import org.apache.kafka.clients.producer.Producer;
+import org.apache.kafka.clients.producer.ProducerRecord;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatchers;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+
+class KafkaPublisherWrapperTest {
+
+ private KafkaPublisherWrapper kafkaPublisherWrapper;
+ private Producer<String, String> mockProducer;
+ private BusTopicParams mockBusTopicParams;
+
+ @BeforeEach
+ void setUp() {
+ mockProducer = mock(KafkaProducer.class);
+ mockBusTopicParams = mock(BusTopicParams.class);
+
+ when(mockBusTopicParams.getTopic()).thenReturn("testTopic");
+ when(mockBusTopicParams.getServers()).thenReturn(Collections.singletonList("localhost:9092"));
+ when(mockBusTopicParams.isTopicInvalid()).thenReturn(false);
+ when(mockBusTopicParams.isAdditionalPropsValid()).thenReturn(false);
+ when(mockBusTopicParams.isAllowTracing()).thenReturn(false);
+
+ kafkaPublisherWrapper = new KafkaPublisherWrapper(mockBusTopicParams) {
+ private Producer<String, String> createProducer(Properties props) { // NOSONAR instance creation
+ return mockProducer;
+ }
+ };
+ }
+
+ @Test
+ void testConstructor() {
+ verify(mockBusTopicParams).getTopic();
+ verify(mockBusTopicParams).getServers();
+ verify(mockBusTopicParams).isTopicInvalid();
+ verify(mockBusTopicParams).isAdditionalPropsValid();
+ verify(mockBusTopicParams).isAllowTracing();
+ }
+
+ @Test
+ void testSendSuccess() {
+ when(mockProducer.send(ArgumentMatchers.any(ProducerRecord.class))).thenReturn(null);
+ assertTrue(kafkaPublisherWrapper.send("partitionId", "testMessage"));
+ }
+
+ @Test
+ void testSendNullMessage() {
+ IllegalArgumentException thrown = assertThrows(
+ IllegalArgumentException.class,
+ () -> kafkaPublisherWrapper.send("partitionId", null),
+ "Expected send() to throw, but it didn't"
+ );
+ assertEquals("No message provided", thrown.getMessage());
+ }
+
+ @Test
+ void testSendFailure() {
+ when(mockProducer.send(ArgumentMatchers.any(ProducerRecord.class))).thenThrow(RuntimeException.class);
+ assertTrue(kafkaPublisherWrapper.send("partitionId", "testMessage"));
+ }
+
+ @Test
+ void testClose() {
+ assertThatCode(kafkaPublisherWrapper::close).doesNotThrowAnyException();
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicFactoryTestBase.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicFactoryTestBase.java
new file mode 100644
index 00000000..1085ee90
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicFactoryTestBase.java
@@ -0,0 +1,47 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+import java.util.Collections;
+import org.onap.policy.common.message.bus.event.Topic;
+import org.onap.policy.common.message.bus.event.base.BusTopicFactoryTestBase;
+
+/**
+ * Base class for KafkaTopicXxxFactory tests.
+ *
+ * @param <T> type of topic managed by the factory
+ */
+public abstract class KafkaTopicFactoryTestBase<T extends Topic> extends BusTopicFactoryTestBase<T> {
+
+ @Override
+ public void testBuildBusTopicParams_Ex() {
+
+ super.testBuildBusTopicParams_Ex();
+
+ // null servers
+ assertThatIllegalArgumentException().as("null servers")
+ .isThrownBy(() -> buildTopic(makeBuilder().servers(null).build()));
+
+ // empty servers
+ assertThatIllegalArgumentException().as("empty servers")
+ .isThrownBy(() -> buildTopic(makeBuilder().servers(Collections.emptyList()).build()));
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicPropertyBuilder.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicPropertyBuilder.java
new file mode 100644
index 00000000..6a1be7a0
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicPropertyBuilder.java
@@ -0,0 +1,94 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022, 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import static org.onap.policy.common.message.bus.event.base.TopicTestBase.MY_EFFECTIVE_TOPIC;
+import static org.onap.policy.common.message.bus.event.base.TopicTestBase.MY_PARTITION;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_HTTP_HTTPS_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_MANAGED_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_EFFECTIVE_TOPIC_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_SERVERS_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_SINK_PARTITION_KEY_SUFFIX;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import lombok.Getter;
+import org.onap.policy.common.message.bus.event.base.TopicPropertyBuilder;
+import org.onap.policy.common.parameters.topic.TopicParameters;
+
+@Getter
+public class KafkaTopicPropertyBuilder extends TopicPropertyBuilder {
+
+ public static final String SERVER = "localhost:9092";
+ public static final String TOPIC2 = "my-topic-2";
+ public static final String ADDITIONAL_PROPS = "{\"security.protocol\": \"SASL_PLAINTEXT\","
+ + "\"sasl.mechanism\": \"SCRAM-SHA-512\",\"sasl.jaas.config\": "
+ + "\"org.apache.kafka.common.security.plain.PlainLoginModule "
+ + "required username=abc password=abc serviceName=kafka;\"}";
+
+ private final TopicParameters params = new TopicParameters();
+
+ /**
+ * Constructs the object.
+ *
+ * @param prefix the prefix for the properties to be built
+ */
+ public KafkaTopicPropertyBuilder(String prefix) {
+ super(prefix);
+ }
+
+ /**
+ * Adds a topic and configures it's properties with default values.
+ *
+ * @param topic the topic to be added
+ * @return this builder
+ */
+ public KafkaTopicPropertyBuilder makeTopic(String topic) {
+ addTopic(topic);
+
+ setTopicProperty(PROPERTY_TOPIC_EFFECTIVE_TOPIC_SUFFIX, MY_EFFECTIVE_TOPIC);
+ setTopicProperty(PROPERTY_MANAGED_SUFFIX, "true");
+ setTopicProperty(PROPERTY_HTTP_HTTPS_SUFFIX, "true");
+ setTopicProperty(PROPERTY_TOPIC_SINK_PARTITION_KEY_SUFFIX, MY_PARTITION);
+ setTopicProperty(PROPERTY_TOPIC_SERVERS_SUFFIX, SERVER);
+ setTopicProperty(".additionalProps", ADDITIONAL_PROPS);
+
+ params.setTopicCommInfrastructure("kafka");
+ params.setTopic(topic);
+ params.setEffectiveTopic(MY_EFFECTIVE_TOPIC);
+ params.setManaged(true);
+ params.setUseHttps(true);
+ params.setPartitionId(MY_PARTITION);
+ params.setServers(List.of(SERVER));
+ params.setAdditionalProps(getAdditionalProps());
+
+ return this;
+ }
+
+ private Map<String, String> getAdditionalProps() {
+ try {
+ return new ObjectMapper().readValue(ADDITIONAL_PROPS, Map.class);
+ } catch (JsonProcessingException e) {
+ return Collections.emptyMap();
+ }
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSinkFactoryTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSinkFactoryTest.java
new file mode 100644
index 00000000..62210361
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSinkFactoryTest.java
@@ -0,0 +1,200 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022, 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_KAFKA_SINK_TOPICS;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_EFFECTIVE_TOPIC_SUFFIX;
+
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.message.bus.event.base.TopicPropertyBuilder;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+
+class KafkaTopicSinkFactoryTest extends KafkaTopicFactoryTestBase<KafkaTopicSink> {
+
+ private SinkFactory factory;
+ public static final String KAFKA_SERVER = "localhost:9092";
+
+ /**
+ * Creates the object to be tested.
+ */
+ @BeforeEach
+ @Override
+ public void setUp() {
+ super.setUp();
+
+ factory = new SinkFactory();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ factory.destroy();
+ }
+
+ @Test
+ @Override
+ public void testBuildBusTopicParams() {
+ super.testBuildBusTopicParams();
+ super.testBuildBusTopicParams_Ex();
+ }
+
+ @Test
+ @Override
+ public void testBuildListOfStringString() {
+ super.testBuildListOfStringString();
+
+ // check parameters that were used
+ BusTopicParams params = getLastParams();
+ assertFalse(params.isAllowSelfSignedCerts());
+ }
+
+ @Test
+ @Override
+ public void testBuildProperties() {
+ List<KafkaTopicSink> topics = buildTopics(makePropBuilder().makeTopic(MY_TOPIC).build());
+ assertEquals(1, topics.size());
+ assertEquals(MY_TOPIC, topics.get(0).getTopic());
+ assertEquals(MY_EFFECTIVE_TOPIC, topics.get(0).getEffectiveTopic());
+
+ BusTopicParams params = getLastParams();
+ assertTrue(params.isManaged());
+ assertFalse(params.isUseHttps());
+ assertEquals(List.of(KAFKA_SERVER), params.getServers());
+ assertEquals(MY_TOPIC, params.getTopic());
+ assertEquals(MY_EFFECTIVE_TOPIC, params.getEffectiveTopic());
+ assertEquals(MY_PARTITION, params.getPartitionId());
+ assertNotNull(params.getAdditionalProps());
+
+ List<KafkaTopicSink> topics2 = buildTopics(makePropBuilder().makeTopic(TOPIC3)
+ .removeTopicProperty(PROPERTY_TOPIC_EFFECTIVE_TOPIC_SUFFIX).build());
+ assertEquals(1, topics2.size());
+ assertEquals(TOPIC3, topics2.get(0).getTopic());
+ assertEquals(topics2.get(0).getTopic(), topics2.get(0).getEffectiveTopic());
+
+ initFactory();
+
+ assertEquals(1, buildTopics(makePropBuilder().makeTopic(MY_TOPIC).build()).size());
+ }
+
+ @Test
+ void testBuildFromProperties() {
+ Properties props = makePropBuilder().makeTopic(MY_TOPIC).build();
+ var listTopic = factory.build(props);
+ assertNotNull(listTopic);
+ }
+
+ @Test
+ @Override
+ public void testDestroyString_testGet_testInventory() {
+ super.testDestroyString_testGet_testInventory();
+ super.testDestroyString_Ex();
+ }
+
+ @Test
+ @Override
+ public void testDestroy() {
+ super.testDestroy();
+ }
+
+ @Test
+ void testGet() {
+ super.testGet_Ex();
+ }
+
+ @Test
+ void testToString() {
+ assertTrue(factory.toString().startsWith("IndexedKafkaTopicSinkFactory ["));
+ }
+
+ @Override
+ protected void initFactory() {
+ if (factory != null) {
+ factory.destroy();
+ }
+
+ factory = new SinkFactory();
+ }
+
+ @Override
+ protected List<KafkaTopicSink> buildTopics(Properties properties) {
+ return factory.build(properties);
+ }
+
+ @Override
+ protected KafkaTopicSink buildTopic(BusTopicParams params) {
+ return factory.build(params);
+ }
+
+ @Override
+ protected KafkaTopicSink buildTopic(List<String> servers, String topic) {
+ return factory.build(servers, topic);
+ }
+
+ @Override
+ protected void destroyFactory() {
+ factory.destroy();
+ }
+
+ @Override
+ protected void destroyTopic(String topic) {
+ factory.destroy(topic);
+ }
+
+ @Override
+ protected List<KafkaTopicSink> getInventory() {
+ return factory.inventory();
+ }
+
+ @Override
+ protected KafkaTopicSink getTopic(String topic) {
+ return factory.get(topic);
+ }
+
+ @Override
+ protected BusTopicParams getLastParams() {
+ return factory.params.getLast();
+ }
+
+ @Override
+ protected TopicPropertyBuilder makePropBuilder() {
+ return new KafkaTopicPropertyBuilder(PROPERTY_KAFKA_SINK_TOPICS);
+ }
+
+ /**
+ * Factory that records the parameters of all the sinks it creates.
+ */
+ private static class SinkFactory extends IndexedKafkaTopicSinkFactory {
+ private Deque<BusTopicParams> params = new LinkedList<>();
+
+ @Override
+ protected KafkaTopicSink makeSink(BusTopicParams busTopicParams) {
+ params.add(busTopicParams);
+ return super.makeSink(busTopicParams);
+ }
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSinkTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSinkTest.java
new file mode 100644
index 00000000..8818a27f
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSinkTest.java
@@ -0,0 +1,34 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP Policy Engine - Common Modules
+ * ================================================================================
+ * Copyright (C) 2022, 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+
+class KafkaTopicSinkTest {
+
+ @Test
+ void test() {
+ assertNotNull(KafkaTopicFactories.getSinkFactory());
+ }
+
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSourceFactoryTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSourceFactoryTest.java
new file mode 100644
index 00000000..c91e548b
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSourceFactoryTest.java
@@ -0,0 +1,168 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP Policy Engine - Common Modules
+ * ================================================================================
+ * Copyright (C) 2022-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_KAFKA_SOURCE_TOPICS;
+
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.message.bus.event.base.TopicPropertyBuilder;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+
+class KafkaTopicSourceFactoryTest extends KafkaTopicFactoryTestBase<KafkaTopicSource> {
+
+ private SourceFactory factory;
+
+ public static final String KAFKA_SERVER = "localhost:9092";
+
+ /**
+ * Creates the object to be tested.
+ */
+ @BeforeEach
+ @Override
+ public void setUp() {
+ super.setUp();
+
+ factory = new SourceFactory();
+ }
+
+ @AfterEach
+ public void tearDown() {
+ factory.destroy();
+ }
+
+ @Test
+ @Override
+ public void testBuildProperties() {
+
+ initFactory();
+
+ List<KafkaTopicSource> topics = buildTopics(makePropBuilder().makeTopic(MY_TOPIC).build());
+ assertEquals(1, topics.size());
+ assertEquals(MY_TOPIC, topics.get(0).getTopic());
+ assertEquals(MY_EFFECTIVE_TOPIC, topics.get(0).getEffectiveTopic());
+
+ BusTopicParams params = getLastParams();
+ assertTrue(params.isManaged());
+ assertFalse(params.isUseHttps());
+ assertEquals(List.of(KAFKA_SERVER), params.getServers());
+ assertEquals(MY_TOPIC, params.getTopic());
+ assertEquals(MY_EFFECTIVE_TOPIC, params.getEffectiveTopic());
+ }
+
+ @Test
+ @Override
+ public void testDestroyString_testGet_testInventory() {
+ super.testDestroyString_testGet_testInventory();
+ super.testDestroyString_Ex();
+ }
+
+ @Test
+ @Override
+ public void testDestroy() {
+ super.testDestroy();
+ }
+
+ @Test
+ void testGet() {
+ super.testGet_Ex();
+ }
+
+ @Test
+ void testToString() {
+ assertTrue(factory.toString().startsWith("IndexedKafkaTopicSourceFactory ["));
+ }
+
+ @Override
+ protected void initFactory() {
+ if (factory != null) {
+ factory.destroy();
+ }
+
+ factory = new SourceFactory();
+ }
+
+ @Override
+ protected List<KafkaTopicSource> buildTopics(Properties properties) {
+ return factory.build(properties);
+ }
+
+ @Override
+ protected KafkaTopicSource buildTopic(BusTopicParams params) {
+ return factory.build(params);
+ }
+
+ @Override
+ protected KafkaTopicSource buildTopic(List<String> servers, String topic) {
+ return factory.build(servers, topic);
+ }
+
+ @Override
+ protected void destroyFactory() {
+ factory.destroy();
+ }
+
+ @Override
+ protected void destroyTopic(String topic) {
+ factory.destroy(topic);
+ }
+
+ @Override
+ protected List<KafkaTopicSource> getInventory() {
+ return factory.inventory();
+ }
+
+ @Override
+ protected KafkaTopicSource getTopic(String topic) {
+ return factory.get(topic);
+ }
+
+ @Override
+ protected BusTopicParams getLastParams() {
+ return factory.params.getLast();
+ }
+
+ @Override
+ protected TopicPropertyBuilder makePropBuilder() {
+ return new KafkaTopicPropertyBuilder(PROPERTY_KAFKA_SOURCE_TOPICS);
+ }
+
+ /**
+ * Factory that records the parameters of all the sources it creates.
+ */
+ private static class SourceFactory extends IndexedKafkaTopicSourceFactory {
+ private final Deque<BusTopicParams> params = new LinkedList<>();
+
+ @Override
+ protected KafkaTopicSource makeSource(BusTopicParams busTopicParams) {
+ params.add(busTopicParams);
+ return super.makeSource(busTopicParams);
+ }
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSourceTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSourceTest.java
new file mode 100644
index 00000000..5572bc01
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/KafkaTopicSourceTest.java
@@ -0,0 +1,34 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP Policy Engine - Common Modules
+ * ================================================================================
+ * Copyright (C) 2022-2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+
+class KafkaTopicSourceTest {
+
+ @Test
+ void verifyKafkaTopicFactoriesNotNull() {
+ assertNotNull(KafkaTopicFactories.getSourceFactory());
+ }
+
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/SingleThreadedKafkaTopicSourceTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/SingleThreadedKafkaTopicSourceTest.java
new file mode 100644
index 00000000..0c732ba7
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/kafka/SingleThreadedKafkaTopicSourceTest.java
@@ -0,0 +1,61 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2018-2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.kafka;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.event.base.TopicTestBase;
+
+class SingleThreadedKafkaTopicSourceTest extends TopicTestBase {
+ private SingleThreadedKafkaTopicSource source;
+
+ /**
+ * Creates the object to be tested.
+ */
+ @BeforeEach
+ @Override
+ public void setUp() {
+ super.setUp();
+
+ source = new SingleThreadedKafkaTopicSource(makeKafkaBuilder().build());
+ }
+
+ @AfterEach
+ public void tearDown() {
+ source.shutdown();
+ }
+
+ @Test
+ void testToString() {
+ assertTrue(source.toString().startsWith("SingleThreadedKafkaTopicSource ["));
+ source.shutdown();
+ }
+
+ @Test
+ void testGetTopicCommInfrastructure() {
+ assertEquals(CommInfrastructure.KAFKA, source.getTopicCommInfrastructure());
+ }
+
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicEndpointTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicEndpointTest.java
new file mode 100644
index 00000000..3ebec83c
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicEndpointTest.java
@@ -0,0 +1,121 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.noop;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.event.TopicListener;
+import org.onap.policy.common.message.bus.event.base.TopicTestBase;
+
+public abstract class NoopTopicEndpointTest<F extends NoopTopicFactory<T>, T extends NoopTopicEndpoint>
+ extends TopicTestBase {
+
+ protected final F factory;
+ protected T endpoint;
+
+ public NoopTopicEndpointTest(F factory) {
+ this.factory = factory;
+ }
+
+ protected abstract boolean io(String message);
+
+ @BeforeEach
+ @Override
+ public void setUp() {
+ super.setUp();
+ this.endpoint = this.factory.build(servers, MY_TOPIC);
+ }
+
+ @Test
+ void testIo() {
+ TopicListener listener = mock(TopicListener.class);
+ this.endpoint.register(listener);
+ this.endpoint.start();
+
+ assertTrue(io(MY_MESSAGE));
+ assertSame(MY_MESSAGE, this.endpoint.getRecentEvents()[0]);
+ assertEquals(Collections.singletonList(MY_MESSAGE), Arrays.asList(this.endpoint.getRecentEvents()));
+ verify(listener).onTopicEvent(CommInfrastructure.NOOP, MY_TOPIC, MY_MESSAGE);
+
+ this.endpoint.unregister(listener);
+ }
+
+ @Test
+ void testIoNullMessage() {
+ assertThatThrownBy(() -> io(null)).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void testIoEmptyMessage() {
+ assertThatThrownBy(() -> io("")).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void testOfferNotStarted() {
+ assertThatThrownBy(() -> io(MY_MESSAGE)).isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ void testGetTopicCommInfrastructure() {
+ assertEquals(CommInfrastructure.NOOP, this.endpoint.getTopicCommInfrastructure());
+ }
+
+ @Test
+ void testStart_testStop_testShutdown() {
+ this.endpoint.start();
+ assertTrue(this.endpoint.isAlive());
+
+ // start again
+ this.endpoint.start();
+ assertTrue(this.endpoint.isAlive());
+
+ // stop
+ this.endpoint.stop();
+ assertFalse(this.endpoint.isAlive());
+
+ // re-start again
+ this.endpoint.start();
+ assertTrue(this.endpoint.isAlive());
+
+ // shutdown
+ this.endpoint.shutdown();
+ assertFalse(this.endpoint.isAlive());
+ }
+
+ @Test
+ void testStart_Locked() {
+ this.endpoint.lock();
+ assertThatThrownBy(() -> this.endpoint.start()).isInstanceOf(IllegalStateException.class);
+ }
+
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicFactoryTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicFactoryTest.java
new file mode 100644
index 00000000..4dcba86e
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicFactoryTest.java
@@ -0,0 +1,257 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.noop;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_MANAGED_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_SERVERS_SUFFIX;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.event.base.TopicFactoryTestBase;
+import org.onap.policy.common.message.bus.event.base.TopicPropertyBuilder;
+import org.onap.policy.common.message.bus.event.base.TopicTestBase;
+import org.onap.policy.common.parameters.topic.BusTopicParams;
+
+public abstract class NoopTopicFactoryTest<F extends NoopTopicFactory<T>, T extends NoopTopicEndpoint>
+ extends TopicFactoryTestBase<T> {
+
+ private static final List<String> NOOP_SERVERS = List.of(CommInfrastructure.NOOP.toString());
+ private F factory = null;
+
+ protected abstract F buildFactory();
+
+ /**
+ * Creates the object to be tested.
+ */
+ @BeforeEach
+ @Override
+ public void setUp() {
+ super.setUp();
+ initFactory();
+ }
+
+ @AfterEach
+ void tearDown() {
+ factory.destroy();
+ }
+
+ @Test
+ void testBuildBusTopicParams() {
+ initFactory();
+
+ T item1 = buildTopic(makeParams(servers));
+ assertNotNull(item1);
+
+ assertEquals(servers, item1.getServers());
+ assertEquals(MY_TOPIC, item1.getTopic());
+ }
+
+ @Test
+ void testBuildListOfStringStringBoolean() {
+ initFactory();
+
+ T item1 = buildTopic(servers, MY_TOPIC, true);
+ assertNotNull(item1);
+
+ assertEquals(servers, item1.getServers());
+ assertEquals(MY_TOPIC, item1.getTopic());
+
+ // managed topic - should not build a new one
+ assertEquals(item1, buildTopic(servers, MY_TOPIC, true));
+
+ T item2 = buildTopic(servers, TOPIC2, true);
+ assertNotNull(item2);
+ assertNotSame(item1, item2);
+
+ // duplicate - should be the same, as these topics are managed
+ List<String> randomServers = new ArrayList<>();
+ randomServers.add(RandomStringUtils.randomAlphanumeric(8));
+ T item3 = buildTopic(randomServers, TOPIC2, true);
+ assertSame(item2, item3);
+
+ T item4 = buildTopic(Collections.emptyList(), TOPIC2, true);
+ assertSame(item3, item4);
+
+ // null server list
+ initFactory();
+ assertEquals(NOOP_SERVERS, buildTopic(null, MY_TOPIC, true).getServers());
+
+ // empty server list
+ initFactory();
+ assertEquals(NOOP_SERVERS, buildTopic(Collections.emptyList(), MY_TOPIC, true).getServers());
+
+ // unmanaged topic
+ initFactory();
+ item1 = buildTopic(servers, MY_TOPIC, false);
+ assertNotSame(item1, buildTopic(servers, MY_TOPIC, false));
+ }
+
+ @Test
+ void testBuildListOfStringStringBoolean_NullTopic() {
+ assertThatThrownBy(() -> buildTopic(servers, null, true)).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void testBuildListOfStringStringBoolean_EmptyTopic() {
+ assertThatThrownBy(() -> buildTopic(servers, "", true)).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void testBuildProperties() {
+ // managed topic
+ initFactory();
+ assertEquals(1, buildTopics(makePropBuilder().makeTopic(MY_TOPIC).build()).size());
+ assertNotNull(factory.get(MY_TOPIC));
+
+ // unmanaged topic - get() will throw an exception
+ initFactory();
+ assertEquals(1, buildTopics(makePropBuilder().makeTopic(MY_TOPIC)
+ .setTopicProperty(PROPERTY_MANAGED_SUFFIX, "false").build()).size());
+ assertThatIllegalStateException().isThrownBy(() -> factory.get(MY_TOPIC));
+
+ // managed undefined - default to true
+ initFactory();
+ assertEquals(1, buildTopics(
+ makePropBuilder().makeTopic(MY_TOPIC).removeTopicProperty(PROPERTY_MANAGED_SUFFIX).build())
+ .size());
+ assertNotNull(factory.get(MY_TOPIC));
+
+ // managed empty - default to true
+ initFactory();
+ assertEquals(1, buildTopics(
+ makePropBuilder().makeTopic(MY_TOPIC).setTopicProperty(PROPERTY_MANAGED_SUFFIX, "").build())
+ .size());
+ assertNotNull(factory.get(MY_TOPIC));
+
+ initFactory();
+
+ // null topic list
+ assertTrue(buildTopics(makePropBuilder().build()).isEmpty());
+
+ // empty topic list
+ assertTrue(buildTopics(makePropBuilder().addTopic("").build()).isEmpty());
+
+ // null server list
+ initFactory();
+ T endpoint = buildTopics(makePropBuilder().makeTopic(MY_TOPIC)
+ .removeTopicProperty(PROPERTY_TOPIC_SERVERS_SUFFIX).build()).get(0);
+ assertEquals(NOOP_SERVERS, endpoint.getServers());
+
+ // empty server list
+ initFactory();
+ endpoint = buildTopics(makePropBuilder().makeTopic(MY_TOPIC).setTopicProperty(PROPERTY_TOPIC_SERVERS_SUFFIX, "")
+ .build()).get(0);
+ assertEquals(NOOP_SERVERS, endpoint.getServers());
+
+ // test other options
+ super.testBuildProperties_Multiple();
+ }
+
+ @Test
+ @Override
+ public void testDestroyString_testGet_testInventory() {
+ super.testDestroyString_testGet_testInventory();
+ super.testDestroyString_Ex();
+ }
+
+ @Test
+ @Override
+ public void testDestroy() {
+ super.testDestroy();
+ }
+
+ @Test
+ void testGet() {
+ super.testGet_Ex();
+ }
+
+ @Override
+ protected void initFactory() {
+ if (factory != null) {
+ factory.destroy();
+ }
+
+ factory = buildFactory();
+ }
+
+ @Override
+ protected List<T> buildTopics(Properties properties) {
+ return factory.build(properties);
+ }
+
+ protected T buildTopic(BusTopicParams param) {
+ return factory.build(param);
+ }
+
+ protected T buildTopic(List<String> servers, String topic, boolean managed) {
+ return factory.build(servers, topic, managed);
+ }
+
+ @Override
+ protected void destroyFactory() {
+ factory.destroy();
+ }
+
+ @Override
+ protected void destroyTopic(String topic) {
+ factory.destroy(topic);
+ }
+
+ @Override
+ protected List<T> getInventory() {
+ return factory.inventory();
+ }
+
+ @Override
+ protected T getTopic(String topic) {
+ return factory.get(topic);
+ }
+
+ @Override
+ protected TopicPropertyBuilder makePropBuilder() {
+ return new NoopTopicPropertyBuilder(factory.getTopicsPropertyName());
+ }
+
+ private BusTopicParams makeParams(List<String> servers) {
+ BusTopicParams params = new BusTopicParams();
+
+ params.setServers(servers);
+ params.setTopic(TopicTestBase.MY_TOPIC);
+ params.setManaged(true);
+
+ return params;
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicPropertyBuilder.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicPropertyBuilder.java
new file mode 100644
index 00000000..57b78435
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicPropertyBuilder.java
@@ -0,0 +1,77 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2018-2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.noop;
+
+import static org.onap.policy.common.message.bus.event.base.TopicTestBase.MY_EFFECTIVE_TOPIC;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_ALLOW_SELF_SIGNED_CERTIFICATES_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_HTTP_HTTPS_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_MANAGED_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_EFFECTIVE_TOPIC_SUFFIX;
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_TOPIC_SERVERS_SUFFIX;
+
+import java.util.List;
+import lombok.Getter;
+import org.onap.policy.common.message.bus.event.base.TopicPropertyBuilder;
+import org.onap.policy.common.parameters.topic.TopicParameters;
+
+@Getter
+public class NoopTopicPropertyBuilder extends TopicPropertyBuilder {
+
+ public static final String SERVER = "my-server";
+
+ private final TopicParameters params = new TopicParameters();
+
+ /**
+ * Constructs the object.
+ *
+ * @param prefix the prefix for the properties to be built
+ */
+ public NoopTopicPropertyBuilder(String prefix) {
+ super(prefix);
+ }
+
+ /**
+ * Adds a topic and configures it's properties with default values.
+ *
+ * @param topic the topic to be added
+ * @return this builder
+ */
+ public NoopTopicPropertyBuilder makeTopic(String topic) {
+ addTopic(topic);
+
+ setTopicProperty(PROPERTY_TOPIC_EFFECTIVE_TOPIC_SUFFIX, MY_EFFECTIVE_TOPIC);
+ setTopicProperty(PROPERTY_MANAGED_SUFFIX, "true");
+ setTopicProperty(PROPERTY_HTTP_HTTPS_SUFFIX, "true");
+ setTopicProperty(PROPERTY_ALLOW_SELF_SIGNED_CERTIFICATES_SUFFIX, "true");
+ setTopicProperty(PROPERTY_TOPIC_SERVERS_SUFFIX, SERVER);
+
+ params.setTopicCommInfrastructure("noop");
+ params.setTopic(topic);
+ params.setEffectiveTopic(MY_EFFECTIVE_TOPIC);
+ params.setManaged(true);
+ params.setUseHttps(true);
+ params.setAllowSelfSignedCerts(true);
+ params.setServers(List.of(SERVER));
+
+ return this;
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSinkFactoryTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSinkFactoryTest.java
new file mode 100644
index 00000000..ecd4df1e
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSinkFactoryTest.java
@@ -0,0 +1,39 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2018-2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.noop;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class NoopTopicSinkFactoryTest extends NoopTopicFactoryTest<NoopTopicSinkFactory, NoopTopicSink> {
+
+ @Override
+ protected NoopTopicSinkFactory buildFactory() {
+ return new NoopTopicSinkFactory();
+ }
+
+ @Test
+ void testToString() {
+ assertTrue(new NoopTopicSinkFactory().toString().startsWith("NoopTopicSinkFactory ["));
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSinkTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSinkTest.java
new file mode 100644
index 00000000..ecba379f
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSinkTest.java
@@ -0,0 +1,58 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.noop;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import org.junit.jupiter.api.Test;
+
+class NoopTopicSinkTest extends NoopTopicEndpointTest<NoopTopicSinkFactory, NoopTopicSink> {
+
+ public NoopTopicSinkTest() {
+ super(new NoopTopicSinkFactory());
+ }
+
+ @Override
+ protected boolean io(String message) {
+ return endpoint.send(message);
+ }
+
+ @Test
+ void testToString() {
+ assertThat(endpoint.toString()).startsWith("NoopTopicSink");
+ }
+
+ @Test
+ void testSend() {
+ NoopTopicSink sink = new NoopTopicSink(servers, MY_TOPIC) {
+ @Override
+ protected boolean broadcast(String message) {
+ throw new RuntimeException(EXPECTED);
+ }
+
+ };
+
+ sink.start();
+ assertFalse(sink.send(MY_MESSAGE));
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSourceFactoryTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSourceFactoryTest.java
new file mode 100644
index 00000000..c9038068
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSourceFactoryTest.java
@@ -0,0 +1,39 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.noop;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class NoopTopicSourceFactoryTest extends NoopTopicFactoryTest<NoopTopicSourceFactory, NoopTopicSource> {
+
+ @Override
+ protected NoopTopicSourceFactory buildFactory() {
+ return new NoopTopicSourceFactory();
+ }
+
+ @Test
+ void testToString() {
+ assertTrue(new NoopTopicSourceFactory().toString().startsWith("NoopTopicSourceFactory ["));
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSourceTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSourceTest.java
new file mode 100644
index 00000000..51ff109b
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/event/noop/NoopTopicSourceTest.java
@@ -0,0 +1,56 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.event.noop;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class NoopTopicSourceTest extends NoopTopicEndpointTest<NoopTopicSourceFactory, NoopTopicSource> {
+
+ public NoopTopicSourceTest() {
+ super(new NoopTopicSourceFactory());
+ }
+
+ @Override
+ protected boolean io(String message) {
+ return this.endpoint.offer(message);
+ }
+
+ @Test
+ void testToString() {
+ assertTrue(this.endpoint.toString().startsWith("NoopTopicSource"));
+ }
+
+ @Test
+ void testOffer() {
+ NoopTopicSource source = new NoopTopicSource(servers, MY_TOPIC) {
+ @Override
+ protected boolean broadcast(String message) {
+ throw new RuntimeException(EXPECTED);
+ }
+
+ };
+
+ source.start();
+ assertFalse(source.offer(MY_MESSAGE));
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/features/NetLoggerFeatureApiTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/features/NetLoggerFeatureApiTest.java
new file mode 100644
index 00000000..776c5d57
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/features/NetLoggerFeatureApiTest.java
@@ -0,0 +1,86 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.features;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.utils.NetLoggerUtil.EventType;
+import org.slf4j.Logger;
+
+@ExtendWith(MockitoExtension.class)
+class NetLoggerFeatureApiTest {
+
+ @Mock
+ private Logger mockLogger;
+
+ @Mock
+ private EventType mockEventType;
+
+ @Mock
+ private CommInfrastructure mockCommInfrastructure;
+
+ private NetLoggerFeatureApi featureApi;
+
+ @BeforeEach
+ public void setUp() {
+ featureApi = new NetLoggerFeatureApi() {
+ @Override
+ public boolean beforeLog(Logger eventLogger, EventType type, CommInfrastructure protocol, String topic,
+ String message) {
+ return NetLoggerFeatureApi.super.beforeLog(eventLogger, type, protocol, topic, message);
+ }
+
+ @Override
+ public boolean afterLog(Logger eventLogger, EventType type, CommInfrastructure protocol, String topic,
+ String message) {
+ return NetLoggerFeatureApi.super.afterLog(eventLogger, type, protocol, topic, message);
+ }
+
+ @Override
+ public int getSequenceNumber() {
+ return 0;
+ }
+
+ @Override
+ public String getName() {
+ return NetLoggerFeatureApi.super.getName();
+ }
+ };
+ }
+
+ @Test
+ void testBeforeLogDefaultBehavior() {
+ boolean result = featureApi.beforeLog(mockLogger, mockEventType, mockCommInfrastructure,
+ "testTopic", "testMessage");
+ assertFalse(result, "Expected beforeLog to return false by default");
+ }
+
+ @Test
+ void testAfterLogDefaultBehavior() {
+ boolean result = featureApi.afterLog(mockLogger, mockEventType, mockCommInfrastructure,
+ "testTopic", "testMessage");
+ assertFalse(result, "Expected afterLog to return false by default");
+ }
+}
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/utils/KafkaPropertyUtilsTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/utils/KafkaPropertyUtilsTest.java
new file mode 100644
index 00000000..55f6a69f
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/utils/KafkaPropertyUtilsTest.java
@@ -0,0 +1,54 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.utils;
+
+import static org.onap.policy.common.message.bus.properties.MessageBusProperties.PROPERTY_ADDITIONAL_PROPS_SUFFIX;
+
+import java.util.Properties;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.utils.properties.PropertyUtils;
+
+class KafkaPropertyUtilsTest {
+
+ @Test
+ void test() {
+ var properties = new Properties();
+ properties.setProperty("mytopic" + PROPERTY_ADDITIONAL_PROPS_SUFFIX, "{444-");
+ PropertyUtils props = new PropertyUtils(properties, "mytopic", null);
+
+ var build = KafkaPropertyUtils.makeBuilder(props, "mytopic", "servers").build();
+ Assertions.assertTrue(build.getAdditionalProps().isEmpty());
+
+ properties.setProperty("mytopic" + PROPERTY_ADDITIONAL_PROPS_SUFFIX,
+ "{\"security.protocol\": \"SASL_PLAINTEXT\"}");
+ build = KafkaPropertyUtils.makeBuilder(props, "mytopic", "servers").build();
+ Assertions.assertTrue(build.getAdditionalProps().containsKey("security.protocol"));
+
+ properties.setProperty("mytopic" + PROPERTY_ADDITIONAL_PROPS_SUFFIX,
+ "{\"security.protocol\": false }");
+ build = KafkaPropertyUtils.makeBuilder(props, "mytopic", "servers").build();
+ Assertions.assertTrue(build.getAdditionalProps().isEmpty());
+
+ properties.setProperty("mytopic" + PROPERTY_ADDITIONAL_PROPS_SUFFIX, "");
+ build = KafkaPropertyUtils.makeBuilder(props, "mytopic", "servers").build();
+ Assertions.assertTrue(build.getAdditionalProps().isEmpty());
+ }
+
+} \ No newline at end of file
diff --git a/message-bus/src/test/java/org/onap/policy/common/message/bus/utils/NetLoggerUtilTest.java b/message-bus/src/test/java/org/onap/policy/common/message/bus/utils/NetLoggerUtilTest.java
new file mode 100644
index 00000000..df621f5b
--- /dev/null
+++ b/message-bus/src/test/java/org/onap/policy/common/message/bus/utils/NetLoggerUtilTest.java
@@ -0,0 +1,269 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.message.bus.utils;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.AppenderBase;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.message.bus.event.Topic.CommInfrastructure;
+import org.onap.policy.common.message.bus.features.NetLoggerFeatureApi;
+import org.onap.policy.common.message.bus.features.NetLoggerFeatureProviders;
+import org.onap.policy.common.message.bus.utils.NetLoggerUtil.EventType;
+import org.slf4j.Logger;
+
+/**
+ * Test class for network log utilities such as logging and feature invocation.
+ */
+class NetLoggerUtilTest {
+
+ private static final String TEST_TOPIC = "test-topic";
+ private static final String MESSAGE = "hello world!";
+ /**
+ * Test feature used for junits.
+ */
+ private static NetLoggerFeature netLoggerFeature;
+
+ /**
+ * Obtains the test implementation of NetLoggerFeatureApi.
+ */
+ @BeforeAll
+ public static void setUp() {
+ netLoggerFeature = (NetLoggerFeature) NetLoggerFeatureProviders.getProviders().getList().get(0);
+ }
+
+ /**
+ * Clears events list and resets return/exceptions flags before invoking every unit test.
+ */
+ @BeforeEach
+ public void reset() {
+ TestAppender.clear();
+ netLoggerFeature.setReturnValue(false, false);
+ netLoggerFeature.setExceptions(false, false);
+ }
+
+ /**
+ * Tests obtaining the network logger instance.
+ */
+ @Test
+ void getNetworkLoggerTest() {
+ assertEquals("network", NetLoggerUtil.getNetworkLogger().getName());
+ }
+
+ /**
+ * Tests logging a message to the network logger and invoking features before/after logging.
+ */
+ @Test
+ void logTest() {
+ NetLoggerUtil.log(EventType.IN, CommInfrastructure.NOOP, TEST_TOPIC, MESSAGE);
+ assertEquals(3, TestAppender.events.size());
+ }
+
+ /**
+ * Tests that the network logger is used to log messages if a logger is not passed in.
+ */
+ @Test
+ void logDefaultTest() {
+ NetLoggerUtil.log(null, EventType.IN, CommInfrastructure.NOOP, TEST_TOPIC, MESSAGE);
+ assertEquals(3, TestAppender.events.size());
+ assertEquals("network", TestAppender.events.get(0).getLoggerName());
+ }
+
+ /**
+ * Tests a NetLoggerFeature that replaces base implementation before logging.
+ */
+ @Test
+ void beforeLogReturnTrueTest() {
+ netLoggerFeature.setReturnValue(true, false);
+ NetLoggerUtil.log(null, EventType.IN, CommInfrastructure.NOOP, TEST_TOPIC, MESSAGE);
+ assertEquals(1, TestAppender.events.size());
+ }
+
+ /**
+ * Tests a NetLoggerFeature that post processes a logged message.
+ */
+ @Test
+ void afterLogReturnTrueTest() {
+ netLoggerFeature.setReturnValue(false, true);
+ NetLoggerUtil.log(null, EventType.IN, CommInfrastructure.NOOP, TEST_TOPIC, MESSAGE);
+ assertEquals(3, TestAppender.events.size());
+ }
+
+ /**
+ * Tests throwing an exception in the before hook.
+ */
+ @Test
+ void beforeLogExceptionTest() {
+ netLoggerFeature.setExceptions(true, false);
+ NetLoggerUtil.log(null, EventType.IN, CommInfrastructure.NOOP, TEST_TOPIC, MESSAGE);
+ assertEquals(2, TestAppender.events.size());
+ }
+
+ /**
+ * Tests throwing an exception in the after hook.
+ */
+ @Test
+ void afterLogExceptionTest() {
+ netLoggerFeature.setExceptions(false, true);
+ NetLoggerUtil.log(null, EventType.IN, CommInfrastructure.NOOP, TEST_TOPIC, MESSAGE);
+ assertEquals(2, TestAppender.events.size());
+ }
+
+ /**
+ * A custom list appender to track messages being logged to the network logger.
+ * NOTE: Check src/test/resources/logback-test.xml for network logger configurations.
+ */
+ public static class TestAppender extends AppenderBase<ILoggingEvent> {
+
+ /**
+ * List of logged events.
+ */
+ private static final List<ILoggingEvent> events = new ArrayList<>();
+
+ /**
+ * Called after every unit test to clear list of events.
+ */
+ public static void clear() {
+ events.clear();
+ }
+
+ /**
+ * Appends each event to the event list.
+ */
+ @Override
+ protected void append(ILoggingEvent event) {
+ events.add(event);
+ }
+
+ }
+
+ /**
+ * Test implementation of NetLoggerFeatureApi to be used by junits.
+ */
+ public static class NetLoggerFeature implements NetLoggerFeatureApi {
+
+ /**
+ * Used for setting the return values of before/after hooks.
+ */
+ private boolean beforeReturn = false;
+ private boolean afterReturn = false;
+
+ /**
+ * Used for throwing an exception in the before/after hooks.
+ */
+ private boolean beforeException = false;
+ private boolean afterException = false;
+
+
+ /**
+ * Gets sequence number.
+ */
+ @Override
+ public int getSequenceNumber() {
+ return 0;
+ }
+
+ /**
+ * Get beforeLog return value.
+ */
+ public boolean getBeforeReturn() {
+ return this.beforeReturn;
+ }
+
+ /**
+ * Get afterLog return value.
+ */
+ public boolean getAfterReturn() {
+ return this.afterReturn;
+ }
+
+ /**
+ * Sets the return value for the before/after hooks.
+ *
+ * @param beforeVal beforeLog() return value
+ * @param afterVal afterLog() return value
+ */
+ public void setReturnValue(boolean beforeVal, boolean afterVal) {
+ this.beforeReturn = beforeVal;
+ this.afterReturn = afterVal;
+ }
+
+ /**
+ * Gets beforeException boolean.
+ */
+ public boolean getBeforeException() {
+ return this.beforeException;
+ }
+
+ /**
+ * Gets afterException boolean.
+ */
+ public boolean getAfterException() {
+ return this.afterException;
+ }
+
+ /**
+ * Sets before/after flags to determine if the feature should throw an exception.
+ */
+ public void setExceptions(boolean beforeException, boolean afterException) {
+ this.beforeException = beforeException;
+ this.afterException = afterException;
+ }
+
+ /**
+ * Simple beforeLog message.
+ */
+ @Override
+ public boolean beforeLog(Logger eventLogger, EventType type, CommInfrastructure protocol, String topic,
+ String message) {
+
+ if (beforeException) {
+ throw new RuntimeException("beforeLog exception");
+ }
+
+ eventLogger.info("before feature test");
+
+ return this.beforeReturn;
+ }
+
+ /**
+ * Simple afterLog message.
+ */
+ @Override
+ public boolean afterLog(Logger eventLogger, EventType type, CommInfrastructure protocol, String topic,
+ String message) {
+
+ if (afterException) {
+ throw new RuntimeException("afterLog exception");
+ }
+
+ eventLogger.info("after feature test");
+
+ return this.afterReturn;
+ }
+
+ }
+
+}
diff --git a/message-bus/src/test/resources/META-INF/services/org.onap.policy.common.message.bus.features.NetLoggerFeatureApi b/message-bus/src/test/resources/META-INF/services/org.onap.policy.common.message.bus.features.NetLoggerFeatureApi
new file mode 100644
index 00000000..7889cb8f
--- /dev/null
+++ b/message-bus/src/test/resources/META-INF/services/org.onap.policy.common.message.bus.features.NetLoggerFeatureApi
@@ -0,0 +1 @@
+org.onap.policy.common.message.bus.utils.NetLoggerUtilTest$NetLoggerFeature \ No newline at end of file
diff --git a/message-bus/src/test/resources/logback-test.xml b/message-bus/src/test/resources/logback-test.xml
new file mode 100644
index 00000000..ddf4f8bb
--- /dev/null
+++ b/message-bus/src/test/resources/logback-test.xml
@@ -0,0 +1,42 @@
+<!--
+ ============LICENSE_START=======================================================
+ ONAP
+ ================================================================================
+ Copyright (C) 2024 Nordix Foundation.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+ -->
+<configuration>
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <Pattern>
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M\(%line\) - %msg%n
+ </Pattern>
+ </encoder>
+ </appender>
+
+ <appender name="testAppender" class="org.onap.policy.common.message.bus.utils.NetLoggerUtilTest$TestAppender"/>
+
+ <logger name="org.onap.policy.drools.http.server.test" level="INFO"/>
+
+ <logger name="network" level="INFO">
+ <appender-ref ref="testAppender"/>
+ </logger>
+
+ <root level="WARN">
+ <appender-ref ref="STDOUT"/>
+ </root>
+
+</configuration> \ No newline at end of file
diff --git a/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/TopicEndpointProxyTest.json b/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/TopicEndpointProxyTest.json
new file mode 100644
index 00000000..5b2e712a
--- /dev/null
+++ b/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/TopicEndpointProxyTest.json
@@ -0,0 +1,30 @@
+{
+ "locked": false,
+ "alive": false,
+ "topicSources": [
+ {
+ "servers": [
+ "my-server"
+ ],
+ "topic": "noop-source",
+ "effectiveTopic": "noop-source",
+ "recentEvents": [],
+ "alive": false,
+ "locked": false,
+ "topicCommInfrastructure": "NOOP"
+ }
+ ],
+ "topicSinks": [
+ {
+ "servers": [
+ "my-server"
+ ],
+ "topic": "noop-sink",
+ "effectiveTopic": "noop-sink",
+ "recentEvents": [],
+ "alive": false,
+ "locked": false,
+ "topicCommInfrastructure": "NOOP"
+ }
+ ]
+}
diff --git a/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/BusTopicBaseTest.json b/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/BusTopicBaseTest.json
new file mode 100644
index 00000000..462278a4
--- /dev/null
+++ b/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/BusTopicBaseTest.json
@@ -0,0 +1,14 @@
+{
+ "servers" : [ "svra", "svrb" ],
+ "topic" : "my-topic",
+ "effectiveTopic" : "my-effective-topic",
+ "recentEvents" : [ ],
+ "alive" : false,
+ "locked" : false,
+ "apiKey" : "my-api-key",
+ "apiSecret" : "my-api-secret",
+ "useHttps" : true,
+ "allowTracing": true,
+ "allowSelfSignedCerts" : true,
+ "topicCommInfrastructure" : "NOOP"
+}
diff --git a/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/InlineBusTopicSinkTest.json b/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/InlineBusTopicSinkTest.json
new file mode 100644
index 00000000..1f2fb55f
--- /dev/null
+++ b/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/InlineBusTopicSinkTest.json
@@ -0,0 +1,15 @@
+{
+ "servers" : [ "svra", "svrb" ],
+ "topic" : "my-topic",
+ "effectiveTopic" : "my-effective-topic",
+ "recentEvents" : [ ],
+ "alive" : false,
+ "locked" : false,
+ "apiKey" : "my-api-key",
+ "apiSecret" : "my-api-secret",
+ "useHttps" : true,
+ "allowTracing": true,
+ "allowSelfSignedCerts" : true,
+ "topicCommInfrastructure" : "NOOP",
+ "partitionKey" : "my-partition"
+}
diff --git a/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/SingleThreadedBusTopicSourceTest.json b/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/SingleThreadedBusTopicSourceTest.json
new file mode 100644
index 00000000..305620c8
--- /dev/null
+++ b/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/SingleThreadedBusTopicSourceTest.json
@@ -0,0 +1,18 @@
+{
+ "servers" : [ "svra", "svrb" ],
+ "topic" : "my-topic",
+ "effectiveTopic" : "my-effective-topic",
+ "recentEvents" : [ ],
+ "alive" : false,
+ "locked" : false,
+ "apiKey" : "my-api-key",
+ "apiSecret" : "my-api-secret",
+ "useHttps" : true,
+ "allowTracing": true,
+ "allowSelfSignedCerts" : true,
+ "consumerGroup" : "my-cons-group",
+ "consumerInstance" : "my-cons-inst",
+ "fetchTimeout" : 101,
+ "fetchLimit" : 100,
+ "topicCommInfrastructure" : "NOOP"
+}
diff --git a/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/TopicBaseTest.json b/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/TopicBaseTest.json
new file mode 100644
index 00000000..b72b4efd
--- /dev/null
+++ b/message-bus/src/test/resources/org/onap/policy/common/message/bus/event/base/TopicBaseTest.json
@@ -0,0 +1,9 @@
+{
+ "servers" : [ "svra", "svrb" ],
+ "topic" : "my-topic",
+ "effectiveTopic" : "my-topic",
+ "recentEvents" : [ ],
+ "alive" : false,
+ "locked" : false,
+ "topicCommInfrastructure" : "NOOP"
+}
diff --git a/message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_all_params.json b/message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_all_params.json
new file mode 100644
index 00000000..89e464dd
--- /dev/null
+++ b/message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_all_params.json
@@ -0,0 +1,62 @@
+{
+ "topicSources" : [ {
+ "topic" : "policy-pdp-pap1",
+ "servers" : [ "kafka2, kafka3" ],
+ "topicCommInfrastructure" : "kafka",
+ "effectiveTopic" : "my-effective-topic",
+ "apiKey" : "my-api-key",
+ "apiSecret" : "my-api-secret",
+ "port": 123,
+ "useHttps" : true,
+ "allowTracing": true,
+ "allowSelfSignedCerts" : true,
+ "consumerGroup" : "consumer group",
+ "consumerInstance" : "consumer instance",
+ "fetchTimeout" : 15000,
+ "fetchLimit" : 100,
+ "userName": "username",
+ "password": "password",
+ "managed": true,
+ "environment": "environment1",
+ "aftEnvironment": "aftEnvironment1",
+ "partner": "partner1",
+ "latitude": "1234",
+ "longitude": "1234",
+ "partitionId": "partition_id",
+ "additionalProps": {"xyz":"xyz"},
+ "clientName": "clientName1",
+ "hostname": "hostname1",
+ "basePath": "basePath1",
+ "serializationProvider": "serializationProvider1"
+ }],
+ "topicSinks" : [ {
+ "topic" : "policy-pdp-pap1",
+ "servers" : [ "kafka2, kafka3" ],
+ "topicCommInfrastructure" : "kafka",
+ "effectiveTopic" : "my-effective-topic",
+ "apiKey" : "my-api-key",
+ "apiSecret" : "my-api-secret",
+ "port": 123,
+ "useHttps" : true,
+ "allowTracing": true,
+ "allowSelfSignedCerts" : true,
+ "consumerGroup" : "consumer group",
+ "consumerInstance" : "consumer instance",
+ "fetchTimeout" : 15000,
+ "fetchLimit" : 100,
+ "userName": "username",
+ "password": "password",
+ "managed": true,
+ "environment": "environment1",
+ "aftEnvironment": "aftEnvironment1",
+ "partner": "partner1",
+ "latitude": "1234",
+ "longitude": "1234",
+ "partitionId": "partition_id",
+ "additionalProps": {"xyz":"xyz"},
+ "clientName": "clientName1",
+ "hostname": "hostname1",
+ "basePath": "basePath1",
+ "serializationProvider": "serializationProvider1"
+ }]
+} \ No newline at end of file
diff --git a/message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_invalid.json b/message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_invalid.json
new file mode 100644
index 00000000..775b4886
--- /dev/null
+++ b/message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_invalid.json
@@ -0,0 +1,6 @@
+{
+ "topicSources" : [{
+ "topic" : "ueb-source",
+ "servers" : ["my-server"]
+ }]
+} \ No newline at end of file
diff --git a/message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_missing_mandatory.json b/message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_missing_mandatory.json
new file mode 100644
index 00000000..216c11ec
--- /dev/null
+++ b/message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_missing_mandatory.json
@@ -0,0 +1,12 @@
+{
+ "topicSources" : [ {
+ "topic" : "policy-pdp-pap1",
+ "servers" : [],
+ "topicCommInfrastructure" : "kafka"
+ }],
+ "topicSinks" : [ {
+ "topic" : "policy-pdp-pap2",
+ "servers" : [ "kafka1, kafka2" ],
+ "topicCommInfrastructure" : "kafka"
+ }]
+} \ No newline at end of file
diff --git a/message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_valid.json b/message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_valid.json
new file mode 100644
index 00000000..2603bfdc
--- /dev/null
+++ b/message-bus/src/test/resources/org/onap/policy/common/message/bus/parameters/TopicParameters_valid.json
@@ -0,0 +1,30 @@
+{
+ "topicSources" : [ {
+ "topic" : "ueb-source",
+ "servers" : [ "my-server" ],
+ "topicCommInfrastructure" : "ueb"
+ },{
+ "topic" : "policy-pdp-pap1",
+ "servers" : [ "kafka1, kafka2" ],
+ "topicCommInfrastructure" : "kafka"
+ },{
+ "topic" : "policy-pdp-pap2",
+ "servers" : [ "kafka2, kafka3" ],
+ "topicCommInfrastructure" : "kafka"
+ }],
+ "topicSinks" : [ {
+ "topic" : "ueb-sink",
+ "servers" : [ "my-server" ],
+ "topicCommInfrastructure" : "ueb"
+ },{
+ "topic" : "policy-pdp-pap2",
+ "servers" : [ "kafka1, kafka2" ],
+ "topicCommInfrastructure" : "kafka"
+ },{
+ "topic" : "policy-pdp-pap3",
+ "servers" : [ "kafka2, kafka3" ],
+ "topicCommInfrastructure" : "kafka",
+ "effectiveTopic":"effectiveTopic1",
+ "allowSelfSignedCerts":true
+ }]
+} \ No newline at end of file