From 4ac697769f46e3f6d52a086bcc8e15d89755dd0d Mon Sep 17 00:00:00 2001 From: Jim Hahn Date: Wed, 20 Mar 2019 17:58:49 -0400 Subject: Add simple Registry class to manage singletons 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 --- .../policy/common/utils/services/Registry.java | 151 +++++++++++++++++++++ .../policy/common/utils/services/RegistryTest.java | 139 +++++++++++++++++++ 2 files changed, 290 insertions(+) create mode 100644 utils/src/main/java/org/onap/policy/common/utils/services/Registry.java create mode 100644 utils/src/test/java/org/onap/policy/common/utils/services/RegistryTest.java 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 index 00000000..c209379f --- /dev/null +++ b/utils/src/main/java/org/onap/policy/common/utils/services/Registry.java @@ -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 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 get(String name, Class 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 getOrDefault(String name, Class 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 index 00000000..f4b95089 --- /dev/null +++ b/utils/src/test/java/org/onap/policy/common/utils/services/RegistryTest.java @@ -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)); + } + +} -- cgit 1.2.3-korg