summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore7
-rw-r--r--feature-healthcheck/src/assembly/assemble_zip.xml2
-rw-r--r--feature-healthcheck/src/main/feature/config/feature-healthcheck.properties2
-rw-r--r--feature-session-persistence/.gitignore1
-rw-r--r--feature-session-persistence/pom.xml177
-rw-r--r--feature-session-persistence/src/assembly/assemble_zip.xml76
-rw-r--r--feature-session-persistence/src/main/feature/config/feature-session-persistence.properties28
-rw-r--r--feature-session-persistence/src/main/feature/db/sessionpersistence/sql/18020-sessionpersistence.upgrade.sql60
-rw-r--r--feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsPersistenceProperties.java34
-rw-r--r--feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSession.java35
-rw-r--r--feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSessionConnector.java38
-rw-r--r--feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSessionEntity.java140
-rw-r--r--feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/EntityMgrCloser.java49
-rw-r--r--feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/EntityMgrTrans.java81
-rw-r--r--feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/JpaDroolsSessionConnector.java116
-rw-r--r--feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/PersistenceFeature.java1014
-rw-r--r--feature-session-persistence/src/main/resources/META-INF/persistence.xml65
-rw-r--r--feature-session-persistence/src/main/resources/META-INF/services/org.onap.policy.drools.core.PolicySessionFeatureAPI1
-rw-r--r--feature-session-persistence/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyEngineFeatureAPI1
-rw-r--r--feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/DroolsSessionEntityTest.java198
-rw-r--r--feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/EntityMgrCloserTest.java100
-rw-r--r--feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/EntityMgrTransTest.java232
-rw-r--r--feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/GenSchemaTest.java58
-rw-r--r--feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/JpaDroolsSessionConnectorTest.java160
-rw-r--r--feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/PersistenceFeatureTest.java1291
-rw-r--r--feature-session-persistence/src/test/resources/META-INF/persistence.xml42
-rw-r--r--feature-session-persistence/src/test/resources/feature-session-persistence.properties28
-rw-r--r--feature-session-persistence/src/test/resources/logback-test.xml39
-rw-r--r--packages/install/pom.xml6
-rw-r--r--pom.xml1
30 files changed, 4077 insertions, 5 deletions
diff --git a/.gitignore b/.gitignore
index f7ef0620..817b204f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,9 +7,10 @@
target
.metadata/
policy-endpoints/logs/
-policy-persistence/sql/
-policy-persistence/testingLogs/
+feature-session-persistence/sql/
+feature-session-persistence/testingLogs/
policy-utils/debug-logs/
policy-utils/logs/
/bin/
-policy-core/src/main/java/META-INF/
+*.log
+policy-management/config/unnamed-controller.properties
diff --git a/feature-healthcheck/src/assembly/assemble_zip.xml b/feature-healthcheck/src/assembly/assemble_zip.xml
index fcbb652a..0e2eaa8b 100644
--- a/feature-healthcheck/src/assembly/assemble_zip.xml
+++ b/feature-healthcheck/src/assembly/assemble_zip.xml
@@ -1,6 +1,6 @@
<!--
============LICENSE_START=======================================================
- policy-persistence
+ feature-healthcheck
================================================================================
Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
================================================================================
diff --git a/feature-healthcheck/src/main/feature/config/feature-healthcheck.properties b/feature-healthcheck/src/main/feature/config/feature-healthcheck.properties
index 5aaae4a1..9311680d 100644
--- a/feature-healthcheck/src/main/feature/config/feature-healthcheck.properties
+++ b/feature-healthcheck/src/main/feature/config/feature-healthcheck.properties
@@ -1,6 +1,6 @@
###
# ============LICENSE_START=======================================================
-# policy-management
+# feature-healthcheck
# ================================================================================
# Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
# ================================================================================
diff --git a/feature-session-persistence/.gitignore b/feature-session-persistence/.gitignore
new file mode 100644
index 00000000..b83d2226
--- /dev/null
+++ b/feature-session-persistence/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/feature-session-persistence/pom.xml b/feature-session-persistence/pom.xml
new file mode 100644
index 00000000..4e3b9e9b
--- /dev/null
+++ b/feature-session-persistence/pom.xml
@@ -0,0 +1,177 @@
+<!--
+ ============LICENSE_START=======================================================
+ ONAP Policy Engine - Drools PDP
+ ================================================================================
+ 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=========================================================
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onap.policy.drools-pdp</groupId>
+ <artifactId>drools-pdp</artifactId>
+ <version>1.1.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>feature-session-persistence</artifactId>
+
+ <name>feature-session-persistence</name>
+ <description>Separately loadable feature module with session persistence code</description>
+
+ <properties>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ <powermock.version>1.6.6</powermock.version>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>2.6</version>
+ <executions>
+ <execution>
+ <id>zipfile</id>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <attach>true</attach>
+ <finalName>${project.artifactId}-${project.version}</finalName>
+ <descriptors>
+ <descriptor>src/assembly/assemble_zip.xml</descriptor>
+ </descriptors>
+ <appendAssemblyId>false</appendAssemblyId>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <version>2.8</version>
+ <executions>
+ <execution>
+ <id>copy-dependencies</id>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <phase>prepare-package</phase>
+ <configuration>
+ <transitive>false</transitive>
+ <outputDirectory>${project.build.directory}/assembly/lib</outputDirectory>
+ <overWriteReleases>false</overWriteReleases>
+ <overWriteSnapshots>true</overWriteSnapshots>
+ <overWriteIfNewer>true</overWriteIfNewer>
+ <useRepositoryLayout>false</useRepositoryLayout>
+ <addParentPoms>false</addParentPoms>
+ <copyPom>false</copyPom>
+ <includeScope>runtime</includeScope>
+ <excludeScope>provided</excludeScope>
+ <excludeTransitive>true</excludeTransitive>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onap.policy.drools-pdp</groupId>
+ <artifactId>policy-core</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.drools-pdp</groupId>
+ <artifactId>policy-management</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito</artifactId>
+ <version>${powermock.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-module-junit4</artifactId>
+ <version>${powermock.version}</version>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-core</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-reflect</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.javassist</groupId>
+ <artifactId>javassist</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.javassist</groupId>
+ <artifactId>javassist</artifactId>
+ <version>3.21.0-GA</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.btm</groupId>
+ <artifactId>btm</artifactId>
+ <version>2.1.4</version>
+ </dependency>
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ <version>[1.4.186,)</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate.common</groupId>
+ <artifactId>hibernate-commons-annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.persistence</groupId>
+ <artifactId>eclipselink</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/feature-session-persistence/src/assembly/assemble_zip.xml b/feature-session-persistence/src/assembly/assemble_zip.xml
new file mode 100644
index 00000000..1cc3ce5a
--- /dev/null
+++ b/feature-session-persistence/src/assembly/assemble_zip.xml
@@ -0,0 +1,76 @@
+<!--
+ ============LICENSE_START=======================================================
+ feature-session-persistence
+ ================================================================================
+ 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=========================================================
+ -->
+
+<!-- Defines how we build the .zip file which is our distribution. -->
+
+<assembly
+ xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+ <id>session-persistence</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+
+ <!-- we want "system" and related files right at the root level as this
+ file is suppose to be unzip on top of a karaf distro. -->
+ <includeBaseDirectory>false</includeBaseDirectory>
+
+ <fileSets>
+ <fileSet>
+ <directory>target</directory>
+ <outputDirectory>lib/feature</outputDirectory>
+ <includes>
+ <include>feature-session-persistence-${project.version}.jar</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>target/assembly/lib</directory>
+ <outputDirectory>lib/dependencies</outputDirectory>
+ <includes>
+ <include>*.jar</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>src/main/feature/config</directory>
+ <outputDirectory>config</outputDirectory>
+ <fileMode>0644</fileMode>
+ <excludes/>
+ </fileSet>
+ <fileSet>
+ <directory>src/main/feature/bin</directory>
+ <outputDirectory>bin</outputDirectory>
+ <fileMode>0744</fileMode>
+ <excludes/>
+ </fileSet>
+ <fileSet>
+ <directory>src/main/feature/db</directory>
+ <outputDirectory>db</outputDirectory>
+ <fileMode>0744</fileMode>
+ <excludes/>
+ </fileSet>
+ <fileSet>
+ <directory>src/main/feature/install</directory>
+ <outputDirectory>install</outputDirectory>
+ <fileMode>0744</fileMode>
+ <excludes/>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/feature-session-persistence/src/main/feature/config/feature-session-persistence.properties b/feature-session-persistence/src/main/feature/config/feature-session-persistence.properties
new file mode 100644
index 00000000..6204b5ed
--- /dev/null
+++ b/feature-session-persistence/src/main/feature/config/feature-session-persistence.properties
@@ -0,0 +1,28 @@
+###
+# ============LICENSE_START=======================================================
+# feature-session-persistence
+# ================================================================================
+# 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=========================================================
+###
+
+javax.persistence.jdbc.driver= org.mariadb.jdbc.Driver
+javax.persistence.jdbc.url=jdbc:mariadb://${{SQL_HOST}}:3306/sessionpersistence
+javax.persistence.jdbc.user=${{SQL_USER}}
+javax.persistence.jdbc.password=${{SQL_PASSWORD}}
+hibernate.dataSource=org.mariadb.jdbc.MySQLDataSource
+
+#Seconds timeout - 15 minutes
+persistence.sessioninfo.timeout=900 \ No newline at end of file
diff --git a/feature-session-persistence/src/main/feature/db/sessionpersistence/sql/18020-sessionpersistence.upgrade.sql b/feature-session-persistence/src/main/feature/db/sessionpersistence/sql/18020-sessionpersistence.upgrade.sql
new file mode 100644
index 00000000..0e980968
--- /dev/null
+++ b/feature-session-persistence/src/main/feature/db/sessionpersistence/sql/18020-sessionpersistence.upgrade.sql
@@ -0,0 +1,60 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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=========================================================
+ */
+
+set foreign_key_checks=0;
+
+CREATE TABLE if not exists sessionpersistence.DROOLSSESSIONENTITY
+(
+sessionName VARCHAR(255) NOT NULL,
+sessionId BIGINT NOT NULL,
+createdDate TIMESTAMP NOT NULL,
+updatedDate TIMESTAMP NOT NULL,
+PRIMARY KEY (sessionName)
+);
+
+CREATE TABLE if not exists sessionpersistence.SESSIONINFO
+(
+ID BIGINT NOT NULL AUTO_INCREMENT,
+LASTMODIFICATIONDATE TIMESTAMP,
+RULESBYTEARRAY BLOB,
+STARTDATE TIMESTAMP default current_timestamp,
+OPTLOCK INTEGER,
+PRIMARY KEY (ID)
+);
+
+CREATE TABLE if not exists sessionpersistence.WORKITEMINFO
+(
+WORKITEMID BIGINT NOT NULL AUTO_INCREMENT,
+CREATIONDATE TIMESTAMP default current_timestamp,
+`NAME` VARCHAR(500),
+PROCESSINSTANCEID BIGINT,
+STATE BIGINT,
+OPTLOCK INTEGER,
+WORKITEMBYTEARRAY BLOB,
+PRIMARY KEY (WORKITEMID)
+);
+
+CREATE TABLE IF NOT EXISTS sessionpersistence.SESSIONINFO_ID_SEQ (next_val bigint) engine=MyISAM;
+INSERT INTO sessionpersistence.SESSIONINFO_ID_SEQ (next_val) SELECT 1 WHERE NOT EXISTS (SELECT * FROM sessionpersistence.SESSIONINFO_ID_SEQ);
+
+CREATE TABLE IF NOT EXISTS sessionpersistence.WORKITEMINFO_ID_SEQ (next_val bigint) engine=MyISAM;
+INSERT INTO sessionpersistence.WORKITEMINFO_ID_SEQ (next_val) SELECT 1 WHERE NOT EXISTS (SELECT * FROM sessionpersistence.WORKITEMINFO_ID_SEQ);
+
+set foreign_key_checks=1; \ No newline at end of file
diff --git a/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsPersistenceProperties.java b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsPersistenceProperties.java
new file mode 100644
index 00000000..fabcbc03
--- /dev/null
+++ b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsPersistenceProperties.java
@@ -0,0 +1,34 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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.onap.policy.drools.persistence;
+
+public class DroolsPersistenceProperties {
+ /*
+ * feature-session-persistence.properties parameter key values
+ */
+ public static final String DB_DRIVER = "javax.persistence.jdbc.driver";
+ public static final String DB_DATA_SOURCE = "hibernate.dataSource";
+ public static final String DB_URL = "javax.persistence.jdbc.url";
+ public static final String DB_USER = "javax.persistence.jdbc.user";
+ public static final String DB_PWD = "javax.persistence.jdbc.password";
+ public static final String DB_SESSIONINFO_TIMEOUT =
+ "persistence.sessioninfo.timeout";
+}
diff --git a/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSession.java b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSession.java
new file mode 100644
index 00000000..a012f3c2
--- /dev/null
+++ b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSession.java
@@ -0,0 +1,35 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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.onap.policy.drools.persistence;
+
+import java.util.Date;
+
+public interface DroolsSession {
+
+ public String getSessionName();
+ public void setSessionName(String sessionName);
+ public long getSessionId();
+ public void setSessionId(long sessionId);
+ public Date getCreatedDate();
+ public void setCreatedDate(Date createdDate);
+ public Date getUpdatedDate();
+ public void setUpdatedDate(Date updatedDate);
+}
diff --git a/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSessionConnector.java b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSessionConnector.java
new file mode 100644
index 00000000..e2f4ac54
--- /dev/null
+++ b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSessionConnector.java
@@ -0,0 +1,38 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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.onap.policy.drools.persistence;
+
+public interface DroolsSessionConnector
+{
+ /**
+ * Gets a session by PDP id and name.
+ * @param sessName
+ * @return a session, or {@code null} if it is not found
+ */
+ public DroolsSession get(String sessName);
+
+ /**
+ * Replaces a session, adding it if it does not exist.
+ * @param sess session to be replaced
+ */
+ public void replace(DroolsSession sess);
+
+}
diff --git a/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSessionEntity.java b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSessionEntity.java
new file mode 100644
index 00000000..71f5ec9a
--- /dev/null
+++ b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSessionEntity.java
@@ -0,0 +1,140 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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.onap.policy.drools.persistence;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.PrePersist;
+import javax.persistence.PreUpdate;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+@Entity
+public class DroolsSessionEntity implements Serializable, DroolsSession {
+
+ private static final long serialVersionUID = -5495057038819948709L;
+
+ @Id
+ @Column(name="sessionName", nullable=false)
+ private String sessionName = "-1";
+
+ @Column(name="sessionId", nullable=false)
+ private long sessionId = -1L;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name="createdDate", nullable=false)
+ private Date createdDate;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name="updatedDate", nullable=false)
+ private Date updatedDate;
+
+
+ public DroolsSessionEntity() {
+
+ }
+
+ public DroolsSessionEntity(String sessionName,
+ long sessionId) {
+ this.sessionName = sessionName;
+ this.sessionId = sessionId;
+
+ }
+
+ @PrePersist
+ public void prePersist() {
+ this.createdDate = new Date();
+ this.updatedDate = new Date();
+ }
+
+ @PreUpdate
+ public void preUpdate() {
+ this.updatedDate = new Date();
+ }
+
+ @Override
+ public String getSessionName() {
+ return sessionName;
+ }
+ @Override
+ public void setSessionName(String sessionName) {
+ this.sessionName = sessionName;
+ }
+ @Override
+ public long getSessionId() {
+ return sessionId;
+ }
+
+ @Override
+ public void setSessionId(long sessionId) {
+ this.sessionId = sessionId;
+ }
+
+ public Date getCreatedDate() {
+ return createdDate;
+ }
+
+
+ public void setCreatedDate(Date createdDate) {
+ this.createdDate = createdDate;
+ }
+
+
+ public Date getUpdatedDate() {
+ return updatedDate;
+ }
+
+
+ public void setUpdatedDate(Date updatedDate) {
+ this.updatedDate = updatedDate;
+ }
+
+
+ @Override
+ public boolean equals(Object other){
+ if(other instanceof DroolsSession){
+ DroolsSession p = (DroolsSession) other;
+ return this.getSessionName().equals(p.getSessionName());
+ }else{
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + getSessionName().hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "{name=" + getSessionName()
+ + ", id=" + getSessionId() + "}";
+ }
+
+
+}
diff --git a/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/EntityMgrCloser.java b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/EntityMgrCloser.java
new file mode 100644
index 00000000..58292117
--- /dev/null
+++ b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/EntityMgrCloser.java
@@ -0,0 +1,49 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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.onap.policy.drools.persistence;
+
+import javax.persistence.EntityManager;
+
+/**
+ * Wrapper for an <i>EntityManager</i>, providing auto-close functionality.
+ */
+public class EntityMgrCloser implements AutoCloseable {
+
+ /**
+ * The wrapper manager.
+ */
+ private final EntityManager em;
+
+ /**
+ *
+ * @param em
+ * manager to be auto-closed
+ */
+ public EntityMgrCloser(EntityManager em) {
+ this.em = em;
+ }
+
+ @Override
+ public void close() {
+ em.close();
+ }
+
+}
diff --git a/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/EntityMgrTrans.java b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/EntityMgrTrans.java
new file mode 100644
index 00000000..79b620d3
--- /dev/null
+++ b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/EntityMgrTrans.java
@@ -0,0 +1,81 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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.onap.policy.drools.persistence;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityTransaction;
+
+/**
+ * Wrapper for an <i>EntityManager</i> that creates a transaction that is
+ * auto-rolled back when closed.
+ */
+public class EntityMgrTrans extends EntityMgrCloser {
+
+ /**
+ * Transaction to be rolled back.
+ */
+ private EntityTransaction trans;
+
+ /**
+ *
+ * @param em
+ * entity for which a transaction is to be begun
+ */
+ public EntityMgrTrans(EntityManager em) {
+ super(em);
+
+ try {
+ trans = em.getTransaction();
+ trans.begin();
+
+ } catch (RuntimeException e) {
+ em.close();
+ throw e;
+ }
+ }
+
+ /**
+ * Commits the transaction.
+ */
+ public void commit() {
+ trans.commit();
+ }
+
+ /**
+ * Rolls back the transaction.
+ */
+ public void rollback() {
+ trans.rollback();
+ }
+
+ @Override
+ public void close() {
+ try {
+ if (trans.isActive()) {
+ trans.rollback();
+ }
+
+ } finally {
+ super.close();
+ }
+ }
+
+}
diff --git a/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/JpaDroolsSessionConnector.java b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/JpaDroolsSessionConnector.java
new file mode 100644
index 00000000..76c09681
--- /dev/null
+++ b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/JpaDroolsSessionConnector.java
@@ -0,0 +1,116 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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.onap.policy.drools.persistence;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class JpaDroolsSessionConnector implements DroolsSessionConnector {
+
+ private static Logger logger = LoggerFactory.getLogger(JpaDroolsSessionConnector.class);
+
+ private final EntityManagerFactory emf;
+
+
+ public JpaDroolsSessionConnector(EntityManagerFactory emf) {
+ this.emf = emf;
+ }
+
+ @Override
+ public DroolsSession get(String sessName) {
+
+ EntityManager em = emf.createEntityManager();
+ DroolsSessionEntity s = null;
+
+ try(EntityMgrTrans trans = new EntityMgrTrans(em)) {
+
+ s = em.find(DroolsSessionEntity.class, sessName);
+ if(s != null) {
+ em.refresh(s);
+ }
+
+ trans.commit();
+ }
+
+ return s;
+ }
+
+ @Override
+ public void replace(DroolsSession sess) {
+ String sessName = sess.getSessionName();
+
+ logger.info("replace: Entering and manually updating session name= {}", sessName);
+
+ EntityManager em = emf.createEntityManager();
+
+ try(EntityMgrTrans trans = new EntityMgrTrans(em)) {
+
+ if( ! update(em, sess)) {
+ add(em, sess);
+ }
+
+ trans.commit();
+ }
+
+ logger.info("replace: Exiting");
+ }
+
+ /**
+ * Adds a session to the persistent store.
+ * @param em entity manager
+ * @param sess session to be added
+ */
+ private void add(EntityManager em, DroolsSession sess) {
+ logger.info("add: Inserting session id={}", sess.getSessionId());
+
+ DroolsSessionEntity ent =
+ new DroolsSessionEntity(
+ sess.getSessionName(),
+ sess.getSessionId());
+
+ em.persist(ent);
+ }
+
+ /**
+ * Updates a session, if it exists within the persistent store.
+ * @param em entity manager
+ * @param sess session data to be persisted
+ * @return {@code true} if a record was updated, {@code false} if it
+ * was not found
+ */
+ private boolean update(EntityManager em, DroolsSession sess) {
+
+ DroolsSessionEntity s =
+ em.find(DroolsSessionEntity.class, sess.getSessionName());
+ if(s == null) {
+ return false;
+ }
+
+ logger.info("update: Updating session id to {}", sess.getSessionId());
+ s.setSessionId( sess.getSessionId());
+
+ return true;
+ }
+}
diff --git a/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/PersistenceFeature.java b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/PersistenceFeature.java
new file mode 100644
index 00000000..e6603b68
--- /dev/null
+++ b/feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/PersistenceFeature.java
@@ -0,0 +1,1014 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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.onap.policy.drools.persistence;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+
+import org.eclipse.persistence.config.PersistenceUnitProperties;
+import org.kie.api.KieServices;
+import org.kie.api.runtime.Environment;
+import org.kie.api.runtime.EnvironmentName;
+import org.kie.api.runtime.KieSession;
+import org.kie.api.runtime.KieSessionConfiguration;
+import org.onap.policy.drools.core.PolicyContainer;
+import org.onap.policy.drools.core.PolicySession;
+import org.onap.policy.drools.core.PolicySessionFeatureAPI;
+import org.onap.policy.drools.features.PolicyEngineFeatureAPI;
+import org.onap.policy.drools.system.PolicyController;
+import org.onap.policy.drools.system.PolicyEngine;
+import org.onap.policy.drools.utils.PropertyUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import bitronix.tm.BitronixTransactionManager;
+import bitronix.tm.Configuration;
+import bitronix.tm.TransactionManagerServices;
+import bitronix.tm.resource.jdbc.PoolingDataSource;
+
+/**
+ * If this feature is supported, there is a single instance of it. It adds
+ * persistence to Drools sessions. In addition, if an active-standby feature
+ * exists, then that is used to determine the active and last-active PDP. If it
+ * does not exist, then the current host name is used as the PDP id.
+ *
+ * The bulk of the code here was once in other classes, such as
+ * 'PolicyContainer' and 'Main'. It was moved here as part of making this a
+ * separate optional feature.
+ */
+public class PersistenceFeature implements PolicySessionFeatureAPI, PolicyEngineFeatureAPI {
+
+ private static final Logger logger = LoggerFactory.getLogger(PersistenceFeature.class);
+
+
+ /**
+ * Standard factory used to get various items.
+ */
+ private static Factory stdFactory = new Factory();
+
+ /**
+ * Factory used to get various items.
+ */
+ private Factory fact = stdFactory;
+
+ /**
+ * KieService factory.
+ */
+ private KieServices kieSvcFact;
+
+ /**
+ * Host name.
+ */
+ private String hostName;
+
+ /**
+ * Persistence properties.
+ */
+ private Properties persistProps;
+
+ /**
+ * Whether or not the SessionInfo records should be cleaned out.
+ */
+ private boolean sessInfoCleaned;
+
+ /**
+ * SessionInfo timeout, in milli-seconds, as read from
+ * {@link #persistProps}.
+ */
+ private long sessionInfoTimeoutMs;
+
+ /**
+ * Object used to serialize cleanup of sessioninfo table.
+ */
+ private Object cleanupLock = new Object();
+
+ /**
+ * Sets the factory to be used during junit testing.
+ *
+ * @param fact
+ * factory to be used
+ */
+ protected void setFactory(Factory fact) {
+ this.fact = fact;
+ }
+
+ /**
+ * Lookup the adjunct for this feature that is associated with the specified
+ * PolicyContainer. If not found, create one.
+ *
+ * @param policyContainer
+ * the container whose adjunct we are looking up, and possibly
+ * creating
+ * @return the associated 'ContainerAdjunct' instance, which may be new
+ */
+ private ContainerAdjunct getContainerAdjunct(PolicyContainer policyContainer) {
+
+ Object rval = policyContainer.getAdjunct(this);
+
+ if (rval == null || !(rval instanceof ContainerAdjunct)) {
+ // adjunct does not exist, or has the wrong type (should never
+ // happen)
+ rval = new ContainerAdjunct(policyContainer);
+ policyContainer.setAdjunct(this, rval);
+ }
+
+ return ((ContainerAdjunct) rval);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getSequenceNumber() {
+ return (1);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void globalInit(String args[], String configDir) {
+
+ kieSvcFact = fact.getKieServices();
+
+ initHostName();
+
+ try {
+ persistProps = fact.loadProperties(configDir + "/feature-session-persistence.properties");
+
+ } catch (IOException e1) {
+ logger.error("initializePersistence: ", e1);
+ }
+
+ sessionInfoTimeoutMs = getPersistenceTimeout();
+
+ Configuration bitronixConfiguration = fact.getTransMgrConfig();
+ bitronixConfiguration.setJournal(null);
+ bitronixConfiguration.setServerId(hostName);
+ }
+
+ /**
+ * Creates a persistent KieSession, loading it from the persistent store, or
+ * creating one, if it does not exist yet.
+ */
+ @Override
+ public KieSession activatePolicySession(PolicyContainer policyContainer, String name, String kieBaseName) {
+
+ if (isPersistenceEnabled(policyContainer, name)) {
+ cleanUpSessionInfo();
+
+ return getContainerAdjunct(policyContainer).newPersistentKieSession(name, kieBaseName);
+ }
+
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public PolicySession.ThreadModel selectThreadModel(PolicySession session) {
+ PolicyContainer policyContainer = session.getPolicyContainer();
+ if (isPersistenceEnabled(policyContainer, session.getName())) {
+ return (new PersistentThreadModel(session, getProperties(policyContainer)));
+ }
+ return (null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void disposeKieSession(PolicySession policySession) {
+
+ ContainerAdjunct contAdj = (ContainerAdjunct) policySession.getPolicyContainer().getAdjunct(this);
+ if(contAdj != null) {
+ contAdj.disposeKieSession( policySession.getName());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void destroyKieSession(PolicySession policySession) {
+
+ ContainerAdjunct contAdj = (ContainerAdjunct) policySession.getPolicyContainer().getAdjunct(this);
+ if(contAdj != null) {
+ contAdj.destroyKieSession( policySession.getName());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean afterStart(PolicyEngine engine) {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean beforeStart(PolicyEngine engine) {
+ synchronized (cleanupLock) {
+ sessInfoCleaned = false;
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean beforeActivate(PolicyEngine engine) {
+ synchronized (cleanupLock) {
+ sessInfoCleaned = false;
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean afterActivate(PolicyEngine engine) {
+ return false;
+ }
+
+ /* ============================================================ */
+
+ /**
+ * Gets the persistence timeout value for sessioninfo records.
+ *
+ * @return the timeout value, in milli-seconds, or {@code -1} if it is
+ * unspecified or invalid
+ */
+ private long getPersistenceTimeout() {
+ String timeoutString = null;
+
+ try {
+ timeoutString = persistProps.getProperty(DroolsPersistenceProperties.DB_SESSIONINFO_TIMEOUT);
+
+ if (timeoutString != null) {
+ // timeout parameter is specified
+ return Long.valueOf(timeoutString) * 1000;
+ }
+
+ } catch (NumberFormatException e) {
+ logger.error("Invalid value for Drools persistence property persistence.sessioninfo.timeout: {}",
+ timeoutString, e);
+ }
+
+ return -1;
+ }
+
+ /**
+ * Initializes {@link #hostName}.
+ */
+ private void initHostName() {
+
+ try {
+ hostName = fact.getHostName();
+
+ } catch (UnknownHostException e) {
+ throw new RuntimeException("cannot determine local hostname", e);
+ }
+ }
+
+ /* ============================================================ */
+
+ /**
+ * Each instance of this class is a logical extension of a 'PolicyContainer'
+ * instance. Its reference is stored in the 'adjuncts' table within the
+ * 'PolicyContainer', and will be garbage-collected with the container.
+ */
+ protected class ContainerAdjunct {
+ /**
+ * 'PolicyContainer' instance that this adjunct is extending.
+ */
+ private PolicyContainer policyContainer;
+
+ /**
+ * Maps a KIE session name to its data source.
+ */
+ private Map<String,PoolingDataSource> name2ds = new HashMap<>();
+
+ /**
+ * Constructor - initialize a new 'ContainerAdjunct'
+ *
+ * @param policyContainer
+ * the 'PolicyContainer' instance this adjunct is extending
+ */
+ private ContainerAdjunct(PolicyContainer policyContainer) {
+ this.policyContainer = policyContainer;
+ }
+
+ /**
+ * Create a new persistent KieSession. If there is already a
+ * corresponding entry in the database, it is used to initialize the
+ * KieSession. If not, a completely new session is created.
+ *
+ * @param name
+ * the name of the KieSession (which is also the name of the
+ * associated PolicySession)
+ * @param kieBaseName
+ * the name of the 'KieBase' instance containing this session
+ * @return a new KieSession with persistence enabled
+ */
+ private KieSession newPersistentKieSession(String name, String kieBaseName) {
+
+ long desiredSessionId;
+
+ DroolsSessionConnector conn = getDroolsSessionConnector("onapPU");
+
+ desiredSessionId = getSessionId(conn, name);
+
+ logger.info("\n\nThis controller is primary... coming up with session {} \n\n", desiredSessionId);
+
+ // session does not exist -- attempt to create one
+ logger.info("getPolicySession:session does not exist -- attempt to create one with name {}", name);
+
+ System.getProperties().put("java.naming.factory.initial", "bitronix.tm.jndi.BitronixInitialContextFactory");
+
+ Environment env = kieSvcFact.newEnvironment();
+ String dsName = loadDataSource(name);
+
+ configureKieEnv(name, env, dsName);
+
+ KieSessionConfiguration kConf = kieSvcFact.newKieSessionConfiguration();
+
+ KieSession kieSession = (desiredSessionId >= 0 ? loadKieSession(kieBaseName, desiredSessionId, env, kConf)
+ : null);
+
+ if (kieSession == null) {
+ // loadKieSession() returned null or desiredSessionId < 0
+ logger.info("LOADING We cannot load session {}. Going to create a new one", desiredSessionId);
+
+ kieSession = newKieSession(kieBaseName, env);
+ }
+
+ replaceSession(conn, name, kieSession);
+
+ return kieSession;
+ }
+
+ /**
+ * Loads a data source into {@link #name2ds}, if one doesn't exist
+ * yet.
+ * @param sessName session name
+ * @return the unique data source name
+ */
+ private String loadDataSource(String sessName) {
+ PoolingDataSource ds = name2ds.get(sessName);
+
+ if(ds == null) {
+ Properties props = new Properties();
+ addOptProp(props, "URL", persistProps.getProperty(DroolsPersistenceProperties.DB_URL));
+ addOptProp(props, "user", persistProps.getProperty(DroolsPersistenceProperties.DB_USER));
+ addOptProp(props, "password", persistProps.getProperty(DroolsPersistenceProperties.DB_PWD));
+
+ ds = fact.makePoolingDataSource();
+ ds.setUniqueName("jdbc/BitronixJTADataSource/" + sessName);
+ ds.setClassName(persistProps.getProperty(DroolsPersistenceProperties.DB_DATA_SOURCE));
+ ds.setMaxPoolSize(3);
+ ds.setIsolationLevel("SERIALIZABLE");
+ ds.setAllowLocalTransactions(true);
+ ds.getDriverProperties().putAll(props);
+ ds.init();
+
+ name2ds.put(sessName, ds);
+ }
+
+ return ds.getUniqueName();
+ }
+
+ /**
+ * Configures a Kie Environment
+ *
+ * @param name
+ * session name
+ * @param env
+ * environment to be configured
+ * @param dsName
+ * data source name
+ */
+ private void configureKieEnv(String name, Environment env, String dsName) {
+ Properties emfProperties = new Properties();
+ emfProperties.setProperty(PersistenceUnitProperties.JTA_DATASOURCE, dsName);
+
+ EntityManagerFactory emfact = fact.makeEntMgrFact("onapsessionsPU", emfProperties);
+
+ env.set(EnvironmentName.ENTITY_MANAGER_FACTORY, emfact);
+ env.set(EnvironmentName.TRANSACTION_MANAGER, fact.getTransMgr());
+ }
+
+ /**
+ * Loads an existing KieSession from the persistent store.
+ *
+ * @param kieBaseName
+ * the name of the 'KieBase' instance containing this session
+ * @param desiredSessionId
+ * id of the desired KieSession
+ * @param env
+ * Kie Environment for the session
+ * @param kConf
+ * Kie Configuration for the session
+ * @return the persistent session, or {@code null} if it could not be
+ * loaded
+ */
+ private KieSession loadKieSession(String kieBaseName, long desiredSessionId, Environment env,
+ KieSessionConfiguration kConf) {
+ try {
+ KieSession kieSession = kieSvcFact.getStoreServices().loadKieSession(desiredSessionId,
+ policyContainer.getKieContainer().getKieBase(kieBaseName), kConf, env);
+
+ logger.info("LOADING Loaded session {}", desiredSessionId);
+
+ return kieSession;
+
+ } catch (Exception e) {
+ logger.error("loadKieSession error: ", e);
+ return null;
+ }
+ }
+
+ /**
+ * Creates a new, persistent KieSession.
+ *
+ * @param kieBaseName
+ * the name of the 'KieBase' instance containing this session
+ * @param env
+ * Kie Environment for the session
+ * @return a new, persistent session
+ */
+ private KieSession newKieSession(String kieBaseName, Environment env) {
+ KieSession kieSession = kieSvcFact.getStoreServices()
+ .newKieSession(policyContainer.getKieContainer().getKieBase(kieBaseName), null, env);
+
+ logger.info("LOADING CREATED {}", kieSession.getIdentifier());
+
+ return kieSession;
+ }
+
+ /**
+ * Closes the data source associated with a session.
+ * @param name name of the session being destroyed
+ */
+ private void destroyKieSession(String name) {
+ closeDataSource(name);
+ }
+
+ /**
+ * Closes the data source associated with a session.
+ * @param name name of the session being disposed of
+ */
+ private void disposeKieSession(String name) {
+ closeDataSource(name);
+ }
+
+ /**
+ * Closes the data source associated with a session.
+ * @param name name of the session whose data source is to be closed
+ */
+ private void closeDataSource(String name) {
+ PoolingDataSource ds = name2ds.remove(name);
+ if(ds != null) {
+ ds.close();
+ }
+ }
+ }
+
+ /* ============================================================ */
+
+ /**
+ * Removes "old" Drools 'sessioninfo' records, so they aren't used to
+ * restore data to Drools sessions. This also has the useful side-effect of
+ * removing abandoned records as well.
+ */
+ private void cleanUpSessionInfo() {
+
+ synchronized (cleanupLock) {
+
+ if (sessInfoCleaned) {
+ logger.info("Clean up of sessioninfo table: already done");
+ return;
+ }
+
+ if (sessionInfoTimeoutMs < 0) {
+ logger.info("Clean up of sessioninfo table: no timeout specified");
+ return;
+ }
+
+ // get DB connection properties
+ String url = persistProps.getProperty(DroolsPersistenceProperties.DB_URL);
+ String user = persistProps.getProperty(DroolsPersistenceProperties.DB_USER);
+ String password = persistProps.getProperty(DroolsPersistenceProperties.DB_PWD);
+
+ if (url == null || user == null || password == null) {
+ logger.error("Missing DB properties for clean up of sessioninfo table");
+ return;
+ }
+
+ // now do the record deletion
+ try (Connection connection = fact.makeDbConnection(url, user, password);
+ PreparedStatement statement = connection.prepareStatement(
+ "DELETE FROM sessioninfo WHERE timestampdiff(second,lastmodificationdate,now()) > ?")) {
+ statement.setLong(1, sessionInfoTimeoutMs/1000);
+
+ int count = statement.executeUpdate();
+ logger.info("Cleaning up sessioninfo table -- {} records removed", count);
+
+ } catch (SQLException e) {
+ logger.error("Clean up of sessioninfo table failed", e);
+ }
+
+ // TODO: delete DroolsSessionEntity where sessionId not in
+ // (sessinfo.xxx)
+
+ sessInfoCleaned = true;
+ }
+ }
+
+ /**
+ * Gets a connector for manipulating DroolsSession objects within the
+ * persistent store.
+ *
+ * @param pu
+ * @return a connector for DroolsSession objects
+ */
+ private DroolsSessionConnector getDroolsSessionConnector(String pu) {
+
+ Properties propMap = new Properties();
+ addOptProp(propMap, "javax.persistence.jdbc.driver",
+ persistProps.getProperty(DroolsPersistenceProperties.DB_DRIVER));
+ addOptProp(propMap, "javax.persistence.jdbc.url", persistProps.getProperty(DroolsPersistenceProperties.DB_URL));
+ addOptProp(propMap, "javax.persistence.jdbc.user",
+ persistProps.getProperty(DroolsPersistenceProperties.DB_USER));
+ addOptProp(propMap, "javax.persistence.jdbc.password",
+ persistProps.getProperty(DroolsPersistenceProperties.DB_PWD));
+
+ return fact.makeJpaConnector(pu, propMap);
+ }
+
+ /**
+ * Adds an optional property to a set of properties.
+ * @param propMap map into which the property should be added
+ * @param name property name
+ * @param value property value, or {@code null} if it should not
+ * be added
+ */
+ private void addOptProp(Properties propMap, String name, String value) {
+ if (value != null) {
+ propMap.put(name, value);
+ }
+ }
+
+ /**
+ * Gets a session's ID from the persistent store.
+ *
+ * @param conn
+ * persistence connector
+ * @param sessnm
+ * name of the session
+ * @return the session's id, or {@code -1} if the session is not found
+ */
+ private long getSessionId(DroolsSessionConnector conn, String sessnm) {
+ DroolsSession sess = conn.get(sessnm);
+ return (sess != null ? sess.getSessionId() : -1);
+ }
+
+ /**
+ * Replaces a session within the persistent store, if it exists. Adds
+ * it otherwise.
+ *
+ * @param conn
+ * persistence connector
+ * @param sessnm
+ * name of session to be updated
+ * @param kieSession
+ * new session information
+ */
+ private void replaceSession(DroolsSessionConnector conn, String sessnm, KieSession kieSession) {
+
+ DroolsSessionEntity sess = new DroolsSessionEntity();
+
+ sess.setSessionName(sessnm);
+ sess.setSessionId(kieSession.getIdentifier());
+
+ conn.replace(sess);
+ }
+
+ /**
+ * Determine whether persistence is enabled for a specific container
+ *
+ * @param container
+ * container to be checked
+ * @param sessionName
+ * name of the session to be checked
+ * @return {@code true} if persistence is enabled for this container, and
+ * {@code false} if not
+ */
+ private boolean isPersistenceEnabled(PolicyContainer container, String sessionName) {
+ Properties properties = getProperties(container);
+ boolean rval = false;
+
+ if (properties != null) {
+ // fetch the 'type' property
+ String type = getProperty(properties, sessionName, "type");
+ rval = ("auto".equals(type) || "native".equals(type));
+ }
+
+ return (rval);
+ }
+
+ /**
+ * Determine the controller properties associated with the policy container.
+ *
+ * @param container
+ * container whose properties are to be retrieved
+ * @return the container's properties, or {@code null} if not found
+ */
+ private Properties getProperties(PolicyContainer container) {
+ try {
+ return (fact.getPolicyContainer(container).getProperties());
+ } catch (IllegalArgumentException e) {
+ logger.error("getProperties exception: ", e);
+ return (null);
+ }
+ }
+
+ /**
+ * Fetch the persistence property associated with a session. The name may
+ * have the form:
+ * <ul>
+ * <li>persistence.SESSION-NAME.PROPERTY</li>
+ * <li>persistence.PROPERTY</li>
+ * </ul>
+ *
+ * @param properties
+ * properties from which the value is to be retrieved
+ * @param sessionName
+ * session name of interest
+ * @param property
+ * property name of interest
+ * @return the property value, or {@code null} if not found
+ */
+ private String getProperty(Properties properties, String sessionName, String property) {
+ String value = properties.getProperty("persistence." + sessionName + "." + property);
+ if (value == null) {
+ value = properties.getProperty("persistence." + property);
+ }
+
+ return (value);
+ }
+
+ /* ============================================================ */
+
+ /**
+ * This 'ThreadModel' variant periodically calls
+ * 'KieSession.fireAllRules()', because the 'fireUntilHalt' method isn't
+ * compatible with persistence.
+ */
+ public class PersistentThreadModel implements Runnable, PolicySession.ThreadModel {
+
+ /**
+ * Session associated with this persistent thread.
+ */
+ private final PolicySession session;
+
+ /**
+ * The session thread.
+ */
+ private final Thread thread;
+
+ /**
+ * Used to indicate that processing should stop.
+ */
+ private final CountDownLatch stopped = new CountDownLatch(1);
+
+ /**
+ * Minimum time, in milli-seconds, that the thread should sleep
+ * before firing rules again.
+ */
+ long minSleepTime = 100;
+
+ /**
+ * Maximum time, in milli-seconds, that the thread should sleep
+ * before firing rules again. This is a "half" time, so that
+ * we can multiply it by two without overflowing the word size.
+ */
+ long halfMaxSleepTime = 5000 / 2;
+
+ /**
+ * Constructor - initialize variables and create thread
+ *
+ * @param session
+ * the 'PolicySession' instance
+ * @param properties
+ * may contain additional session properties
+ */
+ public PersistentThreadModel(PolicySession session, Properties properties) {
+ this.session = session;
+ this.thread = new Thread(this, getThreadName());
+
+ if (properties == null) {
+ return;
+ }
+
+ // extract 'minSleepTime' and/or 'maxSleepTime'
+ String name = session.getName();
+
+ // fetch 'minSleepTime' value, and update if defined
+ String sleepTimeString = getProperty(properties, name, "minSleepTime");
+ if (sleepTimeString != null) {
+ try {
+ minSleepTime = Math.max(1, Integer.valueOf(sleepTimeString));
+ } catch (Exception e) {
+ logger.error(sleepTimeString + ": Illegal value for 'minSleepTime'", e);
+ }
+ }
+
+ // fetch 'maxSleepTime' value, and update if defined
+ long maxSleepTime = 2 * halfMaxSleepTime;
+ sleepTimeString = getProperty(properties, name, "maxSleepTime");
+ if (sleepTimeString != null) {
+ try {
+ maxSleepTime = Math.max(1, Integer.valueOf(sleepTimeString));
+ } catch (Exception e) {
+ logger.error(sleepTimeString + ": Illegal value for 'maxSleepTime'", e);
+ }
+ }
+
+ // swap values if needed
+ if (minSleepTime > maxSleepTime) {
+ logger.error("minSleepTime(" + minSleepTime + ") is greater than maxSleepTime(" + maxSleepTime
+ + ") -- swapping");
+ long tmp = minSleepTime;
+ minSleepTime = maxSleepTime;
+ maxSleepTime = tmp;
+ }
+
+ halfMaxSleepTime = Math.max(1, maxSleepTime/2);
+ }
+
+ /**
+ * @return the String to use as the thread name
+ */
+ private String getThreadName() {
+ return ("Session " + session.getFullName() + " (persistent)");
+ }
+
+ /***************************/
+ /* 'ThreadModel' interface */
+ /***************************/
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void start() {
+ thread.start();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void stop() {
+ // tell the thread to stop
+ stopped.countDown();
+
+ // wait up to 10 seconds for the thread to stop
+ try {
+ thread.join(10000);
+
+ } catch (InterruptedException e) {
+ logger.error("stopThread exception: ", e);
+ }
+
+ // verify that it's done
+ if(thread.isAlive()) {
+ logger.error("stopThread: still running");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void updated() {
+ // the container artifact has been updated -- adjust the thread name
+ thread.setName(getThreadName());
+ }
+
+ /************************/
+ /* 'Runnable' interface */
+ /************************/
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void run() {
+ logger.info("PersistentThreadModel running");
+
+ // set thread local variable
+ session.setPolicySession();
+
+ KieSession kieSession = session.getKieSession();
+ long sleepTime = 2 * halfMaxSleepTime;
+
+ // We want to continue, despite any exceptions that occur
+ // while rules are fired.
+
+ for(;;) {
+
+ try {
+ if (kieSession.fireAllRules() > 0) {
+ // some rules fired -- reduce poll delay
+ sleepTime = Math.max(minSleepTime, sleepTime/2);
+ } else {
+ // no rules fired -- increase poll delay
+ sleepTime = 2 * Math.min(halfMaxSleepTime, sleepTime);
+ }
+
+ } catch (Throwable e) {
+ logger.error("startThread exception: ", e);
+ }
+
+
+ try {
+ if(stopped.await(sleepTime, TimeUnit.MILLISECONDS)) {
+ break;
+ }
+
+ } catch (InterruptedException e) {
+ logger.error("startThread exception: ", e);
+ break;
+ }
+ }
+
+ logger.info("PersistentThreadModel completed");
+ }
+ }
+
+ /* ============================================================ */
+
+ /**
+ * Factory for various items. Methods can be overridden for junit testing.
+ */
+ protected static class Factory {
+
+ /**
+ * Gets the configuration for the transaction manager.
+ *
+ * @return the configuration for the transaction manager
+ */
+ public Configuration getTransMgrConfig() {
+ return TransactionManagerServices.getConfiguration();
+ }
+
+ /**
+ * Gets the transaction manager.
+ *
+ * @return the transaction manager
+ */
+ public BitronixTransactionManager getTransMgr() {
+ return TransactionManagerServices.getTransactionManager();
+ }
+
+ /**
+ * Gets the KIE services.
+ *
+ * @return the KIE services
+ */
+ public KieServices getKieServices() {
+ return KieServices.Factory.get();
+ }
+
+ /**
+ * Gets the current host name.
+ *
+ * @return the current host name, associated with the IP address of the
+ * local machine
+ * @throws UnknownHostException
+ */
+ public String getHostName() throws UnknownHostException {
+ return InetAddress.getLocalHost().getHostName();
+ }
+
+ /**
+ * Loads properties from a file.
+ *
+ * @param filenm
+ * name of the file to load
+ * @return properties, as loaded from the file
+ * @throws IOException
+ * if an error occurs reading from the file
+ */
+ public Properties loadProperties(String filenm) throws IOException {
+ return PropertyUtil.getProperties(filenm);
+ }
+
+ /**
+ * Makes a connection to the DB.
+ *
+ * @param url
+ * DB URL
+ * @param user
+ * user name
+ * @param pass
+ * password
+ * @return a new DB connection
+ * @throws SQLException
+ */
+ public Connection makeDbConnection(String url, String user, String pass) throws SQLException {
+
+ return DriverManager.getConnection(url, user, pass);
+ }
+
+ /**
+ * Makes a new pooling data source.
+ *
+ * @return a new pooling data source
+ */
+ public PoolingDataSource makePoolingDataSource() {
+ return new PoolingDataSource();
+ }
+
+ /**
+ * Makes a new JPA connector for drools sessions.
+ *
+ * @param pu
+ * PU for the entity manager factory
+ * @param propMap
+ * properties with which the factory should be configured
+ * @return a new JPA connector for drools sessions
+ */
+ public DroolsSessionConnector makeJpaConnector(String pu, Properties propMap) {
+
+ EntityManagerFactory emf = makeEntMgrFact(pu, propMap);
+
+ return new JpaDroolsSessionConnector(emf);
+ }
+
+ /**
+ * Makes a new entity manager factory.
+ *
+ * @param pu
+ * PU for the entity manager factory
+ * @param propMap
+ * properties with which the factory should be configured
+ * @return a new entity manager factory
+ */
+ public EntityManagerFactory makeEntMgrFact(String pu, Properties propMap) {
+ return Persistence.createEntityManagerFactory(pu, propMap);
+ }
+
+ /**
+ * Gets the policy controller associated with a given policy container.
+ *
+ * @param container
+ * container whose controller is to be retrieved
+ * @return the container's controller
+ */
+ public PolicyController getPolicyContainer(PolicyContainer container) {
+ return PolicyController.factory.get(container.getGroupId(), container.getArtifactId());
+ }
+ }
+}
diff --git a/feature-session-persistence/src/main/resources/META-INF/persistence.xml b/feature-session-persistence/src/main/resources/META-INF/persistence.xml
new file mode 100644
index 00000000..7ddd2fd3
--- /dev/null
+++ b/feature-session-persistence/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ============LICENSE_START=======================================================
+ policy-core
+ ================================================================================
+ 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=========================================================
+ -->
+
+<persistence version="2.1"
+ xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
+
+ <persistence-unit name="onapPU" transaction-type="RESOURCE_LOCAL">
+ <!-- This is for database access by non-drools methods -->
+ <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
+ <class>org.onap.policy.drools.persistence.DroolsSessionEntity</class>
+ <class>org.drools.persistence.info.SessionInfo</class>
+ <class>org.drools.persistence.info.WorkItemInfo</class>
+ <properties>
+ <!-- Properties are passed in -->
+ </properties>
+ </persistence-unit>
+
+ <persistence-unit name="onapsessionsPU" transaction-type="JTA">
+ <!-- Used for drools session data access -->
+ <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
+ <class>org.drools.persistence.info.SessionInfo</class>
+ <class>org.drools.persistence.info.WorkItemInfo</class>
+ <properties>
+ <property name="hibernate.dialect" value="org.hibernate.dialect.MariaDBDialect" />
+ <property name="hibernate.max_fetch_depth" value="3" />
+ <property name="hibernate.hbm2ddl.auto" value="update" />
+ <property name="hibernate.show_sql" value="false" />
+ <property name="hibernate.transaction.manager_lookup_class"
+ value="org.hibernate.transaction.BTMTransactionManagerLookup" />
+ </properties>
+ </persistence-unit>
+
+ <persistence-unit name="schemaDroolsPU" transaction-type="RESOURCE_LOCAL">
+ <!-- Limited use for generating the DB and schema files for drools DB - uses eclipselink for convenience -->
+ <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
+ <class>org.onap.policy.drools.persistence.DroolsSessionEntity</class>
+ <class>org.drools.persistence.info.SessionInfo</class>
+ <class>org.drools.persistence.info.WorkItemInfo</class>
+ <properties>
+ <property name="javax.persistence.schema-generation.scripts.action" value="drop-and-create"/>
+ <property name="javax.persistence.schema-generation.scripts.create-target" value="sql/generatedCreateDrools.ddl"/>
+ <property name="javax.persistence.schema-generation.scripts.drop-target" value="sql/generatedDropDrools.ddl"/>
+ </properties>
+ </persistence-unit>
+
+</persistence>
diff --git a/feature-session-persistence/src/main/resources/META-INF/services/org.onap.policy.drools.core.PolicySessionFeatureAPI b/feature-session-persistence/src/main/resources/META-INF/services/org.onap.policy.drools.core.PolicySessionFeatureAPI
new file mode 100644
index 00000000..26366fd3
--- /dev/null
+++ b/feature-session-persistence/src/main/resources/META-INF/services/org.onap.policy.drools.core.PolicySessionFeatureAPI
@@ -0,0 +1 @@
+org.onap.policy.drools.persistence.PersistenceFeature
diff --git a/feature-session-persistence/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyEngineFeatureAPI b/feature-session-persistence/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyEngineFeatureAPI
new file mode 100644
index 00000000..26366fd3
--- /dev/null
+++ b/feature-session-persistence/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyEngineFeatureAPI
@@ -0,0 +1 @@
+org.onap.policy.drools.persistence.PersistenceFeature
diff --git a/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/DroolsSessionEntityTest.java b/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/DroolsSessionEntityTest.java
new file mode 100644
index 00000000..c7fa8486
--- /dev/null
+++ b/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/DroolsSessionEntityTest.java
@@ -0,0 +1,198 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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.onap.policy.drools.persistence;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Date;
+
+import org.junit.Test;
+import org.onap.policy.drools.persistence.DroolsSessionEntity;
+
+public class DroolsSessionEntityTest {
+
+ @Test
+ public void testHashCode() {
+ DroolsSessionEntity e = makeEnt("mynameA", 1);
+
+ DroolsSessionEntity e2 = makeEnt("mynameA", 2);
+
+ // session id is not part of hash code
+ assertTrue(e.hashCode() == e2.hashCode());
+
+ // diff sess name
+ e2 = makeEnt("mynameB", 1);
+ assertTrue(e.hashCode() != e2.hashCode());
+ }
+
+ /**
+ * Ensures that hashCode() functions as expected when the getXxx methods
+ * are overridden.
+ */
+ @Test
+ public void testHashCode_Subclass() {
+ DroolsSessionEntity e = makeEnt2("mynameA", 1);
+
+ DroolsSessionEntity e2 = makeEnt("mynameA", 2);
+
+ // session id is not part of hash code
+ assertTrue(e.hashCode() == e2.hashCode());
+
+ // diff sess name
+ e2 = makeEnt("mynameB", 1);
+ assertTrue(e.hashCode() != e2.hashCode());
+ }
+
+ @Test
+ public void testGetSessionName_testSetSessionName() {
+ DroolsSessionEntity e = makeEnt("mynameZ", 1);
+
+ assertEquals("mynameZ", e.getSessionName());
+
+ e.setSessionName("another");
+ assertEquals("another", e.getSessionName());
+
+ // others unchanged
+ assertEquals(1, e.getSessionId());
+ }
+
+ @Test
+ public void testGetSessionId_testSetSessionId() {
+ DroolsSessionEntity e = makeEnt("mynameA", 1);
+
+ assertEquals(1, e.getSessionId());
+
+ e.setSessionId(20);
+ assertEquals(20, e.getSessionId());
+
+ // others unchanged
+ assertEquals("mynameA", e.getSessionName());
+ }
+
+ @Test
+ public void testGetCreatedDate_testSetCreatedDate_testGetUpdatedDate_testSetUpdatedDate() {
+ DroolsSessionEntity e = new DroolsSessionEntity();
+
+ Date crtdt = new Date(System.currentTimeMillis() - 100);
+ e.setCreatedDate(crtdt);
+
+ Date updt = new Date(System.currentTimeMillis() - 200);
+ e.setUpdatedDate(updt);
+
+ assertEquals(crtdt, e.getCreatedDate());
+ assertEquals(updt, e.getUpdatedDate());
+ }
+
+ @Test
+ public void testEqualsObject() {
+ DroolsSessionEntity e = makeEnt("mynameA", 1);
+
+ // reflexive
+ assertTrue(e.equals(e));
+
+ DroolsSessionEntity e2 = makeEnt("mynameA", 2);
+
+ // session id is not part of hash code
+ assertTrue(e.equals(e2));
+ assertTrue(e.equals(e2));
+
+ // diff sess name
+ e2 = makeEnt("mynameB", 1);
+ assertFalse(e.equals(e2));
+ assertFalse(e.equals(e2));
+ }
+
+ /**
+ * Ensures that equals() functions as expected when the getXxx methods
+ * are overridden.
+ */
+ @Test
+ public void testEqualsObject_Subclass() {
+ DroolsSessionEntity e = makeEnt2("mynameA", 1);
+
+ // reflexive
+ assertTrue(e.equals(e));
+
+ DroolsSessionEntity e2 = makeEnt("mynameA", 2);
+
+ // session id is not part of hash code
+ assertTrue(e.equals(e2));
+ assertTrue(e.equals(e2));
+
+ // diff sess name
+ e2 = makeEnt("mynameB", 1);
+ assertFalse(e.equals(e2));
+ assertFalse(e.equals(e2));
+ }
+
+ @Test
+ public void testToString() {
+ DroolsSessionEntity e = makeEnt("mynameA", 23);
+
+ assertEquals("{name=mynameA, id=23}", e.toString());
+ }
+
+ /**
+ * Makes a session Entity. The parameters are stored into the Entity
+ * object via the setXxx methods.
+ * @param sessnm session name
+ * @param sessid session id
+ * @return a new session Entity
+ */
+ private DroolsSessionEntity makeEnt(String sessnm, long sessid) {
+
+ DroolsSessionEntity e = new DroolsSessionEntity();
+
+ e.setSessionName(sessnm);
+ e.setSessionId(sessid);
+
+ return e;
+ }
+
+ /**
+ * Makes a session Entity that overrides the getXxx methods. The
+ * parameters that are provided are returned by the overridden methods,
+ * but they are <i>not</i> stored into the Entity object via the setXxx
+ * methods.
+ * @param sessnm session name
+ * @param sessid session id
+ * @return a new session Entity
+ */
+ @SuppressWarnings("serial")
+ private DroolsSessionEntity makeEnt2(String sessnm, long sessid) {
+
+ return new DroolsSessionEntity() {
+
+ @Override
+ public String getSessionName() {
+ return sessnm;
+ }
+
+ @Override
+ public long getSessionId() {
+ return sessid;
+ }
+ };
+ }
+
+}
diff --git a/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/EntityMgrCloserTest.java b/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/EntityMgrCloserTest.java
new file mode 100644
index 00000000..7350a7f7
--- /dev/null
+++ b/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/EntityMgrCloserTest.java
@@ -0,0 +1,100 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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.onap.policy.drools.persistence;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import javax.persistence.EntityManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.drools.persistence.EntityMgrCloser;
+
+public class EntityMgrCloserTest {
+
+ private EntityManager mgr;
+
+
+ @Before
+ public void setUp() throws Exception {
+ mgr = mock(EntityManager.class);
+ }
+
+
+ /**
+ * Verifies that the constructor does not do anything extra before
+ * being closed.
+ */
+ @Test
+ public void testEntityMgrCloser() {
+ EntityMgrCloser c = new EntityMgrCloser(mgr);
+
+ // verify not closed yet
+ verify(mgr, never()).close();
+
+ c.close();
+ }
+
+ /**
+ * Verifies that the manager gets closed when close() is invoked.
+ */
+ @Test
+ public void testClose() {
+ EntityMgrCloser c = new EntityMgrCloser(mgr);
+
+ c.close();
+
+ // should be closed
+ verify(mgr).close();
+ }
+
+ /**
+ * Ensures that the manager gets closed when "try" block exits normally.
+ */
+ @Test
+ public void testClose_TryWithoutExcept() {
+ try(EntityMgrCloser c = new EntityMgrCloser(mgr)) {
+
+ }
+
+ verify(mgr).close();
+ }
+
+ /**
+ * Ensures that the manager gets closed when "try" block throws an
+ * exception.
+ */
+ @Test
+ public void testClose_TryWithExcept() {
+ try {
+ try(EntityMgrCloser c = new EntityMgrCloser(mgr)) {
+ throw new Exception("expected exception");
+ }
+
+ } catch (Exception e) {
+ }
+
+ verify(mgr).close();
+ }
+
+}
diff --git a/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/EntityMgrTransTest.java b/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/EntityMgrTransTest.java
new file mode 100644
index 00000000..0165b1e4
--- /dev/null
+++ b/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/EntityMgrTransTest.java
@@ -0,0 +1,232 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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.onap.policy.drools.persistence;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityTransaction;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.drools.persistence.EntityMgrTrans;
+
+public class EntityMgrTransTest {
+
+ private EntityTransaction trans;
+ private EntityManager mgr;
+
+ @Before
+ public void setUp() throws Exception {
+ trans = mock(EntityTransaction.class);
+ mgr = mock(EntityManager.class);
+
+ when(mgr.getTransaction()).thenReturn(trans);
+ }
+
+
+
+ /**
+ * Verifies that the constructor starts a transaction, but does not do
+ * anything extra before being closed.
+ */
+ @Test
+ public void testEntityMgrTrans() {
+ EntityMgrTrans t = new EntityMgrTrans(mgr);
+
+ // verify that transaction was started
+ verify(trans).begin();
+
+ // verify not closed, committed, or rolled back yet
+ verify(trans, never()).commit();
+ verify(trans, never()).rollback();
+ verify(mgr, never()).close();
+
+ t.close();
+ }
+
+ /**
+ * Verifies that the transaction is rolled back and the manager is
+ * closed when and a transaction is active.
+ */
+ @Test
+ public void testClose_Active() {
+ EntityMgrTrans t = new EntityMgrTrans(mgr);
+
+ when(trans.isActive()).thenReturn(true);
+
+ t.close();
+
+ // closed and rolled back, but not committed
+ verify(trans, never()).commit();
+ verify(trans).rollback();
+ verify(mgr).close();
+ }
+
+ /**
+ * Verifies that the manager is closed, but that the transaction is
+ * <i>not</i> rolled back and when and no transaction is active.
+ */
+ @Test
+ public void testClose_Inactive() {
+ EntityMgrTrans t = new EntityMgrTrans(mgr);
+
+ when(trans.isActive()).thenReturn(false);
+
+ t.close();
+
+ // closed, but not committed or rolled back
+ verify(mgr).close();
+ verify(trans, never()).commit();
+ verify(trans, never()).rollback();
+ }
+
+ /**
+ * Verifies that the manager is closed and the transaction rolled back
+ * when "try" block exits normally and a transaction is active.
+ */
+ @Test
+ public void testClose_TryWithoutExcept_Active() {
+ when(trans.isActive()).thenReturn(true);
+
+ try(EntityMgrTrans t = new EntityMgrTrans(mgr)) {
+
+ }
+
+ // closed and rolled back, but not committed
+ verify(trans, never()).commit();
+ verify(trans).rollback();
+ verify(mgr).close();
+ }
+
+ /**
+ * Verifies that the manager is closed, but that the transaction is
+ * <i>not</i> rolled back when "try" block exits normally and no
+ * transaction is active.
+ */
+ @Test
+ public void testClose_TryWithoutExcept_Inactive() {
+ when(trans.isActive()).thenReturn(false);
+
+ try(EntityMgrTrans t = new EntityMgrTrans(mgr)) {
+
+ }
+
+ // closed, but not rolled back or committed
+ verify(trans, never()).commit();
+ verify(trans, never()).rollback();
+ verify(mgr).close();
+ }
+
+ /**
+ * Verifies that the manager is closed and the transaction rolled back
+ * when "try" block throws an exception and a transaction is active.
+ */
+ @Test
+ public void testClose_TryWithExcept_Active() {
+ when(trans.isActive()).thenReturn(true);
+
+ try {
+ try(EntityMgrTrans t = new EntityMgrTrans(mgr)) {
+ throw new Exception("expected exception");
+ }
+
+ } catch (Exception e) {
+ }
+
+ // closed and rolled back, but not committed
+ verify(trans, never()).commit();
+ verify(trans).rollback();
+ verify(mgr).close();
+ }
+
+ /**
+ * Verifies that the manager is closed, but that the transaction is
+ * <i>not</i> rolled back when "try" block throws an exception and no
+ * transaction is active.
+ */
+ @Test
+ public void testClose_TryWithExcept_Inactive() {
+ when(trans.isActive()).thenReturn(false);
+
+ try {
+ try(EntityMgrTrans t = new EntityMgrTrans(mgr)) {
+ throw new Exception("expected exception");
+ }
+
+ } catch (Exception e) {
+ }
+
+ // closed, but not rolled back or committed
+ verify(trans, never()).commit();
+ verify(trans, never()).rollback();
+ verify(mgr).close();
+ }
+
+ /**
+ * Verifies that commit() only commits, and that the subsequent close()
+ * does not re-commit.
+ */
+ @Test
+ public void testCommit() {
+ EntityMgrTrans t = new EntityMgrTrans(mgr);
+
+ t.commit();
+
+ // committed, but not closed or rolled back
+ verify(trans).commit();
+ verify(trans, never()).rollback();
+ verify(mgr, never()).close();
+
+ // closed, but not re-committed
+ t.close();
+
+ verify(trans, times(1)).commit();
+ verify(mgr).close();
+ }
+
+ /**
+ * Verifies that rollback() only rolls back, and that the subsequent
+ * close() does not re-roll back.
+ */
+ @Test
+ public void testRollback() {
+ EntityMgrTrans t = new EntityMgrTrans(mgr);
+
+ t.rollback();
+
+ // rolled back, but not closed or committed
+ verify(trans, never()).commit();
+ verify(trans).rollback();
+ verify(mgr, never()).close();
+
+ // closed, but not re-rolled back
+ t.close();
+
+ verify(trans, times(1)).rollback();
+ verify(mgr).close();
+ }
+
+}
diff --git a/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/GenSchemaTest.java b/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/GenSchemaTest.java
new file mode 100644
index 00000000..b58c22c6
--- /dev/null
+++ b/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/GenSchemaTest.java
@@ -0,0 +1,58 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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.onap.policy.drools.persistence;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+
+import org.junit.Test;
+
+/**
+ * Generates the schema DDL files.
+ */
+public class GenSchemaTest {
+
+ private EntityManagerFactory emf;
+
+
+ /*
+ * This is a JUnit which is provided as a utility for producing a basic
+ * ddl schema file in the sql directory.
+ *
+ * To run this simple add @Test ahead of the method and then run this
+ * as a JUnit.
+ */
+ public void generate() throws Exception {
+ Map<String, Object> propMap = new HashMap<>();
+
+ propMap.put("javax.persistence.jdbc.driver", "org.h2.Driver");
+ propMap.put("javax.persistence.jdbc.url",
+ "jdbc:h2:mem:JpaDroolsSessionConnectorTest");
+
+ emf = Persistence.createEntityManagerFactory(
+ "schemaDroolsPU", propMap);
+
+ emf.close();
+ }
+}
diff --git a/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/JpaDroolsSessionConnectorTest.java b/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/JpaDroolsSessionConnectorTest.java
new file mode 100644
index 00000000..c16a1bbd
--- /dev/null
+++ b/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/JpaDroolsSessionConnectorTest.java
@@ -0,0 +1,160 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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.onap.policy.drools.persistence;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.drools.persistence.DroolsSessionEntity;
+import org.onap.policy.drools.persistence.EntityMgrTrans;
+import org.onap.policy.drools.persistence.JpaDroolsSessionConnector;
+
+public class JpaDroolsSessionConnectorTest {
+
+ private EntityManagerFactory emf;
+ private JpaDroolsSessionConnector conn;
+
+
+ @Before
+ public void setUp() throws Exception {
+ Map<String, Object> propMap = new HashMap<>();
+
+ propMap.put("javax.persistence.jdbc.driver", "org.h2.Driver");
+ propMap.put("javax.persistence.jdbc.url",
+ "jdbc:h2:mem:JpaDroolsSessionConnectorTest");
+
+ emf = Persistence.createEntityManagerFactory(
+ "junitDroolsSessionEntityPU", propMap);
+
+ conn = new JpaDroolsSessionConnector(emf);
+ }
+
+ @After
+ public void tearDown() {
+ // this will cause the memory db to be dropped
+ emf.close();
+ }
+
+ @Test
+ public void testGet() {
+ /*
+ * Load up the DB with some data.
+ */
+
+ addSession("nameA", 10);
+ addSession("nameY", 20);
+
+
+ /*
+ * Now test the functionality.
+ */
+
+ // not found
+ assertNull( conn.get("unknown"));
+
+ assertEquals("{name=nameA, id=10}",
+ conn.get("nameA").toString());
+
+ assertEquals("{name=nameY, id=20}",
+ conn.get("nameY").toString());
+ }
+
+ @Test
+ public void testReplace_Existing() {
+ addSession("nameA", 10);
+
+ DroolsSessionEntity sess =
+ new DroolsSessionEntity("nameA", 30);
+
+ conn.replace(sess);
+
+ // id should be changed
+ assertEquals(sess.toString(),
+ conn.get("nameA").toString());
+ }
+
+ @Test
+ public void testReplace_New() {
+ DroolsSessionEntity sess =
+ new DroolsSessionEntity("nameA", 30);
+
+ conn.replace(sess);
+
+ assertEquals(sess.toString(),
+ conn.get("nameA").toString());
+ }
+
+ @Test
+ public void testAdd() {
+ DroolsSessionEntity sess =
+ new DroolsSessionEntity("nameA", 30);
+
+ conn.replace(sess);
+
+ assertEquals(sess.toString(),
+ conn.get("nameA").toString());
+ }
+
+ @Test
+ public void testUpdate() {
+ addSession("nameA", 10);
+
+ DroolsSessionEntity sess =
+ new DroolsSessionEntity("nameA", 30);
+
+ conn.replace(sess);
+
+ // id should be changed
+ assertEquals("{name=nameA, id=30}",
+ conn.get("nameA").toString());
+ }
+
+
+ /**
+ * Adds a session to the DB.
+ * @param sessnm session name
+ * @param sessid session id
+ */
+ private void addSession(String sessnm, int sessid) {
+ EntityManager em = emf.createEntityManager();
+
+ try(EntityMgrTrans trans = new EntityMgrTrans(em)) {
+ DroolsSessionEntity ent = new DroolsSessionEntity();
+
+ ent.setSessionName(sessnm);
+ ent.setSessionId(sessid);
+
+ em.persist(ent);
+
+ trans.commit();
+ }
+ }
+}
diff --git a/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/PersistenceFeatureTest.java b/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/PersistenceFeatureTest.java
new file mode 100644
index 00000000..e73031dd
--- /dev/null
+++ b/feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/PersistenceFeatureTest.java
@@ -0,0 +1,1291 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * feature-session-persistence
+ * ================================================================================
+ * 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.onap.policy.drools.persistence;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import javax.persistence.EntityManagerFactory;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.kie.api.KieBase;
+import org.kie.api.KieServices;
+import org.kie.api.persistence.jpa.KieStoreServices;
+import org.kie.api.runtime.Environment;
+import org.kie.api.runtime.EnvironmentName;
+import org.kie.api.runtime.KieContainer;
+import org.kie.api.runtime.KieSession;
+import org.kie.api.runtime.KieSessionConfiguration;
+import org.mockito.ArgumentCaptor;
+import org.onap.policy.drools.persistence.DroolsPersistenceProperties;
+import org.onap.policy.drools.persistence.DroolsSession;
+import org.onap.policy.drools.persistence.DroolsSessionConnector;
+import org.onap.policy.drools.persistence.PersistenceFeature;
+import org.onap.policy.drools.core.PolicyContainer;
+import org.onap.policy.drools.core.PolicySession;
+import org.onap.policy.drools.system.PolicyController;
+
+import bitronix.tm.BitronixTransactionManager;
+import bitronix.tm.Configuration;
+import bitronix.tm.resource.jdbc.PoolingDataSource;
+
+public class PersistenceFeatureTest {
+
+ private static final String JDBC_DATASRC = "fake.datasource";
+ private static final String JDBC_DRIVER = "fake.driver";
+ private static final String JDBC_URL = "fake.url";
+ private static final String JDBC_USER = "fake.user";
+ private static final String JDBC_PASSWD = "fake.password";
+ private static final String SRC_TEST_RESOURCES = "src/test/resources";
+
+ private static Properties stdprops;
+
+ private DroolsSessionConnector jpa;
+ private DroolsSession sess;
+ private PoolingDataSource pds;
+ private KieSession kiesess;
+ private Properties dsprops;
+ private EntityManagerFactory emf;
+ private Connection conn;
+ private Properties props;
+ private KieServices kiesvc;
+ private Environment kieenv;
+ private KieSessionConfiguration kiecfg;
+ private KieBase kiebase;
+ private KieStoreServices kiestore;
+ private KieContainer kiecont;
+ private Configuration bitcfg;
+ private BitronixTransactionManager bittrans;
+ private PolicyController polctlr;
+ private PolicyContainer polcont;
+ private PolicySession polsess;
+ private PersistenceFeature.Factory fact;
+
+ private PersistenceFeature feat;
+
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ stdprops = new Properties();
+
+ stdprops.put(DroolsPersistenceProperties.DB_DATA_SOURCE, JDBC_DATASRC);
+ stdprops.put(DroolsPersistenceProperties.DB_DRIVER, JDBC_DRIVER);
+ stdprops.put(DroolsPersistenceProperties.DB_URL, JDBC_URL);
+ stdprops.put(DroolsPersistenceProperties.DB_USER, JDBC_USER);
+ stdprops.put(DroolsPersistenceProperties.DB_PWD, JDBC_PASSWD);
+ stdprops.put(DroolsPersistenceProperties.DB_SESSIONINFO_TIMEOUT, "50");
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ jpa = mock(DroolsSessionConnector.class);
+ sess = mock(DroolsSession.class);
+ pds = mock(PoolingDataSource.class);
+ kiesess = mock(KieSession.class);
+ dsprops = new Properties();
+ emf = null;
+ conn = null;
+ props = new Properties();
+ kiesvc = mock(KieServices.class);
+ kieenv = mock(Environment.class);
+ kiecfg = mock(KieSessionConfiguration.class);
+ kiebase = mock(KieBase.class);
+ kiestore = mock(KieStoreServices.class);
+ kiecont = mock(KieContainer.class);
+ bitcfg = mock(Configuration.class);
+ bittrans = mock(BitronixTransactionManager.class);
+ polcont = mock(PolicyContainer.class);
+ polctlr = mock(PolicyController.class);
+ polsess = mock(PolicySession.class);
+ fact = mock(PersistenceFeature.Factory.class);
+
+ feat = new PersistenceFeature();
+ feat.setFactory(fact);
+
+ props.putAll(stdprops);
+
+ when(pds.getUniqueName()).thenReturn("myds");
+
+ when(fact.getKieServices()).thenReturn(kiesvc);
+ when(fact.getTransMgrConfig()).thenReturn(bitcfg);
+ when(fact.getTransMgr()).thenReturn(bittrans);
+ when(fact.loadProperties(anyString())).thenReturn(props);
+
+ when(kiesvc.newEnvironment()).thenReturn(kieenv);
+ when(kiesvc.getStoreServices()).thenReturn(kiestore);
+ when(kiesvc.newKieSessionConfiguration()).thenReturn(kiecfg);
+
+ when(polcont.getKieContainer()).thenReturn(kiecont);
+
+ when(polsess.getPolicyContainer()).thenReturn(polcont);
+
+ when(kiecont.getKieBase(anyString())).thenReturn(kiebase);
+ }
+
+ @After
+ public void tearDown() {
+ // this will cause the in-memory test DB to be dropped
+ if(conn != null) {
+ try { conn.close(); } catch (SQLException e) { }
+ }
+
+ if(emf != null) {
+ try { emf.close(); } catch (Exception e) { }
+ }
+ }
+
+ @Test
+ public void testGetContainerAdjunct_New() throws Exception {
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+ // force getContainerAdjunct() to be invoked
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ ArgumentCaptor<PersistenceFeature.ContainerAdjunct> adjcap =
+ ArgumentCaptor.forClass(PersistenceFeature.ContainerAdjunct.class);
+
+ verify(polcont, times(1)).setAdjunct(any(), adjcap.capture());
+
+ assertNotNull( adjcap.getValue());
+ }
+
+ @Test
+ public void testGetContainerAdjunct_Existing() throws Exception {
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+ // force getContainerAdjunct() to be invoked
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ ArgumentCaptor<PersistenceFeature.ContainerAdjunct> adjcap =
+ ArgumentCaptor.forClass(PersistenceFeature.ContainerAdjunct.class);
+
+ verify(polcont, times(1)).setAdjunct(any(), adjcap.capture());
+
+ // return adjunct on next call
+ when(polcont.getAdjunct(any())).thenReturn( adjcap.getValue());
+
+ // force getContainerAdjunct() to be invoked again
+ setUpKie("myname2", 999L, true);
+ feat.activatePolicySession(polcont, "myname2", "mybase");
+
+ // ensure it isn't invoked again
+ verify(polcont, times(1)).setAdjunct(any(), any());
+ }
+
+ @Test
+ public void testGetSequenceNumber() {
+ assertEquals(1, feat.getSequenceNumber());
+ }
+
+ @Test
+ public void testGlobalInit() throws Exception {
+ when(fact.getHostName()).thenReturn("myhost");
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ // verify that various factory methods were invoked
+ verify(fact).getHostName();
+ verify(fact).getKieServices();
+ verify(fact).getTransMgrConfig();
+ verify(fact).loadProperties("src/test/resources/feature-session-persistence.properties");
+
+ verify(bitcfg).setJournal(null);
+ verify(bitcfg).setServerId("myhost");
+ }
+
+ @Test
+ public void testActivatePolicySession() throws Exception {
+ PreparedStatement ps = mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+ feat.beforeActivate(null);
+
+ KieSession s =
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(kiestore).loadKieSession(anyLong(), any(), any(), any());
+ verify(kiestore, never()).newKieSession(any(), any(), any());
+
+ assertEquals(s, kiesess);
+
+ verify(ps).executeUpdate();
+
+ verify(kieenv, times(2)).set(anyString(), any());
+ verify(pds).init();
+ assertFalse( dsprops.isEmpty());
+
+ verify(jpa).get("myname");
+ verify(jpa).replace(any());
+ }
+
+ @Test
+ public void testActivatePolicySession_NoPersistence() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ PreparedStatement ps = mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+ props.remove("persistence.type");
+
+ feat.beforeStart(null);
+
+ assertNull( feat.activatePolicySession(polcont, "myname", "mybase"));
+
+ verify(ps, never()).executeUpdate();
+ verify(kiestore, never()).loadKieSession(anyLong(), any(), any(), any());
+ verify(kiestore, never()).newKieSession(any(), any(), any());
+ }
+
+ /**
+ * Verifies that a new KIE session is created when there is no existing
+ * session entity.
+ */
+ @Test
+ public void testActivatePolicySession_New() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("noName", 999L, true);
+
+
+ KieSession s =
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(kiestore, never()).loadKieSession(anyLong(), any(), any(), any());
+ verify(kiestore).newKieSession(any(), any(), any());
+
+ assertEquals(s, kiesess);
+
+ verify(kieenv, times(2)).set(anyString(), any());
+ verify(pds).init();
+ assertFalse( dsprops.isEmpty());
+
+ verify(jpa).get("myname");
+ verify(jpa).replace(any());
+ }
+
+ /**
+ * Verifies that a new KIE session is created when there KIE fails
+ * to load an existing session.
+ */
+ @Test
+ public void testActivatePolicySession_LoadFailed() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, false);
+
+
+ KieSession s =
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(kiestore).loadKieSession(anyLong(), any(), any(), any());
+ verify(kiestore).newKieSession(any(), any(), any());
+
+ assertEquals(s, kiesess);
+
+ verify(kieenv, times(2)).set(anyString(), any());
+ verify(pds).init();
+ assertFalse( dsprops.isEmpty());
+
+ verify(jpa).get("myname");
+
+ ArgumentCaptor<DroolsSession> d =
+ ArgumentCaptor.forClass(DroolsSession.class);
+ verify(jpa).replace( d.capture());
+
+ assertEquals("myname", d.getValue().getSessionName());
+ assertEquals(100L, d.getValue().getSessionId());
+ }
+
+ @Test
+ public void testConfigureKieEnv() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, false);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(kieenv, times(2)).set(any(), any());
+
+ verify(kieenv).set(EnvironmentName.ENTITY_MANAGER_FACTORY, emf);
+ verify(kieenv).set(EnvironmentName.TRANSACTION_MANAGER, bittrans);
+ }
+
+ @Test
+ public void testInitDataSource() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, false);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ assertEquals(JDBC_URL, dsprops.getProperty("URL"));
+ assertEquals(JDBC_USER, dsprops.getProperty("user"));
+ assertEquals(JDBC_PASSWD, dsprops.getProperty("password"));
+
+ verify(pds).setUniqueName("jdbc/BitronixJTADataSource/myname");
+ verify(pds).setClassName(JDBC_DATASRC);
+ verify(pds).setMaxPoolSize(anyInt());
+ verify(pds).setIsolationLevel("SERIALIZABLE");
+ verify(pds).setAllowLocalTransactions(true);
+ verify(pds).init();
+ }
+
+ @Test
+ public void testLoadKieSession() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+
+ KieSession s =
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(kiestore).loadKieSession(999L, kiebase, kiecfg, kieenv);
+ verify(kiestore, never()).newKieSession(any(), any(), any());
+
+ assertEquals(s, kiesess);
+ }
+
+ /*
+ * Verifies that loadKieSession() returns null (thus causing newKieSession()
+ * to be called) when an Exception occurs.
+ */
+ @Test
+ public void testLoadKieSession_Ex() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, false);
+
+ when(kiestore.loadKieSession(anyLong(), any(), any(), any()))
+ .thenThrow( new RuntimeException("expected exception"));
+
+
+ KieSession s =
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(kiestore).loadKieSession(anyLong(), any(), any(), any());
+ verify(kiestore).newKieSession(any(), any(), any());
+
+ assertEquals(s, kiesess);
+ }
+
+ @Test
+ public void testNewKieSession() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, false);
+
+
+ KieSession s =
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(kiestore).newKieSession(kiebase, null, kieenv);
+
+ assertEquals(s, kiesess);
+ }
+
+ @Test
+ public void testLoadDataSource_RepeatSameSession() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, false);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ ArgumentCaptor<PersistenceFeature.ContainerAdjunct> adjcap =
+ ArgumentCaptor.forClass(PersistenceFeature.ContainerAdjunct.class);
+
+ verify(polcont).setAdjunct(any(), adjcap.capture());
+
+ when(polcont.getAdjunct(any())).thenReturn( adjcap.getValue());
+
+ // invoke it again
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(fact, times(1)).makePoolingDataSource();
+ }
+
+ @Test
+ public void testLoadDataSource_DiffSession() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, false);
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ ArgumentCaptor<PersistenceFeature.ContainerAdjunct> adjcap =
+ ArgumentCaptor.forClass(PersistenceFeature.ContainerAdjunct.class);
+
+ verify(polcont).setAdjunct(any(), adjcap.capture());
+
+ when(polcont.getAdjunct(any())).thenReturn( adjcap.getValue());
+
+ setUpKie("myname2", 999L, false);
+
+ // invoke it again
+ feat.activatePolicySession(polcont, "myname2", "mybase");
+
+ verify(fact, times(2)).makePoolingDataSource();
+ }
+
+ @Test
+ public void testDisposeKieSession() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ ArgumentCaptor<PersistenceFeature.ContainerAdjunct> adjcap =
+ ArgumentCaptor.forClass(PersistenceFeature.ContainerAdjunct.class);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, false);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(pds, never()).close();
+ verify(polcont).setAdjunct(any(), adjcap.capture());
+
+ when(polcont.getAdjunct(any())).thenReturn( adjcap.getValue());
+
+ feat.disposeKieSession(polsess);
+
+ // call twice to ensure it isn't re-closed
+ feat.disposeKieSession(polsess);
+
+ verify(pds, times(1)).close();
+ }
+
+ @Test
+ public void testDisposeKieSession_NoAdjunct() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ feat.disposeKieSession(polsess);
+ }
+
+ @Test
+ public void testDisposeKieSession_NoPersistence() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ ArgumentCaptor<PersistenceFeature.ContainerAdjunct> adjcap =
+ ArgumentCaptor.forClass(PersistenceFeature.ContainerAdjunct.class);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, false);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(pds, never()).close();
+ verify(polcont).setAdjunct(any(), adjcap.capture());
+
+ when(polcont.getAdjunct(any())).thenReturn( adjcap.getValue());
+
+ // specify a session that was never loaded
+ when(polsess.getName()).thenReturn("anotherName");
+
+ feat.disposeKieSession(polsess);
+
+ verify(pds, never()).close();
+ }
+
+ @Test
+ public void testDestroyKieSession() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ ArgumentCaptor<PersistenceFeature.ContainerAdjunct> adjcap =
+ ArgumentCaptor.forClass(PersistenceFeature.ContainerAdjunct.class);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, false);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(pds, never()).close();
+ verify(polcont).setAdjunct(any(), adjcap.capture());
+
+ when(polcont.getAdjunct(any())).thenReturn( adjcap.getValue());
+
+ feat.destroyKieSession(polsess);
+
+ // call twice to ensure it isn't re-closed
+ feat.destroyKieSession(polsess);
+
+ verify(pds, times(1)).close();
+ }
+
+ @Test
+ public void testDestroyKieSession_NoAdjunct() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ feat.destroyKieSession(polsess);
+ }
+
+ @Test
+ public void testDestroyKieSession_NoPersistence() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ ArgumentCaptor<PersistenceFeature.ContainerAdjunct> adjcap =
+ ArgumentCaptor.forClass(PersistenceFeature.ContainerAdjunct.class);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, false);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(pds, never()).close();
+ verify(polcont).setAdjunct(any(), adjcap.capture());
+
+ when(polcont.getAdjunct(any())).thenReturn( adjcap.getValue());
+
+ // specify a session that was never loaded
+ when(polsess.getName()).thenReturn("anotherName");
+
+ feat.destroyKieSession(polsess);
+
+ verify(pds, never()).close();
+ }
+
+ @Test
+ public void testAfterStart() {
+ assertFalse( feat.afterStart(null));
+ }
+
+ @Test
+ public void testBeforeStart() {
+ assertFalse( feat.beforeStart(null));
+ }
+
+ @Test
+ public void testBeforeShutdown() {
+ assertFalse( feat.beforeShutdown(null));
+ }
+
+ @Test
+ public void testAfterShutdown() {
+ assertFalse( feat.afterShutdown(null));
+ }
+
+ @Test
+ public void testBeforeConfigure() {
+ assertFalse( feat.beforeConfigure(null, null));
+ }
+
+ @Test
+ public void testAfterConfigure() {
+ assertFalse( feat.afterConfigure(null));
+ }
+
+ @Test
+ public void testBeforeActivate() {
+ assertFalse( feat.beforeActivate(null));
+ }
+
+ @Test
+ public void testAfterActivate() {
+ assertFalse( feat.afterActivate(null));
+ }
+
+ @Test
+ public void testBeforeDeactivate() {
+ assertFalse( feat.beforeDeactivate(null));
+ }
+
+ @Test
+ public void testAfterDeactivate() {
+ assertFalse( feat.afterDeactivate(null));
+ }
+
+ @Test
+ public void testBeforeStop() {
+ assertFalse( feat.beforeStop(null));
+ }
+
+ @Test
+ public void testAfterStop() {
+ assertFalse( feat.afterStop(null));
+ }
+
+ @Test
+ public void testBeforeLock() {
+ assertFalse( feat.beforeLock(null));
+ }
+
+ @Test
+ public void testAfterLock() {
+ assertFalse( feat.afterLock(null));
+ }
+
+ @Test
+ public void testBeforeUnlock() {
+ assertFalse( feat.beforeUnlock(null));
+ }
+
+ @Test
+ public void testAfterUnlock() {
+ assertFalse( feat.afterUnlock(null));
+ }
+
+ @Test
+ public void testGetPersistenceTimeout_Valid() throws Exception {
+ PreparedStatement s = mockDbConn(5);
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ setUpKie("myname", 999L, true);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(s).executeUpdate();
+ }
+
+ @Test
+ public void testGetPersistenceTimeout_Missing() throws Exception {
+
+ props.remove(DroolsPersistenceProperties.DB_SESSIONINFO_TIMEOUT);
+
+ PreparedStatement s = mockDbConn(0);
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ setUpKie("myname", 999L, true);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(s, never()).executeUpdate();
+ }
+
+ @Test
+ public void testGetPersistenceTimeout_Invalid() throws Exception {
+ props.setProperty(DroolsPersistenceProperties.DB_SESSIONINFO_TIMEOUT, "abc");
+ PreparedStatement s = mockDbConn(0);
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ setUpKie("myname", 999L, true);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(s, never()).executeUpdate();
+ }
+
+ @Test
+ public void testInitHostName() throws Exception {
+ when(fact.getHostName()).thenReturn("myhost");
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ verify(bitcfg).setServerId("myhost");
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void testInitHostName_Ex() throws Exception {
+ when(fact.getHostName())
+ .thenThrow(
+ new UnknownHostException("expected exception"));
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+ }
+
+ @Test
+ public void testCleanUpSessionInfo() throws Exception {
+ setUpKie("myname", 999L, true);
+
+ // use a real DB so we can verify that the "delete" works correctly
+ fact = new PartialFactory();
+ feat.setFactory(fact);
+
+ makeSessionInfoTbl(20000);
+
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ feat.beforeStart(null);
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ assertEquals("[1, 4, 5]", getSessions().toString());
+ }
+
+ @Test
+ public void testCleanUpSessionInfo_WithBeforeStart() throws Exception {
+ PreparedStatement s = mockDbConn(0);
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ setUpKie("myname", 999L, true);
+
+ // reset
+ feat.beforeStart(null);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+ verify(s, times(1)).executeUpdate();
+
+ // should not clean-up again
+ feat.activatePolicySession(polcont, "myname", "mybase");
+ feat.activatePolicySession(polcont, "myname", "mybase");
+ verify(s, times(1)).executeUpdate();
+
+
+ // reset
+ feat.beforeStart(null);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+ verify(s, times(2)).executeUpdate();
+
+ // should not clean-up again
+ feat.activatePolicySession(polcont, "myname", "mybase");
+ feat.activatePolicySession(polcont, "myname", "mybase");
+ verify(s, times(2)).executeUpdate();
+ }
+
+ @Test
+ public void testCleanUpSessionInfo_WithBeforeActivate() throws Exception {
+ PreparedStatement s = mockDbConn(0);
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ setUpKie("myname", 999L, true);
+
+ // reset
+ feat.beforeActivate(null);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+ verify(s, times(1)).executeUpdate();
+
+ // should not clean-up again
+ feat.activatePolicySession(polcont, "myname", "mybase");
+ feat.activatePolicySession(polcont, "myname", "mybase");
+ verify(s, times(1)).executeUpdate();
+
+
+ // reset
+ feat.beforeActivate(null);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+ verify(s, times(2)).executeUpdate();
+
+ // should not clean-up again
+ feat.activatePolicySession(polcont, "myname", "mybase");
+ feat.activatePolicySession(polcont, "myname", "mybase");
+ verify(s, times(2)).executeUpdate();
+ }
+
+ @Test
+ public void testCleanUpSessionInfo_NoTimeout() throws Exception {
+
+ props.remove(DroolsPersistenceProperties.DB_SESSIONINFO_TIMEOUT);
+
+ PreparedStatement s = mockDbConn(0);
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ setUpKie("myname", 999L, true);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(s, never()).executeUpdate();
+ }
+
+ @Test
+ public void testCleanUpSessionInfo_NoUrl() throws Exception {
+ PreparedStatement s = mockDbConn(0);
+
+ props.remove(DroolsPersistenceProperties.DB_URL);
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ setUpKie("myname", 999L, true);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(s, never()).executeUpdate();
+ }
+
+ @Test
+ public void testCleanUpSessionInfo_NoUser() throws Exception {
+ PreparedStatement s = mockDbConn(0);
+
+ props.remove(DroolsPersistenceProperties.DB_USER);
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ setUpKie("myname", 999L, true);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(s, never()).executeUpdate();
+ }
+
+ @Test
+ public void testCleanUpSessionInfo_NoPassword() throws Exception {
+ PreparedStatement s = mockDbConn(0);
+
+ props.remove(DroolsPersistenceProperties.DB_PWD);
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ setUpKie("myname", 999L, true);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(s, never()).executeUpdate();
+ }
+
+ @Test
+ public void testCleanUpSessionInfo_SqlEx() throws Exception {
+ PreparedStatement s = mockDbConn(-1);
+
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ setUpKie("myname", 999L, true);
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(s).executeUpdate();
+ }
+
+ @Test
+ public void testGetDroolsSessionConnector() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+
+ ArgumentCaptor<Properties> propcap =
+ ArgumentCaptor.forClass(Properties.class);
+
+ verify(fact).makeJpaConnector(anyString(), propcap.capture());
+
+ Properties p = propcap.getValue();
+ assertNotNull(p);
+
+ assertEquals(JDBC_DRIVER,
+ p.getProperty("javax.persistence.jdbc.driver"));
+
+ assertEquals(JDBC_URL,
+ p.getProperty("javax.persistence.jdbc.url"));
+
+ assertEquals(JDBC_USER,
+ p.getProperty("javax.persistence.jdbc.user"));
+
+ assertEquals(JDBC_PASSWD,
+ p.getProperty("javax.persistence.jdbc.password"));
+ }
+
+ @Test
+ public void testReplaceSession() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ ArgumentCaptor<DroolsSession> sesscap =
+ ArgumentCaptor.forClass(DroolsSession.class);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+
+ feat.activatePolicySession(polcont, "myname", "mybase");
+
+ verify(jpa).replace( sesscap.capture());
+
+ assertEquals("myname", sesscap.getValue().getSessionName());
+ assertEquals(999L, sesscap.getValue().getSessionId());
+ }
+
+ @Test
+ public void testIsPersistenceEnabled_Auto() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+ props.setProperty("persistence.type", "auto");
+
+ assertNotNull( feat.activatePolicySession(polcont, "myname", "mybase"));
+ }
+
+ @Test
+ public void testIsPersistenceEnabled_Native() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+ props.setProperty("persistence.type", "native");
+
+ assertNotNull( feat.activatePolicySession(polcont, "myname", "mybase"));
+ }
+
+ @Test
+ public void testIsPersistenceEnabled_None() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+ props.remove("persistence.type");
+
+ assertNull( feat.activatePolicySession(polcont, "myname", "mybase"));
+ }
+
+ @Test
+ public void testGetProperties_Ex() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+ when(fact.getPolicyContainer(polcont))
+ .thenThrow( new IllegalArgumentException("expected exception"));
+
+ assertNull( feat.activatePolicySession(polcont, "myname", "mybase"));
+ }
+
+ @Test
+ public void testGetProperty_Specific() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+ props.remove("persistence.type");
+ props.setProperty("persistence.myname.type", "auto");
+
+ assertNotNull( feat.activatePolicySession(polcont, "myname", "mybase"));
+ }
+
+ @Test
+ public void testGetProperty_Specific_None() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+ props.remove("persistence.type");
+ props.setProperty("persistence.xxx.type", "auto");
+
+ assertNull( feat.activatePolicySession(polcont, "myname", "mybase"));
+ }
+
+ @Test
+ public void testGetProperty_Both_SpecificOn() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+ props.setProperty("persistence.type", "other");
+ props.setProperty("persistence.myname.type", "auto");
+
+ assertNotNull( feat.activatePolicySession(polcont, "myname", "mybase"));
+ }
+
+ @Test
+ public void testGetProperty_Both_SpecificDisabledOff() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+ props.setProperty("persistence.type", "auto");
+ props.setProperty("persistence.myname.type", "other");
+
+ assertNull( feat.activatePolicySession(polcont, "myname", "mybase"));
+ }
+
+ @Test
+ public void testGetProperty_None() throws Exception {
+ feat.globalInit(null, SRC_TEST_RESOURCES);
+
+ mockDbConn(5);
+ setUpKie("myname", 999L, true);
+
+ props.remove("persistence.type");
+
+ assertNull( feat.activatePolicySession(polcont, "myname", "mybase"));
+ }
+
+
+ /**
+ * Gets an ordered list of ids of the current SessionInfo records.
+ * @return ordered list of SessInfo IDs
+ * @throws SQLException
+ * @throws IOException
+ */
+ private List<Integer> getSessions() throws SQLException, IOException {
+ attachDb();
+
+ ArrayList<Integer> lst = new ArrayList<>(5);
+
+ try(
+ PreparedStatement stmt = conn.prepareStatement("SELECT id from sessioninfo order by id");
+ ResultSet rs = stmt.executeQuery()) {
+
+ while(rs.next()) {
+ lst.add( rs.getInt(1));
+ }
+ }
+
+ return lst;
+ }
+
+ /**
+ * Sets up for doing invoking the newKieSession() method.
+ * @param sessnm name to which JPA should respond with a session
+ * @param sessid session id to be returned by the session
+ * @param loadOk {@code true} if loadKieSession() should return a
+ * value, {@code false} to return null
+ */
+ private void setUpKie(String sessnm, long sessid, boolean loadOk) {
+
+ when(fact.makeJpaConnector(any(), any())).thenReturn(jpa);
+ when(fact.makePoolingDataSource()).thenReturn(pds);
+ when(fact.getPolicyContainer(polcont)).thenReturn(polctlr);
+
+ props.setProperty("persistence.type", "auto");
+
+ when(polctlr.getProperties()).thenReturn(props);
+
+ when(jpa.get(sessnm)).thenReturn(sess);
+
+ when(pds.getDriverProperties()).thenReturn(dsprops);
+
+ when(sess.getSessionId()).thenReturn(sessid);
+
+ when(polsess.getPolicyContainer()).thenReturn(polcont);
+ when(polsess.getName()).thenReturn(sessnm);
+
+ if(loadOk) {
+ when(kiesess.getIdentifier()).thenReturn(sessid);
+ when(kiestore.loadKieSession(anyLong(), any(), any(), any()))
+ .thenReturn(kiesess);
+
+ } else {
+ // use an alternate id for the new session
+ when(kiesess.getIdentifier()).thenReturn(100L);
+ when(kiestore.loadKieSession(anyLong(), any(), any(), any()))
+ .thenReturn(null);
+ }
+
+ when(kiestore.newKieSession(any(), any(), any())).thenReturn(kiesess);
+ }
+
+ /**
+ * Creates the SessionInfo DB table and populates it with some data.
+ * @param expMs number of milli-seconds for expired sessioninfo records
+ * @throws SQLException
+ * @throws IOException
+ */
+ private void makeSessionInfoTbl(int expMs)
+ throws SQLException, IOException {
+
+ attachDb();
+
+ try(
+ PreparedStatement stmt = conn.prepareStatement(
+ "CREATE TABLE sessioninfo(id int, lastmodificationdate timestamp)")) {
+
+ stmt.executeUpdate();
+ }
+
+ try(
+ PreparedStatement stmt = conn.prepareStatement(
+ "INSERT into sessioninfo(id, lastmodificationdate) values(?, ?)")) {
+
+ Timestamp ts;
+
+ // current data
+ ts = new Timestamp( System.currentTimeMillis());
+ stmt.setTimestamp(2, ts);
+
+ stmt.setInt(1, 1);
+ stmt.executeUpdate();
+
+ stmt.setInt(1, 4);
+ stmt.executeUpdate();
+
+ stmt.setInt(1, 5);
+ stmt.executeUpdate();
+
+ // expired data
+ ts = new Timestamp( System.currentTimeMillis() - expMs);
+ stmt.setTimestamp(2, ts);
+
+ stmt.setInt(1, 2);
+ stmt.executeUpdate();
+
+ stmt.setInt(1, 3);
+ stmt.executeUpdate();
+ }
+ }
+
+ /**
+ * Attaches {@link #conn} to the DB, if it isn't already attached.
+ * @throws SQLException
+ * @throws IOException if the property file cannot be read
+ */
+ private void attachDb() throws SQLException, IOException {
+ if(conn == null) {
+ Properties p = loadDbProps();
+
+ conn = DriverManager.getConnection(
+ p.getProperty(DroolsPersistenceProperties.DB_URL),
+ p.getProperty(DroolsPersistenceProperties.DB_USER),
+ p.getProperty(DroolsPersistenceProperties.DB_PWD));
+ conn.setAutoCommit(true);
+ }
+ }
+
+ /**
+ * Loads the DB properties from the file,
+ * <i>feature-session-persistence.properties</i>.
+ * @return the properties that were loaded
+ * @throws IOException if the property file cannot be read
+ * @throws FileNotFoundException if the property file does not exist
+ */
+ private Properties loadDbProps()
+ throws IOException, FileNotFoundException {
+
+ Properties p = new Properties();
+
+ try(FileReader rdr = new FileReader(
+ "src/test/resources/feature-session-persistence.properties")) {
+ p.load(rdr);
+ }
+
+ return p;
+ }
+
+ /**
+ * Create a mock DB connection and statement.
+ * @param retval value to be returned when the statement is executed,
+ * or negative to throw an exception
+ * @return the statement that will be returned by the connection
+ * @throws SQLException
+ */
+ private PreparedStatement mockDbConn(int retval) throws SQLException {
+ Connection c = mock(Connection.class);
+ PreparedStatement s = mock(PreparedStatement.class);
+
+ when(fact.makeDbConnection(anyString(), anyString(), anyString()))
+ .thenReturn(c);
+ when(c.prepareStatement(anyString())).thenReturn(s);
+
+ if(retval < 0) {
+ // should throw an exception
+ when(s.executeUpdate())
+ .thenThrow( new SQLException("expected exception"));
+
+ } else {
+ // should return the value
+ when(s.executeUpdate()).thenReturn(retval);
+ }
+
+ return s;
+ }
+
+ /**
+ * A partial factory, which exports a few of the real methods, but
+ * overrides the rest.
+ */
+ private class PartialFactory extends PersistenceFeature.Factory {
+
+ @Override
+ public PoolingDataSource makePoolingDataSource() {
+ return pds;
+ }
+
+ @Override
+ public KieServices getKieServices() {
+ return kiesvc;
+ }
+
+ @Override
+ public BitronixTransactionManager getTransMgr() {
+ return null;
+ }
+
+ @Override
+ public EntityManagerFactory makeEntMgrFact(String pu,
+ Properties propMap) {
+ if(pu.equals("onapsessionsPU")) {
+ return null;
+ }
+
+ return super.makeEntMgrFact("junitPersistenceFeaturePU", propMap);
+ }
+
+ @Override
+ public PolicyController getPolicyContainer(PolicyContainer container) {
+ return polctlr;
+ }
+
+ }
+}
diff --git a/feature-session-persistence/src/test/resources/META-INF/persistence.xml b/feature-session-persistence/src/test/resources/META-INF/persistence.xml
new file mode 100644
index 00000000..6794e24e
--- /dev/null
+++ b/feature-session-persistence/src/test/resources/META-INF/persistence.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ============LICENSE_START=======================================================
+ feature-session-persistence
+ ================================================================================
+ 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=========================================================
+ -->
+
+<persistence version="2.1"
+ xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
+
+ <persistence-unit name="junitDroolsSessionEntityPU" transaction-type="RESOURCE_LOCAL">
+ <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
+ <class>org.onap.policy.drools.persistence.DroolsSessionEntity</class>
+ <properties>
+ <property name="javax.persistence.schema-generation.database.action" value="create"/>
+ </properties>
+ </persistence-unit>
+
+ <persistence-unit name="junitPersistenceFeaturePU" transaction-type="RESOURCE_LOCAL">
+ <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
+ <class>org.onap.policy.drools.persistence.DroolsSessionEntity</class>
+ <properties>
+ <property name="javax.persistence.schema-generation.database.action" value="create"/>
+ </properties>
+ </persistence-unit>
+
+</persistence>
diff --git a/feature-session-persistence/src/test/resources/feature-session-persistence.properties b/feature-session-persistence/src/test/resources/feature-session-persistence.properties
new file mode 100644
index 00000000..6b448dc8
--- /dev/null
+++ b/feature-session-persistence/src/test/resources/feature-session-persistence.properties
@@ -0,0 +1,28 @@
+###
+# ============LICENSE_START=======================================================
+# feature-session-persistence
+# ================================================================================
+# 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=========================================================
+###
+
+javax.persistence.jdbc.driver=org.h2.Driver
+javax.persistence.jdbc.url=jdbc:h2:mem:TestPersistenceFeature
+javax.persistence.jdbc.user=testuser
+javax.persistence.jdbc.password=testpass
+
+hibernate.dataSource=org.h2.jdbcx.JdbcDataSource
+
+persistence.sessioninfo.timeout=10
diff --git a/feature-session-persistence/src/test/resources/logback-test.xml b/feature-session-persistence/src/test/resources/logback-test.xml
new file mode 100644
index 00000000..5aeaf90f
--- /dev/null
+++ b/feature-session-persistence/src/test/resources/logback-test.xml
@@ -0,0 +1,39 @@
+<!--
+ ============LICENSE_START=======================================================
+ feature-session-persistence
+ ================================================================================
+ 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=========================================================
+ -->
+
+<!-- Controls the output of logs for JUnit tests -->
+
+<configuration>
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+ <Pattern>
+ %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M\(%line\) - %msg%n
+ </Pattern>
+ </encoder>
+ </appender>
+
+ <logger name="org.onap.policy.drools.persistence" level="INFO"/>
+
+ <root level="warn">
+ <appender-ref ref="STDOUT"/>
+ </root>
+
+</configuration>
diff --git a/packages/install/pom.xml b/packages/install/pom.xml
index e85dc53f..db71e872 100644
--- a/packages/install/pom.xml
+++ b/packages/install/pom.xml
@@ -83,6 +83,12 @@
<version>${project.version}</version>
<type>zip</type>
</dependency>
+ <dependency>
+ <groupId>org.onap.policy.drools-pdp</groupId>
+ <artifactId>feature-session-persistence</artifactId>
+ <version>${project.version}</version>
+ <type>zip</type>
+ </dependency>
</dependencies>
</project>
diff --git a/pom.xml b/pom.xml
index 4858e2f1..0dc78b76 100644
--- a/pom.xml
+++ b/pom.xml
@@ -69,6 +69,7 @@
<module>policy-management</module>
<module>feature-healthcheck</module>
<module>feature-eelf</module>
+ <module>feature-session-persistence</module>
<module>packages</module>
</modules>