Sorry for not replying sooner -- been down a rabbit hole with those
stateless pooling changes.
On Mar 17, 2010, at 3:14 PM, Jonathan Gallimore wrote:
I've managed to do some work on the no-interface view work. I've
managed to
read the localbean ejb-jar.xml config and @LocalBean annotation, and
propagate this through to CoreDeploymentInfo. I've got to a point
where I
can lookup an EJB and get a proxy back.
Nice!
There's still some work to do, as
I'm getting this exception:
//START SNIPPET: localbean
public void testCalculatorViaLocalBeanInterface() throws
Exception {
Object object =
initialContext.lookup("CalculatorImplLocalBean");
assertNotNull(object);
assertTrue(object instanceof CalculatorImpl);
CalculatorImpl calc = (CalculatorImpl) object;
assertEquals(10, calc.sum(4,6));
assertEquals(12, calc.multiply(3,4));
}
java.lang.reflect.UndeclaredThrowableException
at $LocalBeanProxy0.sum($LocalBeanProxy0.java)
at
Just a side note. It always annoyed me that the vm used "$Proxy123"
instead of even trying to incorporate at least one of my interface
names in the generated proxy name. Here, we're subclassing so we only
have one clear name we could use to "seed" the generated proxy name,
so we could finally offer people more intuitive looking stack traces.
Caused by: java.lang.IllegalStateException: Received method
invocation and
cannot determine corresponding business interface: method=public int
$LocalBeanProxy0.sum(int,int)
at
org
.apache
.openejb
.core
.ivm
.BaseEjbProxyHandler.getInvokedInterface(BaseEjbProxyHandler.java:188)
Standard issue in that an interface was always required and the code
is therefore only written to think in those terms. There'll probably
be a few other places where we need to "widen" the understanding of
the code. For the most part we should try and get everything done by
passing the bean class name as the "interface", but we will have to
add LocalBean as an understood view type. In CoreDeploymentInfo we
have a list of business local interfaces that we populate when that
object is constructed. We'll probably want to do identical processing
for the "LocalBean" view.
I believe the code above is trying pull out the interface that was
used so that the SessionContext.getInvokedBusinessInterface method
will work. This might be one place where we can just pass the ejb
class as the "interface".
JNDI:
I've added the following code to JndiBuilder.bind():
try {
if (deployment.isLocalbean()) {
Class beanClass = deployment.getBeanClass();
LocalBeanReference ref = new
LocalBeanReference(deployment);
optionalBind(bindings, ref, "openejb/Deployment/" +
format(deployment.getDeploymentID(), beanClass.getName(), null));
String internalName = "openejb/Deployment/" +
format(deployment.getDeploymentID(), beanClass.getName(),
InterfaceType.LOCALBEAN);
bind(internalName, ref, bindings, beanInfo, beanClass);
String name = strategy.getName(beanClass,
JndiNameStrategy.Interface.LOCALBEAN);
bind("openejb/local/" + name, ref, bindings, beanInfo,
beanClass);
bind("openejb/remote/" + name, ref, bindings, beanInfo,
beanClass);
}
} catch (NamingException e) {
throw new RuntimeException("Unable to bind localbean
deployment
in jndi.", e);
}
That seems good.
I created a LocalBeanReference class - which what is bound into
JNDI. This
takes a CoreDeploymentInfo object, and its getObject() creates a
proxy.
Here's the code:
package org.apache.openejb.core.ivm.naming;
import org.apache.openejb.core.CoreDeploymentInfo;
import javax.naming.*;
public class LocalBeanReference extends Reference {
private CoreDeploymentInfo deployment;
public LocalBeanReference(CoreDeploymentInfo deployment) {
this.deployment = deployment;
}
public Object getObject() throws javax.naming.NamingException {
return deployment.getLocalBean();
}
}
Nice.
I'm concerned that this isn't the right approach - I notice that the
other
interfaces bind a BusinessRemoteHome/BusinessLocalHome class, should
this
follow the same pattern
Oh yea, you raise good point. We definitely do need some create
method to be called in the reference, basically for the benefit of
types like @Stateless and the new @ManagedBean which are instantiated
on lookup. The container needs to get invoked on the lookup. I
actually fabricated those BusinessRemoteHome/BusinessLocalHome just
because all the code to do all this work was already everywhere in the
system and the only thing missing was that EJB 3.0 business interfaces
didn't have the option to have "home"s (aka a Factory). So I just
fabricated one for each type.
So we probably do need some sort of LocalBeanHome kind of thing. Not
sure if we could get by just reusing BusinessLocalHome, but if not,
all the code that understands BusinessLocalHome will need to
understand the new "LocalBeanHome".
Also, the default Global JNDI name is <className>LocalBean - is this
ok?
Seems fine to me. The JNDI name <className> is tempting, but knowing
the code I know that it complicates things. Users can always change
it so it's a good start regardless.
Class loading:
I've added a method to CoreDeploymentInfo, which will create a new
proxy.
The code I've written to create the proxy using the ASM library
returns a
byte[]. To load this class I'm creating a new ClassLoader, which is
a child
of the ClassLoader that the bean itself is created from, and calling
defineClass(). I'm not sure if this is right, I'd appreciate any
thoughts
anyone has.
Not sure of other options on that front. I'd investigate what CGLib
does, might be an idea there worth copying.
In terms of plain memory, we could probably share that fabricated
classloader accross the whole app. Maybe create it in the assembler
and stuff the same one in all the DeploymentInfo objects... or better
yet create the proxies themselves in the assembler and stuff the proxy
class in the DeploymentInfo as the "localBean" class.
-David