From 0e16acf4d8579fd82349d6cd24e236275735a818 Mon Sep 17 00:00:00 2001 From: Pamela Dragosh Date: Tue, 14 Feb 2017 19:45:48 -0500 Subject: Initial OpenECOMP policy/drools-pdp commit Change-Id: I0072ccab6f40ed32da39667f9f8523b6d6dad2e2 Signed-off-by: Pamela Dragosh --- policy-utils/pom.xml | 75 ++++ .../policy/drools/utils/OrderedService.java | 35 ++ .../policy/drools/utils/OrderedServiceImpl.java | 117 ++++++ .../org/openecomp/policy/drools/utils/Pair.java | 51 +++ .../policy/drools/utils/PropertyUtil.java | 403 +++++++++++++++++++++ .../policy/drools/utils/ReflectionUtil.java | 92 +++++ .../org/openecomp/policy/drools/utils/Triple.java | 45 +++ .../policy/drools/utils/PropertyUtilTest.java | 204 +++++++++++ policy-utils/src/test/resources/log4j.properties | 26 ++ 9 files changed, 1048 insertions(+) create mode 100644 policy-utils/pom.xml create mode 100644 policy-utils/src/main/java/org/openecomp/policy/drools/utils/OrderedService.java create mode 100644 policy-utils/src/main/java/org/openecomp/policy/drools/utils/OrderedServiceImpl.java create mode 100644 policy-utils/src/main/java/org/openecomp/policy/drools/utils/Pair.java create mode 100644 policy-utils/src/main/java/org/openecomp/policy/drools/utils/PropertyUtil.java create mode 100644 policy-utils/src/main/java/org/openecomp/policy/drools/utils/ReflectionUtil.java create mode 100644 policy-utils/src/main/java/org/openecomp/policy/drools/utils/Triple.java create mode 100644 policy-utils/src/test/java/org/openecomp/policy/drools/utils/PropertyUtilTest.java create mode 100644 policy-utils/src/test/resources/log4j.properties (limited to 'policy-utils') diff --git a/policy-utils/pom.xml b/policy-utils/pom.xml new file mode 100644 index 00000000..b7ac7fb2 --- /dev/null +++ b/policy-utils/pom.xml @@ -0,0 +1,75 @@ + + + + + 4.0.0 + + policy-utils + + + org.openecomp.policy.drools-pdp + drools-pdp + 1.0.0-SNAPSHOT + + + + + + junit + junit + 4.11 + + + log4j + log4j + 1.2.17 + provided + + + com.att.eelf + eelf-core + 0.0.1 + + + ch.qos.logback + logback-classic + 1.1.1 + + + ch.qos.logback + logback-core + 1.1.1 + + + org.slf4j + slf4j-api + 1.7.6 + + + org.openecomp.policy.common + ECOMP-Logging + ${common-modules.version} + + + + + 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 +{ + // sorted list of instances implementing the service + private List implementers = null; + + // 'ServiceLoader' that is used to discover and create the services + private ServiceLoader 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 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 rebuildList() + { + // build a list of all of the current implementors + List tmp = new LinkedList(); + 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() + { + 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 { + + 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 changedKeys); + } + + // this table maps canonical file into a 'ListenerRegistration' instance + static private HashMap registrations = + new HashMap(); + + /** + * 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 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(); + + // 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 changedProperties = + new HashSet(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 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 tmpChangedProperties = + new HashSet(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 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 { + + 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 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 -- cgit 1.2.3-korg