Calling Python from Java, a quick way to implement a rule engine

This is a robot that my son made with some parts I was using to assemble a crib.
It is amazing what you can build with small simple parts!

I have the following situation at work: One of our applications gets data from a proprietary data source and the rules on how to filter the data change very often; On top of that the traders are the ones who know what is the best criteria so hard-coding this rules into Java is tedious, error prone and takes more time than it should.

I’ve been using Jython for a while and I must say I’m in love with the project. I have the flexibility of a scripting language with the ability to use all the Java libraries and also our application classes, which is something that is priceless as the majority of the code is tested and I can reuse a lot of it.

So, what about calling Python from Java? It would be great it I can let the traders implement their own business rules (we will work together but they can prototype stuff faster that way). Also the traders I work with are very technical savvy 🙂

After checking the chapter ‘Jython and Java integration‘ from the Jython book I decided to experiment myself.

For the sake of the example, I want to have a simple Python class where I receive a Map and a String and print some values:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from com.kodegeek.blog.jython import HelperInt;
 
class Helper(HelperInt):
 
        def doSomething(self, data, name):
 
                if name == None:
                        raise Exception("Name is missing!")
 
                if data == None:
                        raise Exception("No data was provided!")
 
                print "Hello, my name is: %s" % name
                if data.containsKey('age'):
                        age = data.get('age')
                        if age > 21:
                                print "%s, you can also have a beer!" % name
                        else:
                                print "%s, it is time for your milshake :-)" % name

So far is good, but who is the class ‘HelperInt’? Is an interface and that will help us to glue our Python class into the Java world:

1
2
3
4
5
6
7
8
9
package com.kodegeek.blog.jython;
 
import java.util.Map;
 
public interface HelperInt {
 
        public void doSomething(Map data, String name);
 
}

Nothing weird so far, uh?. Now the most complicated part is to bring the Jython implementation back into the Java world. The book give us a really nice Singleton Factory class that I used as is (the code is below, but I strongly suggest you read the book explanation on how it works. It seems there are many ways to skin this cat):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.kodegeek.blog.jython;
 
import org.python.core.Py;
import org.python.core.PyObject;
import org.python.core.PySystemState;
 
/**
 * Jython Object Factory using PySystemState
 */
public class JythonObjectFactory {
 
 private final Class interfaceType;
 private final PyObject klass;
 
 // Constructor obtains a reference to the importer, module, and the class name
 public JythonObjectFactory(PySystemState state, Class interfaceType, String moduleName, String className) {
     this.interfaceType = interfaceType;
     PyObject importer = state.getBuiltins().__getitem__(Py.newString("__import__"));
     PyObject module = importer.__call__(Py.newString(moduleName));
     klass = module.__getattr__(className);
     System.err.println("module=" + module + ",class=" + klass);
 }
 
 // This constructor passes through to the other constructor
 public JythonObjectFactory(Class interfaceType, String moduleName, String className) {
     this(new PySystemState(), interfaceType, moduleName, className);
 }
 
 // All of the followng methods return
 // a coerced Jython object based upon the pieces of information
 // that were passed into the factory. The differences are
 // between them are the number of arguments that can be passed
 // in as arguents to the object.
 
 public Object createObject() {
     return klass.__call__().__tojava__(interfaceType);
 }
 
 
 public Object createObject(Object arg1) {
     return klass.__call__(Py.java2py(arg1)).__tojava__(interfaceType);
 }
 
 public Object createObject(Object arg1, Object arg2) {
     return klass.__call__(Py.java2py(arg1), Py.java2py(arg2)).__tojava__(interfaceType);
 }
 
 public Object createObject(Object arg1, Object arg2, Object arg3)
 {
     return klass.__call__(Py.java2py(arg1), Py.java2py(arg2),
         Py.java2py(arg3)).__tojava__(interfaceType);
 }
 
 public Object createObject(Object args[], String keywords[]) {
     PyObject convertedArgs[] = new PyObject[args.length];
     for (int i = 0; i < args.length; i++) {
         convertedArgs[i] = Py.java2py(args[i]);
     }
 
     return klass.__call__(convertedArgs, keywords).__tojava__(interfaceType);
 }
 
 public Object createObject(Object... args) {
     return createObject(args, Py.NoKeywords);
 }

The last piece of the puzzle is our application, which calls the factory and then the Python object from Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.kodegeek.blog.app;
 
import java.util.Map;
import java.util.HashMap;
 
import com.kodegeek.blog.jython.HelperInt;
import com.kodegeek.blog.jython.JythonObjectFactory;
 
public final class Caller {
 
        public static void main(final String [] args) throws Exception {
 
                JythonObjectFactory factory = new JythonObjectFactory(HelperInt.class, "JHelper", "Helper");
                HelperInt helper = (HelperInt) factory.createObject();
                Map<string , Integer> data = new HashMap</string><string , Integer>();
                data.put("age", 39);
                String name = "Jose Vicente";
                helper.doSomething(data, name);
        }
 
}
</string>

Time to compile and run the stuff. As usual, make sure you have Jython on the Classpath:

1
2
3
4
5
6
7
8
9
Macintosh:jython josevnz$ javac -classpath /users/Shared/jython2.5.2/jython.jar:. -d . *.java
Macintosh:jython josevnz$ 
Macintosh:jython josevnz$ java -classpath /users/Shared/jython2.5.2/jython.jar:. com.kodegeek.blog.app.Caller
*sys-package-mgr*: processing new jar, '/Library/Java/JavaVirtualMachines/1.7.0.jdk/Contents/Home/jre/lib/resources.jar'
*sys-package-mgr*: processing new jar, '/Library/Java/JavaVirtualMachines/1.7.0.jdk/Contents/Home/jre/lib/ext/zipfs.jar'
module=<module 'JHelper' from '__pyclasspath__/JHelper.py'>,class=<class 'JHelper.Helper'>
Hello, my name is: Jose Vicente
Jose Vicente, you can also have a beer!
</class></module>

Success!. Let’s see how far I can get with this on a real application 🙂

2 thoughts on “Calling Python from Java, a quick way to implement a rule engine

  1. Pingback: BlogESfera.com
  2. Pingback: Bitacoras.com

Los comentarios estan cerrados