I am trying to call RES server (v 7.1) from EAR deployed on WAS (8.5) instance. I was able to invoke rule server from standalone program and its working without any problems.
However my main problem is to invoke EJB deployed on RES server remotely from another EAR deployed on some other WAS instance. In this case we are not able to look-up the EJB remotely.
As per below thread we should bypass the EJB3 IlrSessionFactory API and should use Java EJB API to look up rule sessions directly.
http://www-01.ibm.com/support/docview.wss?uid=swg21586621
Recommendation from IBM is to use standard java api for ejb lookup or to upgrade to Rule Server 7.5 (latest 8.x).
Code snippet
// Initialization
Map<String, Object> outputParms = null;
IlrStatelessSession session=null;
IlrSessionResponse response=null;
// IlrSessionFactory factory = getFactory();
try {
sessionFactory = JRulesInvoker.getFactory();
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.ibm.websphere.naming.WsnInitialContextFactory");
env.put(Context.PROVIDER_URL,"corbaloc:iiop:localhost:28004");
Context ctx = new InitialContext(env);
Object lookupResult = ctx.lookup("ilog.rules.res.session.impl.ejb3.IlrStatelessSessionRemote");
PortableRemoteObject aPortableRemoteObject = new PortableRemoteObject();
session = (IlrStatelessSession) aPortableRemoteObject.narrow(lookupResult, IlrStatelessSession.class);
IlrPath path = new IlrPath(ruleApp, ruleSet);
IlrSessionRequest request = sessionFactory.createRequest();
request.setRulesetPath(path);
request.setInputParameters(inputParms);
request.getTraceFilter().setInfoTotalRulesFired(true);
request.getTraceFilter().setInfoExecutionEvents(true);
request.setTraceEnabled(true);
// session = sessionFactory.createStatelessSession();
System.out.println("created session " + IlrJNDIConstants.STATELESS_SESSION_EJB3_NAME);
response = session.execute(request);
System.out.println(response.getRulesetExecutionTrace().getTotalRulesFired() + " rule(s) fired.");
System.out.println("Execution output=" + response.getRulesetExecutionOutput());
// Return the result(s)
outputParms = response.getOutputParameters();
if (logger.isEnabledFor(Level.DEBUG)) {
if (response.getRulesetExecutionOutput() != null) {
logger.debug("RuleSet execution output: \n" + response.getRulesetExecutionOutput());
}
}
}catch (IlrSessionCreationException cx) {
if (logger.isEnabledFor(Level.ERROR)) {
logger.error(cx.getMessage(), cx);
}
} catch (IlrSessionException e) {
if (logger.isEnabledFor(Level.ERROR)) {
logger.error(e.getMessage(), e);
}
} catch (NamingException e) {
if (logger.isEnabledFor(Level.ERROR)) {
logger.error(e.getMessage(), e);
}
}
Error
Context: idewas/nodes/ide/servers/server1, name: ilog.rules.res.session.impl.ejb3.IlrStatelessSessionRemote: First component in name ilog.rules.res.session.impl.ejb3.IlrStatelessSessionRemote not found.
javax.naming.NameNotFoundException: Context: idewas/nodes/ide/servers/server1, name: ilog.rules.res.session.impl.ejb3.IlrStatelessSessionRemote: First component in name ilog.rules.res.session.impl.ejb3.IlrStatelessSessionRemote not found. [Root exception is org.omg.CosNaming.NamingContextPackage.NotFound: IDL:omg.org/CosNaming/NamingContext/NotFound:1.0]
at com.ibm.ws.naming.jndicos.CNContextImpl.mapNotFoundException(CNContextImpl.java:4563)
at com.ibm.ws.naming.jndicos.CNContextImpl.doLookup(CNContextImpl.java:1821)
at com.ibm.ws.naming.jndicos.CNContextImpl.doLookup(CNContextImpl.java:1776)
at com.ibm.ws.naming.jndicos.CNContextImpl.lookupExt(CNContextImpl.java:1433)
at com.ibm.ws.naming.jndicos.CNContextImpl.lookup(CNContextImpl.java:615)
at com.ibm.ws.naming.util.WsnInitCtx.lookup(WsnInitCtx.java:165)
at com.ibm.ws.naming.util.WsnInitCtx.lookup(WsnInitCtx.java:179)
at org.apache.aries.jndi.DelegateContext.lookup(DelegateContext.java:161)
at javax.naming.InitialContext.lookup(InitialContext.java:436)
Check in the SystemOut.log of the RES server what are the binding names for EJBs as it looks like there is no ilog.rules.res.session.impl.ejb3.IlrStatelessSessionRemote there. Also if you have two servers on the same host under the same name e.g. server1 you may have interoberability issues and need to set JVM property com.ibm.websphere.orb.uniqueServerName to true. For more details check the following page Application access problems
Related
I have my T3 client code like this:
private InitialContext initContext() {
Properties p = new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
p.put(Context.PROVIDER_URL, context.providerURL);
for (Map.Entry<String, String> entry : getEnvironmentProperties().entrySet()) {
p.put(entry.getKey(), entry.getValue());
}
InitialContext res = null;
try {
res = new InitialContext(p);
} catch (NamingException e) {
e.printStackTrace();
}
return res;
}
My t3 client deployed on Tomcat (uses wlthint3client-12.1.3.jar) and trying to lookup remote bean of external system which deployed on Weblogic.
However when I trying to perform new InitialContext(p) I receive SSLHandshake exception, because it gets standart SSLSocketFactory with standart SSLConext and standart java trust store.
My question - is there any way to give to InitialContext some property which will override SSLSocketFacory. My aim is to populate my cutom trust store to this t3 client.
Changing standart trust store like this
System.setProperty("javax.net.ssl.trustStore", "pathToTrustStore");
works fine, however in case if my t3 client is used to communicate with 2 different external systems, it might be a problem in doing so.
Is there some property that I can populate?
Properties p = new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
**p.put("CUSTOM SSL SOCKET FACTORY, "MY CLASS");**
Problem was solved by adding few parameters on application side
export JAVA_OPTS ="$JAVA_OPTS -Djavax.net.ssl.trustStore=path/truststore.jks"
export JAVA_OPTS ="$JAVA_OPTS -Djavax.net.ssl.trustStorePassword=changeIT"
I currently face the problem that an LDAP-Query is passed to an LDAP-Server and the LDAP-Server does not deliver results.
The query: (&(objectCategory=user)(mail=tester#oop-expert.de))
The given E-Mail is meant to not be found. So an empty result is expected.
In most environment configurations this query will pass perfectly and the LDAP returns an empty result immediately.
I broke down the problem to something that may relate to the network or host from where the query is sent. So the query will be fine if sent from one host/network and from another host/network the LDAP-Server will "starve" my LDAP-Client so the LDAP-Client closes the connection for a client-side timeout.
On the other hand: searching for an email that exists will always lead to an immediate result. Doesn't matter from which host/network.
The LDAP-Server is an Active Directory. There a several domain controllers providing an LDAP-Service, configured "round robin". Access per ip or dns does not make any difference on this subject.
The communication is secured via ssl. (ldaps)
In all situations the connection was established. So authentication and passing the query to the LDAP went fine.
Authorization should not be an issue either. I used the same LDAP user for all situations.
The LDAP client is always a JAVA implementation using InitialContext.
private InitialDirContext createDirContext(String principal, String credentials) throws NamingException {
if (credentials == null || credentials.isEmpty()) {
throw new LDAPLoginException();
}
return new InitialDirContext(createEnvironment(principal, credentials));
}
private Hashtable<String, String> createEnvironment(String principal, String credentials) {
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, this.ldapUrl);
// To get rid of the PartialResultException when using Active Directory
env.put(Context.REFERRAL, "follow");
// Needed for the Bind (User Authorized to Query the LDAP server)
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, principal);
env.put(Context.SECURITY_CREDENTIALS, credentials);
return env;
}
The code that builds and executes the query:
private LDAPUser getLDAPUserInfoByUniqueField(String attr, String value) {
DirContext serviceUserContext = null;
NamingEnumeration<SearchResult> results = null;
try {
String searchString = "(&(objectCategory=user)(" + attr + "=" + value + "))";
serviceUserContext = createDirContext(this.serviceUserPrincipal, serviceUserCredentials);
results = serviceUserContext.search("", searchString, createSearchControls()); // blocking...
return createLDAPUserInfo(results);
} catch (LDAPLoginException e) {
throw e;
} catch (Exception e) {
throw new LDAPFatalException(e);
} finally {
try {
if (results != null)
results.close();
} catch (NamingException e) {
}
try {
if (serviceUserContext != null)
serviceUserContext.close();
} catch (NamingException e) {
}
}
}
Utility methods:
private LDAPUser createLDAPUserInfo(NamingEnumeration<SearchResult> results) throws NamingException {
LDAPUser ldapUserInfo = null;
if (results.hasMore()) { // blocking here
SearchResult result = (SearchResult) results.next();
String sAMAccountName = extractsAMAccountName(result);
String distinguishName = extractDistinguishName(result);
String department = extractDepartment(result);
String email = extractEmail(result);
ldapUserInfo = new LDAPUser(sAMAccountName, distinguishName, department, email);
}
return ldapUserInfo;
}
private String extractsAMAccountName(SearchResult result) throws NamingException {
Attributes attrs = result.getAttributes();
Attribute attr = attrs.get("sAMAccountName");
return (String) attr.get();
}
The exception:
de.oopexpert.business.ldap.LDAPFatalException: javax.naming.PartialResultException [Root exception is javax.naming.CommunicationException: oopexpert.de:636 [Root exception is java.net.ConnectException: Connection timed out]]
at de.oopexpert.business.ldap.impl.LDAPImpl.getLDAPUserInfoByUniqueField(LDAPImpl.java:90)
at de.oopexpert.business.ldap.impl.LDAPImpl.getLDAPUserInfoByEmail(LDAPImpl.java:57)
Caused by: javax.naming.PartialResultException [Root exception is javax.naming.CommunicationException: oopexpert.de:636 [Root exception is java.net.ConnectException: Connection timed out]]
at com.sun.jndi.ldap.LdapNamingEnumeration.hasMoreImpl(LdapNamingEnumeration.java:242)
at com.sun.jndi.ldap.LdapNamingEnumeration.hasMore(LdapNamingEnumeration.java:189)
at de.oopexpert.business.ldap.impl.LDAPImpl.createLDAPUserInfo(LDAPImpl.java:139)
at de.oopexpert.business.ldap.impl.LDAPImpl.getLDAPUserInfoByUniqueField(LDAPImpl.java:84)
Any hints?
We struggled with four persons what is going on. We figured out, that it has something to do with "name resolution problems" on the server side when "following refferals". The customer specific environment configuration inherently prohibits us to prevent this behaviour. So we came up with a workaround.
As we are operating in an active directory domain we have global catalog servers. The important statement to this is from Microsoft "technet":
The global catalog is a distributed data repository that contains a searchable, partial representation of every object in every domain in a multidomain Active Directory Domain Services (AD DS) forest. The global catalog is stored on domain controllers that have been designated as global catalog servers and is distributed through multimaster replication. Searches that are directed to the global catalog are faster because they do not involve referrals to different domain controllers.
(from What Is the Global Catalog?)
The phrase "do not involve refferals" leads us to my JNDI environment configuration where I set the following:
env.put(Context.REFERRAL, "follow");
So as I did that, the LDAP-Server will ask other LDAP-Servers if the result of a query seems to be incomplete. This was confirmed by our administrators when I started the query and they debugged it on TCP-Level.
Somehow, I maybe do not really represent it correctly, the name of the other LDAP-Server could not be resolved which leads the client to starvation as the first LDAP-Server waits for resolution.
We tried to omit the client parameter "Context.Refferals=follow". Here we get immediate response. But the response wasn't as expected:
javax.naming.PartialResultException: Unprocessed Continuation Reference(s); remaining name ''
This came up because the first LDAP-Server has the opinion to return incomplete data.
Our administrators say that this can never be the case, because "every domain controller" is a "Global Catalog Server".
So my workaround is: I will deal with this exception and interprete it as "no result".
I have been following this url
http://docs.oracle.com/javaee/1.4/tutorial/doc/JMS5.html
And i have created the connectionfactory, queue and topic. I have been using the source code given on above url to connect the JMS. While i run the following code, there is no any compiling error but the code doesnot run while passing the parameter on it.
import javax.jms.*;
import javax.naming.*;
public class SimpleProducer {
public static void main(String[] args) {
final int NUM_MSGS;
if ((args.length < 1) || (args.length > 2)) {
System.out.println("Program takes one or two arguments: " +
"<dest_name> [<number-of-messages>]");
System.exit(1);
}
String destName = new String(args[0]);
System.out.println("Destination name is " + destName);
if (args.length == 2) {
NUM_MSGS = (new Integer(args[1])).intValue();
} else {
NUM_MSGS = 1;
}
Context jndiContext = null;
try {
jndiContext = new InitialContext();
} catch (NamingException e) {
System.out.println("Could not create JNDI API context: " + e.toString());
System.exit(1);
}
/*
* Look up connection factory and destination. If either
* does not exist, exit. If you look up a
* TopicConnectionFactory or a QueueConnectionFactory,
* program behavior is the same.
*/
ConnectionFactory connectionFactory = null;
Destination dest = null;
try {
connectionFactory = (ConnectionFactory) jndiContext.lookup("jms/ConnectionFactory");
dest = (Destination) jndiContext.lookup(destName);
} catch (Exception e) {
System.out.println("JNDI API lookup failed: " + e.toString());
e.printStackTrace();
System.exit(1);
}
/*
* Create connection.
* Create session from connection; false means session is
* not transacted.
* Create producer and text message.
* Send messages, varying text slightly.
* Send end-of-messages message.
* Finally, close connection.
*/
Connection connection = null;
MessageProducer producer = null;
try {
connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
producer = session.createProducer(dest);
TextMessage message = session.createTextMessage();
for (int i = 0; i < NUM_MSGS; i++) {
message.setText("This is message " + (i + 1));
System.out.println("Sending message: " + message.getText());
producer.send(message);
}
/*
* Send a non-text control message indicating end of
* messages.
*/
producer.send(session.createMessage());
} catch (JMSException e) {
System.out.println("Exception occurred: " + e.toString());
} finally {
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
}
}
}
}
}
When i run the code using eclipse and passing the parameter for the class i.e paramater myqueue and 3. It returns the following error.
Destination name is jms/Queue
JNDI API lookup failed: javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial
javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial
at javax.naming.spi.NamingManager.getInitialContext(Unknown Source)
at javax.naming.InitialContext.getDefaultInitCtx(Unknown Source)
at javax.naming.InitialContext.getURLOrDefaultInitCtx(Unknown Source)
at javax.naming.InitialContext.lookup(Unknown Source)
at SimpleProducer.main(SimpleProducer.java:53)
Do i have to configure any thing in jndi.properties config file in glassfish server. Currently the jndi.properties final has
java.naming.factory.initial=com.sun.enterprise.naming.impl.SerialInitContextFactory
The URL you provided has a section "Creating JMS Administered Objects". You should follow those steps to properly configure your testing environment.
The tutorial teaches you how to configure that using a web interface, but you should be able to find the config files on the server to apply the same data to your Eclipse environment later.
Glassfish has a similar web console. You should be able to create Connection Factory in a similar way as the tutorial teaches.
First of all this is my first question on StackOverflow and I'm an Intern in a Company in Germany, so My English is a little broken and my Knowledge might be limited.
I Try to connectio to a Jboss 6.1.0 eap remotely.
I'm using Eclipse as IDE for the EJB and the EAR but I run the Jboss form cmd
My ejb3 definition look like that:
package de.jack;
import javax.ejb.Remote;
#Remote
public interface TestServiceRemote {
public void sayRemote();
}
package de.jack;
import javax.ejb.Stateless;
/**
* Session Bean implementation class TestService
*/
#Stateless
public class TestService implements TestServiceRemote {
public TestService() { }
public void sayRemote() {
System.out.println("\n\nHello");
}
}
After gernerating the .ear file I deploy them in the JBoss AS and all that works fine
I can view them in the browser under localhost:9990 and check that they are deployed
Now to the Part where I fail - the Client:
public static void main(String argv[]){
Properties props = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
props.put(Context.PROVIDER_URL, "remote://localhost:4447");
props.put(Context.SECURITY_PRINCIPAL, "jack");
props.put(Context.SECURITY_CREDENTIALS, "katze");
props.put("jboss.naming.client.ejb.context", true);
// create a context passing these properties
InitialContext context;
Object test = null;
try {
context = new InitialContext(props);
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return;
}
try {
test =
context.lookup("ConnectorBean/TestService!de.jack.TestServiceRemote");
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return;
}
On Run I get the exception:
org.jboss.naming.remote.protocol.NamingIOException: Failed to lookup [Root exception is java.io.IOException: java.lang.ClassNotFoundException: de.jack.TestServiceRemote]
at org.jboss.naming.remote.client.ClientUtil.namingException(ClientUtil.java:49)
at org.jboss.naming.remote.protocol.v1.Protocol$1.execute(Protocol.java:104)
at org.jboss.naming.remote.protocol.v1.RemoteNamingStoreV1.lookup(RemoteNamingStoreV1.java:95)
at org.jboss.naming.remote.client.HaRemoteNamingStore$1.operation(HaRemoteNamingStore.java:245)
...
I not sure what exactly I did wrong
one reason could be that I do not have admin-rights on the maschine or I mixed up the properties on Client side
Sorry for my bad english and I'm very thankful for any help!
modify TestService class
#Stateless
#Remote(TestServiceRemote.class)
public class TestService implements TestServiceRemote {
public TestService() { }
public void sayRemote() {
System.out.println("\n\nHello");
}
}
ensure remote client has a reference of TestServiceRemote.class
change lookup jndi name
// The app name is the application name of the deployed EJBs. This is typically the ear name
// without the .ear suffix. However, the application name could be overridden in the application.xml of the
// EJB deployment on the server.
// Since we haven't deployed the application as a .ear, the app name for us will be an empty string
final String appName = "";
// This is the module name of the deployed EJBs on the server. This is typically the jar name of the
// EJB deployment, without the .jar suffix, but can be overridden via the ejb-jar.xml
// In this example, we have deployed the EJBs in a jboss-as-ejb-remote-app.jar, so the module name is
// jboss-as-ejb-remote-app
final String moduleName = "jboss-as-ejb-remote-app";
// AS7 allows each deployment to have an (optional) distinct name. We haven't specified a distinct name for
// our EJB deployment, so this is an empty string
final String distinctName = "";
// The EJB name which by default is the simple class name of the bean implementation class
final String beanName = TestService.class.getSimpleName();
// the remote view fully qualified class name
final String viewClassName = TestServiceRemote.class.getName();
String jndiName= "ejb:" + appName + "/" + moduleName + "/" + distinctName + "/" + beanName + "!" + viewClassName;
TestServiceRemote service = (TestServiceRemote)context.lookup(jndiName);
Detail please reffer: https://docs.jboss.org/author/display/AS71/EJB+invocations+from+a+remote+client+using+JNDI
Server log should show correct global JNDI name for the bean. It should be like foo/EJB-NAME/remote. Then you need change it in context.lookup("ConnectorBean/TestService!de.jack.TestServiceRemote").
Please check -
http://docs.jboss.org/ejb3/docs/tutorial/1.0.7/html/JNDI_Bindings.html
Does anyone now a way to obtain server Context using Embeddable API (using org.glassfish.embeddable.GlassFish, not javax.ejb.embeddable.EJBContainer)?
It would be possible if there's a way to obtain EJBContainer from a running Glassfish, but I can't find even the list of services available for lookup.
Here's a workaround - we can obtain InitialContext as an external client.
For the full explanation check EJB_FAQ . This way at least remote EJBs could be tested:
So the full example will look like:
//Start GF
GlassFishRuntime gfRuntime = GlassFishRuntime.bootstrap();
GlassFish gf = gfRuntime.newGlassFish();
gf.start();
//Deploy application with EJBs
Deployer deployer = gf.getService(Deployer.class);
String deployedApp = deployer.deploy(new File(...), "--force=true");
//Create InitialContext
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");
props.setProperty("org.omg.CORBA.ORBInitialHost", "localhost");
props.setProperty("org.omg.CORBA.ORBInitialPort", "3700");
InitialContext ic = new InitialContext(props);
//Lookup EJBs
ic.lookup(...)
//Stop GF
gf.stop();
gfRuntime.shutdown();
//CORBA stuck thread, have to kill it manually
System.exit(0);
Note there's a System.exit(0) at the end - com.sun.corba.ee.impl.javax.rmi.CORBA.Util.KeepAlive thread is running even after the server stop preventing JVM from stopping...
As far as I know, you can initialize the InitialContext class to obtain a context, that can further be used to perform the lookup. This was tested, and found to work in the context of looking up an EJB, deployed in the embedded container. The EJB was not configured to allow access to specific roles, in which case the com.sun.appserv.security.ProgrammaticLogin class (not exposed via the Embeddable EJB API) might help; this was not tested, but is the recommended way to initialize the Principal for the thread accessing an EJB.
A more or less complete example that runs from Maven and uses the embedded Glassfish dependency in a POM (not reproduced here, for brevity) follows:
The EJB interface:
public interface EchoManager
{
String echo(String message);
}
The Session Bean:
#Local(EchoManager.class)
#Stateless
#EJB(name="java:global/glassfish-ejb-validation/EchoManager",beanInterface=EchoManager.class)
public class EchoManagerBean implements EchoManager
{
public String echo(String message)
{
return message;
}
}
The unit test:
public class EchoManagerTest
{
#Rule
public TestName testMethod = new TestName();
private static final Logger logger = Logger.getLogger(EchoManagerTest.class.getName());
#Test
public void testEchoWithGlassfishRuntime() throws Exception
{
logger.info("Starting execution of test" + testMethod.getMethodName());
GlassFish glassFish = null;
Deployer deployer = null;
String appName = null;
try
{
//Setup
BootstrapProperties bootstrapProps = new BootstrapProperties();
GlassFishRuntime glassFishRuntime = GlassFishRuntime.bootstrap(bootstrapProps);
GlassFishProperties gfProps = new GlassFishProperties();
glassFish = glassFishRuntime.newGlassFish(gfProps);
glassFish.start();
deployer = glassFish.getDeployer();
ScatteredArchive archive = new ScatteredArchive("glassfish-ejb-validation", Type.JAR);
archive.addClassPath(new File("target", "classes"));
archive.addClassPath(new File("target", "test-classes"));
appName = deployer.deploy(archive.toURI(), "--force=true");
// Setup the context
InitialContext context = new InitialContext();
//Execute (after lookup the EJB from the context)
EchoManager manager = (EchoManager) context.lookup("java:global/glassfish-ejb-validation/EchoManager");
String echo = manager.echo("Hello World");
//Verify
assertEquals("Hello World", echo);
}
finally
{
if(deployer != null && appName != null)
{
deployer.undeploy(appName);
}
if(glassFish != null)
{
glassFish.stop();
glassFish.dispose();
}
logger.info("Ending execution of test" + testMethod.getMethodName());
}
}
}
Note that the EJB is deployed with a explicit portable JNDI name (via the #EJB annotation), as I have other tests that use the public embeddable EJB API in other tests, and it is more or less difficult to specify an application name in such tests; each test execution might result in a different JNDI name for the EJB, thus necessitating an explicit JNDI name to be specified.