/* * Copyright 2017 Red Hat, Inc. and/or its affiliates. * * 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. */ package org.drools.persistence.session; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; import javax.naming.InitialContext; import javax.transaction.UserTransaction; import org.drools.compiler.Address; import org.drools.compiler.Person; import org.drools.core.SessionConfiguration; import org.drools.core.command.impl.CommandBasedStatefulKnowledgeSession; import org.drools.core.command.impl.FireAllRulesInterceptor; import org.drools.core.command.impl.LoggingInterceptor; import org.drools.core.runtime.ChainableRunner; import org.drools.persistence.PersistableRunner; import org.drools.persistence.util.DroolsPersistenceUtil; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.kie.api.KieBase; import org.kie.api.KieServices; import org.kie.api.io.ResourceType; 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.kie.api.runtime.rule.FactHandle; import org.kie.internal.KnowledgeBaseFactory; import org.kie.internal.command.CommandFactory; import org.kie.internal.persistence.jpa.JPAKnowledgeService; import org.kie.internal.runtime.StatefulKnowledgeSession; import org.kie.internal.utils.KieHelper; import static org.assertj.core.api.Assertions.*; import static org.drools.persistence.util.DroolsPersistenceUtil.*; @RunWith(Parameterized.class) public class JpaPersistentStatefulSessionTest { private Map<String, Object> context; private Environment env; private final boolean locking; @Parameters(name = "{0}") public static Collection<Object[]> persistence() { final Object[][] locking = new Object[][]{ {OPTIMISTIC_LOCKING}, {PESSIMISTIC_LOCKING} }; return Arrays.asList(locking); } public JpaPersistentStatefulSessionTest(final String locking) { this.locking = PESSIMISTIC_LOCKING.equals(locking); } @Before public void setUp() throws Exception { context = DroolsPersistenceUtil.setupWithPoolingDataSource(DROOLS_PERSISTENCE_UNIT_NAME); env = createEnvironment(context); if (locking) { env.set(EnvironmentName.USE_PESSIMISTIC_LOCKING, true); } } @After public void tearDown() throws Exception { DroolsPersistenceUtil.cleanUp(context); } @Test public void testFactHandleSerialization() { factHandleSerialization(false); } @Test public void testFactHandleSerializationWithOOPath() { factHandleSerialization(true); } private void factHandleSerialization(final boolean withOOPath) { final String str = "package org.kie.test\n" + "import java.util.concurrent.atomic.AtomicInteger\n" + "global java.util.List list\n" + "rule rule1\n" + "when\n" + (withOOPath ? " AtomicInteger($i: /intValue[this > 0])\n" : " $i: AtomicInteger(intValue > 0)\n") + "then\n" + " list.add( $i );\n" + "end\n" + "\n"; final KieBase kbase = new KieHelper().addContent(str, ResourceType.DRL).build(); KieSession ksession = KieServices.get().getStoreServices().newKieSession(kbase, null, env); List<AtomicInteger> list = new ArrayList<>(); ksession.setGlobal("list", list); final AtomicInteger value = new AtomicInteger(4); FactHandle atomicFH = ksession.insert(value); ksession.fireAllRules(); assertThat(list).hasSize(1); value.addAndGet(1); ksession.update(atomicFH, value); ksession.fireAllRules(); assertThat(list).hasSize(2); final String externalForm = atomicFH.toExternalForm(); ksession = KieServices.get().getStoreServices().loadKieSession(ksession.getIdentifier(), kbase, null, env); atomicFH = ksession.execute(CommandFactory.fromExternalFactHandleCommand(externalForm)); value.addAndGet(1); ksession.update(atomicFH, value); ksession.fireAllRules(); list = (List<AtomicInteger>) ksession.getGlobal("list"); assertThat(list).hasSize(3); } @Test public void testLocalTransactionPerStatement() { localTransactionPerStatement(false); } @Test public void testLocalTransactionPerStatementWithOOPath() { localTransactionPerStatement(true); } private void localTransactionPerStatement(final boolean withOOPath) { final String rule = getSimpleRule(withOOPath); final KieBase kbase = new KieHelper().addContent(rule, ResourceType.DRL).build(); final KieSession ksession = KieServices.get().getStoreServices().newKieSession(kbase, null, env); final List<Integer> list = new ArrayList<>(); ksession.setGlobal("list", list); insertIntRange(ksession, 1, 3); ksession.fireAllRules(); assertThat(list).hasSize(3); } @Test public void testUserTransactions() throws Exception { userTransactions(false); } @Test public void testUserTransactionsWithOOPath() throws Exception { userTransactions(true); } private void userTransactions(final boolean withOOPath) throws Exception { final String str = "package org.kie.test\n" + "global java.util.List list\n" + "rule rule1\n" + "when\n" + (withOOPath ? " $i: Integer( /intValue[this > 0])\n" : " $i : Integer(intValue > 0)\n") + "then\n" + " list.add( $i );\n" + "end\n"; final KieBase kbase = new KieHelper().addContent(str, ResourceType.DRL).build(); UserTransaction ut = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); ut.begin(); KieSession ksession = KieServices.get().getStoreServices().newKieSession(kbase, null, env); ut.commit(); final List<Integer> list = new ArrayList<>(); // insert and commit ut = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); ut.begin(); ksession.setGlobal("list", list); insertIntRange(ksession, 1, 2); ksession.fireAllRules(); ut.commit(); // insert and rollback ut = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); ut.begin(); ksession.insert(3); ut.rollback(); // check we rolled back the state changes from the 3rd insert ut = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); ut.begin(); ksession.fireAllRules(); ut.commit(); assertThat(list).hasSize(2); // insert and commit ut = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); ut.begin(); insertIntRange(ksession, 3, 4); ut.commit(); // rollback again, this is testing that we can do consecutive rollbacks and commits without issue ut = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); ut.begin(); insertIntRange(ksession, 5, 6); ut.rollback(); ksession.fireAllRules(); assertThat(list).hasSize(4); // now load the ksession ksession = JPAKnowledgeService.loadStatefulKnowledgeSession(ksession.getIdentifier(), kbase, null, env); ut = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); ut.begin(); insertIntRange(ksession, 7, 8); ut.commit(); ksession.fireAllRules(); assertThat(list).hasSize(6); } @Test public void testInterceptor() { interceptor(false); } @Test public void testInterceptorWithOOPath() { interceptor(true); } private void interceptor(final boolean withOOPath) { final String str = getSimpleRule(withOOPath); final KieBase kbase = new KieHelper().addContent(str, ResourceType.DRL).build(); final KieSession ksession = KieServices.get().getStoreServices().newKieSession(kbase, null, env); final PersistableRunner sscs = (PersistableRunner) ((CommandBasedStatefulKnowledgeSession) ksession).getRunner(); sscs.addInterceptor(new LoggingInterceptor()); sscs.addInterceptor(new FireAllRulesInterceptor()); sscs.addInterceptor(new LoggingInterceptor()); final List<Integer> list = new ArrayList<>(); ksession.setGlobal("list", list); insertIntRange(ksession, 1, 3); ksession.getWorkItemManager().completeWorkItem(0, null); assertThat(list).hasSize(3); } @Test public void testInterceptorOnRollback() throws Exception { interceptorOnRollback(false); } @Test public void testInterceptorOnRollbackWithOOPAth() throws Exception { interceptorOnRollback(true); } private void interceptorOnRollback(final boolean withOOPath) throws Exception { final String str = getSimpleRule(withOOPath); final KieBase kbase = new KieHelper().addContent(str, ResourceType.DRL).build(); final KieSession ksession = KieServices.get().getStoreServices().newKieSession(kbase, null, env); final PersistableRunner sscs = (PersistableRunner) ((CommandBasedStatefulKnowledgeSession) ksession).getRunner(); sscs.addInterceptor(new LoggingInterceptor()); sscs.addInterceptor(new FireAllRulesInterceptor()); sscs.addInterceptor(new LoggingInterceptor()); ChainableRunner runner = sscs.getChainableRunner(); assertThat(runner.getClass()).isEqualTo(LoggingInterceptor.class); runner = (ChainableRunner) runner.getNext(); assertThat(runner.getClass()).isEqualTo(FireAllRulesInterceptor.class); runner = (ChainableRunner) runner.getNext(); assertThat(runner.getClass()).isEqualTo(LoggingInterceptor.class); final UserTransaction ut = InitialContext.doLookup("java:comp/UserTransaction"); ut.begin(); final List<?> list = new ArrayList<>(); ksession.setGlobal("list", list); ksession.insert(1); ut.rollback(); ksession.insert(3); runner = sscs.getChainableRunner(); assertThat(runner.getClass()).isEqualTo(LoggingInterceptor.class); runner = (ChainableRunner) runner.getNext(); assertThat(runner.getClass()).isEqualTo(FireAllRulesInterceptor.class); runner = (ChainableRunner) runner.getNext(); assertThat(runner.getClass()).isEqualTo(LoggingInterceptor.class); } @Test public void testSetFocus() { testFocus(false); } @Test public void testSetFocusWithOOPath() { testFocus(true); } private void testFocus(final boolean withOOPath) { final String str = "package org.kie.test\n" + "global java.util.List list\n" + "rule rule1\n" + "agenda-group \"badfocus\"" + "when\n" + (withOOPath ? " Integer(/intValue[this > 0])\n" : " Integer(intValue > 0)\n") + "then\n" + " list.add( 1 );\n" + "end\n" + "\n"; final KieBase kbase = new KieHelper().addContent(str, ResourceType.DRL).build(); final KieSession ksession = KieServices.get().getStoreServices().newKieSession(kbase, null, env); final List<Integer> list = new ArrayList<>(); ksession.setGlobal("list", list); insertIntRange(ksession, 1, 3); ksession.getAgenda().getAgendaGroup("badfocus").setFocus(); ksession.fireAllRules(); assertThat(list).hasSize(3); } @Test public void testSharedReferences() { final KieBase kbase = new KieHelper().getKieContainer().getKieBase(); final KieSession ksession = KieServices.get().getStoreServices().newKieSession(kbase, null, env); final Person x = new Person("test"); final List<Person> test = new ArrayList<>(); final List<Person> test2 = new ArrayList<>(); test.add(x); test2.add(x); assertThat(test.get(0)).isSameAs(test2.get(0)); ksession.insert(test); ksession.insert(test2); ksession.fireAllRules(); final StatefulKnowledgeSession ksession2 = JPAKnowledgeService.loadStatefulKnowledgeSession(ksession.getIdentifier(), kbase, null, env); final Iterator c = ksession2.getObjects().iterator(); final List ref1 = (List) c.next(); final List ref2 = (List) c.next(); assertThat(ref1.get(0)).isSameAs(ref2.get(0)); } @Test public void testMergeConfig() { // JBRULES-3155 final KieBase kbase = new KieHelper().getKieContainer().getKieBase(); final Properties properties = new Properties(); properties.put("drools.processInstanceManagerFactory", "com.example.CustomJPAProcessInstanceManagerFactory"); final KieSessionConfiguration config = KnowledgeBaseFactory.newKnowledgeSessionConfiguration(properties); final KieSession ksession = KieServices.get().getStoreServices().newKieSession(kbase, config, env); final SessionConfiguration sessionConfig = (SessionConfiguration) ksession.getSessionConfiguration(); assertThat(sessionConfig.getProcessInstanceManagerFactory()).isEqualTo("com.example.CustomJPAProcessInstanceManagerFactory"); } @Test(expected = IllegalStateException.class) public void testCreateAndDestroySession() { createAndDestroySession(false); } @Test(expected = IllegalStateException.class) public void testCreateAndDestroySessionWithOOPath() { createAndDestroySession(true); } public void createAndDestroySession(final boolean withOOPath) { final String str = getSimpleRule(withOOPath); final KieBase kbase = new KieHelper().addContent(str, ResourceType.DRL).build(); final KieSession ksession = KieServices.get().getStoreServices().newKieSession(kbase, null, env); final List<Integer> list = new ArrayList<>(); ksession.setGlobal("list", list); insertIntRange(ksession, 1, 3); ksession.fireAllRules(); assertThat(list).hasSize(3); final long ksessionId = ksession.getIdentifier(); ksession.destroy(); JPAKnowledgeService.loadStatefulKnowledgeSession(ksessionId, kbase, null, env); fail("There should not be any session with id " + ksessionId); } @Test(expected = IllegalStateException.class) public void testCreateAndDestroyNonPersistentSession() { createAndDestroyNonPersistentSession(false); } @Test(expected = IllegalStateException.class) public void testCreateAndDestroyNonPersistentSessionWithOOPath() { createAndDestroyNonPersistentSession(true); } private void createAndDestroyNonPersistentSession(final boolean withOOPath) { final String str = getSimpleRule(withOOPath); final KieBase kbase = new KieHelper().addContent(str, ResourceType.DRL).build(); final KieSession ksession = kbase.newKieSession(); final List<Integer> list = new ArrayList<>(); ksession.setGlobal("list", list); insertIntRange(ksession, 1, 3); ksession.fireAllRules(); assertThat(list).hasSize(3); final long ksessionId = ksession.getIdentifier(); ksession.destroy(); ksession.fireAllRules(); fail("Session should already be disposed " + ksessionId); } @Test public void testFromNodeWithModifiedCollection() { fromNodeWithModifiedCollection(false); } @Test public void testFromNodeWithModifiedCollectionWithOOPath() { fromNodeWithModifiedCollection(true); } private void fromNodeWithModifiedCollection(final boolean withOOPath) { // DROOLS-376 final String str = "package org.drools.test\n" + "import org.drools.compiler.Person\n" + "import org.drools.compiler.Address\n" + "rule rule1\n" + "when\n" + (withOOPath ? " $p: Person($list : addresses, /addresses[street == \"y\"])\n" : " $p: Person($list : addresses)\n" + " $a: Address(street == \"y\") from $list\n" ) + "then\n" + " $list.add( new Address(\"z\") );\n" + " $list.add( new Address(\"w\") );\n" + "end\n"; final KieBase kbase = new KieHelper().addContent(str, ResourceType.DRL).build(); final KieSession ksession = KieServices.get().getStoreServices().newKieSession(kbase, null, env); final Person p1 = new Person("John"); p1.addAddress(new Address("x")); p1.addAddress(new Address("y")); ksession.insert(p1); ksession.fireAllRules(); assertThat(p1.getAddresses()).hasSize(4); ksession.dispose(); // Should not fail here } private String getSimpleRule(final boolean withOOPath) { return "package org.kie.test\n" + "global java.util.List list\n" + "rule rule1\n" + "when\n" + (withOOPath ? " Integer(/intValue[this > 0])\n" : " Integer(intValue > 0)\n") + "then\n" + " list.add( 1 );\n" + "end\n" + "\n"; } /** * Insert integer range into session * * @param ksession Session to insert ints in * @param from start of the range of ints to be inserted to ksession (inclusive) * @param to end of the range of ints to be inserted to ksession (inclusive) */ private void insertIntRange(final KieSession ksession, final int from, final int to){ IntStream.rangeClosed(from, to).forEach(ksession::insert); } }