Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Extend your Java Application with Embedded Languages : Page 3

A scripting engine, built in the language of your choice, and embedded into your application can make a huge difference to your customers, who have concerns about extensibility. Even better, creating a scripting engine is easy and fun. See three versions of a spam filter engine built in Groovy, Jython, and BeanShell.


advertisement

WEBINAR: On-Demand

Unleash Your DevOps Strategy by Synchronizing Application and Database Changes REGISTER >

Sample App: A Flexible Spam Killer
The example application for this article is an e-mail application with spam filtering. Spam changes fast so we want to make it easy for people to extend their filtering to block out new types of spam. The application should have a simple filter configuration dialog, while also providing a way of creating a much more focused filter through a scripting language.

To test the application you'll need two pieces of mail. One telling you that you've been accepted for a new job, which you want. And one trying to sell you Vialis, which you don't want. Vialis just came out, so the pre-configured filters in the application don't catch it. To cut down on the Vialis spam you obviously want to identify any mail that references 'Vialis' in the message body as spam.

The application architecture is fairly simple; it maintains simple Mail objects:



public class Mail { public String subject; public String message; public Mail( String subject, String message ) { this.subject = subject; this.message = message; } }

The application will send these mail message objects to the script. The job of the script is to determine whether the message is spam. When a mail comes in the system invokes the script with the mail message object (see Figure 3). The method will return true if the message is spam.
Figure 3. Spam Chute: The diagram shows the flow of the spam filter through the scripting engine.

My first implementation of the scriptable spam filter is in Groovy.

Groovy
After installing Groovy and adding its JAR files to the project you can access the groovy.lang namespace and use the Groovy interpreter:

