Thursday, June 16, 2011

Best We Can Get--Working For Now

After reviewing many posts I have observed the consensus opinion that to make the no-interface feature of EJB 3.1 work in Scala you must decorate your bean with the new LocalBean annotation like this:


package com.scalaejb

import javax.ejb.{Stateless,LocalBean}

@Stateless
@LocalBean
class ScalaEjbImpl {
def sayHi( name:String ) { println("Hi 4, "+name ) }
}

This works. For me this is acceptable but mildly disappointing. Most of the time when I want local-only access to a bean, this approach is fine. But if I do want a remotely visible API for my bean then presumably I'll also need to specify a dummy local interface as well, something I wouldn't need to do if my bean were written in Java.

So, good 'nuf, I suppose.

Trying it in Java

Just to be sure the no-local-interface-needed bit works in my setup, I re-write the bean implementation in Java (and got rid of all interfaces):

package com.scalaejb;

import javax.ejb.Stateless;

@Stateless
public class ScalaEjbImpl {
public void sayHi( String name ) { System.out.println("Hi J, "+name); }
}

I also had to modify my client with a new injection. Instead of injecting a Local reference I just inject a bean reference (still in Scala):

@EJB private var localEJB:ScalaEjbImpl = _

All good and working. Of course I don't want the Java implementation of the bean, so its back to debugging the problem with the Scala version. At least we've proven:

  • Scala EJBs work
  • No-Interface EJBs (in Java) work w/Glassfish 3.1
Now we just need to get these working together.

Boom!

The next test is to try to go for the gold and move everything to Scala and get rid of the local interface. Here's the bean implementation:


package com.scalaejb

import javax.ejb.Stateless

trait ScalaEjbRemote

@Stateless
class ScalaEjbImpl extends ScalaEjbRemote {
def sayHi( name:String ) { println("Hi 3, "+name ) }
}
Looks really nice, doesn't it? Too bad it goes boom when deployed.
Here's the exception you'll get:

