Function Call From an Action

73 views
Skip to first unread message

Barry Dresdner

unread,
Jan 12, 2012, 11:27:49 AM1/12/12
to DTRules
Hi,
Is it possible to invoke a custom function call (e.g., a static method
of a Java class) from a rule action? For example, let's say I have a
Java method that receives ZipCode as a parameter and returns State.
So I have something like ZipCodeHelper.getStateForZipCode(String
ZipCode) which returns a String.
In my DTRule action, can I do something like set customer.State =
ZipCodeHelper.getStateForZipCode(customer.ZipCode)?
Thanks alot - Barry

Barry Dresdner

unread,
Jan 13, 2012, 1:22:13 PM1/13/12
to DTRules
Can an Operator Statement be what I am looking for? Is there any
detailed documentation about this?

paul

unread,
Jan 16, 2012, 12:10:53 PM1/16/12
to DTRules
Yes, you can execute java directly from DTRules by injecting an
operator to do this. I'll write something up for you.

Sorry I missed your posts. Normally I am pretty good at responding to
posts to the forum.

Barry Dresdner

unread,
Jan 16, 2012, 1:17:13 PM1/16/12
to DTRules
That's great. Thanks very much.

paul

unread,
Jan 16, 2012, 2:51:53 PM1/16/12
to dtr...@googlegroups.com
Here is a quick discussion on the issue.  I will get a working example together (maybe using CHIP) soon.  But in the interest of getting you some info quickly, I'll lay out how this is done given the source you have already.

First, look at how operators are added to DTRules in the source.  If you go to the class in the dtrules-engine project:
  •   package com.dtrules.interpreter.operators.RMath
You will see the following:

public class RMath {
    static {
        new Add();      new Mul();        new Sub();        new Div();
        new FAdd();     new FMul();       new FSub();       new FDiv();
        new Abs();      new Negate();
        new FAbs();     new FNegate();
        new Roundto();
    }

The point here is that each operator is stateless.  The construction of each operator adds it to the primitives dictionary, and makes it available to the decision tables.  These operators are what the compiler uses to implement the functionality of the syntax.  In other words, EL generates postfix code (made up of these operators) and adds this postfix to the XML of the rule set.  DTRules then uses this to execute the tables.  This is why you do not have to deploy a compiler (like EL) in your applications.

Anyway, if you look at one of these operators, you can see how they are constructed:

   /**
     * Absolute value of an integer
     * @author paul snow
     *
     */
    public static class Abs extends ROperator {
        Abs(){super("abs"); }
        
        public void execute(DTState state)throws RulesException {
            state.datapush(
              RInteger.getRIntegerValue(
                 Math.abs(state.datapop().intValue())
              )
            );
        }
    }

The easiest thing to do is have your code extend ROperator (as shown above).  You should then call the super constructor with the name of your function.  This name cannot conflict with any of the operator names in the system.  The name can be made up of any symbols at all other than white space.  This example comes from the 5.0-SNAPSHOT, and going forward you must implement at minimum execute(). You can use the state variable to pull values from the data stack, and push your results to the data stack.  state actually allows you to look at any values in the Rules Engine for use by your function.

This is all you have to do.  If your function takes a zipcode as a number and returns the state as a string, you can you EL to call your function:

Set the State = the string value of (78750)

Or you can modify EL to create a more usable syntax, maybe something like:

Set the State = the state for the zipcode 78750

You can do this by cloning the el project, and modifying the parser.cup and scanner.flex files.

First add STATEFORZIPCODE variable to parser.cup on line 115.
Add an implementation for STATEFORZIPCODE for converting things to strings on line  811, something like this (I am including a bit of bold.  This is at the end of strexpr, so you remove the ';' for the previous option, and replace it with an '|' for your option, then your code ends with the ';'.  Again, only consider the bold blue):

|  
// relationship Syntax              
RELATIONSHIP_BETWEEN eexpr:e1 AND eexpr:e2 {: RESULT = e1+e2+
"{ type } { '' } "+ 
                         "{ over source req { pop dup target req } over if } "+
                         "relationships forfirstelse swap pop swap pop "; :} 
|
STATEFORZIPCODE iexpr:i {: RESULT = i+"stateForZipcode "; :}
;

 Then in scanner.sh, add (maybe around line 259) the following code (I am again including a bit of context; you are adding the bold):

  "map"                             {return build(sym.MAP); }
  "mapping"{ws}+"key"               {return build(sym.MAPPINGKEY); }
  "through"                         {return build(sym.THROUGH); }                                        
    
