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