Monday, June 1, 2009

Enterprise Scala Beans

This is a "hello world" tutorial to show how to get a Scala class configured to run as an EJB 3.0 inside a JEE app server, in this case Glassfish V2.

Why exactly are you doing this?

Well mostly to see if it's possible, one of Scala's benefits is the fact that it runs on a Java Virtual Machine with Java interoperability as a feature, this would allow developers to make use of Scala's additional language features to create a little more concise code and still make use of an enterprise's existing investments in JEE.

First up.

To create EJB3.0 beans, a language will need to support the following:

- A compiler which compiles to standard Java classes (with the actual implementation bean using the standard no-args constructor for instantiation).
- A construct which can map logically and physically to a Java interface in order to support the EJB's local and remote interfaces.
- Annotations (although you could get away with ejb-jar.xml but that sucks) in order to integrate into the EJB container.

Huh?! you need interfaces! Scala doesn't have interfaces!.

Scala does not have interfaces per se, however it has traits (basically interfaces which allow implementation of methods). As it turns out Scala compiles the basic type skeleton specified by the trait into an interface.

Now with that out the way here's the code:

First we define the Trait which will serve as a remote Interface, for our example we are going to define a simple trait called ITest:

package net.jexenberger

trait ITest {


def doStuff(x: String) : String;

def hello() : Unit = {
System.out.println("hello world");
}


}

This trait has one unimplemented method and one implemented method (this is to illustrate that you don't lose the functionality of traits with Scala when you have to work in a pure interface world).

Secondly we define the actual implemention of the trait ITest in a class TestBean as follows:

package net.jexenberger

import javax.ejb._;
import javax.annotation._;

@Stateless { val name="ScalaTestBean", val mappedName="ScalaTestBean" }
@Remote {val value = Array(classOf[ITest])}
class TestBean extends ITest {

@Resource
var ctx:SessionContext = null

def doStuff(x : String) : String = {
"hello "+x+" called with "+this.ctx.toString()
}

@PostConstruct
def postConstruct() : Unit = {
System.out.println("Post Construct called");
}

}

Note the following about the implementation:

Firstly you will see that annotations are defined almost exactly the same as in Java, however the annotations have been "Scalafied" to look more Scala like with the use val and functions and of course Arrays, as illustrated by the @Remote usage above the class declaration. Finally we also inject the java.ejb.SessionContext object as an instance variable ctx and a @PostConstruct method; postConstruct(), this is to test that we can map Scala variables and methods directly to Java methods and variables in order to utilise the EJB container's dependency injection mechanism and to integrate into the EJB lifecycle.

We can now package the bean into a standard java jar (with META-INF/ejb-jar.xml if required), it can then be deployed as part of an EAR or indeed as a standalone EJB application. I built and packaged the project using Maven's standard maven-ejb-plugin and the Maven Scala plugin (org.scala-tools.maven-scala-plugin).

Finally we write a remote client to invoke the Scala EJB:

package net.jexenberger;

import java.util.Properties;
import javax.naming.Context;

public class JavaTest
{

public static void main(String[] args) throws Exception {
Properties props = new Properties();
props.setProperty("java.naming.factory.initial", "com.sun.enterprise.naming.SerialInitContextFactory");
props.setProperty("java.naming.factory.url.pkgs", "com.sun.enterprise.naming");
props.setProperty("java.naming.factory.state", "com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl");

Context ctx = new javax.naming.InitialContext(props);
ITest instance = (ITest) ctx.lookup("ScalaTestBean");
System.out.println(instance.doStuff("world"));
instance.hello();

}

}

Note that is a straight Java class, used to illustrate that the EJB could still be consumed by using straight Java code, also note that ITest is recognised as a standard Java interface.

The output produced by by running the client is:

net.jexenberger.personal._ITest_Wrapper@4a1c06a1
hello world called with ScalaTestBean; id: [B@12d9e34

And on the server we get:

[#|2009-06-01T13:13:12.538+0200|INFO|sun-appserver2.1|javax.enterprise.system.stream.out|_ThreadID=23;_ThreadName=p: thread-pool-1; w: 9;|
Post Construct called|#]

[#|2009-06-01T13:13:12.541+0200|INFO|sun-appserver2.1|javax.enterprise.system.stream.out|_ThreadID=23;_ThreadName=p: thread-pool-1; w: 9;|
hello world|#]

And there we have it. An EJB 3.0 compliant Scala Enterprise Bean!

A final note on packaging and deployment:

Scala obviously requires its supporting runtime and libraries, in order to do this, you will need to package and specify the dependencies in an EAR or copy it into a location in your app Server. In Glassfish the easiest thing to do is copy scala-library.jar to [Glassfish home]/lib/endorsed.

3 comments:

Unknown said...

I am trying to do the same with EJB 3.1 and GFv3, where there should be no need for an interface. But I am having no luck--see http://forums.java.net/jive/thread.jspa?threadID=62951. Did you ever try this?

I am also wondering why you name the session bean? Can you use the default name?

Thanks,

Cay

julian_za said...

Hi Cay,

Have you checked the JNDI browser in the Glassfish admin console? Im assuming the app started up ok so It managed to find the bean, It's possible perhaps with all additional classes that Scala compiles, Glassfish picked up something else as the EJB.

As for the second call it looks like you have some security constraints defined on your app, perhaps some security roles defined on the invoked method, in such a case you will need to login to Glassfish before making the call using JAAS.

I have no specific reason for naming the beans other than force of habit from the old EJB2.1 and Spring 1.x days :-), Naming the bean however might resolve your original problem though.

mcahornsirup said...

nice work. but somehow I'am afraid... it is like with all those other java concepts (JPA, JDO, Facelets etc.) the stuff should work, but as soon as things are getting more complex - problems occur. Maybe it is a strength to be able to use the java stuff, but maybe it is a weakness at the end.