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));
}

2 comments:

  1. Does @typesafe(false) make the whole declared class not type-safe or is it a marker for certain fields within a declared class?

    Furthermore your unit tests show the declared type "Person" having a single "Object" field set to the "Address". Is this a restriction (one untyped field per declared type) or simply that you don't declare other fields in the type?

    ReplyDelete
  2. the annotation is at a class level, rather than field level. So it means any patterns of this object type will have all their expressions evaluated in mvel's non strict mode.

    Currently we are unable to index these types of constraints. So that's something to be aware off.

    In the future I'd like to add @typesafe to fields too. So you can have a typesafe class but allow for just one field to be "unsafe".

    The object field is of type Object. So it's just showing that it's able to do an address constraint without knowing the type at compile time.

    Mark

    ReplyDelete