aboutsummaryrefslogtreecommitdiffstats
path: root/policy-core
diff options
context:
space:
mode:
authorStraubs, Ralph (rs8887) <rs8887@att.com>2019-11-11 11:28:47 -0600
committerStraubs, Ralph (rs8887) <rs8887@att.com>2019-11-14 03:07:25 -0600
commit1320df00310f70b3cf6695466eb2d76a2d3dc49b (patch)
treec7d4f55b594db9a2bbec98a5ba1ecb207467770f /policy-core
parent2b0f80062f0609f483eea287bdf503be45f12472 (diff)
Add 'DroolsRunnable' class
This provides a simple way to run arbitrary Java code within the Drools thread. This change also includes a general way to specify Drools rules that are automatically added to every Drools session. Change-Id: I5ddcca4c807dc552fbcbd4a19dce311a4d358279 Issue-ID: POLICY-1948 Signed-off-by: Straubs, Ralph (rs8887) <rs8887@att.com>
Diffstat (limited to 'policy-core')
-rw-r--r--policy-core/src/main/java/org/onap/policy/drools/core/DroolsRunnable.java28
-rw-r--r--policy-core/src/main/java/org/onap/policy/drools/core/PolicyContainer.java43
-rw-r--r--policy-core/src/main/java/org/onap/policy/drools/util/KieUtils.java95
-rw-r--r--policy-core/src/main/resources/META-INF/drools/drl40
-rw-r--r--policy-core/src/test/java/org/onap/policy/drools/core/DroolsContainerTest.java12
-rw-r--r--policy-core/src/test/java/org/onap/policy/drools/util/KieUtilsTest.java62
6 files changed, 279 insertions, 1 deletions
diff --git a/policy-core/src/main/java/org/onap/policy/drools/core/DroolsRunnable.java b/policy-core/src/main/java/org/onap/policy/drools/core/DroolsRunnable.java
new file mode 100644
index 00000000..18e66e90
--- /dev/null
+++ b/policy-core/src/main/java/org/onap/policy/drools/core/DroolsRunnable.java
@@ -0,0 +1,28 @@
+/*
+ * ============LICENSE_START=======================================================
+ * policy-core
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.drools.core;
+
+/**
+ * This class provides the ability to execute arbitrary code within a
+ * Drools thread.
+ */
+public interface DroolsRunnable extends Runnable {
+}
diff --git a/policy-core/src/main/java/org/onap/policy/drools/core/PolicyContainer.java b/policy-core/src/main/java/org/onap/policy/drools/core/PolicyContainer.java
index 4e1b1d6c..0fe1f855 100644
--- a/policy-core/src/main/java/org/onap/policy/drools/core/PolicyContainer.java
+++ b/policy-core/src/main/java/org/onap/policy/drools/core/PolicyContainer.java
@@ -33,9 +33,11 @@ import org.kie.api.builder.KieScanner;
import org.kie.api.builder.Message;
import org.kie.api.builder.ReleaseId;
import org.kie.api.builder.Results;
+import org.kie.api.definition.KiePackage;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.onap.policy.common.capabilities.Startable;
+import org.onap.policy.drools.util.KieUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -74,6 +76,12 @@ public class PolicyContainer implements Startable {
private static final String ERROR_STRING = "ERROR: Feature API: ";
+ // packages that are included in all 'KieContainer' instances
+ private static Collection<KiePackage> commonPackages = null;
+
+ // all resources with this name consist of rules that are added to each container
+ private static final String COMMON_PACKAGES_RESOURCE_NAME = "META-INF/drools/drl";
+
/**
* uses 'groupId', 'artifactId' and 'version', and fetches the associated artifact and remaining
* dependencies from the Maven repository to create the 'PolicyContainer' and associated
@@ -107,6 +115,9 @@ public class PolicyContainer implements Startable {
} else {
kieContainer = kieServices.newKieContainer(newReleaseId);
}
+
+ // add common KiePackage instances
+ addCommonPackages();
synchronized (containers) {
if (newReleaseId != null) {
logger.info("Add a new kieContainer in containers: releaseId: {}", newReleaseId);
@@ -401,6 +412,10 @@ public class PolicyContainer implements Startable {
// update the version
Results results = kieContainer.updateToVersion(releaseId);
+
+ // add common KiePackage instances
+ addCommonPackages();
+
// restart all session threads, and notify the sessions
for (PolicySession session : sessions.values()) {
session.startThread();
@@ -726,4 +741,32 @@ public class PolicyContainer implements Startable {
adjuncts.put(object, value);
}
}
+
+ /**
+ * Add 'KiePackages' that are common to all containers.
+ */
+ private void addCommonPackages() {
+ // contains the list of 'KiePackages' to add to each 'KieBase'
+ Collection<KiePackage> kiePackages;
+ synchronized (PolicyContainer.class) {
+ if (commonPackages == null) {
+ commonPackages = KieUtils.resourceToPackages(
+ PolicyContainer.class.getClassLoader(), COMMON_PACKAGES_RESOURCE_NAME);
+ if (commonPackages == null) {
+ // a problem occurred, which has already been logged --
+ // just store an empty collection, so we don't keep doing
+ // this over again
+ commonPackages = new HashSet<>();
+ return;
+ }
+ }
+ kiePackages = commonPackages;
+ }
+
+ // if we reach this point, 'kiePackages' contains a non-null list
+ // of packages to add
+ for (String name : kieContainer.getKieBaseNames()) {
+ KieUtils.addKiePackages(kieContainer.getKieBase(name), kiePackages);
+ }
+ }
}
diff --git a/policy-core/src/main/java/org/onap/policy/drools/util/KieUtils.java b/policy-core/src/main/java/org/onap/policy/drools/util/KieUtils.java
index 03a307cf..bd1c6cee 100644
--- a/policy-core/src/main/java/org/onap/policy/drools/util/KieUtils.java
+++ b/policy-core/src/main/java/org/onap/policy/drools/util/KieUtils.java
@@ -22,14 +22,25 @@ package org.onap.policy.drools.util;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
import lombok.NonNull;
+import org.apache.commons.io.IOUtils;
import org.drools.compiler.kie.builder.impl.InternalKieModule;
import org.drools.compiler.kproject.models.KieModuleModelImpl;
+import org.drools.core.impl.KnowledgeBaseImpl;
+import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
@@ -41,12 +52,20 @@ import org.kie.api.definition.rule.Rule;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.scanner.KieMavenRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Kie related utilities.
*/
public class KieUtils {
+ private static final Logger logger = LoggerFactory.getLogger(KieUtils.class);
+
+ // resource names used by 'resourceToPackages'
+ private static final String RESOURCE_PREFIX = "src/main/resources/drools";
+ private static final String RESOURCE_SUFFIX = ".drl";
+
private KieUtils() {
// Utility class
}
@@ -154,4 +173,80 @@ public class KieUtils {
}
return kieBuilder;
}
+
+ /**
+ * Find all Drools resources matching a specified name, and generate a
+ * collection of 'KiePackage' instances from those resources.
+ *
+ * @param classLoader the class loader to use when finding resources, or
+ * when building the 'KiePackage' collection
+ * @param resourceName the resource name, without a leading '/' character
+ * @return a collection of 'KiePackage' instances, or 'null' in case of
+ * failure
+ */
+ public static Collection<KiePackage> resourceToPackages(ClassLoader classLoader, String resourceName) {
+
+ // find all resources matching 'resourceName'
+ Enumeration<URL> resources;
+ try {
+ resources = classLoader.getResources(resourceName);
+ } catch (IOException e) {
+ logger.error("Exception fetching resources: " + resourceName, e);
+ return null;
+ }
+ if (!resources.hasMoreElements()) {
+ // no resources found
+ return null;
+ }
+
+ // generate a 'KieFileSystem' from these resources
+ KieServices kieServices = KieServices.Factory.get();
+ KieFileSystem kfs = kieServices.newKieFileSystem();
+ int index = 1;
+ while (resources.hasMoreElements()) {
+ URL url = resources.nextElement();
+ try (InputStream is = url.openStream()) {
+ // convert a resource to a byte array
+ byte[] drl = IOUtils.toByteArray(is);
+
+ // add a new '.drl' entry to the KieFileSystem
+ kfs.write(RESOURCE_PREFIX + index++ + RESOURCE_SUFFIX, drl);
+ } catch (IOException e) {
+ logger.error("Couldn't read in " + url, e);
+ return null;
+ }
+ }
+
+ // do a build of the 'KieFileSystem'
+ KieBuilder builder = kieServices.newKieBuilder(kfs, classLoader);
+ builder.buildAll();
+ List<Message> results = builder.getResults().getMessages();
+ if (!results.isEmpty()) {
+ logger.error("Kie build failed:\n" + results);
+ return null;
+ }
+
+ // generate a KieContainer, and extract the package list
+ return kieServices.newKieContainer(builder.getKieModule().getReleaseId(), classLoader)
+ .getKieBase().getKiePackages();
+ }
+
+ /**
+ * Add a collection of 'KiePackage' instances to the specified 'KieBase'.
+ *
+ * @param kieBase the 'KieBase' instance to add the packages to
+ * @param kiePackages the collection of packages to add
+ */
+ public static void addKiePackages(KieBase kieBase, Collection<KiePackage> kiePackages) {
+ HashSet<KiePackage> stillNeeded = new HashSet<>(kiePackages);
+
+ // update 'stillNeeded' by removing any packages we already have
+ stillNeeded.removeAll(kieBase.getKiePackages());
+
+ if (!stillNeeded.isEmpty()) {
+ // there are still packages we need to add --
+ // this code makes use of an internal class and method
+ ((KnowledgeBaseImpl)kieBase).addPackages(stillNeeded);
+ }
+ }
}
diff --git a/policy-core/src/main/resources/META-INF/drools/drl b/policy-core/src/main/resources/META-INF/drools/drl
new file mode 100644
index 00000000..949f235e
--- /dev/null
+++ b/policy-core/src/main/resources/META-INF/drools/drl
@@ -0,0 +1,40 @@
+/*
+ * ============LICENSE_START=======================================================
+ * policy-core
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.drools.core;
+
+// This rule will match any 'DroolsRunnable' instance placed in Drools
+// memory, and run the 'DroolsRunnable.run()' method. It provides a way
+// to run arbitrary Java code within a Drools session thread by inserting
+// a 'DroolsRunnable' instance into Drools memory.
+
+rule "run-drools-runnable"
+ when
+ $runnable : DroolsRunnable()
+ then
+ {
+ // we retract it first, because the 'run()' method may traverse
+ // Drools objects
+ retract($runnable);
+
+ // run the code within the Drools thread
+ $runnable.run();
+ }
+end
diff --git a/policy-core/src/test/java/org/onap/policy/drools/core/DroolsContainerTest.java b/policy-core/src/test/java/org/onap/policy/drools/core/DroolsContainerTest.java
index d168ca8b..2f45f01e 100644
--- a/policy-core/src/test/java/org/onap/policy/drools/core/DroolsContainerTest.java
+++ b/policy-core/src/test/java/org/onap/policy/drools/core/DroolsContainerTest.java
@@ -165,6 +165,18 @@ public class DroolsContainerTest {
container.insert("session1", result);
assertEquals(48, result.poll(TIMEOUT_SEC, TimeUnit.SECONDS).intValue());
+
+ // verify that default KiePackages have been added by testing
+ // that 'DroolsRunnable' is functioning
+
+ final LinkedBlockingQueue<String> lbq = new LinkedBlockingQueue<>();
+ container.insert("session1", new DroolsRunnable() {
+ @Override
+ public void run() {
+ lbq.add("DroolsRunnable String");
+ }
+ });
+ assertEquals("DroolsRunnable String", lbq.poll(TIMEOUT_SEC, TimeUnit.SECONDS));
} finally {
container.shutdown();
assertFalse(container.isAlive());
diff --git a/policy-core/src/test/java/org/onap/policy/drools/util/KieUtilsTest.java b/policy-core/src/test/java/org/onap/policy/drools/util/KieUtilsTest.java
index 83f62b32..89ab9d0a 100644
--- a/policy-core/src/test/java/org/onap/policy/drools/util/KieUtilsTest.java
+++ b/policy-core/src/test/java/org/onap/policy/drools/util/KieUtilsTest.java
@@ -22,12 +22,15 @@ package org.onap.policy.drools.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import java.io.IOException;
+import java.net.URL;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Enumeration;
import java.util.stream.Collectors;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -119,4 +122,61 @@ public class KieUtilsTest {
public void getFacts() {
assertEquals(0, KieUtils.getFacts(session).size());
}
-} \ No newline at end of file
+
+ @Test
+ public void resourceToPackagesTests() {
+ // Some minimal logging -- it would be nice to verify the 'KieUtils' logger messages
+ StringBuffer log;
+
+ // test IOException from ClassLoader
+ log = new StringBuffer();
+ assertNull(KieUtils.resourceToPackages(new BogusClassLoader(log), "BogusClassLoader"));
+ assertEquals("IOException(BogusClassLoader)", log.toString());
+
+ // test 'null' return when no resources are found
+ assertNull(KieUtils.resourceToPackages(ClassLoader.getSystemClassLoader(), "no/such/url"));
+
+ // test IOException in 'IOUtils.toByteArray()' -> 'InputStream.read()'
+ log = new StringBuffer();
+ assertNull(KieUtils.resourceToPackages(new BogusClassLoader(log), "BogusUrl"));
+ assertEquals("", log.toString());
+
+ // don't know how to test 'KieBuilder' errors at this point
+
+ // success legs are tested in 'DroolsContainerTest'
+ }
+
+ static class BogusClassLoader extends ClassLoader {
+ StringBuffer log;
+
+ BogusClassLoader(StringBuffer log) {
+ this.log = log;
+ }
+
+ @Override
+ public Enumeration<URL> getResources(String name) throws IOException {
+ if ("BogusUrl".equals(name)) {
+ return new Enumeration<URL>() {
+ @Override
+ public boolean hasMoreElements() {
+ return true;
+ }
+
+ @Override
+ public URL nextElement() {
+ try {
+ // when the following URL is used, an IOException will occur
+ return new URL("http://127.0.0.1:1");
+ } catch (IOException e) {
+ // this should never happen, as the URL above is syntactically valid
+ return null;
+ }
+ }
+ };
+ } else {
+ log.append("IOException(BogusClassLoader)");
+ throw new IOException("BogusClassLoader");
+ }
+ }
+ }
+}