import groovy.lang.GroovyShell; import groovy.lang.Binding; import groovy.lang.Closure; import java.io.File; public class MailApp { private GroovyShell _shell; public MailApp() { }

The first step is to load the interpreter with the script, which is located in the file user.groovy. Groovy makes creating a Groovy interpreter and loading a file as simple as two commands:

public void loadInterpreter() throws Exception { // Create the groovy shell _shell = new GroovyShell( new Binding() ); // Read in the script and evaluate it _shell.evaluate( new File( "user.groovy" ) ); }

With the interpreter in hand you can now wrap the invocation of the isSpam closure (the Groovy name for a function) in a simple boolean isSpam call. First you get a reference to the isSpam closure. Then invoke the closure with the mail object and get the return value. You'll then return the boolean return value from the script:

public boolean isSpam( Mail mail ) { // Get the isSpam closure Closure isSpam = (Closure)_shell.getVariable( "isSpam" ); // Run the closure on the mail object Boolean output = (Boolean)isSpam.call( mail ); // Coerce the return value back to a basic boolean return output.booleanValue(); }

The main function creates the mail application, loads the interpreter, and then tests both the good and bad messages to see if they are spam:

public static void main( String []args ) throws Exception { MailApp m = new MailApp(); m.loadInterpreter(); Mail badMail = new Mail( "Hi!", "You will love our vialis!" ); Mail goodMail = new Mail( "Hi!", "You get the job!" ); System.out.println( "Bad mail is spam = " + m.isSpam( badMail ) ); System.out.println( "Good mail is spam = " + m.isSpam( goodMail ) ); } }

The Groovy script file is shown below:

import java.util.regex.Pattern import java.util.regex.Matcher isSpam = { mail | p = Pattern.compile( "(vialis)" ); m = p.matcher( mail.message ); return m.find(); }

It would be difficult for it to be simpler. You create an isSpam variable, which is a closure (function). It takes one argument: the mail message object. Your custom code then compiles a regular expression, matches it against the message of the mail, and returns true if it finds a match.

From this example you can see the fundamentals of embedding scripting languages:

  • Creating an instance of the interpreter.
  • Loading a script into the interpreter.
  • Executing functions or methods within the script.
  • Parsing the return value from the engine.
You could evaluate the script on each invocation, but that would not be as efficient.

Python
The first step in using Jython is installing the interpreter, which is done through a Swing UI application, then including the Jython JARs in your project. With that done you can reference the basic PythonInterpreter class and the PyObject class, which represents a Jython object.

import org.python.util.PythonInterpreter; import org.python.core.PyObject; public class MailApp { private PythonInterpreter _interp; public MailApp() { }

The first step is to create an instance of the interpreter and then load the script file.

public void loadInterpreter() { // Create the interpreter _interp = new PythonInterpreter(); // Load the script file _interp.execfile( "user.py" ); }

You can invoke the spamFilter function by getting the function from the interpreter context and then using the __call__ method to call the function with the mail object. Once you have the result of the call in hand you can convert it to an integer and return true or false based on the value.

public boolean isSpam( Mail mail ) { // Get the function object PyObject spamFilter = _interp.get( "spamFilter" ); // Set a module global to our mail object _interp.set( "mail", mail ); // Run the spamFilter with our mail object PyObject result = spamFilter.__call__( _interp.get( "mail" ) ); // Print the result Integer realResult = (Integer)result.__tojava__( Integer.class ); // Coerce the integer return value to a boolean return realResult.intValue() == 0 ? false : true; }

Notice that in the code above I didn't have to proxy or wrap the mail object. I just send it into the context and Jython does all of the work to reflect against the object and integrate it into the environment.

The final Java section of the example invokes the spamFilter method on the example mail objects.

public static void main( String []args ) throws Exception { MailApp m = new MailApp(); m.loadInterpreter(); Mail badMail = new Mail( "Hi!", "You will love our vialis!" ); Mail goodMail = new Mail( "Hi!", "You get the job!" ); System.out.println( "Bad mail is spam = " + m.isSpam( badMail ) ); System.out.println( "Good mail is spam = " + m.isSpam( goodMail ) ); } }

The Python (or Jython) code for the spamFilter is shown below:

import re def spamFilter( mail ): filter = re.compile( r"vialis" ) if ( filter.search( mail.message ) ): return 1 return 0

This would run just fine under Python. Which is one of the great advantages of Jython. I should also point out that I used the Python regular expression library here but I just as easily could have used the Java regular expression library to do the matching. Of course, if I had done that, the script would not be executable by Python.

BeanShell
BeanShell is the easiest to install, and has the simplest interface of the three embedded languages I've demonstrated in this article. The installation amounts to putting a single .jar file in your project or extensions directory. Then to use it involves just one object.

import bsh.Interpreter; import java.io.BufferedReader; import java.io.FileReader; import java.io.File; public class MailApp { private Interpreter _interp; public MailApp() { }

The first step is to build the interpreter and to load it with the script source. Unlike the other languages you have to do the file I/O yourself, but that's no big deal.

public void loadInterpreter() throws Exception { // Create the interpreter _interp = new Interpreter(); // Read in the script File inFile = new File( "user.bsh" ); BufferedReader reader = new BufferedReader( new FileReader( inFile ) ); StringBuffer userScript = new StringBuffer(); String line; while( ( line = reader.readLine() ) != null ) { userScript.append( line ); } // Evaluate the script into the current context _interp.eval( userScript.toString() ); }

Next, wrap your invocation of the isSpam function by injecting the mail object into the context. Then evaluate the invocation of the call and coerce the return value to a boolean.

public boolean isSpam( Mail mail ) throws Exception { // Set the active mail object _interp.set( "mail", mail ); // Run the script and get the result back Boolean isSpam = (Boolean)_interp.eval( "isSpam( mail );" ); // Coerce the value to the boolean return value return isSpam.booleanValue(); }

I couldn't find a way to access the function to call it directly. Because I use eval I pay a hefty performance penalty with the compilation of the text to running code.

The final Java step, as with all of the previous examples, is to test the spam filter by sending it the two different e-mails.

public static void main( String []args ) throws Exception { MailApp m = new MailApp(); m.loadInterpreter(); Mail badMail = new Mail( "Hi!", "You will love our vialis!" ); Mail goodMail = new Mail( "Hi!", "You get the job!" ); System.out.println( "Bad mail is spam = " + m.isSpam( badMail ) ); System.out.println( "Good mail is spam = " + m.isSpam( goodMail ) ); } }

The BeanShell code is very similar in flavor to Java:

import java.util.regex.Pattern; import java.util.regex.Matcher; boolean isSpam( mail ) { p = Pattern.compile( "(vialis)" ); m = p.matcher( mail.message ); return m.find(); }

Because it's a scripting language BeanShell supports macro (a simple list of commands), procedural, and object-oriented programming paradigms. In this case I chose to use a function but the API could have easily been OO. In fact you can define interfaces in Java and implement them in BeanShell.

As you can see from the different variations the languages may change but the basic structures and data flow remains the same.



Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap
Thanks for your registration, follow us on our social networks to keep up-to-date