/** * Copyright 2010 JBoss Inc * * 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.reteoo; /* * Copyright 2005 JBoss Inc * * 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. */ import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import org.drools.DroolsTestCase; import org.drools.RuleBaseConfiguration; import org.drools.RuleBaseFactory; import org.drools.common.DefaultBetaConstraints; import org.drools.common.DefaultFactHandle; import org.drools.common.EmptyBetaConstraints; import org.drools.common.InternalFactHandle; import org.drools.common.PropagationContextImpl; import org.drools.reteoo.builder.BuildContext; import org.drools.rule.Behavior; import org.drools.rule.ContextEntry; import org.drools.rule.Rule; import org.drools.spi.BetaNodeFieldConstraint; import org.drools.spi.PropagationContext; public class JoinNodeTest extends DroolsTestCase { Rule rule; PropagationContext context; ReteooWorkingMemory workingMemory; MockObjectSource objectSource; MockTupleSource tupleSource; MockLeftTupleSink sink; BetaNode node; BetaMemory memory; BetaNodeFieldConstraint constraint; /** * Setup the BetaNode used in each of the tests */ public void setUp() { // create mock objects constraint = mock(BetaNodeFieldConstraint.class); final ContextEntry c = mock(ContextEntry.class); when(constraint.createContextEntry()).thenReturn(c); this.rule = new Rule("test-rule"); this.context = new PropagationContextImpl(0, PropagationContext.ASSERTION, null, null, null); this.workingMemory = new ReteooWorkingMemory(1, (ReteooRuleBase) RuleBaseFactory.newRuleBase()); this.tupleSource = new MockTupleSource(4); this.objectSource = new MockObjectSource(4); this.sink = new MockLeftTupleSink(); final RuleBaseConfiguration configuration = new RuleBaseConfiguration(); ReteooRuleBase ruleBase = (ReteooRuleBase) RuleBaseFactory .newRuleBase(); BuildContext buildContext = new BuildContext(ruleBase, ruleBase .getReteooBuilder().getIdGenerator()); this.node = new JoinNode(15, this.tupleSource, this.objectSource, new DefaultBetaConstraints( new BetaNodeFieldConstraint[] { this.constraint }, configuration), Behavior.EMPTY_BEHAVIOR_LIST, buildContext); this.node.addTupleSink(this.sink); this.memory = (BetaMemory) this.workingMemory.getNodeMemory(this.node); // check memories are empty assertEquals(0, this.memory.getLeftTupleMemory().size()); assertEquals(0, this.memory.getRightTupleMemory().size()); } public void testAttach() throws Exception { when( constraint.isAllowedCachedLeft(any(ContextEntry.class), any(InternalFactHandle.class))).thenReturn(true); when( constraint.isAllowedCachedRight(any(LeftTuple.class), any(ContextEntry.class))).thenReturn(true); final Field objectFfield = ObjectSource.class.getDeclaredField("sink"); objectFfield.setAccessible(true); ObjectSinkPropagator objectSink = (ObjectSinkPropagator) objectFfield .get(this.objectSource); final Field tupleField = LeftTupleSource.class.getDeclaredField("sink"); tupleField.setAccessible(true); LeftTupleSinkPropagator tupleSink = (LeftTupleSinkPropagator) tupleField .get(this.tupleSource); assertEquals(15, this.node.getId()); assertNotNull(objectSink); assertNotNull(tupleSink); this.node.attach(); objectSink = (ObjectSinkPropagator) objectFfield.get(this.objectSource); tupleSink = (LeftTupleSinkPropagator) tupleField.get(this.tupleSource); assertEquals(1, objectSink.getSinks().length); assertEquals(1, tupleSink.getSinks().length); assertSame(this.node, objectSink.getSinks()[0]); assertSame(this.node, tupleSink.getSinks()[0]); } public void testMemory() { when( constraint.isAllowedCachedLeft(any(ContextEntry.class), any(InternalFactHandle.class))).thenReturn(true); when( constraint.isAllowedCachedRight(any(LeftTuple.class), any(ContextEntry.class))).thenReturn(true); final ReteooWorkingMemory workingMemory = new ReteooWorkingMemory(1, (ReteooRuleBase) RuleBaseFactory.newRuleBase()); final MockObjectSource objectSource = new MockObjectSource(1); final MockTupleSource tupleSource = new MockTupleSource(1); ReteooRuleBase ruleBase = (ReteooRuleBase) RuleBaseFactory .newRuleBase(); BuildContext buildContext = new BuildContext(ruleBase, ruleBase .getReteooBuilder().getIdGenerator()); final JoinNode joinNode = new JoinNode(2, tupleSource, objectSource, EmptyBetaConstraints.getInstance(), Behavior.EMPTY_BEHAVIOR_LIST, buildContext); final BetaMemory memory = (BetaMemory) workingMemory .getNodeMemory(joinNode); assertNotNull(memory); } /** * Test just tuple assertions * * @throws AssertionException */ public void testAssertTuple() throws Exception { when( constraint.isAllowedCachedLeft(any(ContextEntry.class), any(InternalFactHandle.class))).thenReturn(true); when( constraint.isAllowedCachedRight(any(LeftTuple.class), any(ContextEntry.class))).thenReturn(true); final DefaultFactHandle f0 = new DefaultFactHandle(0, "cheese"); final LeftTuple tuple0 = new LeftTuple(f0, this.node, true); // assert tuple, should add one to left memory this.node.assertLeftTuple(tuple0, this.context, this.workingMemory); // check memories, left memory is populated, right memory is emptys assertEquals(1, this.memory.getLeftTupleMemory().size()); assertEquals(0, this.memory.getRightTupleMemory().size()); // assert tuple, should add left memory should be 2 final DefaultFactHandle f1 = new DefaultFactHandle(1, "cheese"); final LeftTuple tuple1 = new LeftTuple(f1, this.node, true); this.node.assertLeftTuple(tuple1, this.context, this.workingMemory); assertEquals(2, this.memory.getLeftTupleMemory().size()); LeftTuple leftTuple = this.memory.getLeftTupleMemory().getFirst( (LeftTuple) null); assertEquals(tuple0, leftTuple); assertEquals(tuple1, leftTuple.getNext()); } /** * Test just tuple assertions * * @throws AssertionException */ public void testAssertTupleSequentialMode() throws Exception { when( constraint.isAllowedCachedLeft(any(ContextEntry.class), any(InternalFactHandle.class))).thenReturn(true); when( constraint.isAllowedCachedRight(any(LeftTuple.class), any(ContextEntry.class))).thenReturn(true); RuleBaseConfiguration conf = new RuleBaseConfiguration(); conf.setSequential(true); this.workingMemory = new ReteooWorkingMemory(1, (ReteooRuleBase) RuleBaseFactory.newRuleBase(conf)); ReteooRuleBase ruleBase = (ReteooRuleBase) RuleBaseFactory .newRuleBase(); BuildContext buildContext = new BuildContext(ruleBase, ruleBase .getReteooBuilder().getIdGenerator()); buildContext.setTupleMemoryEnabled(false); buildContext.setObjectTypeNodeMemoryEnabled(false); // override setup, so its working in sequential mode this.node = new JoinNode( 15, this.tupleSource, this.objectSource, new DefaultBetaConstraints( new BetaNodeFieldConstraint[] { this.constraint }, conf), Behavior.EMPTY_BEHAVIOR_LIST, buildContext); this.node.addTupleSink(this.sink); this.memory = (BetaMemory) this.workingMemory.getNodeMemory(this.node); final DefaultFactHandle f0 = new DefaultFactHandle(0, "cheese"); final LeftTuple tuple0 = new LeftTuple(f0, this.node, true); this.node.assertObject(f0, new PropagationContextImpl(0, PropagationContext.ASSERTION, null, null, f0), this.workingMemory); // assert tuple this.node.assertLeftTuple(tuple0, new PropagationContextImpl(0, PropagationContext.ASSERTION, null, null, f0), this.workingMemory); assertEquals(1, this.sink.getAsserted().size()); assertNull(this.memory.getLeftTupleMemory()); assertEquals(1, this.memory.getRightTupleMemory().size()); assertEquals(new LeftTuple(tuple0, f0.getFirstRightTuple(), this.sink, true), ((Object[]) this.sink.getAsserted().get(0))[0]); } /** * Test just object assertions * * @throws Exception */ public void testAssertObject() throws Exception { when( constraint.isAllowedCachedLeft(any(ContextEntry.class), any(InternalFactHandle.class))).thenReturn(true); when( constraint.isAllowedCachedRight(any(LeftTuple.class), any(ContextEntry.class))).thenReturn(true); final DefaultFactHandle f0 = (DefaultFactHandle) this.workingMemory .insert("test0"); // assert object, should add one to right memory this.node.assertObject(f0, this.context, this.workingMemory); assertEquals(0, this.memory.getLeftTupleMemory().size()); assertEquals(1, this.memory.getRightTupleMemory().size()); // check new objects/handles still assert final DefaultFactHandle f1 = (DefaultFactHandle) this.workingMemory .insert("test1"); this.node.assertObject(f1, this.context, this.workingMemory); assertEquals(2, this.memory.getRightTupleMemory().size()); RightTuple rightTuple = this.memory.getRightTupleMemory().getFirst( new LeftTuple(f0, this.node, true), null); final InternalFactHandle rf0 = rightTuple.getFactHandle(); final InternalFactHandle rf1 = ((RightTuple) rightTuple.getNext()) .getFactHandle(); assertEquals(f0, rf0); assertEquals(f1, rf1); } /** * Test assertion with both Objects and Tuples * * @throws Exception */ public void testAssertPropagations() throws Exception { when( constraint.isAllowedCachedLeft(any(ContextEntry.class), any(InternalFactHandle.class))).thenReturn(true); when( constraint.isAllowedCachedRight(any(LeftTuple.class), any(ContextEntry.class))).thenReturn(true); // assert first right object final DefaultFactHandle f0 = (DefaultFactHandle) this.workingMemory .insert("test0"); this.node.assertObject(f0, this.context, this.workingMemory); // assert tuple, should add left memory should be 2 final DefaultFactHandle f1 = new DefaultFactHandle(1, "cheese"); final LeftTuple tuple1 = new LeftTuple(f1, this.node, true); this.node.assertLeftTuple(tuple1, this.context, this.workingMemory); assertEquals(1, this.sink.getAsserted().size()); assertEquals(new LeftTuple(tuple1, f0.getFirstRightTuple(), this.sink, true), ((Object[]) this.sink.getAsserted().get(0))[0]); final DefaultFactHandle f2 = new DefaultFactHandle(2, "cheese"); final LeftTuple tuple2 = new LeftTuple(f2, this.node, true); this.node.assertLeftTuple(tuple2, this.context, this.workingMemory); assertEquals(2, this.sink.getAsserted().size()); assertEquals(new LeftTuple(tuple2, f0.getFirstRightTuple(), this.sink, true), ((Object[]) this.sink.getAsserted().get(1))[0]); final DefaultFactHandle f3 = (DefaultFactHandle) this.workingMemory .insert("test2"); this.node.assertObject(f3, this.context, this.workingMemory); assertEquals(4, this.sink.getAsserted().size()); final List tuples = new ArrayList(); tuples.add(((Object[]) this.sink.getAsserted().get(2))[0]); tuples.add(((Object[]) this.sink.getAsserted().get(3))[0]); assertTrue(tuples.contains(new LeftTuple(tuple1, f3 .getFirstRightTuple(), this.sink, true))); assertTrue(tuples.contains(new LeftTuple(tuple2, f3 .getFirstRightTuple(), this.sink, true))); } /** * Test Tuple retraction * * @throws Exception * @throws RetractionException */ public void testRetractTuple() throws Exception { when( constraint.isAllowedCachedLeft(any(ContextEntry.class), any(InternalFactHandle.class))).thenReturn(true); when( constraint.isAllowedCachedRight(any(LeftTuple.class), any(ContextEntry.class))).thenReturn(true); // setup 2 tuples 3 fact handles final DefaultFactHandle f0 = (DefaultFactHandle) this.workingMemory .insert("test0"); this.node.assertObject(f0, this.context, this.workingMemory); final DefaultFactHandle f1 = (DefaultFactHandle) this.workingMemory .insert("test1"); final LeftTuple tuple1 = new LeftTuple(f1, this.node, true); this.node.assertLeftTuple(tuple1, this.context, this.workingMemory); final DefaultFactHandle f2 = (DefaultFactHandle) this.workingMemory .insert("test2"); final LeftTuple tuple2 = new LeftTuple(f2, this.node, true); this.node.assertLeftTuple(tuple2, this.context, this.workingMemory); final DefaultFactHandle f3 = (DefaultFactHandle) this.workingMemory .insert("test3"); this.node.assertObject(f3, this.context, this.workingMemory); final DefaultFactHandle f4 = (DefaultFactHandle) this.workingMemory .insert("test4"); this.node.assertObject(f4, this.context, this.workingMemory); assertLength(6, this.sink.getAsserted()); // Double check the item is in memory final BetaMemory memory = (BetaMemory) this.workingMemory .getNodeMemory(this.node); assertTrue(memory.getRightTupleMemory().contains( f0.getFirstRightTuple())); // Retract an object, check propagations and memory this.node.retractRightTuple(f0.getFirstRightTuple(), this.context, this.workingMemory); assertLength(2, this.sink.getRetracted()); List tuples = new ArrayList(); tuples.add(((Object[]) this.sink.getRetracted().get(0))[0]); tuples.add(((Object[]) this.sink.getRetracted().get(1))[0]); assertTrue(tuples.contains(new LeftTuple(tuple1, f0 .getFirstRightTuple(), this.sink, true))); assertTrue(tuples.contains(new LeftTuple(tuple1, f0 .getFirstRightTuple(), this.sink, true))); // Now check the item is no longer in memory assertFalse(memory.getRightTupleMemory().contains( f0.getFirstRightTuple())); this.node.retractLeftTuple(tuple2, this.context, this.workingMemory); assertEquals(4, this.sink.getRetracted().size()); tuples = new ArrayList(); tuples.add(((Object[]) this.sink.getRetracted().get(2))[0]); tuples.add(((Object[]) this.sink.getRetracted().get(3))[0]); assertTrue(tuples.contains(new LeftTuple(tuple2, f3 .getFirstRightTuple(), this.sink, true))); assertTrue(tuples.contains(new LeftTuple(tuple2, f4 .getFirstRightTuple(), this.sink, true))); } public void testConstraintPropagations() throws Exception { when( constraint.isAllowedCachedLeft(any(ContextEntry.class), any(InternalFactHandle.class))).thenReturn(false); when( constraint.isAllowedCachedRight(any(LeftTuple.class), any(ContextEntry.class))).thenReturn(false); // assert first right object final DefaultFactHandle f0 = (DefaultFactHandle) this.workingMemory .insert("test0"); this.node.assertObject(f0, this.context, this.workingMemory); // assert tuple, should add left memory should be 2 final DefaultFactHandle f1 = new DefaultFactHandle(1, "cheese"); final LeftTuple tuple1 = new LeftTuple(f1, this.node, true); this.node.assertLeftTuple(tuple1, this.context, this.workingMemory); // Should be no assertions assertLength(0, this.sink.getAsserted()); this.node.retractRightTuple(f0.getFirstRightTuple(), this.context, this.workingMemory); assertLength(0, this.sink.getRetracted()); } public void testUpdateSink() { when( constraint.isAllowedCachedLeft(any(ContextEntry.class), any(InternalFactHandle.class))).thenReturn(true); when( constraint.isAllowedCachedRight(any(LeftTuple.class), any(ContextEntry.class))).thenReturn(true); final ReteooWorkingMemory workingMemory = new ReteooWorkingMemory(1, (ReteooRuleBase) RuleBaseFactory.newRuleBase()); ReteooRuleBase ruleBase = (ReteooRuleBase) RuleBaseFactory .newRuleBase(); BuildContext buildContext = new BuildContext(ruleBase, ruleBase .getReteooBuilder().getIdGenerator()); final JoinNode joinNode = new JoinNode(1, this.tupleSource, this.objectSource, EmptyBetaConstraints.getInstance(), Behavior.EMPTY_BEHAVIOR_LIST, buildContext); // Add the first tuple sink and assert a tuple and object // The sink has no memory final MockLeftTupleSink sink1 = new MockLeftTupleSink(2); joinNode.addTupleSink(sink1); final DefaultFactHandle f0 = new DefaultFactHandle(0, "string0"); final LeftTuple tuple1 = new LeftTuple(f0, this.node, true); joinNode.assertLeftTuple(tuple1, this.context, workingMemory); final String string1 = "string1"; final DefaultFactHandle string1Handle = new DefaultFactHandle(1, string1); joinNode.assertObject(string1Handle, this.context, workingMemory); assertLength(1, sink1.getAsserted()); // Add the new sink, this should be updated from the re-processed // joinnode memory final MockLeftTupleSink sink2 = new MockLeftTupleSink(3); assertLength(0, sink2.getAsserted()); joinNode.updateSink(sink2, this.context, workingMemory); assertLength(1, sink2.getAsserted()); } }