From 36cf73f8313cbd1baac4bc41565bee23690fc152 Mon Sep 17 00:00:00 2001 From: Kevin McKiou Date: Tue, 22 Aug 2017 16:08:06 -0500 Subject: Add feature-session-persistence This commit adds the feature-session-persistence module which will persist drools session data to allow stateful transactions which can persist across node restarts and failovers. It also picks up recent changes to the master branch to avoid merge conflicts. Issue-ID: POLICY-133 Change-Id: Ifdcd8280ea6df07db79562f1b01fa90296a8b878 Signed-off-by: Kevin McKiou --- .gitignore | 7 +- feature-healthcheck/src/assembly/assemble_zip.xml | 2 +- .../feature/config/feature-healthcheck.properties | 2 +- feature-session-persistence/.gitignore | 1 + feature-session-persistence/pom.xml | 177 +++ .../src/assembly/assemble_zip.xml | 76 ++ .../config/feature-session-persistence.properties | 28 + .../sql/18020-sessionpersistence.upgrade.sql | 60 + .../persistence/DroolsPersistenceProperties.java | 34 + .../policy/drools/persistence/DroolsSession.java | 35 + .../drools/persistence/DroolsSessionConnector.java | 38 + .../drools/persistence/DroolsSessionEntity.java | 140 +++ .../policy/drools/persistence/EntityMgrCloser.java | 49 + .../policy/drools/persistence/EntityMgrTrans.java | 81 ++ .../persistence/JpaDroolsSessionConnector.java | 116 ++ .../drools/persistence/PersistenceFeature.java | 1014 +++++++++++++++ .../src/main/resources/META-INF/persistence.xml | 65 + ...onap.policy.drools.core.PolicySessionFeatureAPI | 1 + ...p.policy.drools.features.PolicyEngineFeatureAPI | 1 + .../persistence/DroolsSessionEntityTest.java | 198 +++ .../drools/persistence/EntityMgrCloserTest.java | 100 ++ .../drools/persistence/EntityMgrTransTest.java | 232 ++++ .../policy/drools/persistence/GenSchemaTest.java | 58 + .../persistence/JpaDroolsSessionConnectorTest.java | 160 +++ .../drools/persistence/PersistenceFeatureTest.java | 1291 ++++++++++++++++++++ .../src/test/resources/META-INF/persistence.xml | 42 + .../feature-session-persistence.properties | 28 + .../src/test/resources/logback-test.xml | 39 + packages/install/pom.xml | 6 + pom.xml | 1 + 30 files changed, 4077 insertions(+), 5 deletions(-) create mode 100644 feature-session-persistence/.gitignore create mode 100644 feature-session-persistence/pom.xml create mode 100644 feature-session-persistence/src/assembly/assemble_zip.xml create mode 100644 feature-session-persistence/src/main/feature/config/feature-session-persistence.properties create mode 100644 feature-session-persistence/src/main/feature/db/sessionpersistence/sql/18020-sessionpersistence.upgrade.sql create mode 100644 feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsPersistenceProperties.java create mode 100644 feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSession.java create mode 100644 feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSessionConnector.java create mode 100644 feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/DroolsSessionEntity.java create mode 100644 feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/EntityMgrCloser.java create mode 100644 feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/EntityMgrTrans.java create mode 100644 feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/JpaDroolsSessionConnector.java create mode 100644 feature-session-persistence/src/main/java/org/onap/policy/drools/persistence/PersistenceFeature.java create mode 100644 feature-session-persistence/src/main/resources/META-INF/persistence.xml create mode 100644 feature-session-persistence/src/main/resources/META-INF/services/org.onap.policy.drools.core.PolicySessionFeatureAPI create mode 100644 feature-session-persistence/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyEngineFeatureAPI create mode 100644 feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/DroolsSessionEntityTest.java create mode 100644 feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/EntityMgrCloserTest.java create mode 100644 feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/EntityMgrTransTest.java create mode 100644 feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/GenSchemaTest.java create mode 100644 feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/JpaDroolsSessionConnectorTest.java create mode 100644 feature-session-persistence/src/test/java/org/onap/policy/drools/persistence/PersistenceFeatureTest.java create mode 100644 feature-session-persistence/src/test/resources/META-INF/persistence.xml create mode 100644 feature-session-persistence/src/test/resources/feature-session-persistence.properties create mode 100644 feature-session-persistence/src/test/resources/logback-test.xml 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 @@ + + + + 4.0.0 + + + org.onap.policy.drools-pdp + drools-pdp + 1.1.0-SNAPSHOT + + + feature-session-persistence + + feature-session-persistence + Separately loadable feature module with session persistence code + + + 1.8 + 1.8 + 1.6.6 + + + + + + maven-assembly-plugin + 2.6 + + + zipfile + + single + + package + + true + ${project.artifactId}-${project.version} + + src/assembly/assemble_zip.xml + + false + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.8 + + + copy-dependencies + + copy-dependencies + + prepare-package + + false + ${project.build.directory}/assembly/lib + false + true + true + false + false + false + runtime + provided + true + + + + + + + + + + org.onap.policy.drools-pdp + policy-core + ${project.version} + provided + + + org.onap.policy.drools-pdp + policy-management + ${project.version} + provided + + + org.powermock + powermock-api-mockito + ${powermock.version} + test + + + org.powermock + powermock-module-junit4 + ${powermock.version} + test + + + junit + junit + + + org.powermock + powermock-core + + + org.powermock + powermock-reflect + + + org.javassist + javassist + + + + + org.javassist + javassist + 3.21.0-GA + test + + + org.codehaus.btm + btm + 2.1.4 + + + com.h2database + h2 + [1.4.186,) + test + + + org.hibernate + hibernate-core + provided + + + org.hibernate.common + hibernate-commons-annotations + provided + + + junit + junit + provided + + + org.eclipse.persistence + eclipselink + provided + + + 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 @@ + + + + + + session-persistence + + zip + + + + false + + + + target + lib/feature + + feature-session-persistence-${project.version}.jar + + + + target/assembly/lib + lib/dependencies + + *.jar + + + + src/main/feature/config + config + 0644 + + + + src/main/feature/bin + bin + 0744 + + + + src/main/feature/db + db + 0744 + + + + src/main/feature/install + install + 0744 + + + + 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 EntityManager, 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 EntityManager 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 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: + *
    + *
  • persistence.SESSION-NAME.PROPERTY
  • + *
  • persistence.PROPERTY
  • + *
