Merge "PropertyUtil: remove sleep when running junit test"
diff --git a/integrity-audit/src/main/java/org/onap/policy/common/ia/AuditThread.java b/integrity-audit/src/main/java/org/onap/policy/common/ia/AuditThread.java
index f1839b1..58ed8b9 100644
--- a/integrity-audit/src/main/java/org/onap/policy/common/ia/AuditThread.java
+++ b/integrity-audit/src/main/java/org/onap/policy/common/ia/AuditThread.java
@@ -25,10 +25,7 @@
 import java.util.Date;
 import java.util.List;
 import java.util.Properties;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-
 import org.onap.policy.common.ia.jpa.IntegrityAuditEntity;
 import org.onap.policy.common.logging.eelf.MessageCodes;
 import org.onap.policy.common.logging.flexlogger.FlexLogger;
@@ -108,18 +105,6 @@
     private IntegrityAudit integrityAudit;
 
     /**
-     * A latch is taken from this queue before starting an audit. May be {@code null}. Used by JUnit
-     * tests.
-     */
-    private BlockingQueue<CountDownLatch> auditLatchQueue;
-
-    /**
-     * Latch to be decremented when the next audit completes. May be {@code null}. Used by JUnit
-     * tests to wait for an audit to complete.
-     */
-    private CountDownLatch auditCompletionLatch = null;
-
-    /**
      * AuditThread constructor.
      * 
      * @param resourceName the resource name
@@ -133,7 +118,7 @@
             int integrityAuditPeriodSeconds, IntegrityAudit integrityAudit) throws IntegrityAuditException {
 
         this(resourceName, persistenceUnit, properties, TimeUnit.SECONDS.toMillis(integrityAuditPeriodSeconds),
-                integrityAudit, null);
+                integrityAudit);
     }
 
     /**
@@ -148,13 +133,12 @@
      * @throws IntegrityAuditException if an error occurs
      */
     public AuditThread(String resourceName, String persistenceUnit, Properties properties, long integrityAuditMillis,
-            IntegrityAudit integrityAudit, BlockingQueue<CountDownLatch> queue) throws IntegrityAuditException {
+            IntegrityAudit integrityAudit) throws IntegrityAuditException {
         this.resourceName = resourceName;
         this.persistenceUnit = persistenceUnit;
         this.properties = properties;
         this.integrityAuditPeriodMillis = integrityAuditMillis;
         this.integrityAudit = integrityAudit;
-        this.auditLatchQueue = queue;
 
         /*
          * The DbDAO Constructor registers this node in the IntegrityAuditEntity table. Each
@@ -174,14 +158,8 @@
         logger.info("AuditThread.run: Entering");
 
         try {
-            /*
-             * For JUnit testing: wait for the first latch, decrement it to indicate that the thread
-             * has started, and then wait for the next latch, before we actually start doing
-             * anything. These simply return if there is no latch queue defined.
-             */
-            getNextLatch();
-            decrementLatch();
-            getNextLatch();
+            // for junit testing
+            runStarted();
 
             /*
              * Triggers change in designation, unless no other viable candidate.
@@ -284,11 +262,8 @@
                      * property, otherwise just sleep the normal interval.
                      */
                     if (auditCompleted) {
-                        // indicate that an audit has completed
-                        decrementLatch();
-
-                        // don't start the next audit cycle until a latch has been provided
-                        getNextLatch();
+                        // for junit testing: indicate that an audit has completed
+                        auditCompleted();
 
                         if (logger.isDebugEnabled()) {
                             logger.debug("AuditThread.run: Audit completed; resourceName=" + this.resourceName
@@ -342,29 +317,6 @@
     }
 
     /**
-     * Gets the next audit-completion latch from the queue. Blocks, if the queue is empty.
-     * 
-     * @throws InterruptedException if interrupted while waiting
-     */
-    private void getNextLatch() throws InterruptedException {
-        BlockingQueue<CountDownLatch> queue = this.auditLatchQueue;
-        if (queue != null) {
-            this.auditCompletionLatch = queue.take();
-        }
-    }
-
-    /**
-     * Decrements the current audit-completion latch, if any.
-     */
-    private void decrementLatch() {
-        CountDownLatch latch = this.auditCompletionLatch;
-        if (latch != null) {
-            this.auditCompletionLatch = null;
-            latch.countDown();
-        }
-    }
-
-    /**
      * Determines if an exception is an InterruptedException or was caused by an
      * InterruptedException.
      * 
@@ -788,6 +740,26 @@
     }
 
     /**
+     * Indicates that the {@link #run()} method has started. This method simply returns,
+     * and may overridden by junit tests.
+     * 
+     * @throws InterruptedException
+     */
+    public void runStarted() throws InterruptedException {
+        // does nothing
+    }
+
+    /**
+     * Indicates that an audit has completed. This method simply returns, and may
+     * overridden by junit tests.
+     * 
+     * @throws InterruptedException
+     */
+    public void auditCompleted() throws InterruptedException {
+        // does nothing
+    }
+
+    /**
      * Adjusts the thread-sleep-interval to be used when an audit has <i>not</i> been completed.
      * Used by JUnit tests.
      * 
diff --git a/integrity-audit/src/main/java/org/onap/policy/common/ia/IntegrityAudit.java b/integrity-audit/src/main/java/org/onap/policy/common/ia/IntegrityAudit.java
index 9abdbe5..b3330fa 100644
--- a/integrity-audit/src/main/java/org/onap/policy/common/ia/IntegrityAudit.java
+++ b/integrity-audit/src/main/java/org/onap/policy/common/ia/IntegrityAudit.java
@@ -21,9 +21,6 @@
 package org.onap.policy.common.ia;
 
 import java.util.Properties;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
-
 import org.onap.policy.common.ia.IntegrityAuditProperties.NodeTypeEnum;
 import org.onap.policy.common.logging.flexlogger.FlexLogger;
 import org.onap.policy.common.logging.flexlogger.Logger;
@@ -217,37 +214,20 @@
      * @throws IntegrityAuditException if an error occurs
      */
     public void startAuditThread() throws IntegrityAuditException {
-        startAuditThread(null);
-    }
-
-    /**
-     * Starts the audit thread.
-     * 
-     * @param queue the queue
-     * @return {@code true} if the thread was started, {@code false} otherwise
-     * @throws IntegrityAuditException if an error occurs
-     */
-    protected boolean startAuditThread(BlockingQueue<CountDownLatch> queue) throws IntegrityAuditException {
-
         logger.info("startAuditThread: Entering");
 
-        boolean success = false;
-
         if (integrityAuditPeriodMillis >= 0) {
-            this.auditThread = new AuditThread(this.resourceName, this.persistenceUnit, this.properties,
-                    integrityAuditPeriodMillis, this, queue);
+            this.auditThread = makeAuditThread(this.resourceName, this.persistenceUnit, this.properties, integrityAuditPeriodMillis);
             logger.info("startAuditThread: Audit started and will run every " + integrityAuditPeriodMillis / 1000
                     + " seconds");
             this.auditThread.start();
-            success = true;
+            
         } else {
             logger.info("startAuditThread: Suppressing integrity audit, integrityAuditPeriodSeconds="
                     + integrityAuditPeriodMillis / 1000);
         }
 
         logger.info("startAuditThread: Exiting");
-
-        return success;
     }
 
     /**
@@ -299,4 +279,29 @@
             return !this.auditThread.isAlive();
         }
     }
+
+    /**
+     * 
+     * @return {@code true} if an audit thread exists, {@code false} otherwise
+     */
+    protected boolean haveAuditThread() {
+        return (this.auditThread != null);
+    }
+
+    /**
+     * Creates an audit thread. May be overridden by junit tests.
+     * 
+     * @param resourceName2
+     * @param persistenceUnit2
+     * @param properties2
+     * @param integrityAuditPeriodMillis2
+     * 
+     * @return a new audit thread
+     * @throws IntegrityAuditException
+     */
+    protected AuditThread makeAuditThread(String resourceName2, String persistenceUnit2, Properties properties2,
+                    long integrityAuditPeriodMillis2) throws IntegrityAuditException {
+        
+        return new AuditThread(resourceName2, persistenceUnit2, properties2, integrityAuditPeriodMillis2, this);
+    }
 }
diff --git a/integrity-audit/src/test/java/org/onap/policy/common/ia/IntegrityAuditTestBase.java b/integrity-audit/src/test/java/org/onap/policy/common/ia/IntegrityAuditTestBase.java
index afbcc45..c917990 100644
--- a/integrity-audit/src/test/java/org/onap/policy/common/ia/IntegrityAuditTestBase.java
+++ b/integrity-audit/src/test/java/org/onap/policy/common/ia/IntegrityAuditTestBase.java
@@ -22,10 +22,6 @@
 
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-
-import ch.qos.logback.classic.Level;
-import ch.qos.logback.classic.Logger;
-
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -34,21 +30,19 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Properties;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
-
 import javax.persistence.EntityManager;
 import javax.persistence.EntityManagerFactory;
 import javax.persistence.EntityTransaction;
 import javax.persistence.Persistence;
-
 import org.onap.policy.common.utils.jpa.EntityMgrCloser;
 import org.onap.policy.common.utils.jpa.EntityMgrFactoryCloser;
 import org.onap.policy.common.utils.jpa.EntityTransCloser;
 import org.onap.policy.common.utils.test.log.logback.ExtractAppender;
 import org.slf4j.LoggerFactory;
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
 
 /**
  * All JUnits are designed to run in the local development environment where they have write
@@ -464,26 +458,26 @@
     protected void runAudit(MyIntegrityAudit... auditors) throws InterruptedException {
 
         // start an audit cycle on each auditor
-        List<CountDownLatch> latches = new ArrayList<>(auditors.length);
+        List<Semaphore> semaphores = new ArrayList<>(auditors.length);
         for (MyIntegrityAudit p : auditors) {
-            latches.add(p.startAudit());
+            semaphores.add(p.startAudit());
         }
 
         // wait for each auditor to complete its cycle
-        for (CountDownLatch latch : latches) {
-            waitLatch(latch);
+        for (Semaphore sem : semaphores) {
+            waitSem(sem);
         }
     }
 
     /**
-     * Waits for a latch to reach zero.
+     * Waits for a semaphore to be released.
      * 
-     * @param latch the latch to wait for
+     * @param sem the semaphore for which to wait
      * @throws InterruptedException if the thread is interrupted
      * @throws AssertionError if the latch did not reach zero in the allotted time
      */
-    protected void waitLatch(CountDownLatch latch) throws InterruptedException {
-        assertTrue(latch.await(WAIT_MS, TimeUnit.SECONDS));
+    protected void waitSem(Semaphore sem) throws InterruptedException {
+        assertTrue(sem.tryAcquire(WAIT_MS, TimeUnit.SECONDS));
     }
 
     /**
@@ -520,11 +514,16 @@
      * Manages audits by inserting latches into a queue for the AuditThread to count.
      */
     protected class MyIntegrityAudit extends IntegrityAudit {
-
+        
         /**
-         * Queue from which the AuditThread will take latches.
+         * Semaphore on which the audit thread should wait.
          */
-        private BlockingQueue<CountDownLatch> queue = null;
+        private Semaphore auditSem = null;
+        
+        /**
+         * Semaphore on which the junit management thread should wait.
+         */
+        private Semaphore junitSem = null;
 
         /**
          * Constructs an auditor and starts the AuditThread.
@@ -550,16 +549,14 @@
         }
 
         /**
-         * Triggers an audit by adding a latch to the queue.
+         * Triggers an audit by releasing the audit thread's semaphore.
          * 
-         * @return the latch that was added
+         * @return the semaphore on which to wait
          * @throws InterruptedException if the thread is interrupted
          */
-        public CountDownLatch startAudit() throws InterruptedException {
-            CountDownLatch latch = new CountDownLatch(1);
-            queue.add(latch);
-
-            return latch;
+        public Semaphore startAudit() throws InterruptedException {
+            auditSem.release();
+            return junitSem;
         }
 
         /**
@@ -567,25 +564,23 @@
          */
         @Override
         public final void startAuditThread() throws IntegrityAuditException {
-            if (queue != null) {
-                // queue up a bogus latch, in case a thread is still running
-                queue.add(new CountDownLatch(1) {
-                    @Override
-                    public void countDown() {
-                        throw new RuntimeException("auditor has multiple threads");
-                    }
-                });
+            if (auditSem != null) {
+                // release a bunch of semaphores, in case a thread is still running
+                auditSem.release(1000);
             }
+            
+            auditSem = new Semaphore(0);
+            junitSem = new Semaphore(0);
+            
+            super.startAuditThread();
 
-            queue = new LinkedBlockingQueue<>();
+            if (haveAuditThread()) {
+                // tell the thread it can run
+                auditSem.release();
 
-            if (super.startAuditThread(queue)) {
                 // wait for the thread to start
-                CountDownLatch latch = new CountDownLatch(1);
-                queue.add(latch);
-
                 try {
-                    waitLatch(latch);
+                    waitSem(junitSem);
 
                 } catch (InterruptedException e) {
                     Thread.currentThread().interrupt();
@@ -605,5 +600,31 @@
 
             assertTrue(waitThread(this));
         }
+
+        @Override
+        protected AuditThread makeAuditThread(String resourceName2, String persistenceUnit2, Properties properties2,
+                        long integrityAuditPeriodMillis2) throws IntegrityAuditException {
+
+            return new AuditThread(resourceName2, persistenceUnit2, properties2, integrityAuditPeriodMillis2, this) {
+
+                private Semaphore auditSem = MyIntegrityAudit.this.auditSem;
+                private Semaphore junitSem = MyIntegrityAudit.this.junitSem;
+
+                @Override
+                public void runStarted() throws InterruptedException {
+                    auditSem.acquire();
+                    
+                    junitSem.release();
+                    auditSem.acquire();
+                }
+
+                @Override
+                public void auditCompleted() throws InterruptedException {
+                    junitSem.release();
+                    auditSem.acquire();
+                }
+                
+            };
+        }
     }
 }
diff --git a/integrity-monitor/src/main/java/org/onap/policy/common/im/IntegrityMonitor.java b/integrity-monitor/src/main/java/org/onap/policy/common/im/IntegrityMonitor.java
index c32a221..38dc20d 100644
--- a/integrity-monitor/src/main/java/org/onap/policy/common/im/IntegrityMonitor.java
+++ b/integrity-monitor/src/main/java/org/onap/policy/common/im/IntegrityMonitor.java
@@ -29,10 +29,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Properties;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-
 import javax.management.JMX;
 import javax.management.MBeanServerConnection;
 import javax.persistence.EntityManager;
@@ -43,7 +40,6 @@
 import javax.persistence.Persistence;
 import javax.persistence.Query;
 import javax.validation.constraints.NotNull;
-
 import org.onap.policy.common.im.jmx.ComponentAdmin;
 import org.onap.policy.common.im.jmx.ComponentAdminMBean;
 import org.onap.policy.common.im.jmx.JmxAgentConnection;
@@ -196,7 +192,7 @@
      */
     protected IntegrityMonitor(String resourceName, Properties properties) throws IntegrityMonitorException {
 
-        this(resourceName, properties, null);
+        this(resourceName, properties, new Factory());
     }
 
     /**
@@ -207,10 +203,10 @@
      * 
      * @param resourceName The resource name of the resource
      * @param properties a set of properties passed in from the resource
-     * @param queue queue to use to control the FPManager thread, or {@code null}
+     * @param factory Factory to use to control the FPManager thread
      * @throws IntegrityMonitorException if any errors are encountered in the constructor
      */
-    protected IntegrityMonitor(String resourceName, Properties properties, BlockingQueue<CountDownLatch> queue)
+    protected IntegrityMonitor(String resourceName, Properties properties, Factory factory)
             throws IntegrityMonitorException {
 
         // singleton check since this constructor can be called from a child or
@@ -357,7 +353,8 @@
             logger.error("ComponentAdmin constructor exception: {}", e.toString(), e);
         }
 
-        fpManager = new FpManager(queue);
+        fpManager = new FpManager(factory);
+        fpManager.start();
 
     }
 
@@ -373,7 +370,7 @@
      */
     public static IntegrityMonitor getInstance(String resourceName, Properties properties)
             throws IntegrityMonitorException {
-        return getInstance(resourceName, properties, null);
+        return getInstance(resourceName, properties, new Factory());
     }
 
     /**
@@ -382,13 +379,13 @@
      * 
      * @param resourceName The resource name of the resource
      * @param properties a set of properties passed in from the resource
-     * @param queue queue to use to control the FPManager thread, or {@code null}
+     * @param factory Factory to use to control the FPManager thread
      * @return The new instance of IntegrityMonitor
      * @throws IntegrityMonitorException if unable to create jmx url or the constructor returns an
      *         exception
      */
     protected static IntegrityMonitor getInstance(String resourceName, Properties properties,
-            BlockingQueue<CountDownLatch> queue) throws IntegrityMonitorException {
+                    Factory factory) throws IntegrityMonitorException {
 
         synchronized (getInstanceLock) {
             logger.debug("getInstance() called - resourceName= {}", resourceName);
@@ -399,7 +396,7 @@
 
             if (instance == null) {
                 logger.debug("Creating new instance of IntegrityMonitor");
-                instance = new IntegrityMonitor(resourceName, properties, queue);
+                instance = new IntegrityMonitor(resourceName, properties, factory);
             }
             return instance;
         }
@@ -1740,18 +1737,15 @@
      * dependencies, does a refresh state audit and runs the stateAudit.
      */
     class FpManager extends Thread {
-        private final CountDownLatch stopper = new CountDownLatch(1);
+        private boolean stopRequested = false;
 
-        private BlockingQueue<CountDownLatch> queue;
-        private CountDownLatch progressLatch = null;
+        private final Factory factory;
 
         // Constructor - start FP manager thread
-        FpManager(BlockingQueue<CountDownLatch> queue) {
-            this.queue = queue;
+        FpManager(Factory factory) {
+            this.factory = factory;
             // set now as the last time the refreshStateAudit ran
             IntegrityMonitor.this.refreshStateAuditLastRunDate = new Date();
-            // start thread
-            this.start();
         }
 
         @Override
@@ -1759,13 +1753,13 @@
             logger.debug("FPManager thread running");
 
             try {
-                getLatch();
-                decrementLatch();
+                factory.runStarted();
 
-                while (!stopper.await(cycleIntervalMillis, TimeUnit.MILLISECONDS)) {
-                    getLatch();
+                while(!stopRequested) {
+                    factory.doSleep(cycleIntervalMillis);
+                    
                     IntegrityMonitor.this.runOnce();
-                    decrementLatch();
+                    factory.monitorCompleted();
                 }
 
             } catch (InterruptedException e) {
@@ -1775,31 +1769,9 @@
         }
 
         public void stopAndExit() {
-            stopper.countDown();
+            stopRequested = true;
             this.interrupt();
         }
-
-        /**
-         * Gets the next latch from the queue.
-         * 
-         * @throws InterruptedException
-         * 
-         */
-        private void getLatch() throws InterruptedException {
-            if (queue != null) {
-                progressLatch = queue.take();
-            }
-        }
-
-        /**
-         * Decrements the current latch.
-         */
-        private void decrementLatch() {
-            if (progressLatch != null) {
-                progressLatch.countDown();
-            }
-        }
-
     }
 
     private void runOnce() {
@@ -1934,6 +1906,40 @@
         return allNotWellMap;
     }
 
+    /**
+     * Used to access various objects. Overridden by junit tests.
+     */
+    public static class Factory {
+
+        /**
+         * Indicates that the {@link FpManager#run()} method has started. This method
+         * simply returns.
+         * 
+         * @throws InterruptedException
+         */
+        public void runStarted() throws InterruptedException {
+            // does nothing
+        }
+
+        /**
+         * Sleeps for a period of time.
+         * @param sleepMs   amount of time to sleep, in milliseconds
+         * @throws InterruptedException 
+         */
+        public void doSleep(long sleepMs) throws InterruptedException {
+            Thread.sleep(sleepMs);
+        }
+
+        /**
+         * Indicates that a monitor activity has completed. This method simply returns.
+         * 
+         * @throws InterruptedException
+         */
+        public void monitorCompleted() throws InterruptedException {
+            // does nothing
+        }
+    }
+
     /*
      * The remaining methods are used by JUnit tests.
      */
diff --git a/integrity-monitor/src/test/java/org/onap/policy/common/im/IntegrityMonitorTest.java b/integrity-monitor/src/test/java/org/onap/policy/common/im/IntegrityMonitorTest.java
index 7f1e551..091dcc9 100644
--- a/integrity-monitor/src/test/java/org/onap/policy/common/im/IntegrityMonitorTest.java
+++ b/integrity-monitor/src/test/java/org/onap/policy/common/im/IntegrityMonitorTest.java
@@ -22,23 +22,19 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
-
 import java.util.Date;
 import java.util.List;
 import java.util.Properties;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
-
+import java.util.concurrent.Semaphore;
 import javax.persistence.EntityTransaction;
 import javax.persistence.Query;
 import javax.persistence.TemporalType;
-
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.onap.policy.common.im.IntegrityMonitor.Factory;
 import org.onap.policy.common.im.jpa.ForwardProgressEntity;
 import org.onap.policy.common.im.jpa.ResourceRegistrationEntity;
 import org.onap.policy.common.im.jpa.StateManagementEntity;
@@ -57,7 +53,8 @@
     private static EntityTransaction et;
     private static String resourceName;
 
-    private BlockingQueue<CountDownLatch> queue;
+    private Semaphore monitorSem;
+    private Semaphore junitSem;
 
     /**
      * Set up for test class.
@@ -900,9 +897,36 @@
     private IntegrityMonitor makeMonitor(String resourceName, Properties myProp) throws Exception {
         IntegrityMonitor.deleteInstance();
 
-        queue = new LinkedBlockingQueue<>();
+        monitorSem = new Semaphore(0);
+        junitSem = new Semaphore(0);
+        
+        Factory factory = new IntegrityMonitor.Factory() {
 
-        IntegrityMonitor im = IntegrityMonitor.getInstance(resourceName, myProp, queue);
+            @Override
+            public void doSleep(long sleepMs) throws InterruptedException {
+                /*
+                 * No need to sleep, as the thread won't progress until the
+                 * semaphore is released.
+                 */
+            }
+
+            @Override
+            public void runStarted() throws InterruptedException {
+                monitorSem.acquire();
+                
+                junitSem.release();
+                monitorSem.acquire();
+            }
+
+            @Override
+            public void monitorCompleted() throws InterruptedException {
+                junitSem.release();
+                monitorSem.acquire();
+            }
+            
+        };
+
+        IntegrityMonitor im = IntegrityMonitor.getInstance(resourceName, myProp, factory);
 
         // wait for the monitor thread to start
         waitStep();
@@ -916,8 +940,7 @@
      * @throws InterruptedException if the thread is interrupted
      */
     private void waitStep() throws InterruptedException {
-        CountDownLatch latch = new CountDownLatch(1);
-        queue.offer(latch);
-        waitLatch(latch);
+        monitorSem.release();
+        waitSem(junitSem);
     }
 }
diff --git a/integrity-monitor/src/test/java/org/onap/policy/common/im/IntegrityMonitorTestBase.java b/integrity-monitor/src/test/java/org/onap/policy/common/im/IntegrityMonitorTestBase.java
index 0c8259b..e556230 100644
--- a/integrity-monitor/src/test/java/org/onap/policy/common/im/IntegrityMonitorTestBase.java
+++ b/integrity-monitor/src/test/java/org/onap/policy/common/im/IntegrityMonitorTestBase.java
@@ -22,17 +22,14 @@
 
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.Properties;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
-
 import javax.persistence.EntityManager;
 import javax.persistence.EntityManagerFactory;
 import javax.persistence.Persistence;
-
 import org.onap.policy.common.utils.jpa.EntityTransCloser;
 import org.onap.policy.common.utils.test.log.logback.ExtractAppender;
 import org.slf4j.Logger;
@@ -243,14 +240,14 @@
     }
 
     /**
-     * Waits for a latch to reach zero.
+     * Waits for a semaphore to be acquired
      * 
-     * @param latch the latch
+     * @param sem the latch
      * @throws InterruptedException if the thread is interrupted
      * @throws AssertionError if the latch did not reach zero in the allotted time
      */
-    protected void waitLatch(CountDownLatch latch) throws InterruptedException {
-        assertTrue(latch.await(WAIT_MS, TimeUnit.SECONDS));
+    protected void waitSem(Semaphore sem) throws InterruptedException {
+        assertTrue(sem.tryAcquire(WAIT_MS, TimeUnit.MILLISECONDS));
     }
 
     /**
diff --git a/utils-test/pom.xml b/utils-test/pom.xml
index a7c2eae..933104b 100644
--- a/utils-test/pom.xml
+++ b/utils-test/pom.xml
@@ -42,6 +42,11 @@
 			<groupId>ch.qos.logback</groupId>
 			<artifactId>logback-classic</artifactId>
 		</dependency>
+		<dependency>
+			<groupId>org.onap.policy.common</groupId>
+			<artifactId>utils</artifactId>
+			<version>${project.version}</version>
+		</dependency>
 	</dependencies>
 
 	<build>
diff --git a/utils-test/src/main/java/org/onap/policy/common/utils/time/TestTime.java b/utils-test/src/main/java/org/onap/policy/common/utils/time/TestTime.java
new file mode 100644
index 0000000..3dfed4b
--- /dev/null
+++ b/utils-test/src/main/java/org/onap/policy/common/utils/time/TestTime.java
@@ -0,0 +1,59 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Common Utils-Test
+ * ================================================================================
+ * Copyright (C) 2018 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.time;
+
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * "Current" time, when running junit tests. This is intended to be injected into classes
+ * under test, to replace their {@link CurrentTime} objects. When {@link #sleep(long)} is
+ * invoked, it simply advances the notion of "current" time and returns immediately.
+ */
+public class TestTime extends CurrentTime {
+
+    /**
+     * "Current" time, in milliseconds, used by tests.
+     */
+    private AtomicLong tcur = new AtomicLong(System.currentTimeMillis());
+
+    /**
+     * 
+     */
+    public TestTime() {
+        super();
+    }
+
+    @Override
+    public long getMillis() {
+        return tcur.get();
+    }
+
+    @Override
+    public Date getDate() {
+        return new Date(tcur.get());
+    }
+
+    @Override
+    public void sleep(long sleepMs) throws InterruptedException {
+        tcur.addAndGet(sleepMs);
+    }
+}
diff --git a/utils-test/src/main/java/org/onap/policy/common/utils/time/TestTimeMulti.java b/utils-test/src/main/java/org/onap/policy/common/utils/time/TestTimeMulti.java
new file mode 100644
index 0000000..7a8277c
--- /dev/null
+++ b/utils-test/src/main/java/org/onap/policy/common/utils/time/TestTimeMulti.java
@@ -0,0 +1,200 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Common Utils-Test
+ * ================================================================================
+ * Copyright (C) 2018 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.time;
+
+import java.util.Date;
+import java.util.PriorityQueue;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * "Current" time, when running junit tests in multiple threads. This is intended to be
+ * injected into classes under test, to replace their {@link CurrentTime} objects. The
+ * {@link #sleep(long)} method blocks until all threads enter and then it moves the notion
+ * of "current" time forward, allowing threads to resume, as the end of their sleep time
+ * is reached. Additional threads do not resume until all threads have once again entered
+ * {@link #sleep(long)} or when {@link #threadCompleted()} is invoked to indicate that a
+ * thread will not re-enter {@link #sleep(long)}.
+ */
+public class TestTimeMulti extends CurrentTime {
+
+    /**
+     * Number of threads that will be sleeping simultaneously.
+     */
+    private int nthreads;
+
+    /**
+     * "Current" time, in milliseconds, used by tests.
+     */
+    private long tcur = System.currentTimeMillis();
+
+    /**
+     * Queue of sleeping threads waiting to be awakened.
+     */
+    private final PriorityQueue<Info> queue = new PriorityQueue<>();
+
+    /**
+     * Used to synchronize updates.
+     */
+    private final Object locker = new Object();
+
+    /**
+     * 
+     * @param nthreads number of threads that will be sleeping simultaneously
+     */
+    public TestTimeMulti(int nthreads) {
+        this.nthreads = nthreads;
+    }
+
+    @Override
+    public long getMillis() {
+        return tcur;
+    }
+
+    @Override
+    public Date getDate() {
+        return new Date(tcur);
+    }
+
+    @Override
+    public void sleep(long sleepMs) throws InterruptedException {
+        if (sleepMs <= 0) {
+            return;
+        }
+
+        Info info = new Info(tcur + sleepMs);
+
+        synchronized (locker) {
+            queue.add(info);
+
+            if (queue.size() == nthreads) {
+                // all threads are now sleeping - wake one up
+                wakeThreads();
+            }
+        }
+
+        // this MUST happen outside of the "synchronized" block
+        info.await();
+    }
+
+    /**
+     * Indicates that a thread has terminated or that it will no longer be invoking
+     * {@link #sleep(long)}. Awakens the next sleeping thread, if the queue is full after
+     * removing the terminated thread.
+     * 
+     * @throws IllegalStateException if the queue is already full
+     */
+    public void threadCompleted() {
+        synchronized (locker) {
+            int sz = queue.size();
+            if (sz >= nthreads) {
+                throw new IllegalStateException("too many threads still sleeping");
+            }
+
+            --nthreads;
+
+            if (sz == nthreads) {
+                // after removing terminated thread - queue is now full; awaken something
+                wakeThreads();
+            }
+        }
+    }
+
+    /**
+     * Advances the "current" time and awakens any threads sleeping until that time.
+     */
+    private void wakeThreads() {
+        Info info = queue.poll();
+        if(info == null) {
+            return;
+        }
+
+        tcur = info.getAwakenAtMs();
+        info.wake();
+
+        while ((info = queue.poll()) != null) {
+            if (tcur == info.getAwakenAtMs()) {
+                info.wake();
+
+            } else {
+                // not ready to wake this thread - put it back in the queue
+                queue.add(info);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Info about a sleeping thread.
+     */
+    private static class Info implements Comparable<Info> {
+
+        /**
+         * Time, in milliseconds, at which the associated thread should awaken.
+         */
+        private final long awakenAtMs;
+
+        /**
+         * This is triggered when the associated thread should awaken.
+         */
+        private final CountDownLatch latch = new CountDownLatch(1);
+
+        /**
+         * @param awakenAtMs time, in milliseconds, at which the associated thread should
+         *        awaken
+         */
+        public Info(long awakenAtMs) {
+            this.awakenAtMs = awakenAtMs;
+        }
+
+        public long getAwakenAtMs() {
+            return awakenAtMs;
+        }
+
+        /**
+         * Awakens the associated thread by decrementing its latch.
+         */
+        public void wake() {
+            latch.countDown();
+        }
+
+        /**
+         * Blocks the current thread until awakened (i.e., until its latch is
+         * decremented).
+         * 
+         * @throws InterruptedException
+         */
+        public void await() throws InterruptedException {
+            latch.await();
+        }
+
+        @Override
+        public int compareTo(Info o) {
+            int diff = Long.compare(awakenAtMs, o.awakenAtMs);
+
+            // this assumes that Object.toString() is unique for each Info object
+            if (diff == 0)
+                diff = this.toString().compareTo(o.toString());
+
+            return diff;
+        }
+
+    }
+}
diff --git a/utils-test/src/test/java/org/onap/policy/common/utils/time/TestTimeMultiTest.java b/utils-test/src/test/java/org/onap/policy/common/utils/time/TestTimeMultiTest.java
new file mode 100644
index 0000000..206ab5f
--- /dev/null
+++ b/utils-test/src/test/java/org/onap/policy/common/utils/time/TestTimeMultiTest.java
@@ -0,0 +1,116 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Common Utils-Test
+ * ================================================================================
+ * Copyright (C) 2018 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.time;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+
+/**
+ * 
+ */
+public class TestTimeMultiTest {
+
+    private static final int NTHREADS = 10;
+    private static final int NTIMES = 100;
+    private static final long WAIT_SEC = 5L;
+    private static final long MIN_SLEEP_MS = 5L;
+
+    private TestTimeMulti ttm;
+    private Semaphore done;
+
+    @Test
+    public void test() throws Exception {
+        ttm = new TestTimeMulti(NTHREADS);
+        done = new Semaphore(0);
+
+        long tbeg = ttm.getMillis();
+
+        // create threads
+        List<MyThread> threads = new ArrayList<>(NTHREADS);
+        for (int x = 0; x < NTHREADS; ++x) {
+            threads.add(new MyThread(x + MIN_SLEEP_MS));
+        }
+
+        // launch threads
+        for (MyThread thr : threads) {
+            thr.start();
+        }
+
+        // wait for each one to complete
+        for (MyThread thr : threads) {
+            assertTrue("complete " + thr.getSleepMs(), done.tryAcquire(WAIT_SEC, TimeUnit.SECONDS));
+            ttm.threadCompleted();
+        }
+
+        // check results
+        for (MyThread thr : threads) {
+            assertEquals("time " + thr.getSleepMs(), thr.texpected, thr.tactual);
+        }
+
+        assertTrue(ttm.getMillis() >= tbeg + NTIMES * MIN_SLEEP_MS);
+    }
+
+    private class MyThread extends Thread {
+
+        private final long sleepMs;
+
+        private volatile long texpected;
+        private volatile long tactual;
+
+        public MyThread(long sleepMs) {
+            this.sleepMs = sleepMs;
+
+            this.setDaemon(true);
+        }
+
+        public long getSleepMs() {
+            return sleepMs;
+        }
+
+        @Override
+        public void run() {
+            try {
+                for (int x = 0; x < NTIMES; ++x) {
+                    texpected = ttm.getMillis() + sleepMs;
+                    ttm.sleep(sleepMs);
+
+                    if ((tactual = ttm.getMillis()) != texpected) {
+                        break;
+                    }
+
+                    if ((tactual = ttm.getDate().getTime()) != texpected) {
+                        break;
+                    }
+                }
+
+            } catch (InterruptedException expected) {
+                Thread.currentThread().interrupt();
+            }
+
+            done.release();
+        }
+    }
+}
diff --git a/utils-test/src/test/java/org/onap/policy/common/utils/time/TestTimeTest.java b/utils-test/src/test/java/org/onap/policy/common/utils/time/TestTimeTest.java
new file mode 100644
index 0000000..c1e15b3
--- /dev/null
+++ b/utils-test/src/test/java/org/onap/policy/common/utils/time/TestTimeTest.java
@@ -0,0 +1,66 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Common Utils-Test
+ * ================================================================================
+ * Copyright (C) 2018 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.time;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+public class TestTimeTest {
+
+    @Test
+    public void test() throws Exception {
+        TestTime tm = new TestTime();
+        TestTime tm2 = new TestTime();
+
+        long treal = System.currentTimeMillis();
+
+        long tcur = tm.getMillis();
+        assertEquals(tcur, tm.getDate().getTime());
+
+        long tsleep = 10000L;
+        long tcur2 = tcur;
+
+        // sleep a bit and then check values
+        tcur2 += tsleep;
+        tm2.sleep(tsleep);
+        assertEquals(tcur2, tm2.getMillis());
+        assertEquals(tcur2, tm2.getDate().getTime());
+
+        // sleep some more and then check values
+        tcur2 += tsleep;
+        tm2.sleep(tsleep);
+        assertEquals(tcur2, tm2.getMillis());
+        assertEquals(tcur2, tm2.getDate().getTime());
+
+        // check again - to ensure unchanged
+        assertEquals(tcur2, tm2.getMillis());
+        assertEquals(tcur2, tm2.getDate().getTime());
+
+        // original should also be unchanged
+        assertEquals(tcur, tm.getMillis());
+        assertEquals(tcur, tm.getDate().getTime());
+
+        // ensure that no real time has elapsed
+        assertTrue(System.currentTimeMillis() < treal + tsleep / 2);
+    }
+
+}
diff --git a/utils/pom.xml b/utils/pom.xml
index cf34bd7..8375408 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -38,6 +38,11 @@
   
 	<dependencies>
 		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+			<version>3.4</version>
+		</dependency>
+		<dependency>
 			<groupId>junit</groupId>
 			<artifactId>junit</artifactId>
 			<scope>test</scope>
diff --git a/utils/src/main/java/org/onap/policy/common/utils/properties/PropertyConfiguration.java b/utils/src/main/java/org/onap/policy/common/utils/properties/PropertyConfiguration.java
index 7253c74..e72ebab 100644
--- a/utils/src/main/java/org/onap/policy/common/utils/properties/PropertyConfiguration.java
+++ b/utils/src/main/java/org/onap/policy/common/utils/properties/PropertyConfiguration.java
@@ -25,8 +25,11 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Properties;
+import org.apache.commons.lang3.StringUtils;
 import org.onap.policy.common.utils.properties.exception.PropertyAccessException;
 import org.onap.policy.common.utils.properties.exception.PropertyException;
 import org.onap.policy.common.utils.properties.exception.PropertyInvalidException;
@@ -35,7 +38,9 @@
 /**
  * Configuration whose fields are initialized by reading from a set of {@link Properties},
  * as directed by the {@link Property} annotations that appear on fields within the
- * subclass.
+ * subclass. The values of the fields are set via <i>setXxx()</i> methods. As a result, if
+ * a field is annotated and there is no corresponding <i>setXxx()</i> method, then an
+ * exception will be thrown.
  * <p>
  * It is possible that an invalid <i>defaultValue</i> is specified via the
  * {@link Property} annotation. This could remain undetected until an optional property is
@@ -104,7 +109,10 @@
 
         checkModifiable(field, prop);
 
-        if (setValue(field, props, prop)) {
+        Method setter = getSetter(field, prop);
+        checkSetter(setter, prop);
+
+        if (setValue(setter, field, props, prop)) {
             return true;
         }
 
@@ -112,15 +120,33 @@
     }
 
     /**
+     * @param field field whose value is to be set
+     * @param prop property of interest
+     * @return the method to be used to set the field's value
+     * @throws PropertyAccessException if a "set" method cannot be identified
+     */
+    private Method getSetter(Field field, Property prop) throws PropertyAccessException {
+        String nm = "set" + StringUtils.capitalize(field.getName());
+
+        try {
+            return this.getClass().getMethod(nm, field.getType());
+
+        } catch (NoSuchMethodException | SecurityException e) {
+            throw new PropertyAccessException(prop.name(), nm, e);
+        }
+    }
+
+    /**
      * Sets a field's value from a particular property.
      * 
+     * @param setter method to be used to set the field's value
      * @param field field whose value is to be set
      * @param props properties from which to get the value
      * @param prop property of interest
      * @return {@code true} if the property's value was set, {@code false} otherwise
      * @throws PropertyException if an error occurs
      */
-    protected boolean setValue(Field field, Properties props, Property prop) throws PropertyException {
+    protected boolean setValue(Method setter, Field field, Properties props, Property prop) throws PropertyException {
 
         try {
             Object val = getValue(field, props, prop);
@@ -128,23 +154,15 @@
                 return false;
 
             } else {
-
-                /*
-                 * According to java docs & blogs, "field" is our own copy, so we're free
-                 * to change the flags without impacting the real permissions of the field
-                 * within the real class.
-                 */
-                field.setAccessible(true);
-
-                field.set(this, val);
+                setter.invoke(this, val);
                 return true;
             }
 
         } catch (IllegalArgumentException e) {
             throw new PropertyInvalidException(prop.name(), field.getName(), e);
 
-        } catch (IllegalAccessException e) {
-            throw new PropertyAccessException(prop.name(), field.getName(), e);
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            throw new PropertyAccessException(prop.name(), setter.getName(), e);
         }
     }
 
@@ -203,6 +221,21 @@
     }
 
     /**
+     * Verifies that the setter method is not <i>static</i>.
+     * 
+     * @param setter method to be checked
+     * @param prop property of interest
+     * @throws PropertyAccessException if the method is static
+     */
+    private void checkSetter(Method setter, Property prop) throws PropertyAccessException {
+        int mod = setter.getModifiers();
+
+        if (Modifier.isStatic(mod)) {
+            throw new PropertyAccessException(prop.name(), setter.getName(), "method is 'static'");
+        }
+    }
+
+    /**
      * Gets a property value, coercing it to a String.
      * 
      * @param fieldName field whose value is to be set
diff --git a/utils/src/main/java/org/onap/policy/common/utils/properties/SpecProperties.java b/utils/src/main/java/org/onap/policy/common/utils/properties/SpecProperties.java
new file mode 100644
index 0000000..0f416c3
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/properties/SpecProperties.java
@@ -0,0 +1,117 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2018 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.properties;
+
+import java.util.Properties;
+
+/**
+ * Properties with an optional specialization (e.g., session name, controller name).
+ */
+public class SpecProperties extends Properties {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * The property prefix, ending with ".".
+     */
+    private final String prefix;
+
+    /**
+     * The specialized property prefix, ending with ".".
+     */
+    private final String specPrefix;
+
+    /**
+     * 
+     * @param prefix the property name prefix that appears before any specialization, may
+     *        be ""
+     * @param specialization the property name specialization (e.g., session name)
+     */
+    public SpecProperties(String prefix, String specialization) {
+        this.prefix = withTrailingDot(prefix);
+        this.specPrefix = withTrailingDot(this.prefix + specialization);
+    }
+
+    /**
+     * 
+     * @param prefix the property name prefix that appears before any specialization, may
+     *        be ""
+     * @param specialization the property name specialization (e.g., session name)
+     * @param props the default properties
+     */
+    public SpecProperties(String prefix, String specialization, Properties props) {
+        super(props);
+
+        this.prefix = withTrailingDot(prefix);
+        this.specPrefix = withTrailingDot(this.prefix + specialization);
+    }
+
+    /**
+     * Adds a trailing "." to a String, if it doesn't already have one.
+     * 
+     * @param text text to which the "." should be added
+     * @return the text, with a trailing "."
+     */
+    private static String withTrailingDot(String text) {
+        return text.isEmpty() || text.endsWith(".") ? text : text + ".";
+    }
+
+    /**
+     * Gets the property whose value has the given key, looking first for the specialized
+     * property name, and then for the generalized property name.
+     * 
+     * @param key property name, without the specialization
+     * @return the value from the property set, or {@code null} if the property set does
+     *         not contain the value
+     */
+    @Override
+    public String getProperty(String key) {
+        if (!key.startsWith(prefix)) {
+            return super.getProperty(key);
+        }
+
+        String suffix = key.substring(prefix.length());
+
+        String val = super.getProperty(specPrefix + suffix);
+        if (val != null) {
+            return val;
+        }
+
+        return super.getProperty(key);
+    }
+
+    protected String getPrefix() {
+        return prefix;
+    }
+
+    protected String getSpecPrefix() {
+        return specPrefix;
+    }
+
+    @Override
+    public final int hashCode() {
+        throw new UnsupportedOperationException("SpecProperties cannot be hashed");
+    }
+
+    @Override
+    public final boolean equals(Object obj) {
+        throw new UnsupportedOperationException("cannot compare SpecProperties");
+    }
+}
diff --git a/utils/src/main/java/org/onap/policy/common/utils/properties/SpecPropertyConfiguration.java b/utils/src/main/java/org/onap/policy/common/utils/properties/SpecPropertyConfiguration.java
deleted file mode 100644
index 9e3767c..0000000
--- a/utils/src/main/java/org/onap/policy/common/utils/properties/SpecPropertyConfiguration.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * ONAP Policy Engine - Common Modules
- * ================================================================================
- * Copyright (C) 2018 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.properties;
-
-import java.util.Properties;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import org.onap.policy.common.utils.properties.exception.PropertyException;
-
-/**
- * PropertyConfiguration whose property names are specialized, using a specialization. A
- * property name can take one of the following forms:
- * <dl>
- * <dt>aaa{$}Ddd</dt>
- * <dd>if the specialization is "Xxx", then it looks for the value associated with the
- * property named "aaaXxxDdd"</dd>
- * <dt>aaa{Bbb?Ccc}Ddd</dt>
- * <dd>if the specialization is "Xxx", then it looks for the value associated with the
- * property named "aaaBbbXxxCccDdd". If the property does not exist, then it looks for the
- * value associated with the property named "aaaDdd" (i.e., without the
- * specialization)</dd>
- * <dt>aaa</dt>
- * <dd>simply looks for the value associated with the property named "aaa", without using
- * the specialization</dd>
- * </dl>
- * <p>
- * In the above examples, any of the components (e.g., "aaa") may be empty.
- */
-public class SpecPropertyConfiguration extends PropertyConfiguration {
-
-    /**
-     * Pattern to extract the specializer from a property name. Group 1 matches the form,
-     * "{$}", while groups 2 and 3 match the prefix and suffix, respectively, of the form,
-     * "{prefix?suffix}".
-     */
-    private static final Pattern SPEC_PAT = Pattern.compile(""
-                    // start of specialization info
-                    + "\\{(?:"
-                    // specialization type 1
-                    + "(\\$)"
-                    // alternative
-                    + "|"
-                    // specialization type 2
-                    + "(?:"
-                    // specialization type 2 prefix, may be empty
-                    + "([^}?]*)"
-                    // place-holder for the specialization, itself
-                    + "\\?"
-                    // specialization type 2 suffix, may be empty
-                    + "([^}]*)"
-                    // end of specialization type 2
-                    + ")"
-                    // end of specialization info
-                    + ")\\}");
-
-    /**
-     * The specialization to be used within property names.
-     */
-    private final String specialization;
-
-    /**
-     * Constructs a configuration, without populating any fields; fields should be
-     * populated later by invoking {@link #setAllFields(Properties)}.
-     * 
-     * @param specialization specialization to be substituted within property names
-     */
-    public SpecPropertyConfiguration(String specialization) {
-        super();
-
-        this.specialization = specialization;
-    }
-
-    /**
-     * 
-     * Initializes each "@Property" field with its value, as found in the properties.
-     * 
-     * @param specialization specialization to be substituted within property names
-     * @param props properties from which to extract the values
-     * @throws PropertyException if an error occurs
-     */
-    public SpecPropertyConfiguration(String specialization, Properties props) throws PropertyException {
-        super();
-
-        this.specialization = specialization;
-
-        setAllFields(props);
-    }
-
-    /**
-     * Gets a property's value, examining the property name for each of the types of
-     * specialization.
-     */
-    @Override
-    protected String getRawPropertyValue(Properties props, String propnm) {
-        Matcher mat = SPEC_PAT.matcher(propnm);
-
-        if (!mat.find()) {
-            // property name isn't specialized - use it as is
-            return super.getRawPropertyValue(props, propnm);
-
-        } else if (mat.group(1) != null) {
-            // replace "{$}" with the specialization name
-            return super.getRawPropertyValue(props, specializeType1(propnm, specialization, mat));
-
-        } else {
-            // first try to get the property using the specialization info
-            String val = super.getRawPropertyValue(props, specializeType2(propnm, specialization, mat));
-            if (val != null) {
-                return val;
-            }
-
-            // wasn't found - try again, without any specialization info
-            return super.getRawPropertyValue(props, generalizeType2(propnm, mat));
-        }
-    }
-
-    /**
-     * Generalizes a property name by stripping any specialization info from it. This is
-     * typically used to construct property names for junit testing.
-     * 
-     * @param propnm property name to be stripped of specialization info
-     * @return the generalized property name
-     * @throws IllegalArgumentException if the property name requires specialization
-     *         (i.e., contains "{$}")
-     */
-    public static String generalize(String propnm) {
-        Matcher mat = SPEC_PAT.matcher(propnm);
-
-        if (!mat.find()) {
-            // property name has no specialization info
-            return propnm;
-
-        } else if (mat.group(1) != null) {
-            // the "{$}" form requires specialization
-            throw new IllegalArgumentException("property requires specialization");
-
-        } else {
-            // property name has specialization info - strip it out
-            return generalizeType2(propnm, mat);
-        }
-    }
-
-    /**
-     * 
-     * Generalizes a property name of specialization type 2 (i.e., "{xxx?yyy}" form).
-     * 
-     * @param propnm property name to be stripped of specialization info
-     * @param matcher the matcher that matched the "{xxx?yyy}"
-     * @return the generalized property name
-     */
-    private static String generalizeType2(String propnm, Matcher mat) {
-        String prefix = propnm.substring(0, mat.start());
-        String suffix = propnm.substring(mat.end());
-        
-        return prefix + suffix;
-    }
-
-    /**
-     * Specializes a property name by applying the specialization. This is typically used
-     * to construct property names for junit testing.
-     * 
-     * @param propnm property name to be stripped of specialization info
-     * @param spec specialization to apply
-     * @return the specialized property name
-     */
-    public static String specialize(String propnm, String spec) {
-        Matcher mat = SPEC_PAT.matcher(propnm);
-
-        if (!mat.find()) {
-            // property name has no specialization info - leave it as is
-            return propnm;
-
-        } else if (mat.group(1) != null) {
-            // the "{$}" form requires specialization
-            return specializeType1(propnm, spec, mat);
-
-        } else {
-            // the "{xxx?yyy}" form requires specialization
-            return specializeType2(propnm, spec, mat);
-        }
-    }
-
-    /**
-     * Specializes a property name of specialization type 1 (i.e., "{$}" form).
-     * 
-     * @param propnm property name to be stripped of specialization info
-     * @param spec specialization to apply
-     * @param matcher the matcher that matched the "{$}"
-     * @return the specialized property name
-     */
-    private static String specializeType1(String propnm, String spec, Matcher mat) {
-        String prefix = propnm.substring(0, mat.start());
-        String suffix = propnm.substring(mat.end());
-
-        return prefix + spec + suffix;
-    }
-
-    /**
-     * Specializes a property name of specialization type 2 (i.e., "{xxx?yyy}" form).
-     * 
-     * @param propnm property name to be stripped of specialization info
-     * @param spec specialization to apply
-     * @param matcher the matcher that matched the "{xxx?yyy}"
-     * @return the specialized property name
-     */
-    private static String specializeType2(String propnm, String spec, Matcher matcher) {
-        String prefix = propnm.substring(0, matcher.start());
-        String suffix = propnm.substring(matcher.end());
-
-        String specPrefix = matcher.group(2);
-        String specSuffix = matcher.group(3);
-
-        return (prefix + specPrefix + spec + specSuffix + suffix);
-    }
-}
diff --git a/utils/src/main/java/org/onap/policy/common/utils/time/CurrentTime.java b/utils/src/main/java/org/onap/policy/common/utils/time/CurrentTime.java
new file mode 100644
index 0000000..cab469e
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/time/CurrentTime.java
@@ -0,0 +1,61 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Common Utils
+ * ================================================================================
+ * Copyright (C) 2018 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.time;
+
+import java.util.Date;
+
+/**
+ * Methods to access the current time. Classes can use objects of this type to get current
+ * time information, while allowing the objects to be overridden by junit tests.
+ */
+public class CurrentTime {
+
+    /**
+     * 
+     */
+    public CurrentTime() {
+        super();
+    }
+
+    /**
+     * @return the current time, in milliseconds
+     */
+    public long getMillis() {
+        return System.currentTimeMillis();
+    }
+
+    /**
+     * @return the current Date
+     */
+    public Date getDate() {
+        return new Date();
+    }
+
+    /**
+     * Sleeps for a period of time.
+     * 
+     * @param sleepMs amount of time to sleep, in milliseconds
+     * @throws InterruptedException
+     */
+    public void sleep(long sleepMs) throws InterruptedException {
+        Thread.sleep(sleepMs);
+    }
+}
diff --git a/utils/src/test/java/org/onap/policy/common/utils/properties/PropertyConfigurationTest.java b/utils/src/test/java/org/onap/policy/common/utils/properties/PropertyConfigurationTest.java
index cf823b5..121ae38 100644
--- a/utils/src/test/java/org/onap/policy/common/utils/properties/PropertyConfigurationTest.java
+++ b/utils/src/test/java/org/onap/policy/common/utils/properties/PropertyConfigurationTest.java
@@ -68,15 +68,9 @@
 
     @Test
     public void testPropertyConfiguration() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private String value;
-        };
-
         props.setProperty(THE_VALUE, STRING_VALUE);
 
-        Config cfg = new Config();
+        PlainStringConfig cfg = new PlainStringConfig();
         assertEquals(null, cfg.value);
 
         cfg.setAllFields(props);
@@ -85,18 +79,8 @@
 
     @Test
     public void testPropertyConfigurationProperties() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private String value;
-
-            public Config(Properties props) throws PropertyException {
-                super(props);
-            }
-        };
-
         props.setProperty(THE_VALUE, STRING_VALUE);
-        Config cfg = new Config(props);
+        PlainStringConfig cfg = new PlainStringConfig(props);
 
         assertEquals(STRING_VALUE, cfg.value);
     }
@@ -111,6 +95,11 @@
 
             @Property(name = "grandparent.value")
             protected boolean grandparentValue;
+
+            @SuppressWarnings("unused")
+            public void setGrandparentValue(boolean grandparentValue) {
+                this.grandparentValue = grandparentValue;
+            }
         };
 
         /*
@@ -120,12 +109,22 @@
 
             @Property(name = "parent.value")
             protected long parentValue;
+
+            @SuppressWarnings("unused")
+            public void setParentValue(long parentValue) {
+                this.parentValue = parentValue;
+            }
         };
 
         class Config extends ParentConfig {
 
             @Property(name = THE_VALUE)
             private String value;
+
+            @SuppressWarnings("unused")
+            public void setValue(String value) {
+                this.value = value;
+            }
         };
 
 
@@ -158,6 +157,11 @@
         class Config extends PropertyConfiguration {
 
             private String value;
+
+            @SuppressWarnings("unused")
+            public void setValue(String value) {
+                this.value = value;
+            }
         };
 
 
@@ -171,18 +175,8 @@
 
     @Test
     public void testSetValueFieldProperties_FieldSet() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private String value;
-
-            public Config(Properties props) throws PropertyException {
-                super(props);
-            }
-        };
-
         props.setProperty(THE_VALUE, STRING_VALUE);
-        Config cfg = new Config(props);
+        PlainStringConfig cfg = new PlainStringConfig(props);
 
         assertEquals(STRING_VALUE, cfg.value);
     }
@@ -196,6 +190,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(String value) {
+                this.value = value;
+            }
         };
 
         props.setProperty(THE_VALUE, STRING_VALUE);
@@ -215,14 +214,19 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(Exception value) {
+                this.value = value;
+            }
         };
 
         props.setProperty(THE_VALUE, STRING_VALUE);
         new Config(props);
     }
 
-    @Test(expected = PropertyMissingException.class)
-    public void testSetValueFieldPropertyProperties_NoProperty_NoDefault() throws PropertyException {
+    @Test(expected = PropertyAccessException.class)
+    public void testGetSetter_NoSetter() throws PropertyException {
         class Config extends PropertyConfiguration {
 
             @Property(name = THE_VALUE)
@@ -233,15 +237,18 @@
             }
         };
 
+        props.setProperty(THE_VALUE, STRING_VALUE);
         new Config(props);
     }
 
-    @Test(expected = PropertyInvalidException.class)
-    public void testSetValueFieldPropertyProperties_InvalidValue() throws PropertyException {
-        class Config extends PropertyConfiguration {
+    @Test(expected = PropertyMissingException.class)
+    public void testSetValueMethodFieldPropertiesProperty_NoProperty_NoDefault() throws PropertyException {
+        new PlainStringConfig(props);
+    }
 
-            @Property(name = THE_VALUE)
-            private int value;
+    @Test(expected = PropertyInvalidException.class)
+    public void testSetValueMethodFieldPropertiesProperty_InvalidValue() throws PropertyException {
+        class Config extends PlainPrimIntConfig {
 
             public Config(Properties props) throws PropertyException {
                 super(props);
@@ -260,6 +267,24 @@
         new Config(props);
     }
 
+    @Test(expected = PropertyAccessException.class)
+    public void testSetValueMethodFieldPropertiesProperty_MethodEx() throws PropertyException {
+        class Config extends PlainStringConfig {
+
+            public Config(Properties props) throws PropertyException {
+                super(props);
+            }
+
+            @Override
+            public void setValue(String value) {
+                throw new IllegalArgumentException("expected exception");
+            }
+        };
+
+        props.setProperty(THE_VALUE, STRING_VALUE);
+        new Config(props);
+    }
+
     @Test
     public void testGetValue() throws PropertyException {
         // this class contains all of the supported field types
@@ -295,6 +320,96 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public String getStringValue() {
+                return stringValue;
+            }
+
+            @SuppressWarnings("unused")
+            public void setStringValue(String stringValue) {
+                this.stringValue = stringValue;
+            }
+
+            @SuppressWarnings("unused")
+            public Boolean getBoolTrueValue() {
+                return boolTrueValue;
+            }
+
+            @SuppressWarnings("unused")
+            public void setBoolTrueValue(Boolean boolTrueValue) {
+                this.boolTrueValue = boolTrueValue;
+            }
+
+            @SuppressWarnings("unused")
+            public Boolean getBoolFalseValue() {
+                return boolFalseValue;
+            }
+
+            @SuppressWarnings("unused")
+            public void setBoolFalseValue(Boolean boolFalseValue) {
+                this.boolFalseValue = boolFalseValue;
+            }
+
+            @SuppressWarnings("unused")
+            public boolean isPrimBoolTrueValue() {
+                return primBoolTrueValue;
+            }
+
+            @SuppressWarnings("unused")
+            public void setPrimBoolTrueValue(boolean primBoolTrueValue) {
+                this.primBoolTrueValue = primBoolTrueValue;
+            }
+
+            @SuppressWarnings("unused")
+            public boolean isPrimBoolFalseValue() {
+                return primBoolFalseValue;
+            }
+
+            @SuppressWarnings("unused")
+            public void setPrimBoolFalseValue(boolean primBoolFalseValue) {
+                this.primBoolFalseValue = primBoolFalseValue;
+            }
+
+            @SuppressWarnings("unused")
+            public Integer getIntValue() {
+                return intValue;
+            }
+
+            @SuppressWarnings("unused")
+            public void setIntValue(Integer intValue) {
+                this.intValue = intValue;
+            }
+
+            @SuppressWarnings("unused")
+            public int getPrimIntValue() {
+                return primIntValue;
+            }
+
+            @SuppressWarnings("unused")
+            public void setPrimIntValue(int primIntValue) {
+                this.primIntValue = primIntValue;
+            }
+
+            @SuppressWarnings("unused")
+            public Long getLongValue() {
+                return longValue;
+            }
+
+            @SuppressWarnings("unused")
+            public void setLongValue(Long longValue) {
+                this.longValue = longValue;
+            }
+
+            @SuppressWarnings("unused")
+            public long getPrimLongValue() {
+                return primLongValue;
+            }
+
+            @SuppressWarnings("unused")
+            public void setPrimLongValue(long primLongValue) {
+                this.primLongValue = primLongValue;
+            }
         };
 
         props.setProperty("string", "a string");
@@ -331,6 +446,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(Exception value) {
+                this.value = value;
+            }
         };
 
         props.setProperty(THE_VALUE, STRING_VALUE);
@@ -354,6 +474,21 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setPublicString(String publicString) {
+                this.publicString = publicString;
+            }
+
+            @SuppressWarnings("unused")
+            public void setPrivateString(String privateString) {
+                this.privateString = privateString;
+            }
+
+            @SuppressWarnings("unused")
+            public void setProtectedString(String protectedString) {
+                this.protectedString = protectedString;
+            }
         };
 
         props.setProperty("public", "a public string");
@@ -370,7 +505,7 @@
     @Test(expected = PropertyAccessException.class)
     public void testCheckModifiable_Static() throws PropertyException {
         props.setProperty(THE_VALUE, STRING_VALUE);
-        new StaticConfig(props);
+        new StaticPropConfig(props);
     }
 
     @Test(expected = PropertyAccessException.class)
@@ -390,38 +525,24 @@
         new Config(props);
     }
 
+    @Test(expected = PropertyAccessException.class)
+    public void testCheckSetter_Static() throws PropertyException {
+        props.setProperty(THE_VALUE, STRING_VALUE);
+        new StaticMethodConfig(props);
+    }
+
     @Test
     public void testGetStringValue() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private String value;
-
-            public Config(Properties props) throws PropertyException {
-                super(props);
-            }
-        };
-
         props.setProperty(THE_VALUE, STRING_VALUE);
-        Config cfg = new Config(props);
+        PlainStringConfig cfg = new PlainStringConfig(props);
 
         assertEquals(STRING_VALUE, cfg.value);
     }
 
     @Test
     public void testGetBooleanValue_NoDefault() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private Boolean value;
-
-            public Config(Properties props) throws PropertyException {
-                super(props);
-            }
-        };
-
         props.setProperty(THE_VALUE, "true");
-        Config cfg = new Config(props);
+        PlainBooleanConfig cfg = new PlainBooleanConfig(props);
 
         assertEquals(true, cfg.value);
     }
@@ -436,6 +557,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(Boolean value) {
+                this.value = value;
+            }
         };
 
         props.setProperty(THE_VALUE, "true");
@@ -452,6 +578,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(Boolean value) {
+                this.value = value;
+            }
         };
 
         // property not defined
@@ -479,6 +610,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(Boolean value) {
+                this.value = value;
+            }
         };
 
         // property not defined
@@ -506,6 +642,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(Integer value) {
+                this.value = value;
+            }
         };
 
         props.setProperty(THE_VALUE, "200");
@@ -524,6 +665,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(Integer value) {
+                this.value = value;
+            }
         };
 
         props.setProperty(THE_VALUE, "200");
@@ -540,6 +686,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(Integer value) {
+                this.value = value;
+            }
         };
 
         // property not defined
@@ -562,6 +713,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(Long value) {
+                this.value = value;
+            }
         };
 
         props.setProperty(THE_VALUE, "20000");
@@ -580,6 +736,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(Long value) {
+                this.value = value;
+            }
         };
 
         props.setProperty(THE_VALUE, "20000");
@@ -596,6 +757,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(Long value) {
+                this.value = value;
+            }
         };
 
         // property not defined
@@ -610,18 +776,8 @@
 
     @Test
     public void testGetPropValue_Prop_NoDefault() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private String value;
-
-            public Config(Properties props) throws PropertyException {
-                super(props);
-            }
-        };
-
         props.setProperty(THE_VALUE, STRING_VALUE);
-        Config cfg = new Config(props);
+        PlainStringConfig cfg = new PlainStringConfig(props);
 
         assertEquals(STRING_VALUE, cfg.value);
     }
@@ -636,6 +792,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(String value) {
+                this.value = value;
+            }
         };
 
         props.setProperty(THE_VALUE, STRING_VALUE);
@@ -654,6 +815,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(String value) {
+                this.value = value;
+            }
         };
 
         props.setProperty(THE_VALUE, "");
@@ -672,6 +838,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(String value) {
+                this.value = value;
+            }
         };
 
         Config cfg = new Config(props);
@@ -689,6 +860,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(String value) {
+                this.value = value;
+            }
         };
 
         Config cfg = new Config(props);
@@ -706,6 +882,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(String value) {
+                this.value = value;
+            }
         };
 
         new Config(props);
@@ -721,6 +902,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(String value) {
+                this.value = value;
+            }
         };
 
         Config cfg = new Config(props);
@@ -730,10 +916,7 @@
 
     @Test
     public void testGetRawPropertyValue() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private String value;
+        class Config extends PlainStringConfig {
 
             public Config(Properties props) throws PropertyException {
                 super(props);
@@ -747,144 +930,64 @@
 
         Config cfg = new Config(props);
 
-        assertEquals(STRING_VALUE, cfg.value);
+        assertEquals(STRING_VALUE, cfg.getValue());
 
     }
 
     @Test
     public void testMakeBoolean_True() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private Boolean value;
-
-            public Config(Properties props) throws PropertyException {
-                super(props);
-            }
-        };
-
         props.setProperty(THE_VALUE, "true");
-        Config cfg = new Config(props);
+        PlainBooleanConfig cfg = new PlainBooleanConfig(props);
 
         assertEquals(true, cfg.value);
     }
 
     @Test
     public void testMakeBoolean_False() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private Boolean value;
-
-            public Config(Properties props) throws PropertyException {
-                super(props);
-            }
-        };
-
         props.setProperty(THE_VALUE, "false");
-        Config cfg = new Config(props);
+        PlainBooleanConfig cfg = new PlainBooleanConfig(props);
 
         assertEquals(false, cfg.value);
     }
 
     @Test(expected = PropertyInvalidException.class)
     public void testMakeBoolean_Invalid() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private Boolean value;
-
-            public Config(Properties props) throws PropertyException {
-                super(props);
-            }
-        };
-
         props.setProperty(THE_VALUE, INVALID_VALUE);
-        new Config(props);
+        new PlainBooleanConfig(props);
     }
 
     @Test
     public void testMakeInteger_Valid() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private int value;
-
-            public Config(Properties props) throws PropertyException {
-                super(props);
-            }
-        };
-
         props.setProperty(THE_VALUE, "300");
-        Config cfg = new Config(props);
+        PlainPrimIntConfig cfg = new PlainPrimIntConfig(props);
 
         assertEquals(300, cfg.value);
     }
 
     @Test(expected = PropertyInvalidException.class)
     public void testMakeInteger_Invalid() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private int value;
-
-            public Config(Properties props) throws PropertyException {
-                super(props);
-            }
-        };
-
         props.setProperty(THE_VALUE, INVALID_VALUE);
-        new Config(props);
+        new PlainPrimIntConfig(props);
     }
 
     @Test(expected = PropertyInvalidException.class)
     public void testMakeInteger_TooBig() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private int value;
-
-            public Config(Properties props) throws PropertyException {
-                super(props);
-            }
-        };
-
         props.setProperty(THE_VALUE, String.valueOf(Integer.MAX_VALUE + 10L));
-        new Config(props);
+        new PlainPrimIntConfig(props);
     }
 
     @Test
     public void testMakeLong_Valid() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private long value;
-
-            public Config(Properties props) throws PropertyException {
-                super(props);
-            }
-        };
-
         props.setProperty(THE_VALUE, "30000");
-        Config cfg = new Config(props);
+        PlainPrimLongConfig cfg = new PlainPrimLongConfig(props);
 
         assertEquals(30000L, cfg.value);
     }
 
     @Test(expected = PropertyInvalidException.class)
     public void testMakeLong_Invalid() throws PropertyException {
-        class Config extends PropertyConfiguration {
-
-            @Property(name = THE_VALUE)
-            private long value;
-
-            public Config(Properties props) throws PropertyException {
-                super(props);
-            }
-        };
-
         props.setProperty(THE_VALUE, INVALID_VALUE);
-        new Config(props);
+        new PlainPrimLongConfig(props);
     }
 
     @Test
@@ -897,6 +1000,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(long value) {
+                this.value = value;
+            }
         };
 
         Config cfg = new Config(props);
@@ -914,6 +1022,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(long value) {
+                this.value = value;
+            }
         };
 
         new Config(props);
@@ -929,6 +1042,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(long value) {
+                this.value = value;
+            }
         };
 
         new Config(props);
@@ -944,6 +1062,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(String value) {
+                this.value = value;
+            }
         };
 
         // missing property - should default to ""
@@ -971,6 +1094,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(long value) {
+                this.value = value;
+            }
         };
 
         new Config(props);
@@ -986,6 +1114,11 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(String value) {
+                this.value = value;
+            }
         };
 
         Config cfg = new Config(props);
@@ -1003,23 +1136,126 @@
             public Config(Properties props) throws PropertyException {
                 super(props);
             }
+
+            @SuppressWarnings("unused")
+            public void setValue(long value) {
+                this.value = value;
+            }
         };
 
         new Config(props);
     }
 
     /**
-     * A config whose annotated property is "static".
+     * Config with a String value having no qualifiers.
      */
-    public static class StaticConfig extends PropertyConfiguration {
+    public class PlainStringConfig extends PropertyConfiguration {
+
+        @Property(name = THE_VALUE)
+        private String value;
+        
+        public PlainStringConfig() {
+            
+        }
+        
+        public PlainStringConfig(Properties props) throws PropertyException {
+            super(props);
+        }
+
+        public String getValue() {
+            return value;
+        }
+
+        public void setValue(String value) {
+            this.value = value;
+        }
+    };
+
+    /**
+     * Config with a Boolean value having no qualifiers.
+     */
+    public class PlainBooleanConfig extends PropertyConfiguration {
+
+        @Property(name = THE_VALUE)
+        private Boolean value;
+        
+        public PlainBooleanConfig(Properties props) throws PropertyException {
+            super(props);
+        }
+
+        public void setValue(Boolean value) {
+            this.value = value;
+        }
+    };
+
+    /**
+     * Config with an int value having no qualifiers.
+     */
+    public class PlainPrimIntConfig extends PropertyConfiguration {
+
+        @Property(name = THE_VALUE)
+        private int value;
+
+        public PlainPrimIntConfig(Properties props) throws PropertyException {
+            super(props);
+        }
+
+        public void setValue(int value) {
+            this.value = value;
+        }
+    };
+
+    /**
+     * Config with a long value having no qualifiers.
+     */
+    public class PlainPrimLongConfig extends PropertyConfiguration {
+
+        @Property(name = THE_VALUE)
+        private long value;
+
+        public PlainPrimLongConfig(Properties props) throws PropertyException {
+            super(props);
+        }
+
+        public void setValue(long value) {
+            this.value = value;
+        }
+    };
+
+    /**
+     * A config whose field is "static".
+     */
+    public static class StaticPropConfig extends PropertyConfiguration {
 
         // "static" field cannot be set
         @Property(name = THE_VALUE)
         private static String value;
 
-        public StaticConfig(Properties props) throws PropertyException {
+        public StaticPropConfig(Properties props) throws PropertyException {
             super(props);
         }
+
+        public static void setValue(String value) {
+            StaticPropConfig.value = value;
+        }
+    };
+
+    /**
+     * A config whose method is "static".
+     */
+    public static class StaticMethodConfig extends PropertyConfiguration {
+
+        // "static" field cannot be set
+        @Property(name = THE_VALUE)
+        private String value;
+
+        public StaticMethodConfig(Properties props) throws PropertyException {
+            super(props);
+        }
+
+        public static void setValue(String value) {
+            
+        }
     };
 
     /**
diff --git a/utils/src/test/java/org/onap/policy/common/utils/properties/SpecPropertiesTest.java b/utils/src/test/java/org/onap/policy/common/utils/properties/SpecPropertiesTest.java
new file mode 100644
index 0000000..01f096d
--- /dev/null
+++ b/utils/src/test/java/org/onap/policy/common/utils/properties/SpecPropertiesTest.java
@@ -0,0 +1,224 @@
+/*
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2018 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.properties;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import java.util.Properties;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SpecPropertiesTest {
+
+    /**
+     * Property prefix of interest.
+     */
+    private static final String MY_PREFIX = "my.prefix";
+
+    /**
+     * Specialization, which follows the prefix.
+     */
+    private static final String MY_SPEC = "my.spec";
+
+    /**
+     * Generalized prefix (i.e., without the spec).
+     */
+    private static final String PREFIX_GEN = MY_PREFIX + ".";
+
+    /**
+     * Specialized prefix (i.e., with the spec).
+     */
+    private static final String PREFIX_SPEC = PREFIX_GEN + MY_SPEC + ".";
+
+    /**
+     * Suffix to add to property names to generate names of properties that are not
+     * populated.
+     */
+    private static final String SUFFIX = ".suffix";
+
+    /**
+     * Property name without a prefix.
+     */
+    private static final String PROP_NO_PREFIX = "other";
+
+    /**
+     * Generalized property name (i.e., without the spec).
+     */
+    private static final String PROP_GEN = PREFIX_GEN + "generalized";
+
+    // property names that include the spec
+    private static final String PROP_SPEC = PREFIX_SPEC + "specialized";
+    private static final String PROP_UNKNOWN = PREFIX_SPEC + "unknown";
+
+    // property values
+    private static final String VAL_NO_PREFIX = "no-prefix";
+    private static final String VAL_GEN = "gen";
+    private static final String VAL_SPEC = "spec";
+
+    private static final String VAL_DEFAULT = "default value";
+
+    private Properties supportingProps;
+    private SpecProperties props;
+
+    @Before
+    public void setUp() {
+        supportingProps = new Properties();
+
+        supportingProps.setProperty(PROP_NO_PREFIX, VAL_NO_PREFIX);
+        supportingProps.setProperty(PROP_GEN, VAL_GEN);
+        supportingProps.setProperty(PROP_SPEC, VAL_SPEC);
+
+        props = new SpecProperties(MY_PREFIX, MY_SPEC);
+
+        props.putAll(supportingProps);
+    }
+
+    @Test
+    public void testSpecPropertiesStringString() {
+
+        // no supporting properties
+        props = new SpecProperties(MY_PREFIX, MY_SPEC);
+
+        assertEquals(PREFIX_GEN, props.getPrefix());
+        assertEquals(PREFIX_SPEC, props.getSpecPrefix());
+
+        // everything is null
+        assertNull(props.getProperty(gen(PROP_NO_PREFIX)));
+        assertNull(props.getProperty(gen(PROP_GEN)));
+        assertNull(props.getProperty(gen(PROP_SPEC)));
+        assertNull(props.getProperty(gen(PROP_UNKNOWN)));
+    }
+
+    @Test
+    public void testSpecPropertiesStringStringProperties() {
+
+        // use supportingProps as default properties
+        props = new SpecProperties(MY_PREFIX, MY_SPEC, supportingProps);
+
+        assertEquals(PREFIX_GEN, props.getPrefix());
+        assertEquals(PREFIX_SPEC, props.getSpecPrefix());
+
+        assertEquals(VAL_NO_PREFIX, props.getProperty(gen(PROP_NO_PREFIX)));
+        assertEquals(VAL_GEN, props.getProperty(gen(PROP_GEN)));
+        assertEquals(VAL_SPEC, props.getProperty(gen(PROP_SPEC)));
+        assertNull(props.getProperty(gen(PROP_UNKNOWN)));
+    }
+
+    @Test
+    public void testSpecPropertiesStringStringProperties_EmptyPrefix() {
+        supportingProps = new Properties();
+
+        supportingProps.setProperty(PROP_NO_PREFIX, VAL_NO_PREFIX);
+        supportingProps.setProperty("a.value", VAL_GEN);
+        supportingProps.setProperty("b.value", VAL_GEN);
+        supportingProps.setProperty(MY_SPEC + ".b.value", VAL_SPEC);
+
+        // no supporting properties
+        props = new SpecProperties("", MY_SPEC, supportingProps);
+
+        assertEquals(VAL_NO_PREFIX, props.getProperty(gen(PROP_NO_PREFIX)));
+        assertEquals(VAL_GEN, props.getProperty(gen("a.value")));
+        assertEquals(VAL_SPEC, props.getProperty(MY_SPEC + ".b.value"));
+        assertNull(props.getProperty(gen(PROP_UNKNOWN)));
+    }
+
+    @Test
+    public void testWithTrailingDot() {
+        // neither has trailing dot
+        assertEquals(PREFIX_GEN, props.getPrefix());
+        assertEquals(PREFIX_SPEC, props.getSpecPrefix());
+
+        // both have trailing dot
+        props = new SpecProperties(PREFIX_GEN, MY_SPEC + ".");
+        assertEquals(PREFIX_GEN, props.getPrefix());
+        assertEquals(PREFIX_SPEC, props.getSpecPrefix());
+
+        // first is empty
+        props = new SpecProperties("", MY_SPEC);
+        assertEquals("", props.getPrefix());
+        assertEquals(MY_SPEC + ".", props.getSpecPrefix());
+
+        // second is empty
+        props = new SpecProperties(PREFIX_GEN, "");
+        assertEquals(PREFIX_GEN, props.getPrefix());
+        assertEquals(PREFIX_GEN, props.getSpecPrefix());
+    }
+
+    @Test
+    public void testGetPropertyString() {
+        // the key does contain the prefix
+        assertEquals(VAL_NO_PREFIX, props.getProperty(gen(PROP_NO_PREFIX)));
+        assertNull(props.getProperty(gen(PROP_NO_PREFIX + SUFFIX)));
+
+        // specialized value exists
+        assertEquals(VAL_GEN, props.getProperty(gen(PROP_GEN)));
+        assertNull(props.getProperty(gen(PROP_GEN + SUFFIX)));
+
+        // generalized value exists
+        assertEquals(VAL_SPEC, props.getProperty(gen(PROP_SPEC)));
+        assertNull(props.getProperty(gen(PROP_SPEC + SUFFIX)));
+
+        // not found
+        assertNull(props.getProperty(gen(PROP_UNKNOWN)));
+        assertNull(props.getProperty(gen(PROP_UNKNOWN + SUFFIX)));
+    }
+
+    @Test
+    public void testGetPropertyStringString() {
+        // the key does contain the prefix
+        assertEquals(VAL_NO_PREFIX, props.getProperty(gen(PROP_NO_PREFIX), VAL_DEFAULT));
+        assertEquals(VAL_DEFAULT, props.getProperty(gen(PROP_NO_PREFIX + SUFFIX), VAL_DEFAULT));
+
+        // specialized value exists
+        assertEquals(VAL_GEN, props.getProperty(gen(PROP_GEN), VAL_DEFAULT));
+        assertEquals(VAL_DEFAULT, props.getProperty(gen(PROP_GEN + SUFFIX), VAL_DEFAULT));
+
+        // generalized value exists
+        assertEquals(VAL_SPEC, props.getProperty(gen(PROP_SPEC), VAL_DEFAULT));
+        assertEquals(VAL_DEFAULT, props.getProperty(gen(PROP_SPEC + SUFFIX), VAL_DEFAULT));
+
+        // not found
+        assertEquals(VAL_DEFAULT, props.getProperty(gen(PROP_UNKNOWN), VAL_DEFAULT));
+        assertEquals(VAL_DEFAULT, props.getProperty(gen(PROP_UNKNOWN + SUFFIX), VAL_DEFAULT));
+
+        // can return null
+        assertNull(props.getProperty(gen(PROP_UNKNOWN), null));
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testHashCode() {
+        props.hashCode();
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testEquals() {
+        props.equals(props);
+    }
+
+    private String gen(String propnm) {
+        if (propnm.startsWith(PREFIX_SPEC)) {
+            return PREFIX_GEN + propnm.substring(PREFIX_SPEC.length());
+        }
+
+        return propnm;
+    }
+
+}
diff --git a/utils/src/test/java/org/onap/policy/common/utils/properties/SpecPropertyConfigurationTest.java b/utils/src/test/java/org/onap/policy/common/utils/properties/SpecPropertyConfigurationTest.java
deleted file mode 100644
index 39c8f01..0000000
--- a/utils/src/test/java/org/onap/policy/common/utils/properties/SpecPropertyConfigurationTest.java
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * ONAP Policy Engine - Common Modules
- * ================================================================================
- * Copyright (C) 2018 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.properties;
-
-import static org.junit.Assert.*;
-import java.util.Properties;
-import org.junit.Before;
-import org.junit.Test;
-import org.onap.policy.common.utils.properties.exception.PropertyException;
-import org.onap.policy.common.utils.properties.exception.PropertyMissingException;
-import static org.onap.policy.common.utils.properties.SpecPropertyConfiguration.*;
-
-/**
- * 
- */
-public class SpecPropertyConfigurationTest {
-    
-    /**
-     * The specializer.
-     */
-    private static final String SPEC = "my.name";
-
-    /**
-     * Properties used when invoking constructors.
-     */
-    private Properties props;
-
-    /**
-     * @throws java.lang.Exception
-     */
-    @Before
-    public void setUp() throws Exception {
-        props = new Properties();
-    }
-
-    /**
-     * Test method for {@link org.onap.policy.common.utils.properties.SpecPropertyConfiguration#getRawPropertyValue(java.util.Properties, java.lang.String)}.
-     * @throws PropertyException 
-     */
-    @Test
-    public void testGetRawPropertyValue() throws PropertyException {
-        class Config extends SpecPropertyConfiguration {
-
-            // no spec
-            @Property(name = "prefix.suffix")
-            private String noSpec;
-            
-            // no spec, other type
-            @Property(name = "no.spec.bool")
-            private boolean noSpecBool;
-            
-            // type 1, no prefix
-            @Property(name = "{$}.suffix")
-            private String type1NoPrefix;
-            
-            // type 1, no suffix
-            @Property(name = "prefix.{$}")
-            private String type1NoSuffix;
-            
-            // type 1, both prefix and suffix
-            @Property(name = "prefix.{$}.suffix")
-            private String type1Both;
-            
-            // type 1, other type
-            @Property(name = "an.{$}.int")
-            private int type1Int;
-            
-            // type 2, no prefix
-            @Property(name = "{abc.?.def}.suffix")
-            private String type2NoPrefix;
-            
-            // type 2, no suffix
-            @Property(name = "prefix.{abc.?.def}")
-            private String type2NoSuffix;
-            
-            // type 2, no spec prefix
-            @Property(name = "prefix.{?.def}.suffix")
-            private String type2NoSpecPrefix;
-            
-            // type 2, no spec suffix
-            @Property(name = "prefix{.abc.?}.suffix")
-            private String type2NoSpecSuffix;
-            
-            // type 2, all components
-            @Property(name = "prefix.{abc.?.def.}suffix")
-            private String type2Both;
-            
-            // type 2, other type
-            @Property(name = "a.{abc.?.def.}long")
-            private long type2Long;
-            
-            public Config(String specialization, Properties props) throws PropertyException {
-                super(specialization, props);
-            }
-        };
-
-        props.setProperty("prefix.suffix", "no.spec");
-        props.setProperty("no.spec.bool", "true");
-        props.setProperty("world.suffix", "type1.no.prefix");
-        props.setProperty("prefix.world", "type1.no.suffix");
-        props.setProperty("prefix.world.suffix", "type1.both");
-        props.setProperty("an.world.int", "200");
-        props.setProperty("abc.world.def.suffix", "type2.no.prefix");
-        props.setProperty("prefix.abc.world.def", "type2.no.suffix");
-        props.setProperty("prefix.world.def.suffix", "type2.no.spec.prefix");
-        props.setProperty("prefix.abc.world.suffix", "type2.no.spec.suffix");
-        props.setProperty("prefix.abc.world.def.suffix", "type2.both");
-        props.setProperty("a.abc.world.def.long", "3000");
-        
-        Config cfg = new Config("world", props);
-        
-        assertEquals("no.spec", cfg.noSpec);
-        assertEquals(true, cfg.noSpecBool);
-        assertEquals("type1.no.prefix", cfg.type1NoPrefix);
-        assertEquals("type1.no.suffix", cfg.type1NoSuffix);
-        assertEquals("type1.both", cfg.type1Both);
-        assertEquals(200, cfg.type1Int);
-        assertEquals("type2.no.prefix", cfg.type2NoPrefix);
-        assertEquals("type2.no.suffix", cfg.type2NoSuffix);
-        assertEquals("type2.no.spec.prefix", cfg.type2NoSpecPrefix);
-        assertEquals("type2.no.spec.suffix", cfg.type2NoSpecSuffix);
-        assertEquals("type2.both", cfg.type2Both);
-        assertEquals(3000L, cfg.type2Long);
-    }
-    @Test
-    public void testGetRawPropertyValue_Type2_Generalized() throws PropertyException {
-        class Config extends SpecPropertyConfiguration {
-            
-            // type 2, all components
-            @Property(name = "prefix.{abc.?.def.}suffix")
-            private String value;
-            
-            public Config(String specialization, Properties props) throws PropertyException {
-                super(specialization, props);
-            }
-        };
-
-        props.setProperty("prefix.suffix", "no.spec");
-        
-        Config cfg = new Config("world", props);
-        
-        assertEquals("no.spec", cfg.value);
-    }
-
-    /**
-     * Test method for {@link org.onap.policy.common.utils.properties.SpecPropertyConfiguration#getRawPropertyValue(java.util.Properties, java.lang.String)}.
-     * @throws PropertyException 
-     */
-    @Test(expected = PropertyMissingException.class)
-    public void testGetRawPropertyValue_NotFound() throws PropertyException {
-        class Config extends SpecPropertyConfiguration {
-            
-            @Property(name = "not.found")
-            private String notFound;
-            
-            public Config(String specialization, Properties props) throws PropertyException {
-                super(specialization, props);
-            }
-        };
-        
-        new Config("not found", props);
-    }
-
-    /**
-     * Test method for {@link org.onap.policy.common.utils.properties.SpecPropertyConfiguration#SpecPropertyConfiguration(java.lang.String)}.
-     * @throws PropertyException 
-     */
-    @Test
-    public void testSpecPropertyConfigurationString() throws PropertyException {
-        final String propnm = "string.{$}.prop";
-        final String propval = "hello";
-        
-        class Config extends SpecPropertyConfiguration {
-            
-            @Property(name = propnm)
-            private String value;
-
-            public Config(String specialization) {
-                super(specialization);
-            }
-        };
-
-        props.setProperty(specialize(propnm, SPEC), propval);
-        
-        Config cfg = new Config(SPEC);
-        assertEquals(null, cfg.value);
-        
-        cfg.setAllFields(props);
-        assertEquals(propval, cfg.value);
-    }
-
-    /**
-     * Test method for {@link org.onap.policy.common.utils.properties.SpecPropertyConfiguration#SpecPropertyConfiguration(java.lang.String, java.util.Properties)}.
-     * @throws PropertyException 
-     */
-    @Test
-    public void testSpecPropertyConfigurationStringProperties() throws PropertyException {
-        final String propnm = "int.{$}.prop";
-        final int propval = 10;
-        
-        class Config extends SpecPropertyConfiguration {
-
-            @Property(name = propnm)
-            private int value;
-            
-            public Config(String specialization, Properties props) throws PropertyException {
-                super(specialization, props);
-            }
-        };
-
-        props.setProperty(specialize(propnm, SPEC), String.valueOf(propval));
-        
-        Config cfg = new Config(SPEC, props);
-        
-        assertEquals(propval, cfg.value);
-    }
-
-    /**
-     * Test method for {@link org.onap.policy.common.utils.properties.SpecPropertyConfiguration#generalize(java.lang.String)}.
-     */
-    @Test
-    public void testGeneralize_NoSpec() {
-        final String xyzPdq = "xyz.pdq";
-        
-        // no spec
-        assertEquals(xyzPdq, generalize(xyzPdq));
-        
-        // spec type 1 throws an exception - we'll test it separately
-
-        // spec type 2
-        assertEquals(xyzPdq, generalize("xyz.{xxx.?.yyy.}pdq"));
-    }
-
-    /**
-     * Test method for {@link org.onap.policy.common.utils.properties.SpecPropertyConfiguration#generalize(java.lang.String)}.
-     */
-    @Test(expected = IllegalArgumentException.class)
-    public void testGeneralize_Spec1() { 
-        generalize("abc.{$}.def");
-    }
-
-    /**
-     * Test method for {@link org.onap.policy.common.utils.properties.SpecPropertyConfiguration#generalizeType2(java.lang.String, java.util.regex.Matcher)}.
-     */
-    @Test
-    public void testGeneralizeType2() {
-        assertEquals("abc.def", generalize("abc.{xyz?pdq}def"));
-
-        assertEquals("", generalize("{xyz?pdq}"));
-    }
-
-    /**
-     * Test method for {@link org.onap.policy.common.utils.properties.SpecPropertyConfiguration#specialize(java.lang.String, java.lang.String)}.
-     */
-    @Test
-    public void testSpecialize() {
-        final String spec = "get.spec";
-        final String abcDef = "abc.def";
-        
-        // no spec
-        assertEquals(abcDef, specialize(abcDef, spec));
-
-        // spec type 1
-        assertEquals("abc.get.spec.def", specialize("abc.{$}.def", spec));
-
-        // spec type 2
-        assertEquals("abc.xxx.get.spec.yyy.def", specialize("abc.{xxx.?.yyy.}def", spec));
-    }
-
-    /**
-     * Test method for {@link org.onap.policy.common.utils.properties.SpecPropertyConfiguration#specializeType1(java.lang.String, java.lang.String, java.util.regex.Matcher)}.
-     */
-    @Test
-    public void testSpecializeType1() {
-        final String spec = "spec1";
-        
-        // no prefix 
-        assertEquals("spec1.def", specialize("{$}.def", spec));
-        
-        // no suffix 
-        assertEquals("abc.spec1", specialize("abc.{$}", spec));
-        
-        // with both prefix and suffix 
-        assertEquals("abc.spec1.def", specialize("abc.{$}.def", spec));
-    }
-
-    /**
-     * Test method for {@link org.onap.policy.common.utils.properties.SpecPropertyConfiguration#specializeType2(java.lang.String, java.lang.String, java.util.regex.Matcher)}.
-     */
-    @Test
-    public void testSpecializeType2() {
-        final String spec = "spec2";
-        
-        // no prefix 
-        assertEquals("xxx.spec2.yyy.def", specialize("{xxx.?.yyy.}def", spec));
-        
-        // no suffix 
-        assertEquals("abc.xxx.spec2.yyy", specialize("abc{.xxx.?.yyy}", spec));
-        
-        // no spec prefix
-        assertEquals("abc.spec2.yyy.def", specialize("abc.{?.yyy.}def", spec));
-        
-        // no spec suffix
-        assertEquals("abc.xxx.spec2.def", specialize("abc.{xxx.?}.def", spec));
-        
-        // no components
-        assertEquals(spec, specialize("{?}", spec));
-        
-        // all components 
-        assertEquals("abc.xxx.spec2.yyy.def", specialize("abc.{xxx.?.yyy.}def", spec));
-    }
-
-}
diff --git a/utils/src/test/java/org/onap/policy/common/utils/time/CurrentTimeTest.java b/utils/src/test/java/org/onap/policy/common/utils/time/CurrentTimeTest.java
new file mode 100644
index 0000000..694a3d2
--- /dev/null
+++ b/utils/src/test/java/org/onap/policy/common/utils/time/CurrentTimeTest.java
@@ -0,0 +1,59 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Common Utils
+ * ================================================================================
+ * Copyright (C) 2018 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.time;
+
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+public class CurrentTimeTest {
+
+    @Test
+    public void testGetMillis() {
+        long tcur = System.currentTimeMillis();
+        long tval = new CurrentTime().getMillis();
+        long tval2 = new CurrentTime().getMillis();
+        long tend = System.currentTimeMillis();
+
+        assertTrue(tval >= tcur && tval <= tend);
+        assertTrue(tval2 >= tcur && tval2 <= tend);
+    }
+
+    @Test
+    public void testGetDate() {
+        long tcur = System.currentTimeMillis();
+        long tval = new CurrentTime().getDate().getTime();
+        long tval2 = new CurrentTime().getDate().getTime();
+        long tend = System.currentTimeMillis();
+
+        assertTrue(tval >= tcur && tval <= tend);
+        assertTrue(tval2 >= tcur && tval2 <= tend);
+    }
+
+    @Test
+    public void testSleep() throws Exception {
+        long tcur = System.currentTimeMillis();
+        new CurrentTime().sleep(10);
+        long tend = System.currentTimeMillis();
+
+        assertTrue(tend >= tcur + 10 - 1);
+    }
+
+}