/**
* Copyright 2010 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.agent.impl;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Set;
import org.drools.SystemEventListener;
import org.drools.SystemEventListenerFactory;
import org.drools.WorkingMemory;
import org.drools.core.util.ReflectiveVisitor;
import org.drools.definition.KnowledgeDefinition;
import org.drools.definitions.impl.KnowledgePackageImp;
import org.drools.agent.ResourceDiffProducer;
import org.drools.rule.Function;
import org.drools.rule.Query;
import org.drools.rule.Rule;
import org.drools.spi.Consequence;
import org.drools.spi.KnowledgeHelper;
/**
*
* @author esteban.aliverti@gmail.com
*/
public class BinaryResourceDiffProducerImpl extends ReflectiveVisitor implements ResourceDiffProducer {
private KnowledgePackageImp newPkg;
private KnowledgePackageImp currentPkg;
private Set<KnowledgeDefinition> unmodifiedDefinitions = new HashSet<KnowledgeDefinition>();
private Set<KnowledgeDefinition> removedDefinitions = new HashSet<KnowledgeDefinition>();
private SystemEventListener listener;
//attributes used during rules comparison
private Calendar now = new GregorianCalendar();
private Consequence dummyConsequence = new DummyConsequence();
public ResourceDiffResult diff(Set<KnowledgeDefinition> originalDefinitions, KnowledgePackageImp newPkg, KnowledgePackageImp currentPkg ) {
this.listener = SystemEventListenerFactory.getSystemEventListener();
this.newPkg = newPkg;
this.currentPkg = currentPkg;
for (KnowledgeDefinition knowledgeDefinition : originalDefinitions) {
this.visit(knowledgeDefinition);
}
//return the whole new package as new
return new ResourceDiffResult(this.newPkg, this.unmodifiedDefinitions, this.removedDefinitions);
}
public void visitRule(final Rule oldRule){
//ok, so I get an old rule: is it modified in the new pkg? is it even present on it?
org.drools.definition.rule.Rule newRule = newPkg.getRule(oldRule.getName());
if (newRule == null){
//the old rule is not present on the new package. Add it to
//removed rules list.
listener.debug("BinaryResourceDiffProducerImpl: "+oldRule+" is not present anymore. Adding to removed list.");
this.removedDefinitions.add(oldRule);
return;
}
//it is possible that the old rule doesn't exist anymore in the current
//pkg. This is because maybe some other resouce updated its definition
//and after that the same resource removed it. If that is the case,
//this resource (the one we are processing) still contain a reference
//to the rule, but it is no present on kbase anymore. If this is the
//case, we wan't to skip this rule. Because remember that someone
//modified it and removed it before this resource. We don't even
//add it to removedDefinitions, because it is no present on kbase. What
//we have to do is remove it from the new pkg so it won't reapears.
if (currentPkg.getRule(oldRule.getName()) == null){
listener.debug("BinaryResourceDiffProducerImpl: "+oldRule+" is not present on current PKG. Removing from new package.");
newPkg.removeRule(oldRule);
return;
}
//I hate to do this. But if it is not instance of
//org.drools.rule.Rule I can't get his LHS.
if (!(newRule instanceof org.drools.rule.Rule)){
listener.warning("BinaryResourceDiffProducerImpl: Rules must be subclasses of org.drools.rule.Rule.");
return;
}
//Queries are not supported yet.
if (newRule instanceof Query){
listener.debug("BinaryResourceDiffProducerImpl: Query diff is not supported yet.");
return;
}
//is newRule equal to oldRule?
if (this.compareRules((Rule)newRule,oldRule)){
//if so, we don't need the rule in the new Package.
listener.debug("BinaryResourceDiffProducerImpl: "+oldRule+" didn't change. Removing from diff package and adding it to unmodified list.");
newPkg.removeRule((org.drools.rule.Rule)newRule);
this.unmodifiedDefinitions.add(oldRule);
}
}
public void visitFunction(final Function oldFunction){
//ok, so I get an old function: is it modified in the new pkg? is it even present on it?
Function newFunction = newPkg.getFunction(oldFunction.getName());
if (newFunction == null){
//the old function is not present on the new package. Add it to
//removed rules list.
listener.debug("BinaryResourceDiffProducerImpl: "+oldFunction+" is not present anymore. Adding to removed list.");
this.removedDefinitions.add(oldFunction);
return;
}
//it is possible that the old function doesn't exist anymore in the current
//pkg. This is because maybe some other resouce updated its definition
//and after that the same resource removed it. If that is the case,
//this resource (the one we are processing) still contain a reference
//to the function, but it is no present on kbase anymore. If this is the
//case, we wan't to skip this function. Remember that someone
//modified it and removed it before this resource. We don't even
//add it to removedDefinitions, because it is no present on kbase. What
//we have to do is remove it from the new pkg so it won't reapears.
if (currentPkg.getFunction(oldFunction.getName()) == null){
listener.debug("BinaryResourceDiffProducerImpl: "+oldFunction+" is not present on current PKG. Removing from new package.");
newPkg.removeFunction(oldFunction.getName());
return;
}
//is newFunction equal to oldFunction?
if (newFunction.equals(oldFunction)){
//if so, we don't need the function in the new Package.
listener.debug("BinaryResourceDiffProducerImpl: "+oldFunction+" didn't change. Removing from diff package and adding it to unmodified list.");
newPkg.removeFunction(newFunction.getName());
this.unmodifiedDefinitions.add(oldFunction);
}else{
//it seems that the kbase doesn't overrides function's definitions.
//that's why we need to mark this function as removed, but don't
//remove it from the new pkg.
listener.debug("BinaryResourceDiffProducerImpl: "+oldFunction+" did change. Marking as removed so it new version could be added later.");
this.removedDefinitions.add(oldFunction);
}
}
public void visitKnowledgeDefinition(final KnowledgeDefinition oldDefinition){
listener.debug("BinaryResourceDiffProducerImpl: Couldn't handle "+oldDefinition+". We must leave it in the new Package.");
}
private boolean compareRules(Rule r1, Rule r2){
listener.debug("BinaryResourceDiffProducerImpl: Comparing "+r1+" against "+r2);
//compares the salinces
String v1 = r1.getSalience()== null?"":r1.getSalience().toString();
String v2 = r2.getSalience()== null?"":r2.getSalience().toString();
if (!v1.equals(v2)){
listener.debug("BinaryResourceDiffProducerImpl: The rules have different saliences: r1= "+v1+", r2= "+v2);
return false;
}
//compares the activation groups
v1 = r1.getActivationGroup()== null?"":r1.getActivationGroup();
v2 = r2.getActivationGroup()== null?"":r2.getActivationGroup();
if (!v1.equals(v2)){
listener.debug("BinaryResourceDiffProducerImpl: The rules have different activation groups: r1= "+v1+", r2= "+v2);
return false;
}
//compares no-loop attribute
if (r1.isNoLoop() != r2.isNoLoop()){
listener.debug("BinaryResourceDiffProducerImpl: The rules have different values for no-loop attribure: r1= "+r1.isNoLoop()+", r2= "+r2.isNoLoop());
return false;
}
//compares lock-on-active attribute
if (r1.isLockOnActive() != r2.isLockOnActive()){
listener.debug("BinaryResourceDiffProducerImpl: The rules have different values for lock-on-active attribure: r1= "+r1.isLockOnActive()+", r2= "+r2.isLockOnActive());
return false;
}
//compares agenda-group attribute
v1 = r1.getAgendaGroup()== null?"":r1.getAgendaGroup();
v2 = r2.getAgendaGroup()== null?"":r2.getAgendaGroup();
if (!v1.equals(v2)){
listener.debug("BinaryResourceDiffProducerImpl: The rules have different agenda groups: r1= "+v1+", r2= "+v2);
return false;
}
//compares auto-focus attribute
if (r1.getAutoFocus() != r2.getAutoFocus()){
listener.debug("BinaryResourceDiffProducerImpl: The rules have different values for auto-focus attribure: r1= "+r1.getAutoFocus()+", r2= "+r2.getAutoFocus());
return false;
}
//compares ruleflow-group attribute
v1 = r1.getRuleFlowGroup()== null?"":r1.getRuleFlowGroup();
v2 = r2.getRuleFlowGroup()== null?"":r2.getRuleFlowGroup();
if (!v1.equals(v2)){
listener.debug("BinaryResourceDiffProducerImpl: The rules have different ruleflow-group attribute: r1= "+v1+", r2= "+v2);
return false;
}
//compares dialect attribute
v1 = r1.getDialect()== null?"":r1.getDialect();
v2 = r2.getDialect()== null?"":r2.getDialect();
if (!v1.equals(v2)){
listener.debug("BinaryResourceDiffProducerImpl: The rules have different dialect attribute: r1= "+v1+", r2= "+v2);
return false;
}
//compares date-effective attribute
Calendar c1 = r1.getDateEffective()== null?now:r1.getDateEffective();
Calendar c2 = r2.getDateEffective()== null?now:r2.getDateEffective();
if (!c1.equals(c2)){
listener.debug("BinaryResourceDiffProducerImpl: The rules have different date-effective attribute: r1= "+c1+", r2= "+c2);
return false;
}
//compares date-expires attribute
c1 = r1.getDateExpires()== null?now:r1.getDateExpires();
c2 = r2.getDateExpires()== null?now:r2.getDateExpires();
if (!c1.equals(c2)){
listener.debug("BinaryResourceDiffProducerImpl: The rules have different date-expires attribute: r1= "+c1+", r2= "+c2);
return false;
}
//compares the rules' LHS
if (!r1.getLhs().equals(r2.getLhs())){
listener.debug("BinaryResourceDiffProducerImpl: The rules have different LHS");
return false;
}
//compares the rules consequences
Consequence consequence1 = r1.getConsequence()== null?dummyConsequence:r1.getConsequence();
Consequence consequence2 = r2.getConsequence()== null?dummyConsequence:r2.getConsequence();
if (!consequence1.equals(consequence2)){
listener.debug("BinaryResourceDiffProducerImpl: The rules have different Consequences: r1= "+consequence1+", r2= "+consequence2);
return false;
}
return true;
}
//Dummy implementation of Consequnce used for rules comparison.
private class DummyConsequence implements Consequence{
public void evaluate(KnowledgeHelper knowledgeHelper, WorkingMemory workingMemory) throws Exception {
throw new UnsupportedOperationException("You should never call this method!!");
}
public String getName() {
return "default";
}
}
}