summaryrefslogtreecommitdiffstats
path: root/policy-utils/src
diff options
context:
space:
mode:
Diffstat (limited to 'policy-utils/src')
-rw-r--r--policy-utils/src/main/java/org/openecomp/policy/drools/utils/OrderedService.java35
-rw-r--r--policy-utils/src/main/java/org/openecomp/policy/drools/utils/OrderedServiceImpl.java117
-rw-r--r--policy-utils/src/main/java/org/openecomp/policy/drools/utils/Pair.java51
-rw-r--r--policy-utils/src/main/java/org/openecomp/policy/drools/utils/PropertyUtil.java403
-rw-r--r--policy-utils/src/main/java/org/openecomp/policy/drools/utils/ReflectionUtil.java92
-rw-r--r--policy-utils/src/main/java/org/openecomp/policy/drools/utils/Triple.java45
-rw-r--r--policy-utils/src/test/java/org/openecomp/policy/drools/utils/PropertyUtilTest.java204
-rw-r--r--policy-utils/src/test/resources/log4j.properties26
8 files changed, 973 insertions, 0 deletions
diff --git a/policy-utils/src/main/java/org/openecomp/policy/drools/utils/OrderedService.java b/policy-utils/src/main/java/org/openecomp/policy/drools/utils/OrderedService.java
new file mode 100644
index 00000000..b50e6e85
--- /dev/null
+++ b/policy-utils/src/main/java/org/openecomp/policy/drools/utils/OrderedService.java
@@ -0,0 +1,35 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * policy-utils
+ * ================================================================================
+ * Copyright (C) 2017 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.openecomp.policy.drools.utils;
+
+/**
+ * This is a base interface that is used to control the order of a list
+ * of services (features) discovered via 'ServiceLoader'. See
+ * 'OrderedServiceImpl' for more details.
+ */
+public interface OrderedService
+{
+ /**
+ * @return an integer sequence number, which determines the order of a list
+ * of objects implementing this interface
+ */
+ public int getSequenceNumber();
+}
diff --git a/policy-utils/src/main/java/org/openecomp/policy/drools/utils/OrderedServiceImpl.java b/policy-utils/src/main/java/org/openecomp/policy/drools/utils/OrderedServiceImpl.java
new file mode 100644
index 00000000..2747e85e
--- /dev/null
+++ b/policy-utils/src/main/java/org/openecomp/policy/drools/utils/OrderedServiceImpl.java
@@ -0,0 +1,117 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * policy-utils
+ * ================================================================================
+ * Copyright (C) 2017 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.openecomp.policy.drools.utils;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ServiceLoader;
+
+/**
+ * This class is a template for building a sorted list of service instances,
+ * which are discovered and created using 'ServiceLoader'.
+ */
+public class OrderedServiceImpl<T extends OrderedService>
+{
+ // sorted list of instances implementing the service
+ private List<T> implementers = null;
+
+ // 'ServiceLoader' that is used to discover and create the services
+ private ServiceLoader<T> serviceLoader = null; //ServiceLoader.load(T.class);
+
+ /**
+ * Constructor - create the 'ServiceLoader' instance
+ *
+ * @param clazz the class object associated with 'T' (I supposed it could
+ * be a subclass, but I'm not sure this is useful)
+ */
+ public OrderedServiceImpl(Class clazz)
+ {
+ // This constructor wouldn't be needed if 'T.class' was legal
+ serviceLoader = ServiceLoader.load(clazz);
+ }
+
+ /**
+ * @return the sorted list of services implementing interface 'T' discovered
+ * by 'ServiceLoader'.
+ */
+ public synchronized List<T> getList()
+ {
+ if (implementers == null)
+ {
+ rebuildList();
+ }
+ return(implementers);
+ }
+
+ /**
+ * This method is called by 'getList', but could also be called directly if
+ * we were running with a 'ClassLoader' that supported the dynamic addition
+ * of JAR files. In this case, it could be invoked in order to discover any
+ * new services implementing interface 'T'. This is probably a relatively
+ * expensive operation in terms of CPU and elapsed time, so it is best if it
+ * isn't invoked too frequently.
+ *
+ * @return the sorted list of services implementing interface 'T' discovered
+ * by 'ServiceLoader'.
+ */
+ public synchronized List<T> rebuildList()
+ {
+ // build a list of all of the current implementors
+ List<T> tmp = new LinkedList<T>();
+ for (T service : serviceLoader)
+ {
+ tmp.add(service);
+ }
+
+ // Sort the list according to sequence number, and then alphabetically
+ // according to full class name.
+ Collections.sort(tmp, new Comparator<T>()
+ {
+ public int compare(T o1, T o2)
+ {
+ int s1 = o1.getSequenceNumber();
+ int s2 = o2.getSequenceNumber();
+ int rval;
+ if (s1 < s2)
+ {
+ rval = -1;
+ }
+ else if (s1 > s2)
+ {
+ rval = 1;
+ }
+ else
+ {
+ rval = o1.getClass().getName().compareTo
+ (o2.getClass().getName());
+ }
+ return(rval);
+ }
+ });
+
+ // create an unmodifiable version of this list
+ implementers = Collections.unmodifiableList(tmp);
+ System.out.println("***** OrderedServiceImpl implementers:\n" + implementers);
+ return(implementers);
+ }
+}
diff --git a/policy-utils/src/main/java/org/openecomp/policy/drools/utils/Pair.java b/policy-utils/src/main/java/org/openecomp/policy/drools/utils/Pair.java
new file mode 100644
index 00000000..a5e34695
--- /dev/null
+++ b/policy-utils/src/main/java/org/openecomp/policy/drools/utils/Pair.java
@@ -0,0 +1,51 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * policy-utils
+ * ================================================================================
+ * Copyright (C) 2017 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.openecomp.policy.drools.utils;
+
+public class Pair<F,S> {
+
+ protected F first;
+ protected S second;
+
+ public Pair(F first, S second){
+ this.first = first;
+ this.second = second;
+ }
+
+ public F first() {return this.first;}
+
+ public S second() {return this.second;}
+
+ public F getFirst() {return this.first;}
+
+ public S getSecond() {return this.second;}
+
+ public void first(F first) {this.first = first;}
+
+ public void second(S second) {this.second = second;}
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Pair [first=").append(first).append(", second=").append(second).append("]");
+ return builder.toString();
+ }
+}
diff --git a/policy-utils/src/main/java/org/openecomp/policy/drools/utils/PropertyUtil.java b/policy-utils/src/main/java/org/openecomp/policy/drools/utils/PropertyUtil.java
new file mode 100644
index 00000000..34ddcc1c
--- /dev/null
+++ b/policy-utils/src/main/java/org/openecomp/policy/drools/utils/PropertyUtil.java
@@ -0,0 +1,403 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * policy-utils
+ * ================================================================================
+ * Copyright (C) 2017 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.openecomp.policy.drools.utils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Properties;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * This class provides utilities to read properties from a properties
+ * file, and optionally get notifications of future changes
+ */
+public class PropertyUtil
+{
+ /**
+ * Read in a properties file
+ * @param file the properties file
+ * @return a Properties object, containing the associated properties
+ * @throws IOException - subclass 'FileNotFoundException' if the file
+ * does not exist or can't be opened, and 'IOException' if there is
+ * a problem loading the properties file.
+ */
+ static public Properties getProperties(File file) throws IOException
+ {
+ // create an InputStream (may throw a FileNotFoundException)
+ FileInputStream fis = new FileInputStream(file);
+ try
+ {
+ // create the properties instance
+ Properties rval = new Properties();
+
+ // load properties (may throw an IOException)
+ rval.load(fis);
+ return(rval);
+ }
+ finally
+ {
+ // close input stream
+ fis.close();
+ }
+ }
+
+ /**
+ * Read in a properties file
+ * @param fileName the properties file
+ * @return a Properties object, containing the associated properties
+ * @throws IOException - subclass 'FileNotFoundException' if the file
+ * does not exist or can't be opened, and 'IOException' if there is
+ * a problem loading the properties file.
+ */
+ static public Properties getProperties(String fileName) throws IOException
+ {
+ return(getProperties(new File(fileName)));
+ }
+
+ /* ============================================================ */
+
+ // timer thread used for polling for property file changes
+ private static Timer timer = null;
+
+ /**
+ * This is the callback interface, used for sending notifications of
+ * changes in the properties file.
+ */
+ public interface Listener
+ {
+ /**
+ * Notification of a properties file change
+ * @param properties the new properties
+ * @param the set of property names that have changed, including
+ * additions and removals
+ */
+ void propertiesChanged(Properties properties, Set<String> changedKeys);
+ }
+
+ // this table maps canonical file into a 'ListenerRegistration' instance
+ static private HashMap<File, ListenerRegistration> registrations =
+ new HashMap<File, ListenerRegistration>();
+
+ /**
+ * This is an internal class - one instance of this exists for each
+ * property file that is being monitored. Note that multiple listeners
+ * can be registered for the same file.
+ */
+ private static class ListenerRegistration
+ {
+ // the canonical path of the file being monitored
+ File file;
+
+ // the most recent value of 'file.lastModified()'
+ long lastModified;
+
+ // the most recent set of properties
+ Properties properties;
+
+ // the set of listeners monitoring this file
+ LinkedList<Listener> listeners;
+
+ // the 'TimerTask' instance, used for periodic polling
+ TimerTask timerTask;
+
+ /**
+ * Constructor - create a 'ListenerRegistration' instance for this
+ * file, but with no listeners
+ */
+ ListenerRegistration(File file) throws IOException
+ {
+ this.file = file;
+
+ // The initial value of 'lastModified' is set to 0 to ensure that we
+ // correctly handle the case where the file is modified within the
+ // same second that polling begins.
+ lastModified = 0;
+
+ // fetch current properties
+ properties = getProperties(file);
+
+ // no listeners yet
+ listeners = new LinkedList<Listener>();
+
+ // add to static table, so this instance can be shared
+ registrations.put(file, this);
+
+ if (timer == null)
+ {
+ // still need to create a timer thread
+ synchronized(PropertyUtil.class)
+ {
+ // an additional check is added inside the 'synchronized' block,
+ // just in case someone beat us to it
+ if (timer == null)
+ {
+ timer = new Timer("PropertyUtil-Timer", true);
+ }
+ }
+ }
+
+ // create and schedule the timer task, so this is periodically polled
+ timerTask = new TimerTask()
+ {
+ public void run()
+ {
+ try
+ {
+ poll();
+ }
+ catch (Exception e)
+ {
+ System.err.println(e);
+ }
+ }
+ };
+ timer.schedule(timerTask, 10000L, 10000L);
+ }
+
+ /**
+ * Add a listener to the notification list
+ * @param listener this is the listener to add to the list
+ * @return the properties at the moment the listener was added to the list
+ */
+ synchronized Properties addListener(Listener listener)
+ {
+ listeners.add(listener);
+ return((Properties)properties.clone());
+ }
+
+ /**
+ * Remove a listener from the notification list
+ * @param listener this is the listener to remove
+ */
+ synchronized void removeListener(Listener listener)
+ {
+ listeners.remove(listener);
+
+ // See if we need to remove this 'ListenerRegistration' instance
+ // from the table. The 'synchronized' block is needed in case
+ // another listener is being added at about the same time that this
+ // one is being removed.
+ synchronized(registrations)
+ {
+ if (listeners.size() == 0)
+ {
+ timerTask.cancel();
+ registrations.remove(file);
+ }
+ }
+ }
+
+ /**
+ * This method is periodically called to check for property list updates
+ * @throws IOException if there is an error in reading the properties file
+ */
+ synchronized void poll() throws IOException
+ {
+ long timestamp = file.lastModified();
+ if (timestamp != lastModified)
+ {
+ // update the record, and send out the notifications
+ lastModified = timestamp;
+
+ // Save old set, and initial set of changed properties.
+ Properties oldProperties = properties;
+ HashSet<String> changedProperties =
+ new HashSet<String>(oldProperties.stringPropertyNames());
+
+ // Fetch the list of listeners that we will potentially notify,
+ // and the new properties. Note that this is in a 'synchronized'
+ // block to ensure that all listeners receiving notifications
+ // actually have a newer list of properties than the one
+ // returned on the initial 'getProperties' call.
+ properties = getProperties(file);
+
+ Set<String> newPropertyNames = properties.stringPropertyNames();
+ changedProperties.addAll(newPropertyNames);
+
+ // At this point, 'changedProperties' is the union of all properties
+ // in both the old and new properties files. Iterate through all
+ // of the entries in the new properties file - if the entry
+ // matches the one in the old file, remove it from
+ // 'changedProperties'.
+ for (String name : newPropertyNames)
+ {
+ if (properties.getProperty(name).equals
+ (oldProperties.getProperty(name)))
+ {
+ // Apparently, any property that exists must be of type
+ // 'String', and can't be null. For this reason, we don't
+ // need to worry about the case where
+ // 'properties.getProperty(name)' returns 'null'. Note that
+ // 'oldProperties.getProperty(name)' may be 'null' if the
+ // old property does not exist.
+ changedProperties.remove(name);
+ }
+ }
+
+ // 'changedProperties' should be correct at this point
+ if (changedProperties.size() != 0)
+ {
+ // there were changes - notify everyone in 'listeners'
+ for (final Listener notify : listeners)
+ {
+ // Copy 'properties' and 'changedProperties', so it doesn't
+ // cause problems if the recipient makes changes.
+ final Properties tmpProperties =
+ (Properties)(properties.clone());
+ final HashSet<String> tmpChangedProperties =
+ new HashSet<String>(changedProperties);
+
+ // Do the notification in a separate thread, so blocking
+ // won't cause any problems.
+ new Thread()
+ {
+ public void run()
+ {
+ notify.propertiesChanged
+ (tmpProperties, tmpChangedProperties);
+ }
+ }.start();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Read in a properties file, and register for update notifications.
+ * NOTE: it is possible that the first callback will occur while this
+ * method is still in progress. To avoid this problem, use 'synchronized'
+ * blocks around this invocation and in the callback -- that will ensure
+ * that the processing of the initial properties complete before any
+ * updates are processed.
+ *
+ * @param file the properties file
+ * @param notify if not null, this is a callback interface that is used for
+ * notifications of changes
+ * @return a Properties object, containing the associated properties
+ * @throws IOException - subclass 'FileNotFoundException' if the file
+ * does not exist or can't be opened, and 'IOException' if there is
+ * a problem loading the properties file.
+ */
+ static public Properties getProperties(File file, Listener listener)
+ throws IOException
+ {
+ if (listener == null)
+ {
+ // no listener specified -- just fetch the properties
+ return(getProperties(file));
+ }
+
+ // Convert the file to a canonical form in order to avoid the situation
+ // where different names refer to the same file.
+ file = file.getCanonicalFile();
+
+ // See if there is an existing registration. The 'synchronized' block
+ // is needed to handle the case where a new listener is added at about
+ // the same time that another one is being removed.
+ synchronized(registrations)
+ {
+ ListenerRegistration reg = registrations.get(file);
+ if (reg == null)
+ {
+ // a new registration is needed
+ reg = new ListenerRegistration(file);
+ }
+ return(reg.addListener(listener));
+ }
+ }
+
+ /**
+ * Read in a properties file, and register for update notifications.
+ * NOTE: it is possible that the first callback will occur while this
+ * method is still in progress. To avoid this problem, use 'synchronized'
+ * blocks around this invocation and in the callback -- that will ensure
+ * that the processing of the initial properties complete before any
+ * updates are processed.
+ *
+ * @param fileName the properties file
+ * @param notify if not null, this is a callback interface that is used for
+ * notifications of changes
+ * @return a Properties object, containing the associated properties
+ * @throws IOException - subclass 'FileNotFoundException' if the file
+ * does not exist or can't be opened, and 'IOException' if there is
+ * a problem loading the properties file.
+ */
+ static public Properties getProperties(String fileName, Listener listener)
+ throws IOException
+ {
+ return(getProperties(new File(fileName), listener));
+ }
+
+ /**
+ * Stop listenening for updates
+ * @param file the properties file
+ * @param notify if not null, this is a callback interface that was used for
+ * notifications of changes
+ */
+ static public void stopListening(File file, Listener listener)
+ {
+ if (listener != null)
+ {
+ ListenerRegistration reg = registrations.get(file);
+ if (reg != null)
+ {
+ reg.removeListener(listener);
+ }
+ }
+ }
+
+ /**
+ * Stop listenening for updates
+ * @param fileName the properties file
+ * @param notify if not null, this is a callback interface that was used for
+ * notifications of changes
+ */
+ static public void stopListening(String fileName, Listener listener)
+ {
+ stopListening(new File(fileName), listener);
+ }
+
+ /* ============================================================ */
+
+ // TEMPORARY - used to test callback interface
+ static public class Test implements Listener
+ {
+ String name;
+
+ public Test(String name)
+ {
+ this.name = name;
+ }
+
+ public void propertiesChanged(Properties properties, Set<String> changedKeys)
+ {
+ System.out.println("Test(" + name + ")\nproperties = " + properties
+ + "\nchangedKeys = " + changedKeys);
+ }
+ }
+}
diff --git a/policy-utils/src/main/java/org/openecomp/policy/drools/utils/ReflectionUtil.java b/policy-utils/src/main/java/org/openecomp/policy/drools/utils/ReflectionUtil.java
new file mode 100644
index 00000000..0b86c2aa
--- /dev/null
+++ b/policy-utils/src/main/java/org/openecomp/policy/drools/utils/ReflectionUtil.java
@@ -0,0 +1,92 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * policy-utils
+ * ================================================================================
+ * Copyright (C) 2017 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.openecomp.policy.drools.utils;
+
+import org.openecomp.policy.common.logging.eelf.PolicyLogger;
+
+/**
+ * Reflection utilities
+ *
+ */
+public class ReflectionUtil {
+
+ /**
+ * returns (if exists) a class fetched from a given classloader
+ *
+ * @param classLoader the class loader
+ * @param classname the class name
+ * @return the PolicyEvent class
+ * @throws IllegalArgumentException if an invalid parameter has been passed in
+ */
+ public static Class<?> fetchClass(ClassLoader classLoader,
+ String classname)
+ throws IllegalArgumentException {
+
+ PolicyLogger.info("FETCH-CLASS: " + classname + " FROM " + classLoader);
+
+ if (classLoader == null)
+ throw new IllegalArgumentException("A class loader must be provided");
+
+ if (classname == null)
+ throw new IllegalArgumentException("A class name to be fetched in class loader " +
+ classLoader + " must be provided");
+
+ try {
+ Class<?> aClass = Class.forName(classname,
+ true,
+ classLoader);
+ return aClass;
+ } catch (Exception e) {
+ e.printStackTrace();
+ PolicyLogger.error("FETCH-CLASS: " + classname + " IN " + classLoader + " does NOT exist");
+ }
+
+ return null;
+ }
+
+ /**
+ *
+ * @param classLoader target class loader
+ * @param classname class name to fetch
+ * @return true if exists
+ * @throws IllegalArgumentException if an invalid parameter has been passed in
+ */
+ public static boolean isClass(ClassLoader classLoader, String classname)
+ throws IllegalArgumentException {
+ return fetchClass(classLoader, classname) != null;
+ }
+
+ /**
+ * is a subclass?
+ * @param parent superclass
+ * @param presumedSubclass subclass
+ * @return
+ */
+ public static boolean isSubclass(Class<?> parent, Class<?> presumedSubclass) {
+ PolicyLogger.debug("IS-SUBCLASS: superclass: " + parent.getCanonicalName() +
+ " subclass: " + presumedSubclass.getCanonicalName());
+ return (parent.isAssignableFrom(presumedSubclass));
+ }
+
+}
diff --git a/policy-utils/src/main/java/org/openecomp/policy/drools/utils/Triple.java b/policy-utils/src/main/java/org/openecomp/policy/drools/utils/Triple.java
new file mode 100644
index 00000000..214d949e
--- /dev/null
+++ b/policy-utils/src/main/java/org/openecomp/policy/drools/utils/Triple.java
@@ -0,0 +1,45 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * policy-utils
+ * ================================================================================
+ * Copyright (C) 2017 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.openecomp.policy.drools.utils;
+
+public class Triple<F,S,T> {
+
+ private F first;
+ private S second;
+ private T third;
+
+ public Triple(F first, S second, T third){
+ this.first = first;
+ this.second = second;
+ this.third = third;
+ }
+ public F first(){ return this.first; }
+
+ public S second(){ return this.second; }
+
+ public T third(){ return this.third; }
+
+ public void first(F first){ this.first = first; }
+
+ public void second(S second){ this.second = second; }
+
+ public void third(T third){ this.third = third; }
+}
diff --git a/policy-utils/src/test/java/org/openecomp/policy/drools/utils/PropertyUtilTest.java b/policy-utils/src/test/java/org/openecomp/policy/drools/utils/PropertyUtilTest.java
new file mode 100644
index 00000000..55091718
--- /dev/null
+++ b/policy-utils/src/test/java/org/openecomp/policy/drools/utils/PropertyUtilTest.java
@@ -0,0 +1,204 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * policy-utils
+ * ================================================================================
+ * Copyright (C) 2017 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.openecomp.policy.drools.utils;
+
+import static org.junit.Assert.*;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import org.apache.log4j.Logger;
+
+import org.openecomp.policy.common.logging.eelf.PolicyLogger;
+
+public class PropertyUtilTest
+{
+ // private static final Logger PolicyLogger =
+// Logger.getLogger(PropertyUtilTest.class.getName());
+
+ private static File directory = null;
+
+ /**
+ * Test Setup -- Create a directory for temporary files
+ */
+ @BeforeClass
+ static public void setup()
+ {
+ PolicyLogger.info("setup: creating a temporary directory");
+
+ // create a directory for temporary files
+ directory = new File(UUID.randomUUID().toString());
+ directory.mkdir();
+ }
+
+ /**
+ * Test Cleanup -- Remove temporary files
+ */
+ @AfterClass
+ static public void teardown()
+ {
+ PolicyLogger.info("teardown: remove the temporary directory");
+
+ // the assumption is that we only have one level of temporary files
+ for (File file : directory.listFiles())
+ {
+ file.delete();
+ }
+ directory.delete();
+ }
+
+ /**
+ * Utility method to write a properties file
+ *
+ * @param name the file name, relative to the temporary directory
+ * @param the properties to store in the file
+ * @return a File instance associated with the newly-created file
+ * @throws IOException if the file can't be created for some reason
+ */
+ File createFile(String name, Properties p) throws IOException
+ {
+ File file = new File(directory, name);
+ FileOutputStream fos = new FileOutputStream(file);
+ try
+ {
+ p.store(fos, "Property file '" + name + "'");
+ }
+ finally
+ {
+ fos.close();
+ }
+ return(file);
+ }
+
+ /**
+ * Create a 'PropertyUtil.Listener' subclass, which receives property
+ * file updates. It stores the latest values in an array, and notifies
+ * any thread waiting on this array.
+ *
+ * @param returns this is an array of length 2 -- the first entry will
+ * contain the 'properties' value, and the second will contain
+ * 'changedKeys'. It is also used to signal any waiting thread
+ * using 'returns.notifyAll()'.
+ */
+ PropertyUtil.Listener createListenerThread(final Object[] returns)
+ {
+ return(new PropertyUtil.Listener()
+ {
+ public void propertiesChanged
+ (Properties properties, Set<String> changedKeys)
+ {
+ // When a notification is received, store the values in the
+ // 'returns' array, and signal using the same array.
+ PolicyLogger.info("Listener invoked: properties=" + properties
+ + ", changedKeys=" + changedKeys);
+ returns[0] = properties;
+ returns[1] = changedKeys;
+ synchronized(returns)
+ {
+ returns.notifyAll();
+ }
+ }
+ });
+ }
+
+ /**
+ * Test the basic properties file interface.
+ */
+ @Test
+ public void testGetProperties() throws Exception
+ {
+ PolicyLogger.info("testGetProperties: test the basic properties file interface");
+
+ // copy system properties
+ PolicyLogger.info("Copy system properties to a file");
+ Properties prop1 = System.getProperties();
+ File file1 = createFile("createAndReadPropertyFile-1", prop1);
+
+ // read in properties, and compare
+ PolicyLogger.info("Read in properties from new file");
+ Properties prop2 = PropertyUtil.getProperties(file1);
+
+ // they should match
+ assertEquals(prop1, prop2);
+ }
+
+ /**
+ * This tests the 'PropertyUtil.Listener' interface.
+ */
+ @Test
+ public void testListenerInterface() throws Exception
+ {
+ PolicyLogger.info("testListenerInterface: test receipt of dynamic updates");
+
+ // create initial property file
+ Properties prop1 = new Properties();
+ prop1.setProperty("p1", "p1 value");
+ prop1.setProperty("p2", "p2 value");
+ prop1.setProperty("p3", "p3 value");
+ PolicyLogger.info("Create initial properties file: " + prop1);
+ File file1 = createFile("createAndReadPropertyFile-2", prop1);
+
+ // create a listener for the notification interface
+ Object[] returns = new Object[2];
+ PropertyUtil.Listener listener = createListenerThread(returns);
+
+ // read it in, and do a comparison
+ Properties prop2 = PropertyUtil.getProperties(file1, listener);
+ PolicyLogger.info("Read in properties: " + prop2);
+ assertEquals(prop1, prop2);
+ assertEquals(prop2.getProperty("p1"), "p1 value");
+ assertEquals(prop2.getProperty("p2"), "p2 value");
+ assertEquals(prop2.getProperty("p3"), "p3 value");
+
+ // make some changes, and update the file (p3 is left unchanged)
+ prop2.remove("p1"); // remove one property
+ prop2.setProperty("p2", "new p2 value"); // change one property
+ prop2.setProperty("p4", "p4 value"); // add a new property
+ PolicyLogger.info("Modified properties: " + prop2);
+
+ // now, update the file, and wait for notification
+ synchronized(returns)
+ {
+ createFile("createAndReadPropertyFile-2", prop2);
+
+ // wait up to 60 seconds, although we should receive notification
+ // in 10 seconds or less (if things are working)
+ returns.wait(60000L);
+ }
+
+ // verify we have the updates
+ assertEquals(prop2, returns[0]);
+
+ // verify that we have the expected set of keys
+ assertEquals(new TreeSet(Arrays.asList(new String[]{"p1", "p2", "p4"})),
+ returns[1]);
+ }
+}
diff --git a/policy-utils/src/test/resources/log4j.properties b/policy-utils/src/test/resources/log4j.properties
new file mode 100644
index 00000000..0063f104
--- /dev/null
+++ b/policy-utils/src/test/resources/log4j.properties
@@ -0,0 +1,26 @@
+###
+# ============LICENSE_START=======================================================
+# policy-utils
+# ================================================================================
+# Copyright (C) 2017 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=========================================================
+###
+
+log4j.rootLogger=INFO, out
+
+# CONSOLE appender not used by default
+log4j.appender.out=org.apache.log4j.ConsoleAppender
+log4j.appender.out.layout=org.apache.log4j.PatternLayout
+log4j.appender.out.layout.ConversionPattern=%d %-5p %-30.30c{1} %4L - %m%n