/*
* Copyright 2011 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 java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.drools.core.RuleBaseConfiguration;
import org.drools.core.common.EventFactHandle;
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.reteoo.ObjectTypeNode.ObjectTypeNodeMemory;
import org.drools.core.reteoo.WindowNode.WindowMemory;
import org.drools.core.reteoo.builder.BuildContext;
import org.drools.core.rule.Behavior;
import org.drools.core.rule.BehaviorManager;
import org.drools.core.rule.EntryPointId;
import org.drools.core.rule.SlidingTimeWindow;
import org.drools.core.spi.AlphaNodeFieldConstraint;
import org.drools.core.spi.PropagationContext;
import org.drools.core.util.bitmask.BitMask;
/**
* <code>WindowNodes</code> are nodes in the <code>Rete</code> network used
* to manage windows. They support multiple types of windows, like
* sliding windows, tumbling windows, etc.
* <p/>
* This class must act as a lock-gate for all working memory actions on it
* and propagated down the network in this branch, as there can be concurrent
* threads propagating events and expiring events working on this node at the
* same time. It requires it to be thread safe.
*/
public class WindowNode extends ObjectSource
implements ObjectSinkNode,
RightTupleSink,
MemoryFactory<WindowMemory> {
private static final long serialVersionUID = 540l;
private List<AlphaNodeFieldConstraint> constraints;
protected BehaviorManager behavior;
private EntryPointId entryPoint;
private ObjectSinkNode previousRightTupleSinkNode;
private ObjectSinkNode nextRightTupleSinkNode;
private transient ObjectTypeNode.Id rightInputOtnId = ObjectTypeNode.DEFAULT_ID;
public WindowNode() {
}
/**
* Construct a <code>WindowNode</code> with a unique id using the provided
* list of <code>AlphaNodeFieldConstraint</code> and the given <code>ObjectSource</code>.
*
* @param id Node's ID
* @param constraints Node's constraints
* @param behaviors list of behaviors for this window node
* @param objectSource Node's object source
*/
public WindowNode(final int id,
final List<AlphaNodeFieldConstraint> constraints,
final List<Behavior> behaviors,
final ObjectSource objectSource,
final BuildContext context) {
super(id,
context.getPartitionId(),
context.getKnowledgeBase().getConfiguration().isMultithreadEvaluation(),
objectSource,
context.getKnowledgeBase().getConfiguration().getAlphaNodeHashingThreshold());
// needs to be cloned as the list is managed externally
this.constraints = new ArrayList<AlphaNodeFieldConstraint>(constraints);
this.behavior = new BehaviorManager(behaviors);
this.entryPoint = context.getCurrentEntryPoint();
for ( Behavior b : behaviors ) {
if ( b instanceof SlidingTimeWindow ) {
((SlidingTimeWindow)b).setWindowNode( this );
}
}
hashcode = calculateHashCode();
initMemoryId( context );
}
@SuppressWarnings("unchecked")
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
super.readExternal(in);
constraints = (List<AlphaNodeFieldConstraint>) in.readObject();
behavior = (BehaviorManager) in.readObject();
entryPoint = (EntryPointId) in.readObject();
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
out.writeObject(constraints);
out.writeObject(behavior);
out.writeObject(entryPoint);
}
public short getType() {
return NodeTypeEnums.WindowNode;
}
/**
* Returns the <code>FieldConstraints</code>
*
* @return <code>FieldConstraints</code>
*/
public List<AlphaNodeFieldConstraint> getConstraints() {
return this.constraints;
}
/**
* Returns the list of behaviors for this window node
*/
public Behavior[] getBehaviors() {
return behavior.getBehaviors();
}
public void attach(BuildContext context) {
this.source.addObjectSink(this);
}
public void assertObject(final InternalFactHandle factHandle,
final PropagationContext pctx,
final InternalWorkingMemory workingMemory) {
EventFactHandle evFh = ( EventFactHandle ) factHandle;
for (AlphaNodeFieldConstraint constraint : constraints) {
if (!constraint.isAllowed(evFh, workingMemory)) {
return;
}
}
RightTuple rightTuple = new RightTupleImpl( evFh, this );
rightTuple.setPropagationContext( pctx );
InternalFactHandle clonedFh = evFh.cloneAndLink(); // this is cloned, as we need to separate the child RightTuple references
rightTuple.setContextObject( clonedFh );
// process the behavior
final WindowMemory memory = workingMemory.getNodeMemory(this);
if (!behavior.assertFact(memory.behaviorContext, clonedFh, pctx, workingMemory)) {
return;
}
this.sink.propagateAssertObject(clonedFh, pctx, workingMemory);
}
@Override
public void retractRightTuple(RightTuple rightTuple, PropagationContext pctx, InternalWorkingMemory wm) {
if (isInUse()) {
// This retraction could be the effect of an event expiration, but this node could be no
// longer in use since an incremental update could have concurrently removed it
WindowMemory memory = wm.getNodeMemory( this );
behavior.retractFact( memory.behaviorContext, rightTuple.getFactHandle(), pctx, wm );
}
InternalFactHandle clonedFh = ( InternalFactHandle ) rightTuple.getContextObject();
ObjectTypeNode.doRetractObject(clonedFh, pctx, wm);
}
@Override
public void modifyRightTuple(RightTuple rightTuple, PropagationContext context, InternalWorkingMemory workingMemory) {
EventFactHandle originalFactHandle = ( EventFactHandle ) rightTuple.getFactHandle();
EventFactHandle cloneFactHandle = ( EventFactHandle ) rightTuple.getContextObject();
originalFactHandle.quickCloneUpdate( cloneFactHandle ); // make sure all fields are updated
// behavior modify
boolean isAllowed = true;
for (AlphaNodeFieldConstraint constraint : constraints) {
if (!constraint.isAllowed(cloneFactHandle,
workingMemory)) {
isAllowed = false;
break;
}
}
if ( isAllowed ) {
ModifyPreviousTuples modifyPreviousTuples = new ModifyPreviousTuples(cloneFactHandle.detachLinkedTuples() );
this.sink.propagateModifyObject(cloneFactHandle,
modifyPreviousTuples,
context,
workingMemory);
modifyPreviousTuples.retractTuples(context, workingMemory);
} else {
ObjectTypeNode.doRetractObject(cloneFactHandle, context, workingMemory);
}
}
public void modifyObject(InternalFactHandle factHandle,
ModifyPreviousTuples modifyPreviousTuples,
PropagationContext context,
InternalWorkingMemory wm) {
RightTuple rightTuple = modifyPreviousTuples.peekRightTuple(partitionId);
// if the peek is for a different OTN we assume that it is after the current one and then this is an assert
while ( rightTuple != null && rightTuple.getInputOtnId().before( getRightInputOtnId() ) ) {
modifyPreviousTuples.removeRightTuple(partitionId);
// we skipped this node, due to alpha hashing, so retract now
rightTuple.setPropagationContext( context );
rightTuple.retractTuple( context, wm );
rightTuple = modifyPreviousTuples.peekRightTuple(partitionId);
}
if ( rightTuple != null && rightTuple.getInputOtnId().equals( getRightInputOtnId()) ) {
modifyPreviousTuples.removeRightTuple(partitionId);
rightTuple.reAdd();
modifyRightTuple( rightTuple, context, wm );
} else {
// RightTuple does not exist for this node, so create and continue as assert
assertObject( factHandle, context, wm );
}
}
public void byPassModifyToBetaNode(InternalFactHandle factHandle,
ModifyPreviousTuples modifyPreviousTuples,
PropagationContext context,
InternalWorkingMemory workingMemory) {
sink.byPassModifyToBetaNode(factHandle, modifyPreviousTuples, context, workingMemory);
}
public void updateSink(final ObjectSink sink,
final PropagationContext context,
final InternalWorkingMemory wm) {
final ObjectTypeNodeMemory omem = wm.getNodeMemory( getObjectTypeNode());
Iterator<InternalFactHandle> it = omem.iterator();
while (it.hasNext()) {
assertObject( it.next(), context, wm );
}
}
/**
* Creates the WindowNode's memory.
*/
public WindowMemory createMemory(final RuleBaseConfiguration config, InternalWorkingMemory wm) {
WindowMemory memory = new WindowMemory();
memory.behaviorContext = this.behavior.createBehaviorContext();
return memory;
}
public String toString() {
return "[WindowNode(" + this.id + ") constraints=" + this.constraints + "]";
}
private int calculateHashCode() {
return this.source.hashCode() * 17 + ((this.constraints != null) ? this.constraints.hashCode() : 0);
}
@Override
public boolean equals(final Object object) {
return this == object ||
( internalEquals( object ) && this.source.thisNodeEquals(((WindowNode) object).source) );
}
@Override
protected boolean internalEquals( Object object ) {
if ( object == null || !(object instanceof WindowNode) || this.hashCode() != object.hashCode() ) {
return false;
}
WindowNode other = (WindowNode) object;
return this.constraints.equals(other.constraints) && behavior.equals(other.behavior);
}
/**
* 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;
}
public EntryPointId getEntryPoint() {
return entryPoint;
}
@Override
public BitMask calculateDeclaredMask(List<String> settableProperties) {
throw new UnsupportedOperationException();
}
public ObjectTypeNode.Id getRightInputOtnId() {
return rightInputOtnId;
}
public void setRightInputOtnId(ObjectTypeNode.Id rightInputOtnId) {
this.rightInputOtnId = rightInputOtnId;
}
public static class WindowMemory implements Memory {
public Behavior.Context[] behaviorContext;
public short getNodeType() {
return NodeTypeEnums.WindowNode;
}
public SegmentMemory getSegmentMemory() {
return null;
}
public void setSegmentMemory( SegmentMemory segmentMemory ) {
throw new UnsupportedOperationException();
}
public Memory getPrevious() {
throw new UnsupportedOperationException();
}
public void setPrevious( Memory previous ) {
throw new UnsupportedOperationException();
}
public Memory getNext() {
throw new UnsupportedOperationException();
}
public void setNext( Memory next ) {
throw new UnsupportedOperationException();
}
public void nullPrevNext() {
throw new UnsupportedOperationException();
}
public void reset() {
}
public Collection<EventFactHandle> getFactHandles() {
List<EventFactHandle> eventFactHandles = new ArrayList<EventFactHandle>( );
for (Behavior.Context ctx : behaviorContext) {
for (EventFactHandle efh : ctx.getFactHandles()) {
eventFactHandles.add(efh.getLinkedFactHandle());
}
}
return eventFactHandles;
}
}
}