/**
* Copyright 2007 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;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.drools.base.ShadowProxy;
import org.drools.common.BaseNode;
import org.drools.common.InternalFactHandle;
import org.drools.common.InternalWorkingMemory;
import org.drools.common.InternalWorkingMemoryEntryPoint;
import org.drools.common.PropagationContextImpl;
import org.drools.common.RuleBasePartitionId;
import org.drools.core.util.Iterator;
import org.drools.core.util.ObjectHashSet;
import org.drools.core.util.ObjectHashSet.ObjectEntry;
import org.drools.reteoo.builder.BuildContext;
import org.drools.rule.EntryPoint;
import org.drools.spi.ObjectType;
import org.drools.spi.PropagationContext;
/**
* 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
*
* @author <a href="mailto:tirelli@post.com">Edson Tirelli</a>
*/
public class EntryPointNode extends ObjectSource
implements
Externalizable,
ObjectSink {
// ------------------------------------------------------------
// Instance members
// ------------------------------------------------------------
private static final long serialVersionUID = 510l;
/**
* The entry point ID for this node
*/
private EntryPoint entryPoint;
/**
* The object type nodes under this node
*/
private Map<ObjectType, ObjectTypeNode> objectTypeNodes;
// ------------------------------------------------------------
// Constructors
// ------------------------------------------------------------
public EntryPointNode() {
}
public EntryPointNode(final int id,
final ObjectSource objectSource,
final BuildContext context) {
this( id,
context.getPartitionId(),
context.getRuleBase().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 EntryPoint 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>();
}
// ------------------------------------------------------------
// Instance methods
// ------------------------------------------------------------
@SuppressWarnings("unchecked")
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
super.readExternal( in );
entryPoint = (EntryPoint) in.readObject();
objectTypeNodes = (Map<ObjectType, ObjectTypeNode>) in.readObject();
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal( out );
out.writeObject( entryPoint );
out.writeObject( objectTypeNodes );
}
/**
* @return the entryPoint
*/
public EntryPoint getEntryPoint() {
return entryPoint;
}
public void assertObject(final InternalFactHandle handle,
final PropagationContext context,
final ObjectTypeConf objectTypeConf,
final InternalWorkingMemory workingMemory) {
// checks if shadow is enabled
if ( objectTypeConf.isShadowEnabled() ) {
// the user has implemented the ShadowProxy interface, let their implementation
// know it is safe to update the information the engine can see.
((ShadowProxy) handle.getObject()).updateProxy();
}
ObjectTypeNode[] cachedNodes = objectTypeConf.getObjectTypeNodes();
for ( int i = 0, length = cachedNodes.length; i < length; i++ ) {
cachedNodes[i].assertObject( handle,
context,
workingMemory );
}
}
public void modifyObject(final InternalFactHandle handle,
final PropagationContext context,
final ObjectTypeConf objectTypeConf,
final InternalWorkingMemory workingMemory) {
// checks if shadow is enabled
if ( objectTypeConf.isShadowEnabled() ) {
// the user has implemented the ShadowProxy interface, let their implementation
// know it is safe to update the information the engine can see.
((ShadowProxy) handle.getObject()).updateProxy();
}
ObjectTypeNode[] cachedNodes = objectTypeConf.getObjectTypeNodes();
// make a reference to the previous tuples, then null then on the handle
ModifyPreviousTuples modifyPreviousTuples = new ModifyPreviousTuples(handle.getFirstLeftTuple(), handle.getFirstRightTuple() );
handle.setFirstLeftTuple( null );
handle.setFirstRightTuple( null );
handle.setLastLeftTuple( null );
handle.setLastRightTuple( null );
for ( int i = 0, length = cachedNodes.length; i < length; i++ ) {
cachedNodes[i].modifyObject( handle,
modifyPreviousTuples,
context, workingMemory );
}
modifyPreviousTuples.retractTuples( context, workingMemory );
}
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) {
ObjectTypeNode[] cachedNodes = objectTypeConf.getObjectTypeNodes();
if ( cachedNodes == null ) {
// it is possible that there are no ObjectTypeNodes for an object being retracted
return;
}
for ( int i = 0; i < cachedNodes.length; i++ ) {
cachedNodes[i].retractObject( handle,
context,
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 );
}
protected void removeObjectSink(final ObjectSink objectSink) {
final ObjectTypeNode node = (ObjectTypeNode) objectSink;
this.objectTypeNodes.remove( node.getObjectType() );
}
public void attach() {
this.source.addObjectSink( this );
}
public void attach(final InternalWorkingMemory[] workingMemories) {
attach();
for ( int i = 0, length = workingMemories.length; i < length; i++ ) {
final InternalWorkingMemory workingMemory = workingMemories[i];
workingMemory.updateEntryPointsCache();
final PropagationContext propagationContext = new PropagationContextImpl( workingMemory.getNextPropagationIdCounter(),
PropagationContext.RULE_ADDITION,
null,
null,
null );
this.source.updateSink( this,
propagationContext,
workingMemory );
}
}
protected void doRemove(final RuleRemovalContext context,
final ReteooBuilder builder,
final BaseNode node,
final InternalWorkingMemory[] workingMemories) {
final ObjectTypeNode objectTypeNode = (ObjectTypeNode) node;
removeObjectSink( objectTypeNode );
}
public Map<ObjectType, ObjectTypeNode> getObjectTypeNodes() {
return this.objectTypeNodes;
}
public int hashCode() {
return this.entryPoint.hashCode();
}
public boolean equals(final Object object) {
if ( object == this ) {
return true;
}
if ( object == null || !(object instanceof EntryPointNode) ) {
return false;
}
final EntryPointNode other = (EntryPointNode) object;
return this.entryPoint.equals( other.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();
InternalWorkingMemoryEntryPoint wmEntryPoint = (InternalWorkingMemoryEntryPoint) workingMemory.getWorkingMemoryEntryPoint( this.entryPoint.getEntryPointId() );
for ( ObjectTypeConf objectTypeConf : wmEntryPoint.getObjectTypeConfigurationRegistry().values() ) {
if ( newObjectType.isAssignableFrom( objectTypeConf.getConcreteObjectTypeNode().getObjectType() ) ) {
objectTypeConf.resetCache();
ObjectTypeNode sourceNode = objectTypeConf.getConcreteObjectTypeNode();
Iterator it = ((ObjectHashSet) workingMemory.getNodeMemory( sourceNode )).iterator();
for ( ObjectEntry entry = (ObjectEntry) it.next(); entry != null; entry = (ObjectEntry) it.next() ) {
sink.assertObject( (InternalFactHandle) entry.getValue(),
context,
workingMemory );
}
}
}
}
public boolean isObjectMemoryEnabled() {
return false;
}
public void setObjectMemoryEnabled(boolean objectMemoryEnabled) {
throw new UnsupportedOperationException( "Entry Point Node has no Object memory" );
}
public String toString() {
return "[EntryPointNode(" + this.id + ") " + this.entryPoint + " ]";
}
}