Add simple Registry class to manage singletons 60/82860/3
authorJim Hahn <jrh3@att.com>
Wed, 20 Mar 2019 21:58:49 +0000 (17:58 -0400)
committerJim Hahn <jrh3@att.com>
Thu, 21 Mar 2019 14:12:53 +0000 (10:12 -0400)
Typically, singleton classes have lots of static methods, or use
a getInstance() method to get the singleton.  This provides an alternative
mechanism, similar to the JDNI and the common-paramater property registry,
where singletons can be registered.

Clean up registry after junit test.
Modified a few comments. (I prefer 90 characters for comments.)

Added a method to register or replace an existing key without throwing
an exception.

Change-Id: I3b62719013d3b5f71adb5e9299d3c1257fb55c80
Issue-ID: POLICY-1542
Signed-off-by: Jim Hahn <jrh3@att.com>
utils/src/main/java/org/onap/policy/common/utils/services/Registry.java [new file with mode: 0644]
utils/src/test/java/org/onap/policy/common/utils/services/RegistryTest.java [new file with mode: 0644]

diff --git a/utils/src/main/java/org/onap/policy/common/utils/services/Registry.java b/utils/src/main/java/org/onap/policy/common/utils/services/Registry.java
new file mode 100644 (file)
index 0000000..c209379
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.services;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a simple object registry, similar in spirit to JNDI, but suitable for use in a
+ * stand-alone JVM.
+ */
+public class Registry {
+    private static final Logger logger = LoggerFactory.getLogger(Registry.class);
+
+    private static volatile Registry instance = new Registry();
+
+    /**
+     * Registry map.
+     */
+    private Map<String, Object> name2object = new ConcurrentHashMap<>();
+
+    /**
+     * Constructs the object.
+     */
+    private Registry() {
+        super();
+    }
+
+    /**
+     * Registers an object.
+     *
+     * @param name name by which the object is known
+     * @param object object to be registered
+     * @throws IllegalStateException if an object is already registered for that name
+     * @throws IllegalArgumentException if either argument is null
+     */
+    public static void register(String name, Object object) {
+        if (name == null) {
+            throw new IllegalArgumentException("attempt to register: " + name);
+        }
+
+        if (object == null) {
+            throw new IllegalArgumentException("attempt to register null object for " + name);
+        }
+
+        instance.name2object.compute(name, (key, oldval) -> {
+
+            if (oldval != null) {
+                throw new IllegalStateException("already registered: " + name);
+            }
+
+            return object;
+        });
+    }
+
+    /**
+     * Registers an object, replacing any previously existing binding.
+     *
+     * @param name name by which the object is known
+     * @param object object to be registered
+     * @throws IllegalArgumentException if either argument is null
+     */
+    public static void registerOrReplace(String name, Object object) {
+        if (name == null) {
+            throw new IllegalArgumentException("attempt to register: " + name);
+        }
+
+        if (object == null) {
+            throw new IllegalArgumentException("attempt to register null object for " + name);
+        }
+
+        instance.name2object.compute(name, (key, oldval) -> {
+
+            if (oldval != null) {
+                logger.warn("replacing previously registered: {}", name);
+            }
+
+            return object;
+        });
+    }
+
+    /**
+     * Unregisters an object.
+     *
+     * @param name name by which the object is known
+     * @return {@code true} if the object was unregistered, {@code false} if it did not
+     *         exist
+     */
+    public static boolean unregister(String name) {
+        return (instance.name2object.remove(name) != null);
+    }
+
+    /**
+     * Gets the object by the given name.
+     *
+     * @param name name of the object to get
+     * @param clazz object's class
+     * @return the object
+     * @throws IllegalArgumentException if no object is registered by the given name
+     */
+    public static <T> T get(String name, Class<T> clazz) {
+        Object obj = instance.name2object.get(name);
+        if (obj == null) {
+            throw new IllegalArgumentException("not registered: " + name);
+        }
+
+        return clazz.cast(obj);
+    }
+
+    /**
+     * Gets the object by the given name, providing a default value if the name is not
+     * registered.
+     *
+     * @param name name of the object to get
+     * @param clazz object's class
+     * @param defaultVal the default value to return, if the object does not exist
+     * @return the object, if it exists, the default value, otherwise
+     */
+    public static <T> T getOrDefault(String name, Class<T> clazz, T defaultVal) {
+        Object obj = instance.name2object.get(name);
+        return (obj != null ? clazz.cast(obj) : defaultVal);
+    }
+
+    /**
+     * Creates a new registry instance. This is typically only used by junit tests, as it
+     * discards any previous registry entries.
+     */
+    public static void newRegistry() {
+        instance = new Registry();
+    }
+}
diff --git a/utils/src/test/java/org/onap/policy/common/utils/services/RegistryTest.java b/utils/src/test/java/org/onap/policy/common/utils/services/RegistryTest.java
new file mode 100644 (file)
index 0000000..f4b9508
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.services;
+
+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.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RegistryTest {
+    private static final String UNKNOWN = "unknown";
+    private static final String NAME_STR = "name-string";
+    private static final String NAME_OBJ = "name-object";
+    private static final String NAME_INT = "name-integer";
+
+    private static final String DATA_STR = "data-1";
+    private static final Object DATA_OBJ = new Object();
+    private static final Integer DATA_INT = 5;
+
+    /**
+     * Set up.
+     */
+    @Before
+    public void setUp() {
+        Registry.newRegistry();
+
+        Registry.register(NAME_STR, DATA_STR);
+        Registry.register(NAME_OBJ, DATA_OBJ);
+        Registry.register(NAME_INT, DATA_INT);
+    }
+
+    /**
+     * Ensure the registry is left empty when done.
+     */
+    @AfterClass
+    public static void tearDownAfterClass() {
+        Registry.newRegistry();
+    }
+
+    /**
+     * Sunny day scenario is tested by other tests, so we focus on exceptions here.
+     */
+    @Test
+    public void testRegister_Ex() {
+        assertThatIllegalStateException().isThrownBy(() -> Registry.register(NAME_STR, DATA_STR));
+
+        assertThatIllegalArgumentException().isThrownBy(() -> Registry.register(null, DATA_STR));
+        assertThatIllegalArgumentException().isThrownBy(() -> Registry.register(UNKNOWN, null));
+    }
+
+    @Test
+    public void testRegisterOrReplace() {
+        // should be able to replace
+        Registry.registerOrReplace(NAME_STR, DATA_STR);
+        Registry.registerOrReplace(NAME_STR, DATA_STR);
+        assertSame(DATA_STR, Registry.get(NAME_STR, String.class));
+
+        // should also be able to add
+        Registry.registerOrReplace(UNKNOWN, DATA_INT);
+        assertSame(DATA_INT, Registry.get(UNKNOWN, Integer.class));
+
+        // check exception cases
+        assertThatIllegalArgumentException().isThrownBy(() -> Registry.registerOrReplace(null, DATA_STR));
+        assertThatIllegalArgumentException().isThrownBy(() -> Registry.registerOrReplace(UNKNOWN, null));
+    }
+
+    @Test
+    public void testUnregister() {
+        assertTrue(Registry.unregister(NAME_STR));
+
+        assertEquals(null, Registry.getOrDefault(NAME_STR, String.class, null));
+
+        assertFalse(Registry.unregister(NAME_STR));
+    }
+
+    @Test
+    public void testGet() {
+        assertSame(DATA_STR, Registry.get(NAME_STR, String.class));
+        assertSame(DATA_OBJ, Registry.get(NAME_OBJ, Object.class));
+        assertSame(DATA_INT, Registry.get(NAME_INT, Integer.class));
+
+        // does not exist
+        assertThatIllegalArgumentException().isThrownBy(() -> Registry.get(UNKNOWN, Object.class));
+
+        // wrong type
+        assertThatThrownBy(() -> Registry.get(NAME_INT, String.class)).isInstanceOf(ClassCastException.class);
+    }
+
+    @Test
+    public void testGetOrDefault() {
+        assertSame(DATA_STR, Registry.getOrDefault(NAME_STR, String.class, null));
+        assertSame(DATA_OBJ, Registry.getOrDefault(NAME_OBJ, Object.class, "xyz"));
+        assertSame(DATA_INT, Registry.getOrDefault(NAME_INT, Integer.class, 10));
+
+        assertEquals(null, Registry.getOrDefault(UNKNOWN, String.class, null));
+        assertEquals("abc", Registry.getOrDefault(UNKNOWN, String.class, "abc"));
+        assertEquals(Integer.valueOf(11), Registry.getOrDefault(UNKNOWN, Integer.class, 11));
+    }
+
+    @Test
+    public void testNewRegistry() {
+        assertSame(DATA_STR, Registry.get(NAME_STR, String.class));
+
+        Registry.newRegistry();
+
+        // should not exist
+        assertEquals(null, Registry.getOrDefault(NAME_STR, String.class, null));
+
+        // should be able to register it again now
+        Registry.register(NAME_STR, DATA_STR);
+        assertSame(DATA_STR, Registry.get(NAME_STR, String.class));
+    }
+
+}