/*
* Copyright 2007 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.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.drools.core.WorkingMemoryEntryPoint;
import org.drools.core.base.ClassObjectType;
import org.drools.core.common.BaseNode;
import org.drools.core.common.EventFactHandle;
import org.drools.core.common.InternalFactHandle;
import org.drools.core.common.InternalWorkingMemory;
import org.drools.core.common.RuleBasePartitionId;
import org.drools.core.phreak.PropagationEntry;
import org.drools.core.reteoo.builder.BuildContext;
import org.drools.core.rule.EntryPointId;
import org.drools.core.spi.ObjectType;
import org.drools.core.spi.PropagationContext;
import org.drools.core.util.bitmask.BitMask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A node that is an entry point into the Rete network.
*
* As we move the design to support network partitions and concurrent processing
* of parts of the network, we also need to support multiple, independent entry
* points and this class represents that.
*
* It replaces the function of the Rete Node class in previous designs.
*
* @see ObjectTypeNode
*/
public class EntryPointNode extends ObjectSource
implements
Externalizable,
ObjectSink {
// ------------------------------------------------------------
// Instance members
// ------------------------------------------------------------
private static final long serialVersionUID = 510l;
protected static final transient Logger log = LoggerFactory.getLogger(EntryPointNode.class);
/**
* The entry point ID for this node
*/
private EntryPointId entryPoint;
/**
* The object type nodes under this node
*/
protected Map<ObjectType, ObjectTypeNode> objectTypeNodes;
protected ObjectTypeNode queryNode;
private ObjectTypeNode activationNode;
// ------------------------------------------------------------
// Constructors
// ------------------------------------------------------------
public EntryPointNode() {
}
public EntryPointNode(final int id,
final ObjectSource objectSource,
final BuildContext context) {
this( id,
context.getPartitionId(),
context.getKnowledgeBase().getConfiguration().isMultithreadEvaluation(),
objectSource,
context.getCurrentEntryPoint() ); // irrelevant for this node, since it overrides sink management
}
public EntryPointNode(final int id,
final RuleBasePartitionId partitionId,
final boolean partitionsEnabled,
final ObjectSource objectSource,
final EntryPointId entryPoint) {
super( id,
partitionId,
partitionsEnabled,
objectSource,
999 ); // irrelevant for this node, since it overrides sink management
this.entryPoint = entryPoint;
this.objectTypeNodes = new ConcurrentHashMap<ObjectType, ObjectTypeNode>();
hashcode = calculateHashCode();
}
// ------------------------------------------------------------
// Instance methods
// ------------------------------------------------------------
@SuppressWarnings("unchecked")
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
super.readExternal( in );
entryPoint = (EntryPointId) in.readObject();
objectTypeNodes = (Map<ObjectType, ObjectTypeNode>) in.readObject();
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
out.writeObject( entryPoint );
out.writeObject( objectTypeNodes );
}
public short getType() {
return NodeTypeEnums.EntryPointNode;
}
/**
* @return the entryPoint
*/
public EntryPointId getEntryPoint() {
return entryPoint;
}
void setEntryPoint(EntryPointId entryPoint) {
this.entryPoint = entryPoint;
}
public void assertQuery(final InternalFactHandle factHandle,
final PropagationContext context,
final InternalWorkingMemory workingMemory) {
throw new UnsupportedOperationException("rete only");
}
public void retractQuery(final InternalFactHandle factHandle,
final PropagationContext context,
final InternalWorkingMemory workingMemory) {
throw new UnsupportedOperationException("rete only");
}
public void modifyQuery(final InternalFactHandle factHandle,
final PropagationContext context,
final InternalWorkingMemory workingMemory) {
throw new UnsupportedOperationException("rete only");
}
public ObjectTypeNode getQueryNode() {
if ( queryNode == null ) {
this.queryNode = objectTypeNodes.get( ClassObjectType.DroolsQuery_ObjectType );
}
return this.queryNode;
}
public void assertActivation(final InternalFactHandle factHandle,
final PropagationContext context,
final InternalWorkingMemory workingMemory) {
if ( activationNode == null ) {
this.activationNode = objectTypeNodes.get( ClassObjectType.Match_ObjectType );
}
if ( activationNode != null ) {
// There may be no queries defined
this.activationNode.propagateAssert(factHandle, context, workingMemory);
}
}
public void retractActivation(final InternalFactHandle factHandle,
final PropagationContext context,
final InternalWorkingMemory workingMemory) {
if ( activationNode == null ) {
this.activationNode = objectTypeNodes.get( ClassObjectType.Match_ObjectType );
}
if ( activationNode != null ) {
// There may be no queries defined
this.activationNode.retractObject(factHandle, context, workingMemory);
}
}
public void modifyActivation(final InternalFactHandle factHandle,
final PropagationContext context,
final InternalWorkingMemory workingMemory) {
if ( activationNode == null ) {
this.activationNode = objectTypeNodes.get( ClassObjectType.Match_ObjectType );
}
if ( activationNode != null ) {
ModifyPreviousTuples modifyPreviousTuples = new ModifyPreviousTuples( factHandle.detachLinkedTuples() );
// There may be no queries defined
this.activationNode.modifyObject( factHandle, modifyPreviousTuples, context, workingMemory );
modifyPreviousTuples.retractTuples(context, workingMemory);
}
}
public void assertObject(final InternalFactHandle handle,
final PropagationContext context,
final ObjectTypeConf objectTypeConf,
final InternalWorkingMemory workingMemory) {
if ( log.isTraceEnabled() ) {
log.trace("Insert {}", handle.toString());
}
if ( partitionsEnabled ) {
// In case of multithreaded evaluation the CompositePartitionAwareObjectSinkAdapter
// used by the OTNs will take care of enqueueing this inseretion on the propagation queues
// of the different agendas
PropagationEntry.Insert.execute( handle, context, workingMemory, objectTypeConf );
} else {
workingMemory.addPropagation( new PropagationEntry.Insert( handle, context, workingMemory, objectTypeConf ) );
}
}
public void modifyObject(final InternalFactHandle handle,
final PropagationContext pctx,
final ObjectTypeConf objectTypeConf,
final InternalWorkingMemory workingMemory) {
if ( log.isTraceEnabled() ) {
log.trace( "Update {}", handle.toString() );
}
workingMemory.addPropagation(new PropagationEntry.Update(handle, pctx, objectTypeConf));
}
public static void propagateModify(InternalFactHandle handle, PropagationContext pctx, ObjectTypeConf objectTypeConf, InternalWorkingMemory wm) {
// make a reference to the previous tuples, then null then on the handle
propagateModify( handle, pctx, objectTypeConf, wm, new ModifyPreviousTuples( handle.detachLinkedTuples() ) );
}
public static void propagateModify( InternalFactHandle handle, PropagationContext pctx, ObjectTypeConf objectTypeConf, InternalWorkingMemory wm, ModifyPreviousTuples modifyPreviousTuples ) {
ObjectTypeNode[] cachedNodes = objectTypeConf.getObjectTypeNodes();
for ( int i = 0, length = cachedNodes.length; i < length; i++ ) {
cachedNodes[i].modifyObject( handle, modifyPreviousTuples, pctx, wm );
if (i < cachedNodes.length - 1) {
removeRightTuplesMatchingOTN( pctx, wm, modifyPreviousTuples, cachedNodes[i], 0 );
}
}
modifyPreviousTuples.retractTuples(pctx, wm);
}
public static void removeRightTuplesMatchingOTN( PropagationContext pctx, InternalWorkingMemory wm, ModifyPreviousTuples modifyPreviousTuples, ObjectTypeNode node, int partition ) {
// remove any right tuples that matches the current OTN before continue the modify on the next OTN cache entry
RightTuple rightTuple = modifyPreviousTuples.peekRightTuple(partition);
while ( rightTuple != null &&
((BaseNode) rightTuple.getTupleSink()).getObjectTypeNode() == node ) {
modifyPreviousTuples.removeRightTuple(partition);
modifyPreviousTuples.doRightDelete(pctx, wm, rightTuple);
rightTuple = modifyPreviousTuples.peekRightTuple(partition);
}
while ( true ) {
LeftTuple leftTuple = modifyPreviousTuples.peekLeftTuple(partition);
ObjectTypeNode otn = null;
if (leftTuple != null) {
LeftTupleSink leftTupleSink = leftTuple.getTupleSink();
if (leftTupleSink instanceof LeftTupleSource ) {
otn = leftTupleSink.getLeftTupleSource().getObjectTypeNode();
} else if (leftTupleSink instanceof RuleTerminalNode ) {
otn = ((RuleTerminalNode)leftTupleSink).getObjectTypeNode();
}
}
if ( otn == node ) {
modifyPreviousTuples.removeLeftTuple(partition);
modifyPreviousTuples.doDeleteObject( pctx, wm, leftTuple );
} else {
break;
}
}
}
public void modifyObject(InternalFactHandle factHandle,
ModifyPreviousTuples modifyPreviousTuples,
PropagationContext context,
InternalWorkingMemory workingMemory) {
// this method was silently failing, so I am now throwing an exception to make
// sure no one calls it by mistake
throw new UnsupportedOperationException( "This method should NEVER EVER be called" );
}
/**
* This is the entry point into the network for all asserted Facts. Iterates a cache
* of matching <code>ObjectTypdeNode</code>s asserting the Fact. If the cache does not
* exist it first iterates and builds the cache.
*
* @param factHandle
* The FactHandle of the fact to assert
* @param context
* The <code>PropagationContext</code> of the <code>WorkingMemory</code> action
* @param workingMemory
* The working memory session.
*/
public void assertObject(final InternalFactHandle factHandle,
final PropagationContext context,
final InternalWorkingMemory workingMemory) {
// this method was silently failing, so I am now throwing an exception to make
// sure no one calls it by mistake
throw new UnsupportedOperationException( "This method should NEVER EVER be called" );
}
/**
* Retract a fact object from this <code>RuleBase</code> and the specified
* <code>WorkingMemory</code>.
*
* @param handle
* The handle of the fact to retract.
* @param workingMemory
* The working memory session.
*/
public void retractObject(final InternalFactHandle handle,
final PropagationContext context,
final ObjectTypeConf objectTypeConf,
final InternalWorkingMemory workingMemory) {
if ( log.isTraceEnabled() ) {
log.trace( "Delete {}", handle.toString() );
}
workingMemory.addPropagation(new PropagationEntry.Delete(this, handle, context, objectTypeConf));
}
public void propagateRetract(InternalFactHandle handle, PropagationContext context, ObjectTypeConf objectTypeConf, InternalWorkingMemory workingMemory) {
ObjectTypeNode[] cachedNodes = objectTypeConf.getObjectTypeNodes();
if ( cachedNodes == null ) {
// it is possible that there are no ObjectTypeNodes for an object being retracted
return;
}
for ( ObjectTypeNode cachedNode : cachedNodes ) {
cachedNode.retractObject( handle,
context,
workingMemory );
}
if (handle.isEvent()) {
((EventFactHandle) handle).unscheduleAllJobs(workingMemory);
}
}
/**
* Adds the <code>ObjectSink</code> so that it may receive
* <code>Objects</code> propagated from this <code>ObjectSource</code>.
*
* @param objectSink
* The <code>ObjectSink</code> to receive propagated
* <code>Objects</code>. Rete only accepts <code>ObjectTypeNode</code>s
* as parameters to this method, though.
*/
public void addObjectSink(final ObjectSink objectSink) {
final ObjectTypeNode node = (ObjectTypeNode) objectSink;
this.objectTypeNodes.put(node.getObjectType(),
node);
}
public void removeObjectSink(final ObjectSink objectSink) {
final ObjectTypeNode node = (ObjectTypeNode) objectSink;
this.objectTypeNodes.remove( node.getObjectType() );
}
public void removeObjectType(ObjectType objectType) {
this.objectTypeNodes.remove( objectType );
}
public void attach() {
attach(null);
}
public void attach( BuildContext context ) {
this.source.addObjectSink( this );
if (context == null ) {
return;
}
for ( InternalWorkingMemory workingMemory : context.getWorkingMemories() ) {
workingMemory.updateEntryPointsCache();
}
}
protected boolean doRemove(final RuleRemovalContext context,
final ReteooBuilder builder,
final InternalWorkingMemory[] workingMemories) {
return false;
}
public Map<ObjectType, ObjectTypeNode> getObjectTypeNodes() {
return this.objectTypeNodes;
}
private int calculateHashCode() {
return this.entryPoint.hashCode();
}
@Override
public boolean equals(final Object object) {
return this == object || internalEquals( object );
}
@Override
protected boolean internalEquals( Object object ) {
return object instanceof EntryPointNode && this.hashCode() == object.hashCode() &&
this.entryPoint.equals( ( (EntryPointNode) object ).entryPoint );
}
public void updateSink(final ObjectSink sink,
final PropagationContext context,
final InternalWorkingMemory workingMemory) {
// @todo
// JBRULES-612: the cache MUST be invalidated when a new node type is added to the network, so iterate and reset all caches.
final ObjectTypeNode node = (ObjectTypeNode) sink;
final ObjectType newObjectType = node.getObjectType();
WorkingMemoryEntryPoint wmEntryPoint = workingMemory.getWorkingMemoryEntryPoint( this.entryPoint.getEntryPointId() );
for ( ObjectTypeConf objectTypeConf : wmEntryPoint.getObjectTypeConfigurationRegistry().values() ) {
if ( objectTypeConf.getConcreteObjectTypeNode() != null && newObjectType.isAssignableFrom( objectTypeConf.getConcreteObjectTypeNode().getObjectType() ) ) {
objectTypeConf.resetCache();
ObjectTypeNode sourceNode = objectTypeConf.getConcreteObjectTypeNode();
Iterator<InternalFactHandle> it = workingMemory.getNodeMemory( sourceNode ).iterator();
while ( it.hasNext() ) {
sink.assertObject( it.next(),
context,
workingMemory );
}
}
}
}
public String toString() {
return "[EntryPointNode(" + this.id + ") " + this.entryPoint + " ]";
}
public void byPassModifyToBetaNode(InternalFactHandle factHandle,
ModifyPreviousTuples modifyPreviousTuples,
PropagationContext context,
InternalWorkingMemory workingMemory) {
throw new UnsupportedOperationException();
}
@Override
public BitMask calculateDeclaredMask(List<String> settableProperties) {
throw new UnsupportedOperationException();
}
}