[#|2011-06-16T15:23:37.188-0500|SEVERE|glassfish3.1|org.apache.catalina.core.ContainerBase|_ThreadID=17;_ThreadName=Thread-1;|ContainerBase.addChild: start:
org.apache.catalina.LifecycleException: java.lang.IllegalArgumentException: javax.servlet.ServletException: com.sun.enterprise.container.common.spi.util.InjectionException: Error creating managed object for class com.scalaejb.servlet.StartupShutdown
at org.apache.catalina.core.StandardContext.start(StandardContext.java:5271)
at com.sun.enterprise.web.WebModule.start(WebModule.java:500)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:917)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:901)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:755)
at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:1980)
at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:1630)
at com.sun.enterprise.web.WebApplication.start(WebApplication.java:100)
at org.glassfish.internal.data.EngineRef.start(EngineRef.java:130)
at org.glassfish.internal.data.ModuleInfo.start(ModuleInfo.java:269)
at org.glassfish.internal.data.ApplicationInfo.start(ApplicationInfo.java:286)
at com.sun.enterprise.v3.server.ApplicationLifecycle.deploy(ApplicationLifecycle.java:461)
at com.sun.enterprise.v3.server.ApplicationLifecycle.deploy(ApplicationLifecycle.java:240)
at org.glassfish.deployment.admin.DeployCommand.execute(DeployCommand.java:370)
at com.sun.enterprise.v3.admin.CommandRunnerImpl$1.execute(CommandRunnerImpl.java:360)
at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:370)
at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:1067)
at com.sun.enterprise.v3.admin.CommandRunnerImpl.access$1200(CommandRunnerImpl.java:96)
at com.sun.enterprise.v3.admin.CommandRunnerImpl$ExecutionContext.execute(CommandRunnerImpl.java:1247)
at org.glassfish.deployment.autodeploy.AutoOperation.run(AutoOperation.java:145)
at org.glassfish.deployment.autodeploy.AutoDeployer.deploy(AutoDeployer.java:577)
at org.glassfish.deployment.autodeploy.AutoDeployer.deployAll(AutoDeployer.java:463)
at org.glassfish.deployment.autodeploy.AutoDeployer.run(AutoDeployer.java:395)
at org.glassfish.deployment.autodeploy.AutoDeployer.run(AutoDeployer.java:380)
at org.glassfish.deployment.autodeploy.AutoDeployService$1.run(AutoDeployService.java:213)
at java.util.TimerThread.mainLoop(Timer.java:512)
at java.util.TimerThread.run(Timer.java:462)
Caused by: java.lang.IllegalArgumentException: javax.servlet.ServletException: com.sun.enterprise.container.common.spi.util.InjectionException: Error creating managed object for class co.bookleaf.servlet.StartupShutdown
at org.apache.catalina.core.StandardContext.addListener(StandardContext.java:2688)
at org.apache.catalina.core.StandardContext.addApplicationListener(StandardContext.java:1932)
at com.sun.enterprise.web.TomcatDeploymentConfig.configureApplicationListener(TomcatDeploymentConfig.java:234)
at com.sun.enterprise.web.TomcatDeploymentConfig.configureWebModule(TomcatDeploymentConfig.java:93)
at com.sun.enterprise.web.WebModuleContextConfig.start(WebModuleContextConfig.java:274)
at com.sun.enterprise.web.WebModuleContextConfig.lifecycleEvent(WebModuleContextConfig.java:172)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:149)
at org.apache.catalina.core.StandardContext.start(StandardContext.java:5268)
... 26 more
Caused by: javax.servlet.ServletException: com.sun.enterprise.container.common.spi.util.InjectionException: Error creating managed object for class co.bookleaf.servlet.StartupShutdown
at org.apache.catalina.core.StandardContext.createListener(StandardContext.java:2798)
at org.apache.catalina.core.StandardContext.loadListener(StandardContext.java:4745)
at com.sun.enterprise.web.WebModule.loadListener(WebModule.java:1603)
at org.apache.catalina.core.StandardContext.addListener(StandardContext.java:2685)
... 33 more
Caused by: com.sun.enterprise.container.common.spi.util.InjectionException: Error creating managed object for class co.bookleaf.servlet.StartupShutdown
at com.sun.enterprise.container.common.impl.util.InjectionManagerImpl.createManagedObject(InjectionManagerImpl.java:317)
at com.sun.enterprise.web.WebContainer.createListenerInstance(WebContainer.java:734)
at com.sun.enterprise.web.WebModule.createListenerInstance(WebModule.java:1981)
at org.apache.catalina.core.StandardContext.createListener(StandardContext.java:2796)
... 36 more
Caused by: com.sun.enterprise.container.common.spi.util.InjectionException: Exception attempting to inject Remote ejb-ref name=co.bookleaf.servlet.StartupShutdown/localEJB,Remote 3.x interface =com.scalaejb.ScalaEjbLocal,ejb-link=null,lookup=,mappedName=,jndi-name=com.scalaejb.ScalaEjbLocal,refType=Session into class co.bookleaf.servlet.StartupShutdown
at com.sun.enterprise.container.common.impl.util.InjectionManagerImpl._inject(InjectionManagerImpl.java:698)
at com.sun.enterprise.container.common.impl.util.InjectionManagerImpl.inject(InjectionManagerImpl.java:468)
at com.sun.enterprise.container.common.impl.util.InjectionManagerImpl.injectInstance(InjectionManagerImpl.java:146)
at com.sun.enterprise.container.common.impl.util.InjectionManagerImpl.injectInstance(InjectionManagerImpl.java:132)
at com.sun.enterprise.container.common.impl.util.InjectionManagerImpl.createManagedObject(InjectionManagerImpl.java:311)
... 39 more
Caused by: javax.naming.NamingException: Lookup failed for 'java:comp/env/co.bookleaf.servlet.StartupShutdown/localEJB' in SerialContext[myEnv={java.naming.factory.initial=com.sun.enterprise.naming.impl.SerialInitContextFactory, java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl, java.naming.factory.url.pkgs=com.sun.enterprise.naming} [Root exception is javax.naming.NamingException: Exception resolving Ejb for 'Remote ejb-ref name=co.bookleaf.servlet.StartupShutdown/localEJB,Remote 3.x interface =com.scalaejb.ScalaEjbLocal,ejb-link=null,lookup=,mappedName=,jndi-name=com.scalaejb.ScalaEjbLocal,refType=Session' . Actual (possibly internal) Remote JNDI name used for lookup is 'com.scalaejb.ScalaEjbLocal#com.scalaejb.ScalaEjbLocal' [Root exception is javax.naming.NamingException: Lookup failed for 'com.scalaejb.ScalaEjbLocal#com.scalaejb.ScalaEjbLocal' in SerialContext[myEnv={java.naming.factory.initial=com.sun.enterprise.naming.impl.SerialInitContextFactory, java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl, java.naming.factory.url.pkgs=com.sun.enterprise.naming} [Root exception is javax.naming.NameNotFoundException: com.scalaejb.ScalaEjbLocal#com.scalaejb.ScalaEjbLocal not found]]]
at com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:518)
at com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:455)
at javax.naming.InitialContext.lookup(InitialContext.java:392)
at javax.naming.InitialContext.lookup(InitialContext.java:392)
at com.sun.enterprise.container.common.impl.util.InjectionManagerImpl._inject(InjectionManagerImpl.java:597)
... 43 more
Caused by: javax.naming.NamingException: Exception resolving Ejb for 'Remote ejb-ref name=co.bookleaf.servlet.StartupShutdown/localEJB,Remote 3.x interface =com.scalaejb.ScalaEjbLocal,ejb-link=null,lookup=,mappedName=,jndi-name=com.scalaejb.ScalaEjbLocal,refType=Session' . Actual (possibly internal) Remote JNDI name used for lookup is 'com.scalaejb.ScalaEjbLocal#com.scalaejb.ScalaEjbLocal' [Root exception is javax.naming.NamingException: Lookup failed for 'com.scalaejb.ScalaEjbLocal#com.scalaejb.ScalaEjbLocal' in SerialContext[myEnv={java.naming.factory.initial=com.sun.enterprise.naming.impl.SerialInitContextFactory, java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl, java.naming.factory.url.pkgs=com.sun.enterprise.naming} [Root exception is javax.naming.NameNotFoundException: com.scalaejb.ScalaEjbLocal#com.scalaejb.ScalaEjbLocal not found]]
at com.sun.ejb.EjbNamingReferenceManagerImpl.resolveEjbReference(EjbNamingReferenceManagerImpl.java:178)
at com.sun.enterprise.container.common.impl.ComponentEnvManagerImpl$EjbReferenceProxy.create(ComponentEnvManagerImpl.java:1097)
at com.sun.enterprise.naming.impl.GlassfishNamingManagerImpl.lookup(GlassfishNamingManagerImpl.java:772)
at com.sun.enterprise.naming.impl.GlassfishNamingManagerImpl.lookup(GlassfishNamingManagerImpl.java:740)
at com.sun.enterprise.naming.impl.JavaURLContext.lookup(JavaURLContext.java:172)
at com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:498)
... 47 more
Caused by: javax.naming.NamingException: Lookup failed for 'com.scalaejb.ScalaEjbLocal#com.scalaejb.ScalaEjbLocal' in SerialContext[myEnv={java.naming.factory.initial=com.sun.enterprise.naming.impl.SerialInitContextFactory, java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl, java.naming.factory.url.pkgs=com.sun.enterprise.naming} [Root exception is javax.naming.NameNotFoundException: com.scalaejb.ScalaEjbLocal#com.scalaejb.ScalaEjbLocal not found]
at com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:518)
at com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:455)
at javax.naming.InitialContext.lookup(InitialContext.java:392)
at javax.naming.InitialContext.lookup(InitialContext.java:392)
at com.sun.ejb.EjbNamingReferenceManagerImpl.resolveEjbReference(EjbNamingReferenceManagerImpl.java:173)
... 52 more
Caused by: javax.naming.NameNotFoundException: com.scalaejb.ScalaEjbLocal#com.scalaejb.ScalaEjbLocal not found
at com.sun.enterprise.naming.impl.TransientContext.doLookup(TransientContext.java:248)
at com.sun.enterprise.naming.impl.TransientContext.lookup(TransientContext.java:215)
at com.sun.enterprise.naming.impl.SerialContextProviderImpl.lookup(SerialContextProviderImpl.java:77)
at com.sun.enterprise.naming.impl.LocalSerialContextProviderImpl.lookup(LocalSerialContextProviderImpl.java:119)
at com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:505)
... 56 more
|#]


Not pretty, is it. Well now the hard part. There seems to be some name mangling going on since the last part of the exception says it couldn't find "com.scalaejb.ScalaEjbLocal#com.scalaejb.ScalaEjbLocal". Need to think on that a bit...

Anyone have any knowledge/ideas on what Glassfish might be looking for?

More Scala

Ok, so now that a basic bean is up and running, let's get rid of the Java ServletContextListener and prove we can successfully do EJB injection in Scala.

Here's the Scala version:

package co.bookleaf.servlet

import javax.servlet.{ServletContextListener,ServletContextEvent}
import javax.ejb._
import com.scalaejb.ScalaEjbLocal

class StartupShutdown extends javax.servlet.ServletContextListener {

@EJB private var localEJB:ScalaEjbLocal = _

override def contextInitialized( sce: ServletContextEvent ) = { println("Here Scala: "+localEJB); localEJB.sayHi("Fred") }
override def contextDestroyed( sce: ServletContextEvent ) = {}
}
And... it works too! Getting there.

Establishing A Baseline

Before we try to do anything fancy, let's get a simple EJB up and running. For this experiment we'll use Java local/remote interfaces, leaving just the bean implementation in Scala.

Remote Interface:

package com.scalaejb;

import javax.ejb.Remote;

@Remote
public interface ScalaEjbRemote {
}


Local Interface:
package com.scalaejb;

import javax.ejb.Local;

@Local
public interface ScalaEjbLocal {
public void sayHi( String name );
}
Bean Implementation:

package com.scalaejb

import javax.ejb.Stateless

@Stateless
class ScalaEjbImpl extends ScalaEjbRemote with ScalaEjbLocal {
def sayHi( name:String ) { println("Hi, "+name ) }
}


And finally a client, which is in my case a ServletContextListener, because I want to inject an EJB reference and call a method on the bean upon startup of the web application.

package com.scalaejb.servlet;

import javax.servlet.*;
import javax.ejb.*;

import com.scalaejb.ScalaEjbLocal;


public class StartupShutdown implements javax.servlet.ServletContextListener {

@EJB private ScalaEjbLocal localEJB;

public void contextInitialized( ServletContextEvent sce ) {
System.out.println("HERE!!! "+localEJB);
localEJB.sayHi("Fred");
}
public void contextDestroyed( ServletContextEvent sce ) { }
}
So... what happens when this is packaged, deployed, and ran? It works. My injection println and my call to sayHi happens and the appropriate output is present in the log file.

This will be my baseline going forward with further experiments.

Wednesday, June 15, 2011

Introduction

This blog is actually a public set of lab notes as I try to figure out how to do EJB using Scala. My goal is simple: Implement an EJB 3.1 stateless bean, written in Scala, deployed on GlassFish 3.1. The bean must take advantage of EJB 3.1's no-interface feature (Local interfaces are optional).

The environment used is:

Scala 2.9.0-1
EJB 3.1
GlassFish 3.1

I've played around with this enough to know this isn't 1-2-3 easy, so I'll log my progress and notes here for myself and anyone else trying to do this who may stumble across the blog.

If I do get stuck, hopefully someone may have the key bit of information to get things moving again.