  /**  Our Zip Code tokens **/
  "state"{ws}+"for"+{ws}+("a"|"an"|"the")+{ws}*+"zipcode"
                                    {return build(sym.STATEFORZIPCODE); }

Then you need to use a bash shell to execute parser.sh and scanner.sh.  Refresh and recompile.  Mysgit comes with a bash shell, if you are using windows.

I hope this helps.

paul

unread,
Jan 16, 2012, 3:02:39 PM1/16/12
to dtr...@googlegroups.com
Grrrr.  Must proof read better.


The line:
  • Set the State = the string value of (78750) 
Should have been:
  • Set the State = the string value of StateForZipcode(78750) 
I also should have provided you a hint on implementation.  You could have the following code in a class that can be used by your test class and your application:

         
    static {
        new StateForZipcode();
    }

    ...


    /**
     * ( zipcode -- State ) Looks up the state for a given zipcode, returning the state as a String
     * @author paul snow
     *
     */
    public static class StateForZipcode extends ROperator {
        Abs(){super("stateforzipcode"); }
        
        public void execute(DTState state)throws RulesException {
            state.datapush(
         int    zipcode = state.datapop().intValue();

              String state   = CallWhateverCodeYouLikeHere(zipcode);

              state.datapush(RString.newRString(state);
            );
        }
    }

Barry Dresdner

unread,
Jan 17, 2012, 7:13:23 AM1/17/12
to DTRules
This was really helpful. It doesn't appear difficult at all to do
this.
Thanks very much,
Barry

paul snow

unread,
Jan 17, 2012, 11:02:25 AM1/17/12
to dtr...@googlegroups.com
Did you get it to work, or are you just saying the explanation makes it seem simple?  

The idea that I had was that I simply cannot anticipate what kind of functionality some application needs within its domain when I don't know what that domain is.  But I can develop libraries of operators so that they can be chosen as needed.  Or expanded as needed.  All without requiring edits to the Rules Engine.

I call this operator injection.  It is very easy to simply add operators to the Rules Engine by supplying their implementation and creating instances of the operators.

I would like to go a step further, and re-factor DTRules to allow object injection.  Currently, the objects supported in the Rules Engine are coded into the Engine.  I have done about 30 to 50 percent of the work to allow object injection, but other priorities have put the task on the back burner.  

Barry Dresdner

unread,
Jan 17, 2012, 11:42:08 AM1/17/12
to DTRules
I read your explanation, and it seemed straight forward enough. I
plan to try to implement something with it fairly soon hopefully. I
should give you some background: we have a requirement where we want
to provide the user with the ability to invoke a function call from
either the LHS or RHS of a rule.

A simple example would be the zip code one I provided earlier. Let's
say I have a function: getStateForZipCode as above.

LHS If Customer.State(getStateForZipCode(Customer.ZipCode)) == "VA"
then SalesTax = 4%.

or RHS If ..... then Customer.State =
getStateForZipCode(Customer.ZipCode))

The functions may return a primitive type, a custom Object or void.
The should be able to do things like make SOAP calls etc. If our
functions (or I guess the correct term is operators) can be written in
Java, that is completely fine. They may do things like invoke web
services, perform Http requests, etc. I hope this helps, and thanks
again.

paul snow

unread,
Jan 17, 2012, 11:54:48 AM1/17/12
to dtr...@googlegroups.com
I think this can work out very well if you plan to just use EL or if you plan to extend EL to support the syntax that you will be using in your rules.  If you plan to extend EL, you might implement a syntax that looks like this for conditions (LHS)
  • The state for the customer's zipcode == "VA"
Or this for actions (RHS):
  • Set the customer's state = the state for the customer's zipcode
You only have to implement the syntax in the parser.cup file as

Where Zipcode is stored as an integer: 
  • STATE FOR iexpr:i  
Where Zipcode is stored as a String;
  • STATE FOR strexpr;s
Keep in mind that SourcePulse (the company I work for) provides consulting and training if you feel you need it.  A little boost up organizing and planning your Rules Based System can really pay off.  You can email me directly if you want to discuss these options.  You can also email me directly if you have questions about proprietary bits of code that you need to keep confidential.  I will pretty much answer any questions where you are doing the work, or prompting me to develop documentation I should have anyway.

Barry Dresdner

unread,
Jan 17, 2012, 12:00:01 PM1/17/12
to DTRules
We are currently evaluating different rule engines and have a list of
requirements. You have convinced me that this requirement can be
accomplished using DTRules. Thanks again for all your assistance.
Reply all
Reply to author
Forward
0 new messages