/** * 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. */ package org.drools.reteoo; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import org.drools.RuleBaseConfiguration; import org.drools.base.ClassObjectType; import org.drools.base.ValueType; import org.drools.common.AbstractRuleBase; import org.drools.common.BaseNode; import org.drools.common.DroolsObjectInputStream; import org.drools.common.EventFactHandle; import org.drools.common.InternalFactHandle; import org.drools.common.InternalWorkingMemory; import org.drools.common.NodeMemory; import org.drools.common.PropagationContextImpl; import org.drools.core.util.Iterator; import org.drools.core.util.ObjectHashSet; import org.drools.core.util.ObjectHashSet.ObjectEntry; import org.drools.reteoo.ReteooWorkingMemory.WorkingMemoryReteExpireAction; import org.drools.reteoo.RuleRemovalContext.CleanupAdapter; import org.drools.reteoo.builder.BuildContext; import org.drools.reteoo.compiled.CompiledNetwork; import org.drools.rule.Declaration; import org.drools.rule.EntryPoint; import org.drools.rule.EvalCondition; import org.drools.spi.Constraint; import org.drools.spi.ObjectType; import org.drools.spi.PropagationContext; import org.drools.time.Job; import org.drools.time.JobContext; import org.drools.time.JobHandle; import org.drools.time.TimerService; import org.drools.time.impl.PointInTimeTrigger; /** * <code>ObjectTypeNodes<code> are responsible for filtering and propagating the matching * fact assertions propagated from the <code>Rete</code> node using <code>ObjectType</code> interface. * <p/> * The assert and retract methods do not attempt to filter as this is the role of the <code>Rete</code> * node which builds up a cache of matching <code>ObjectTypdeNodes</code>s for each asserted object, using * the <code>matches(Object object)</code> method. Incorrect propagation in these methods is not checked and * will result in <code>ClassCastExpcections</code> later on in the network. * <p/> * Filters <code>Objects</code> coming from the <code>Rete</code> using a * <code>ObjectType</code> semantic module. * * @author <a href="mailto:mark.proctor@jboss.com">Mark Proctor</a> * @author <a href="mailto:bob@werken.com">Bob McWhirter</a> * @see ObjectType * @see Rete */ public class ObjectTypeNode extends ObjectSource implements ObjectSink, Externalizable, NodeMemory { // ------------------------------------------------------------ // Instance members // ------------------------------------------------------------ /** * */ private static final long serialVersionUID = 510l; /** * The <code>ObjectType</code> semantic module. */ private ObjectType objectType; private boolean skipOnModify = false; private boolean objectMemoryEnabled; private long expirationOffset = -1; private transient ExpireJob job = new ExpireJob(); private CompiledNetwork compiledNetwork; private boolean isPropagating = false; public ObjectTypeNode() { } /** * Construct given a semantic <code>ObjectType</code> and the provided * unique id. All <code>ObjectTypdeNode</code> have node memory. * * @param id The unique id for the node. * @param objectType The semantic object-type differentiator. */ public ObjectTypeNode(final int id, final EntryPointNode source, final ObjectType objectType, final BuildContext context) { super( id, context.getPartitionId(), context.getRuleBase().getConfiguration().isMultithreadEvaluation(), source, context.getRuleBase().getConfiguration().getAlphaNodeHashingThreshold() ); this.objectType = objectType; setObjectMemoryEnabled( context.isObjectTypeNodeMemoryEnabled() ); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { super.readExternal( in ); objectType = (ObjectType) in.readObject(); // this is here as not all objectTypeNodes used ClassObjectTypes in packages (i.e. rules with those nodes did not exist yet) // and thus have no wiring targets if ( objectType instanceof ClassObjectType ) { objectType = ((AbstractRuleBase) ((DroolsObjectInputStream) in).getRuleBase()).getClassFieldAccessorCache().getClassObjectType( (ClassObjectType) objectType ); } skipOnModify = in.readBoolean(); objectMemoryEnabled = in.readBoolean(); expirationOffset = in.readLong(); } public void writeExternal(ObjectOutput out) throws IOException { super.writeExternal( out ); out.writeObject( objectType ); out.writeBoolean( skipOnModify ); out.writeBoolean( objectMemoryEnabled ); out.writeLong( expirationOffset ); } /** * Retrieve the semantic <code>ObjectType</code> differentiator. * * @return The semantic <code>ObjectType</code> differentiator. */ public ObjectType getObjectType() { return this.objectType; } public boolean isAssignableFrom(final ObjectType objectType) { return this.objectType.isAssignableFrom( objectType ); } public void setCompiledNetwork(CompiledNetwork compiledNetwork) { this.compiledNetwork = compiledNetwork; this.compiledNetwork.setObjectTypeNode( this ); } /** * Propagate the <code>FactHandleimpl</code> through the <code>Rete</code> network. All * <code>FactHandleImpl</code> should be remembered in the node memory, so that later runtime rule attachmnents * can have the matched facts propagated to them. * * @param factHandle The fact handle. * @param object The object to assert. * @param workingMemory The working memory session. */ public void assertObject(final InternalFactHandle factHandle, final PropagationContext context, final InternalWorkingMemory workingMemory) { if ( this.objectMemoryEnabled ) { final ObjectHashSet memory = (ObjectHashSet) workingMemory.getNodeMemory( this ); memory.add( factHandle, false ); } if ( compiledNetwork != null ) { compiledNetwork.assertObject( factHandle, context, workingMemory ); } else { isPropagating = true; this.sink.propagateAssertObject( factHandle, context, workingMemory ); isPropagating = false; } if ( this.expirationOffset >= 0 && this.expirationOffset != Long.MAX_VALUE ) { // schedule expiration WorkingMemoryReteExpireAction expire = new WorkingMemoryReteExpireAction( factHandle, this ); TimerService clock = workingMemory.getTimerService(); long nextTimestamp = Math.max( clock.getCurrentTime() + this.expirationOffset, ((EventFactHandle) factHandle).getStartTimestamp() + this.expirationOffset ); JobContext jobctx = new ExpireJobContext( expire, workingMemory ); JobHandle handle = clock.scheduleJob( job, jobctx, new PointInTimeTrigger( nextTimestamp, null, null ) ); jobctx.setJobHandle( handle ); } } /** * Retract the <code>FactHandleimpl</code> from the <code>Rete</code> network. Also remove the * <code>FactHandleImpl</code> from the node memory. * * @param rightTuple The fact handle. * @param object The object to assert. * @param workingMemory The working memory session. */ public void retractObject(final InternalFactHandle factHandle, final PropagationContext context, final InternalWorkingMemory workingMemory) { if ( this.objectMemoryEnabled ) { final ObjectHashSet memory = (ObjectHashSet) workingMemory.getNodeMemory( this ); memory.remove( factHandle ); } for ( RightTuple rightTuple = factHandle.getFirstRightTuple(); rightTuple != null; rightTuple = (RightTuple) rightTuple.getHandleNext() ) { rightTuple.getRightTupleSink().retractRightTuple( rightTuple, context, workingMemory ); } factHandle.setFirstRightTuple( null ); factHandle.setLastRightTuple( null ); for ( LeftTuple leftTuple = factHandle.getFirstLeftTuple(); leftTuple != null; leftTuple = (LeftTuple) leftTuple.getLeftParentNext() ) { leftTuple.getLeftTupleSink().retractLeftTuple( leftTuple, context, workingMemory ); } factHandle.setFirstLeftTuple( null ); factHandle.setLastLeftTuple( null ); } public void modifyObject(InternalFactHandle factHandle, ModifyPreviousTuples modifyPreviousTuples, PropagationContext context, InternalWorkingMemory workingMemory) { if ( this.skipOnModify && context.getDormantActivations() == 0 ) { // we do this after the shadowproxy update, just so that its up to date for the future return; } if ( compiledNetwork != null ) { compiledNetwork.modifyObject( factHandle, modifyPreviousTuples, context, workingMemory ); } else { this.sink.propagateModifyObject( factHandle, modifyPreviousTuples, context, workingMemory ); } } public void updateSink(final ObjectSink sink, final PropagationContext context, final InternalWorkingMemory workingMemory) { final ObjectHashSet memory = (ObjectHashSet) workingMemory.getNodeMemory( this ); Iterator it = memory.iterator(); InternalFactHandle ctxHandle = (InternalFactHandle)context.getFactHandle(); if (!isPropagating || (isPropagating && context.getLatestPropagationAttempt() == ctxHandle.getId())){ context.resetLatestPropagationAttempt(); for ( ObjectEntry entry = (ObjectEntry) it.next(); entry != null; entry = (ObjectEntry) it.next() ) { // Assert everything sink.assertObject( (InternalFactHandle) entry.getValue(), context, workingMemory ); } } else { for ( ObjectEntry entry = (ObjectEntry) it.next(); entry != null; entry = (ObjectEntry) it.next() ) { InternalFactHandle handle = (InternalFactHandle) entry.getValue(); // Exclude the current fact propagation if (handle.getId() != ctxHandle.getId()) { sink.assertObject( handle, context, workingMemory ); } } } } /** * Rete needs to know that this ObjectTypeNode has been added */ public void attach() { this.source.addObjectSink( this ); } public void attach(final InternalWorkingMemory[] workingMemories) { attach(); // we need to call updateSink on Rete, because someone // might have already added facts matching this ObjectTypeNode // to working memories for ( int i = 0, length = workingMemories.length; i < length; i++ ) { final InternalWorkingMemory workingMemory = workingMemories[i]; final PropagationContextImpl propagationContext = new PropagationContextImpl( workingMemory.getNextPropagationIdCounter(), PropagationContext.RULE_ADDITION, null, null, null ); propagationContext.setEntryPoint( ((EntryPointNode) this.source).getEntryPoint() ); this.source.updateSink( this, propagationContext, workingMemory ); } } public void networkUpdated() { this.skipOnModify = canSkipOnModify( this.sink.getSinks(), true ); } /** * OTN needs to override remove to avoid releasing the node ID, since OTN are * never removed from the rulebase in the current implementation * * @inheritDoc * @see org.drools.common.BaseNode#remove(org.drools.reteoo.RuleRemovalContext, org.drools.reteoo.ReteooBuilder, org.drools.common.BaseNode, org.drools.common.InternalWorkingMemory[]) */ public void remove(RuleRemovalContext context, ReteooBuilder builder, BaseNode node, InternalWorkingMemory[] workingMemories) { doRemove( context, builder, node, workingMemories ); } /** * OTN needs to override remove to avoid releasing the node ID, since OTN are * never removed from the rulebase in the current implementation */ protected void doRemove(final RuleRemovalContext context, final ReteooBuilder builder, final BaseNode node, final InternalWorkingMemory[] workingMemories) { if ( context.getCleanupAdapter() != null ) { for ( InternalWorkingMemory workingMemory : workingMemories ) { CleanupAdapter adapter = context.getCleanupAdapter(); final ObjectHashSet memory = (ObjectHashSet) workingMemory.getNodeMemory( this ); Iterator it = memory.iterator(); for ( ObjectEntry entry = (ObjectEntry) it.next(); entry != null; entry = (ObjectEntry) it.next() ) { InternalFactHandle handle = (InternalFactHandle) entry.getValue(); for ( LeftTuple leftTuple = handle.getFirstLeftTuple(); leftTuple != null; leftTuple = leftTuple.getLeftParentNext() ) { adapter.cleanUp( leftTuple, workingMemory ); } } } context.setCleanupAdapter( null ); } if ( !node.isInUse() ) { removeObjectSink( (ObjectSink) node ); } } /** * Creates memory for the node using PrimitiveLongMap as its optimised for storage and reteivals of Longs. * However PrimitiveLongMap is not ideal for spase data. So it should be monitored incase its more optimal * to switch back to a standard HashMap. */ public Object createMemory(final RuleBaseConfiguration config) { return new ObjectHashSet(); } public boolean isObjectMemoryEnabled() { return this.objectMemoryEnabled; } public void setObjectMemoryEnabled(boolean objectMemoryEnabled) { this.objectMemoryEnabled = objectMemoryEnabled; } public String toString() { return "[ObjectTypeNode(" + this.id + ")::" + ((EntryPointNode) this.source).getEntryPoint() + " objectType=" + this.objectType + " expiration=" + this.expirationOffset + "ms ]"; } /** * Uses he hashCode() of the underlying ObjectType implementation. */ public int hashCode() { return this.objectType.hashCode() ^ this.source.hashCode(); } public boolean equals(final Object object) { if ( this == object ) { return true; } if ( object == null || !(object instanceof ObjectTypeNode) ) { return false; } final ObjectTypeNode other = (ObjectTypeNode) object; return this.objectType.equals( other.objectType ) && this.source.equals( other.source ); } /** * Checks if a modify action on this object type may * be skipped because no constraint is applied to it * * @param sinks * @return */ private boolean canSkipOnModify(final Sink[] sinks, final boolean rootCall) { // If we have no alpha or beta node with constraints on this ObjectType, we can just skip modifies boolean hasConstraints = false; for ( int i = 0; i < sinks.length && !hasConstraints; i++ ) { if ( sinks[i] instanceof AlphaNode || sinks[i] instanceof AccumulateNode || sinks[i] instanceof FromNode ) { hasConstraints = true; } else if ( sinks[i] instanceof BetaNode && ((BetaNode) sinks[i]).getConstraints().length > 0 ) { hasConstraints = rootCall || this.usesDeclaration( ((BetaNode) sinks[i]).getConstraints() ); } else if ( sinks[i] instanceof EvalConditionNode ) { hasConstraints = this.usesDeclaration( ((EvalConditionNode) sinks[i]).getCondition() ); } if ( !hasConstraints && sinks[i] instanceof ObjectSource ) { hasConstraints = !this.canSkipOnModify( ((ObjectSource) sinks[i]).getSinkPropagator().getSinks(), false ); } else if ( !hasConstraints && sinks[i] instanceof LeftTupleSource ) { hasConstraints = !this.canSkipOnModify( ((LeftTupleSource) sinks[i]).getSinkPropagator().getSinks(), false ); } } // Can only skip if we have no constraints return !hasConstraints; } private boolean usesDeclaration(final Constraint[] constraints) { boolean usesDecl = false; for ( int i = 0; !usesDecl && i < constraints.length; i++ ) { usesDecl = this.usesDeclaration( constraints[i] ); } return usesDecl; } private boolean usesDeclaration(final Constraint constraint) { boolean usesDecl = false; final Declaration[] declarations = constraint.getRequiredDeclarations(); for ( int j = 0; !usesDecl && j < declarations.length; j++ ) { usesDecl = (declarations[j].getPattern().getObjectType() == this.objectType); } return usesDecl; } private boolean usesDeclaration(final EvalCondition condition) { boolean usesDecl = false; final Declaration[] declarations = condition.getRequiredDeclarations(); for ( int j = 0; !usesDecl && j < declarations.length; j++ ) { usesDecl = (declarations[j].getPattern().getObjectType() == this.objectType); } return usesDecl; } /** * @return the entryPoint */ public EntryPoint getEntryPoint() { return ((EntryPointNode) this.source).getEntryPoint(); } public long getExpirationOffset() { return expirationOffset; } public void setExpirationOffset(long expirationOffset) { this.expirationOffset = expirationOffset; if ( !this.objectType.getValueType().equals( ValueType.QUERY_TYPE ) ) { if ( this.expirationOffset > 0 ) { // override memory enabled settings this.setObjectMemoryEnabled( true ); } else if ( this.expirationOffset == 0 ) { // disable memory this.setObjectMemoryEnabled( false ); } } } private static class ExpireJob implements Job { public void execute(JobContext ctx) { ExpireJobContext context = (ExpireJobContext) ctx; context.workingMemory.queueWorkingMemoryAction( context.expireAction ); } } private static class ExpireJobContext implements JobContext, Externalizable { public WorkingMemoryReteExpireAction expireAction; public InternalWorkingMemory workingMemory; public JobHandle handle; /** * @param workingMemory * @param behavior * @param behaviorContext */ public ExpireJobContext(WorkingMemoryReteExpireAction expireAction, InternalWorkingMemory workingMemory) { super(); this.expireAction = expireAction; this.workingMemory = workingMemory; } public JobHandle getJobHandle() { return this.handle; } public void setJobHandle(JobHandle jobHandle) { this.handle = jobHandle; } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { //this.behavior = (O) } public void writeExternal(ObjectOutput out) throws IOException { // TODO Auto-generated method stub } } }