/* * Copyright 2005 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.core.reteoo; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import org.drools.core.RuleBaseConfiguration; import org.drools.core.base.ClassObjectType; import org.drools.core.common.InternalFactHandle; import org.drools.core.common.InternalWorkingMemory; import org.drools.core.common.Memory; import org.drools.core.common.MemoryFactory; import org.drools.core.common.RuleBasePartitionId; import org.drools.core.common.TupleSets; import org.drools.core.common.UpdateContext; import org.drools.core.phreak.SegmentUtilities; import org.drools.core.reteoo.ObjectTypeNode.Id; import org.drools.core.reteoo.builder.BuildContext; import org.drools.core.rule.Pattern; import org.drools.core.spi.Activation; import org.drools.core.spi.ClassWireable; import org.drools.core.spi.ObjectType; import org.drools.core.spi.PropagationContext; import org.drools.core.spi.Tuple; import org.drools.core.util.AbstractBaseLinkedListNode; import org.drools.core.util.bitmask.AllSetBitMask; import org.drools.core.util.bitmask.BitMask; import org.kie.api.definition.rule.Rule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.drools.core.phreak.AddRemoveRule.flushLeftTupleIfNecessary; import static org.drools.core.reteoo.PropertySpecificUtil.*; /** * All asserting Facts must propagated into the right <code>ObjectSink</code> side of a BetaNode, if this is the first Pattern * then there are no BetaNodes to propagate to. <code>LeftInputAdapter</code> is used to adapt an ObjectSink propagation into a * <code>TupleSource</code> which propagates a <code>ReteTuple</code> suitable fot the right <code>ReteTuple</code> side * of a <code>BetaNode</code>. */ public class LeftInputAdapterNode extends LeftTupleSource implements ObjectSinkNode, MemoryFactory<LeftInputAdapterNode.LiaNodeMemory> { protected static final transient Logger log = LoggerFactory.getLogger(LeftInputAdapterNode.class); private static final long serialVersionUID = 510l; private ObjectSource objectSource; private ObjectSinkNode previousRightTupleSinkNode; private ObjectSinkNode nextRightTupleSinkNode; private boolean leftTupleMemoryEnabled; private BitMask sinkMask; public LeftInputAdapterNode() { } /** * Constructus a LeftInputAdapterNode with a unique id that receives <code>FactHandle</code> from a * parent <code>ObjectSource</code> and adds it to a given pattern in the resulting Tuples. * * @param id * The unique id of this node in the current Rete network * @param source * The parent node, where Facts are propagated from */ public LeftInputAdapterNode(final int id, final ObjectSource source, final BuildContext context) { super(id, context); this.objectSource = source; this.leftTupleMemoryEnabled = context.isTupleMemoryEnabled(); ObjectSource current = source; while (!(current.getType() == NodeTypeEnums.ObjectTypeNode)) { current = current.getParentObjectSource(); } setStreamMode( context.isStreamMode() && context.getRootObjectTypeNode().getObjectType().isEvent() ); sinkMask = calculateSinkMask(context); hashcode = calculateHashCode(); } private BitMask calculateSinkMask(BuildContext context) { Pattern pattern = context.getLastBuiltPatterns() != null ? context.getLastBuiltPatterns()[0] : null; if (pattern == null) { return AllSetBitMask.get(); } ObjectType objectType = pattern.getObjectType(); if ( !(objectType instanceof ClassObjectType) ) { // Only ClassObjectType can use property specific return AllSetBitMask.get(); } Class objectClass = ((ClassWireable) objectType).getClassType(); return isPropertyReactive( context, objectClass ) ? calculatePositiveMask( pattern.getListenedProperties(), getAccessibleProperties( context.getKnowledgeBase(), objectClass ) ) : AllSetBitMask.get(); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { super.readExternal(in); objectSource = (ObjectSource) in.readObject(); leftTupleMemoryEnabled = in.readBoolean(); sinkMask = (BitMask) in.readObject(); } public void writeExternal(ObjectOutput out) throws IOException { super.writeExternal(out); out.writeObject(objectSource); out.writeBoolean(leftTupleMemoryEnabled); out.writeObject(sinkMask); } public ObjectSource getObjectSource() { return this.objectSource; } public short getType() { return NodeTypeEnums.LeftInputAdapterNode; } @Override public boolean isLeftTupleMemoryEnabled() { return leftTupleMemoryEnabled; } public ObjectSource getParentObjectSource() { return this.objectSource; } public void attach( BuildContext context ) { this.objectSource.addObjectSink( this ); } public void networkUpdated(UpdateContext updateContext) { this.objectSource.networkUpdated(updateContext); } public void assertObject(final InternalFactHandle factHandle, final PropagationContext context, final InternalWorkingMemory workingMemory) { LiaNodeMemory lm = workingMemory.getNodeMemory( this ); if ( lm.getSegmentMemory() == null ) { SegmentUtilities.createSegmentMemory(this, workingMemory); } doInsertObject( factHandle, context, this, workingMemory, lm, true, // queries are handled directly, and not through here true ); } public static void doInsertObject(final InternalFactHandle factHandle, final PropagationContext context, final LeftInputAdapterNode liaNode, final InternalWorkingMemory wm, final LiaNodeMemory lm, boolean linkOrNotify, boolean useLeftMemory) { SegmentMemory sm = lm.getSegmentMemory(); if ( sm.getTipNode() == liaNode) { // liaNode in it's own segment and child segments not yet created if ( sm.isEmpty() ) { SegmentUtilities.createChildSegments( wm, sm, liaNode.getSinkPropagator() ); } sm = sm.getFirst(); // repoint to the child sm } int counter = lm.getAndIncreaseCounter(); // node is not linked, so notify will happen when we link the node boolean notifySegment = linkOrNotify && counter != 0; if ( counter == 0) { // if there is no left mempry, then there is no linking or notification if ( linkOrNotify ) { // link and notify lm.linkNode( wm ); } else { // link without notify, when driven by a query, as we don't want it, placed on the agenda lm.linkNodeWithoutRuleNotify(); } } LeftTupleSink sink = liaNode.getSinkPropagator().getFirstLeftTupleSink(); LeftTuple leftTuple = sink.createLeftTuple( factHandle, sink, useLeftMemory ); leftTuple.setPropagationContext( context ); doInsertSegmentMemory( wm, notifySegment, lm, sm, leftTuple, liaNode.isStreamMode() ); if ( sm.getRootNode() != liaNode ) { // sm points to lia child sm, so iterate for all remaining children for ( sm = sm.getNext(); sm != null; sm = sm.getNext() ) { sink = sm.getSinkFactory(); leftTuple = sink.createPeer( leftTuple ); // pctx is set during peer cloning doInsertSegmentMemory( wm, notifySegment, lm, sm, leftTuple, liaNode.isStreamMode() ); } } } public static void doInsertSegmentMemory( InternalWorkingMemory wm, boolean linkOrNotify, final LiaNodeMemory lm, SegmentMemory sm, LeftTuple leftTuple, boolean streamMode ) { if ( flushLeftTupleIfNecessary( wm, sm, leftTuple, streamMode, Tuple.INSERT ) ) { if ( linkOrNotify ) { lm.setNodeDirty( wm ); } return; } // mask check is necessary if insert is a result of a modify boolean stagedInsertWasEmpty = sm.getStagedLeftTuples().addInsert( leftTuple ); if ( stagedInsertWasEmpty && linkOrNotify ) { // staged is empty, so notify rule, to force re-evaluation. lm.setNodeDirty(wm); } } public static void doDeleteObject(LeftTuple leftTuple, PropagationContext context, SegmentMemory sm, final InternalWorkingMemory wm, final LeftInputAdapterNode liaNode, final boolean linkOrNotify, final LiaNodeMemory lm) { if ( sm.getTipNode() == liaNode ) { // liaNode in it's own segment and child segments not yet created if ( sm.isEmpty() ) { SegmentUtilities.createChildSegments( wm, sm, liaNode.getSinkPropagator() ); } sm = sm.getFirst(); // repoint to the child sm } doDeleteSegmentMemory(leftTuple, context, lm, sm, wm, linkOrNotify, liaNode.isStreamMode()); if ( sm.getNext() != null) { // sm points to lia child sm, so iterate for all remaining children for ( sm = sm.getNext(); sm != null; sm = sm.getNext() ) { // iterate for peers segment memory leftTuple = leftTuple.getPeer(); if (leftTuple == null) { break; } doDeleteSegmentMemory(leftTuple, context, lm, sm, wm, linkOrNotify, liaNode.isStreamMode()); } } if ( lm.getAndDecreaseCounter() == 1 ) { if ( linkOrNotify ) { lm.unlinkNode( wm ); } else { lm.unlinkNodeWithoutRuleNotify(); } } } private static void doDeleteSegmentMemory(LeftTuple leftTuple, PropagationContext pctx, final LiaNodeMemory lm, SegmentMemory sm, InternalWorkingMemory wm, boolean linkOrNotify, boolean streamMode) { leftTuple.setPropagationContext( pctx ); if ( flushLeftTupleIfNecessary( wm, sm, leftTuple, streamMode, Tuple.DELETE ) ) { if ( linkOrNotify ) { lm.setNodeDirty( wm ); } return; } TupleSets<LeftTuple> leftTuples = sm.getStagedLeftTuples(); boolean stagedDeleteWasEmpty = leftTuples.addDelete(leftTuple); if ( stagedDeleteWasEmpty && linkOrNotify ) { // staged is empty, so notify rule, to force re-evaluation lm.setNodeDirty(wm); } } public static void doUpdateObject(LeftTuple leftTuple, PropagationContext context, final InternalWorkingMemory wm, final LeftInputAdapterNode liaNode, final boolean linkOrNotify, final LiaNodeMemory lm, SegmentMemory sm) { if ( sm.getTipNode() == liaNode) { // liaNode in it's own segment and child segments not yet created if ( sm.isEmpty() ) { SegmentUtilities.createChildSegments( wm, sm, liaNode.getSinkPropagator() ); } sm = sm.getFirst(); // repoint to the child sm } TupleSets<LeftTuple> leftTuples = sm.getStagedLeftTuples(); doUpdateSegmentMemory(leftTuple, context, wm, linkOrNotify, lm, leftTuples, sm, liaNode.isStreamMode() ); if ( sm.getNext() != null ) { // sm points to lia child sm, so iterate for all remaining children for ( sm = sm.getNext(); sm != null; sm = sm.getNext() ) { // iterate for peers segment memory leftTuple = leftTuple.getPeer(); leftTuples = sm.getStagedLeftTuples(); doUpdateSegmentMemory(leftTuple, context, wm, linkOrNotify, lm, leftTuples, sm, liaNode.isStreamMode() ); } } } private static void doUpdateSegmentMemory( LeftTuple leftTuple, PropagationContext pctx, InternalWorkingMemory wm, boolean linkOrNotify, final LiaNodeMemory lm, TupleSets<LeftTuple> leftTuples, SegmentMemory sm, boolean streamMode ) { leftTuple.setPropagationContext( pctx ); if ( leftTuple.getStagedType() == LeftTuple.NONE ) { if ( flushLeftTupleIfNecessary( wm, sm, leftTuple, streamMode, Tuple.UPDATE ) ) { if ( linkOrNotify ) { lm.setNodeDirty( wm ); } return; } // if LeftTuple is already staged, leave it there boolean stagedUpdateWasEmpty = leftTuples.addUpdate(leftTuple); if ( stagedUpdateWasEmpty && linkOrNotify ) { // staged is empty, so notify rule, to force re-evaluation lm.setNodeDirty(wm); } } } public void retractLeftTuple(LeftTuple leftTuple, PropagationContext context, InternalWorkingMemory workingMemory) { LiaNodeMemory lm = workingMemory.getNodeMemory( this ); SegmentMemory smem = lm.getSegmentMemory(); if ( smem.getTipNode() == this ) { // segment with only a single LiaNode in it, skip to next segment // as a liaNode only segment has no staging smem = smem.getFirst(); } doDeleteObject( leftTuple, context, smem, workingMemory, this, true, lm ); } public void modifyObject(InternalFactHandle factHandle, final ModifyPreviousTuples modifyPreviousTuples, PropagationContext context, InternalWorkingMemory workingMemory) { ObjectTypeNode.Id otnId = this.sink.getFirstLeftTupleSink().getLeftInputOtnId(); LeftTuple leftTuple = processDeletesFromModify(modifyPreviousTuples, context, workingMemory, otnId); LiaNodeMemory lm = workingMemory.getNodeMemory( this ); if ( lm.getSegmentMemory() == null ) { SegmentUtilities.createSegmentMemory( this, workingMemory ); } if ( leftTuple != null && leftTuple.getInputOtnId().equals( otnId ) ) { modifyPreviousTuples.removeLeftTuple(partitionId); leftTuple.reAdd(); LeftTupleSink sink = getSinkPropagator().getFirstLeftTupleSink(); BitMask mask = sink.getLeftInferredMask(); if ( context.getModificationMask().intersects( mask) ) { doUpdateObject( leftTuple, context, workingMemory, (LeftInputAdapterNode) leftTuple.getTupleSource(), true, lm, lm.getSegmentMemory() ); if (leftTuple instanceof Activation) { ((Activation)leftTuple).setActive(true); } } } else { LeftTupleSink sink = getSinkPropagator().getFirstLeftTupleSink(); BitMask mask = sink.getLeftInferredMask(); if ( context.getModificationMask().intersects( mask) ) { doInsertObject(factHandle, context, this, workingMemory, lm, true, true); } } } private LeftTuple processDeletesFromModify(ModifyPreviousTuples modifyPreviousTuples, PropagationContext context, InternalWorkingMemory workingMemory, Id otnId) { LeftTuple leftTuple = modifyPreviousTuples.peekLeftTuple(partitionId); while ( leftTuple != null && leftTuple.getInputOtnId().before( otnId ) ) { modifyPreviousTuples.removeLeftTuple(partitionId); LeftInputAdapterNode prevLiaNode = (LeftInputAdapterNode) leftTuple.getTupleSource(); LiaNodeMemory prevLm = workingMemory.getNodeMemory( prevLiaNode ); SegmentMemory prevSm = prevLm.getSegmentMemory(); doDeleteObject( leftTuple, context, prevSm, workingMemory, prevLiaNode, true, prevLm ); leftTuple = modifyPreviousTuples.peekLeftTuple(partitionId); } return leftTuple; } public void byPassModifyToBetaNode(InternalFactHandle factHandle, ModifyPreviousTuples modifyPreviousTuples, PropagationContext context, InternalWorkingMemory workingMemory) { modifyObject(factHandle, modifyPreviousTuples, context, workingMemory ); } protected boolean doRemove(final RuleRemovalContext context, final ReteooBuilder builder, final InternalWorkingMemory[] workingMemories) { if (!isInUse()) { objectSource.removeObjectSink(this); return true; } return false; } public LeftTuple createPeer(LeftTuple original) { return null; } /** * Returns the next node * @return * The next ObjectSinkNode */ public ObjectSinkNode getNextObjectSinkNode() { return this.nextRightTupleSinkNode; } /** * Sets the next node * @param next * The next ObjectSinkNode */ public void setNextObjectSinkNode(final ObjectSinkNode next) { this.nextRightTupleSinkNode = next; } /** * Returns the previous node * @return * The previous ObjectSinkNode */ public ObjectSinkNode getPreviousObjectSinkNode() { return this.previousRightTupleSinkNode; } /** * Sets the previous node * @param previous * The previous ObjectSinkNode */ public void setPreviousObjectSinkNode(final ObjectSinkNode previous) { this.previousRightTupleSinkNode = previous; } private int calculateHashCode() { return 31 * this.objectSource.hashCode() + 37 * sinkMask.hashCode(); } @Override public boolean equals(final Object object) { return this == object || ( internalEquals(object) && this.objectSource.equals(((LeftInputAdapterNode)object).objectSource) ); } @Override protected boolean internalEquals( Object object ) { if ( object == null || !(object instanceof LeftInputAdapterNode) || this.hashCode() != object.hashCode() ) { return false; } return this.sinkMask.equals( ((LeftInputAdapterNode) object).sinkMask ); } @Override public ObjectTypeNode getObjectTypeNode() { ObjectSource source = this.objectSource; while ( source != null ) { if ( source instanceof ObjectTypeNode ) { return (ObjectTypeNode) source; } source = source.source; } return null; } public LiaNodeMemory createMemory(RuleBaseConfiguration config, InternalWorkingMemory wm) { return new LiaNodeMemory(); } public static class LiaNodeMemory extends AbstractBaseLinkedListNode<Memory> implements SegmentNodeMemory { private int counter; private SegmentMemory segmentMemory; private long nodePosMaskBit; public LiaNodeMemory() { } public int getCounter() { return counter; } public int getAndIncreaseCounter() { return this.counter++; } public int getAndDecreaseCounter() { return this.counter--; } public void setCounter(int counter) { this.counter = counter; } public SegmentMemory getSegmentMemory() { return segmentMemory; } public void setSegmentMemory(SegmentMemory segmentNodes) { this.segmentMemory = segmentNodes; } public long getNodePosMaskBit() { return nodePosMaskBit; } public void setNodePosMaskBit(long nodePosMask) { nodePosMaskBit = nodePosMask; } public void setNodeDirtyWithoutNotify() { } public void setNodeCleanWithoutNotify() { } public void linkNodeWithoutRuleNotify() { segmentMemory.linkNodeWithoutRuleNotify(nodePosMaskBit); } public void linkNode(InternalWorkingMemory wm) { segmentMemory.linkNode(nodePosMaskBit, wm); } public boolean unlinkNode(InternalWorkingMemory wm) { return segmentMemory.unlinkNode(nodePosMaskBit, wm); } public void unlinkNodeWithoutRuleNotify() { segmentMemory.unlinkNodeWithoutRuleNotify(nodePosMaskBit); } public short getNodeType() { return NodeTypeEnums.LeftInputAdapterNode; } public void setNodeDirty(InternalWorkingMemory wm) { segmentMemory.notifyRuleLinkSegment(wm, nodePosMaskBit); } public void reset() { counter = 0; } } /** * Used with the updateSink method, so that the parent ObjectSource * can update the TupleSink */ public static class RightTupleSinkAdapter implements ObjectSink { private LeftTupleSink sink; private boolean leftTupleMemoryEnabled; private LeftInputAdapterNode liaNode; public RightTupleSinkAdapter(LeftInputAdapterNode liaNode) { this.liaNode = liaNode; } public RightTupleSinkAdapter(final LeftTupleSink sink, boolean leftTupleMemoryEnabled) { this.sink = sink; this.leftTupleMemoryEnabled = leftTupleMemoryEnabled; } public void assertObject(final InternalFactHandle factHandle, final PropagationContext context, final InternalWorkingMemory workingMemory) { if ( liaNode != null ) { // phreak liaNode.assertObject(factHandle, context, workingMemory); } else { final LeftTuple tuple = this.sink.createLeftTuple( factHandle, this.sink, this.leftTupleMemoryEnabled ); this.sink.assertLeftTuple( tuple, context, workingMemory ); } } public void modifyObject(InternalFactHandle factHandle, ModifyPreviousTuples modifyPreviousTuples, PropagationContext context, InternalWorkingMemory workingMemory) { throw new UnsupportedOperationException( "ObjectSinkAdapter onlys supports assertObject method calls" ); } public int getId() { return 0; } public RuleBasePartitionId getPartitionId() { return sink.getPartitionId(); } public void writeExternal(ObjectOutput out) throws IOException { // this is a short living adapter class used only during an update operation, and // as so, no need for serialization code } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // this is a short living adapter class used only during an update operation, and // as so, no need for serialization code } public void byPassModifyToBetaNode(InternalFactHandle factHandle, ModifyPreviousTuples modifyPreviousTuples, PropagationContext context, InternalWorkingMemory workingMemory) { throw new UnsupportedOperationException(); } public short getType() { return NodeTypeEnums.LeftInputAdapterNode; } public int getAssociationsSize() { return sink.getAssociationsSize(); } public int getAssociatedRuleSize() { return sink.getAssociatedRuleSize(); } public int getAssociationsSize(Rule rule) { return sink.getAssociationsSize(rule); } public boolean isAssociatedWith( Rule rule ) { return sink.isAssociatedWith( rule ); } public boolean thisNodeEquals(final Object object) { return false; } public int nodeHashCode() {return this.hashCode();} } @Override public void setSourcePartitionId(BuildContext context, RuleBasePartitionId partitionId) { setSourcePartitionId(objectSource, context, partitionId); } @Override public void setPartitionId(BuildContext context, RuleBasePartitionId partitionId) { if (this.partitionId != null && this.partitionId != partitionId) { objectSource.sink.changeSinkPartition( (ObjectSink)this, this.partitionId, partitionId, objectSource.alphaNodeHashingThreshold ); } this.partitionId = partitionId; } }