+ * + * @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 @@ + + + + + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.onap.policy.drools.persistence.DroolsSessionEntity + org.drools.persistence.info.SessionInfo + org.drools.persistence.info.WorkItemInfo + + + + + + + + org.hibernate.jpa.HibernatePersistenceProvider + org.drools.persistence.info.SessionInfo + org.drools.persistence.info.WorkItemInfo + + + + + + + + + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.onap.policy.drools.persistence.DroolsSessionEntity + org.drools.persistence.info.SessionInfo + org.drools.persistence.info.WorkItemInfo + + + + + + + + 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 not 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 + * not 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 + * not 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 + * not 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 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 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 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 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 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 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 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 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 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 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 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 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 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 getSessions() throws SQLException, IOException { + attachDb(); + + ArrayList 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, + * feature-session-persistence.properties. + * @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 @@ + + + + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.onap.policy.drools.persistence.DroolsSessionEntity + + + + + + + org.eclipse.persistence.jpa.PersistenceProvider + org.onap.policy.drools.persistence.DroolsSessionEntity + + + + + + 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 @@ + + + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M\(%line\) - %msg%n + + + + + + + + + + + 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 @@ ${project.version} zip + + org.onap.policy.drools-pdp + feature-session-persistence + ${project.version} + zip + diff --git a/pom.xml b/pom.xml index 4858e2f1..0dc78b76 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,7 @@ policy-management feature-healthcheck feature-eelf + feature-session-persistence packages -- cgit 1.2.3-korg