Saturday, February 18, 2006

A prototype of Jython ScriptFactory for Spring Framework

SpringFramework 2.0 M2 does not include Jython support for its scripting framework. I don't know the reason. So I decide to create a prototype to prove it is doable.

Suppose we have a Java interface Messenger:

package org.yweng.spike.Spring;

public interface Messenger {
String getMessage();
}

And we have a Jython Script, Messenger.py, which implementes this interface:

from org.yweng.spike.Spring import Messenger

class PythonMessenger(Messenger):
def getMessage(self):
return 'Message from Python Messenger'

def getInstance():
return PythonMessenger()

Notice the getInstance method in the Jython module. The idea is using "naming convention" to help the Jython ScriptFactory instantiate Jython object.

The Spring xml configuration:

<beans>
<bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/>
<bean id="PyMessenger" class="org.yweng.spike.Spring.JythonScriptFactory">
<constructor-arg
value="classpath:org/yweng/spike/Spring/Messenger.py" />
<constructor-arg
value="org.yweng.spike.Spring.Messenger" />
</bean>
</beans>

The JythonScriptFactory class looks like this:


package org.yweng.spike.Spring;

import java.io.IOException;

import org.springframework.scripting.ScriptCompilationException;
import org.springframework.scripting.ScriptFactory;
import org.springframework.scripting.ScriptSource;
import org.springframework.util.Assert;
import org.apache.log4j.Logger;

import org.python.util.PythonInterpreter;


public class JythonScriptFactory implements ScriptFactory {
private static Logger logger =
Logger.getLogger(JythonScriptFactory.class);

private final String scriptSourceLocator;
private final Class[] scriptInterfaces;

public JythonScriptFactory(String scriptSourceLocator,
Class[] scriptInterfaces) {
Assert.hasText(scriptSourceLocator);
Assert.notEmpty(scriptInterfaces);
this.scriptSourceLocator = scriptSourceLocator;
this.scriptInterfaces = scriptInterfaces;
}

public String getScriptSourceLocator() {
return this.scriptSourceLocator;
}

public Class[] getScriptInterfaces() {
return this.scriptInterfaces;
}

public boolean requiresConfigInterface() {
return true;
}

public Object getScriptedObject(ScriptSource scriptSourceLocator,
Class[] scriptInterfaces)
throws IOException, ScriptCompilationException {
String strScript = scriptSourceLocator.getScriptAsString();

if (scriptInterfaces.length > 0) {
try {
PythonInterpreter interp = new PythonInterpreter();
interp.exec(strScript);
interp.exec("this = getInstance()");
return interp.get("this", scriptInterfaces[0]);
} catch (Exception ex) {
throw new ScriptCompilationException(ex.getMessage());
}
}
logger.error("No scriptInterfaces provided. ");
return null;
}
}


Junit Test case:

package org.yweng.spike.Spring;

import junit.framework.TestCase;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.apache.log4j.Logger;

public class TestJythonScriptFactory extends TestCase {
private static Logger logger = Logger.getLogger(TestJythonScriptFactory.class);

private ApplicationContext ctx =
new ClassPathXmlApplicationContext("org/yweng/spike/Spring/ApplicationContext.xml");

public void testPyMessenger() {
Messenger messenger = (Messenger)ctx.getBean("PyMessenger");
assertNotNull(messenger);
String msg = messenger.getMessage();
logger.info(msg);
}
}



It's not pretty but it works. :)

2 Comments:

At 12:03 PM, Blogger James said...

Awesome! Are you willing to contribute this code to the Spring project? If so, then you can create an issue in JIRA and attach the code.

I recently sent them a JavaScriptFactory using Rhino.

 
At 11:11 PM, Blogger reyt said...

if wow gold and maple story mesos wow gold

 

Post a Comment

<< Home