--- /dev/null
+/*
+ * Copyright © 2024 Smartoptics and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.common.device.observer;
+
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import org.slf4j.event.Level;
+
+public class EventSubscriber implements Subscriber {
+
+ private final Map<Level, Set<String>> messages = new ConcurrentHashMap<>();
+
+ @Override
+ public void event(Level level, String message) {
+ if (message == null || message.isEmpty()) {
+ return;
+ }
+
+ if (!messages.containsKey(level)) {
+ messages.put(level, new LinkedHashSet<>());
+ }
+
+ messages.get(level).add(message);
+ }
+
+ @Override
+ public void error(String message) {
+ event(Level.ERROR, message);
+ }
+
+ @Override
+ public void warn(String message) {
+ event(Level.WARN, message);
+ }
+
+ @Override
+ public String first(Level level) {
+ return first(level, "");
+ }
+
+ @Override
+ public String first(Level level, String defaultMessage) {
+ if (!messages.containsKey(level)) {
+ return defaultMessage;
+ }
+
+ return messages.get(level).iterator().next();
+ }
+
+ @Override
+ public String first(Level level, String defaultMessage, int count) {
+ if (!messages.containsKey(level)) {
+ return defaultMessage;
+ }
+
+ return messages.get(level)
+ .stream().limit(Math.max(count, 1))
+ .collect(Collectors.joining(", "));
+ }
+
+ @Override
+ public String last(Level level) {
+ return last(level, "");
+ }
+
+ @Override
+ public String last(Level level, String defaultMessage) {
+ if (!messages.containsKey(level)) {
+ return defaultMessage;
+ }
+
+ Set<String> strings = messages.get(level);
+
+ return strings.stream().skip(strings.size() - 1).findFirst().orElse(defaultMessage);
+
+ }
+
+ @Override
+ public String[] messages(Level level) {
+
+ if (!messages.containsKey(level)) {
+ return new String[0];
+ }
+
+ Object[] arr = messages.get(level).toArray();
+
+ return Arrays.copyOf(arr, arr.length, String[].class);
+
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2024 Smartoptics and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.common.device.observer;
+
+import org.slf4j.event.Level;
+
+public class Ignore implements Subscriber {
+
+ @Override
+ public void event(Level level, String message) {
+ return;
+ }
+
+ @Override
+ public void error(String message) {
+ return;
+ }
+
+ @Override
+ public void warn(String message) {
+ return;
+ }
+
+ @Override
+ public String first(Level level) {
+ return "";
+ }
+
+ @Override
+ public String first(Level level, String defaultMessage) {
+ return "";
+ }
+
+ @Override
+ public String first(Level level, String defaultMessage, int count) {
+ return "";
+ }
+
+ @Override
+ public String last(Level level) {
+ return "";
+ }
+
+ @Override
+ public String last(Level level, String defaultMessage) {
+ return "";
+ }
+
+ @Override
+ public String[] messages(Level level) {
+ return new String[0];
+ }
+
+}
--- /dev/null
+/*
+ * Copyright © 2024 Smartoptics and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.common.device.observer;
+
+import org.slf4j.event.Level;
+
+/**
+ * A class wishing to 'subscribe' to, i.e. be notified about, 'events'
+ * as they happen should implement this interface.
+ *
+ * <p>A client may wish to interrogate this subscriber for the first/last
+ * event received with a particular 'level'. As an example a client
+ * may wish to know what the first or last received error message.
+ */
+public interface Subscriber {
+
+ /**
+ * Send a message to this subscriber.
+ */
+ void event(Level level, String message);
+
+ /**
+ * Send an error message to this subscriber.
+ *
+ * <p>The same as calling {@link #event(Level, String)} Level.ERROR.
+ * @param message error message
+ */
+ void error(String message);
+
+ /**
+ * Send a warning message to this subscriber.
+ *
+ * <p>The same as calling {@link #event(Level, String)} Level.WARN.
+ * @param message warning message
+ */
+ void warn(String message);
+
+ /**
+ * The first message with level caught by this subscriber.
+ */
+ String first(Level level);
+
+ /**
+ * The first message with level caught by this subscriber.
+ *
+ * <p>Return defaultMessage if no message with the specified level is found.
+ */
+ String first(Level level, String defaultMessage);
+
+ /**
+ * Return a certain number of messages with a particular message level.
+ * defaultMessage is returned in case there are no messages.
+ */
+ String first(Level level, String defaultMessage, int count);
+
+ /**
+ * The last message with level caught by this subscriber.
+ */
+ String last(Level level);
+
+ /**
+ * The last message with level caught by this subscriber.
+ *
+ * <p>Return defaultMessage if no message with the specified level is found.
+ */
+ String last(Level level, String defaultMessage);
+
+ /**
+ * All messages with level caught by subscriber
+ * in the order they were caught.
+ */
+ String[] messages(Level level);
+
+}
--- /dev/null
+/*
+ * Copyright © 2024 Smartoptics and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.transportpce.common.device.observer;
+
+import org.junit.Assert;
+import org.junit.jupiter.api.Test;
+import org.slf4j.event.Level;
+
+class EventSubscriberTest {
+
+ @Test
+ void last() {
+ Subscriber subscriber = new EventSubscriber();
+ subscriber.event(Level.ERROR, "First");
+ subscriber.event(Level.ERROR, "Last");
+
+ Assert.assertEquals("Last", subscriber.last(Level.ERROR));
+ }
+
+ @Test
+ void empty() {
+ Subscriber subscriber = new EventSubscriber();
+
+ Assert.assertEquals("", subscriber.last(Level.ERROR));
+ }
+
+ @Test
+ void lastDefault() {
+ Subscriber subscriber = new EventSubscriber();
+
+ Assert.assertEquals("Error", subscriber.last(Level.ERROR, "Error"));
+ }
+
+ @Test
+ void firstNumber() {
+ Subscriber subscriber = new EventSubscriber();
+ subscriber.event(Level.ERROR, "First error");
+ subscriber.event(Level.WARN, "First warn");
+ subscriber.event(Level.ERROR, "Second error");
+ subscriber.event(Level.ERROR, "Third error");
+
+
+ Assert.assertEquals("First error, Second error, Third error", subscriber.first(Level.ERROR, "", 3));
+ }
+
+ @Test
+ void firstNumberContainsLessThanThree() {
+ Subscriber subscriber = new EventSubscriber();
+ subscriber.event(Level.ERROR, "First error");
+ subscriber.event(Level.WARN, "First warn");
+ subscriber.event(Level.ERROR, "Second error");
+
+
+ Assert.assertEquals("First error, Second error", subscriber.first(Level.ERROR, "", 3));
+ }
+
+ @Test
+ void firstNumberEmpty() {
+ Subscriber subscriber = new EventSubscriber();
+
+ Assert.assertEquals("Asdf", subscriber.first(Level.ERROR, "Asdf", 3));
+ }
+
+ @Test
+ void repetitiveMessages() {
+ Subscriber subscriber = new EventSubscriber();
+ subscriber.event(Level.ERROR, "First error");
+ subscriber.event(Level.ERROR, "Second error");
+ subscriber.event(Level.ERROR, "First error");
+
+ Assert.assertEquals("First error, Second error", subscriber.first(Level.ERROR, "Error", 3));
+ }
+}