Tuesday, March 15, 2011

Dynamic (non type safe) Expressions in Rules

Drools is a statically typed language, which means it has to know the types of all fields up front. Sometimes, like in Collections, this just isn't possible.

Previously we hacked this such as if you used a map or array accessor in an expression it would switch dynamic execution in MVEL for this.

We have now cleaned up this feature and made it generally available. Using type declarations you can now declare a type as un-typesafe. That then means it will not validate the expressions or their types at compile time and it will execute those expressions in dynamic (non-strict type) mvel mode.

Here is the unit test for those that want to see it in action:

@Test
public void testNoneTypeSafeDeclarations() {
// same namespace
String str = "package org.drools\n" +
"global java.util.List list\n" +
"declare Person\n" +
" @typesafe(false)\n" +
"end\n" +
"rule testTypeSafe\n dialect \"mvel\" when\n" +
" $p : Person( object.street == 's1' )\n" +
"then\n" +
" list.add( $p );\n" +
"end\n";

executeTypeSafeDeclarations( str, true );

// different namespace with import
str = "package org.drools.test\n" +
"import org.drools.Person\n" +
"global java.util.List list\n" +
"declare Person\n" +
" @typesafe(false)\n" +
"end\n" +
"rule testTypeSafe\n dialect \"mvel\" when\n" +
" $p : Person( object.street == 's1' )\n" +
"then\n" +
" list.add( $p );\n" +
"end\n";
executeTypeSafeDeclarations( str, true );

// different namespace without import using qualified name
str = "package org.drools.test\n" +
"global java.util.List list\n" +
"declare org.drools.Person\n" +
" @typesafe(false)\n" +
"end\n" +
"rule testTypeSafe\n dialect \"mvel\" when\n" +
" $p : org.drools.Person( object.street == 's1' )\n" +
"then\n" +
" list.add( $p );\n" +
"end\n";
executeTypeSafeDeclarations( str, true );

// this should fail as it's not declared non typesafe
str = "package org.drools.test\n" +
"global java.util.List list\n" +
"declare org.drools.Person\n" +
" @typesafe(true)\n" +
"end\n" +
"rule testTypeSafe\n dialect \"mvel\" when\n" +
" $p : org.drools.Person( object.street == 's1' )\n" +
"then\n" +
" list.add( $p );\n" +
"end\n";
executeTypeSafeDeclarations( str, false );
}

private void executeTypeSafeDeclarations(String str, boolean mustSucceed) {
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add( ResourceFactory.newByteArrayResource( str.getBytes() ),
ResourceType.DRL );

if ( kbuilder.hasErrors() ) {
if ( mustSucceed ) {
fail( kbuilder.getErrors().toString() );
} else {
return;
}
}

KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() );

StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
List list = new ArrayList();
ksession.setGlobal( "list",
list );

Address a = new Address("s1" );
Person p = new Person( "yoda" );
p.setObject( a );

ksession.insert( p );
ksession.fireAllRules();
assertEquals( p, list.get(0));
}