Oracle® Application Server Containers for J2EE Enterprise JavaBeans Developer's Guide
10g Release 2 (10.1.2) Part No. B15505-02 |
|
Previous |
Next |
This chapter demonstrates how to access an EJB from a servlet within the application server or from outside the application server—either from an EJB within another application server or from a local client.
Recovering From a NamingException While Accessing a Remote EJB
Recovering From NullPointerException While Accessing a Remote EJB
You can download examples from the OC4J sample code page at on the OTN Web site.
To access an EJB from a client, you must do the following:
If you are remote, download the oc4j.jar
file.
Set up JNDI properties for the connection, if necessary.
Determine which InitialContextFactory
you will use for the connection.
Retrieve an EJB using either the JNDI name or an EJB reference, which is configured in the deployment descriptor.
These subjects are discussed in the following sections:
In order to access EJBs, the client-side must download oc4j_client.zip
file from http://www.oracle.com/technology/software/products/ias/devuse.html
. Unzip the JAR into a directory that is in your CLASSPATH. This JAR contains the classes necessary for client interaction. If you download this JAR into a browser, you must grant certain permissions. See "Granting Permissions in Browser" for a list of these permissions.
If the client is collocated with the target, the client exists within the same application as the target, or the target exists within its parent, then you do not need a JNDI properties file. Else, you must initialize your JNDI properties either within a jndi.properties
file, in the system properties, or within your implementation, before the JNDI call. The following sections discuss these three options:
To specify credentials within the JNDI properties, see "Specifying Credentials in EJB Clients".
Note: A full description of how to use JNDI, see the JNDI chapter in the Oracle Application Server Containers for J2EE Services Guide. |
A servlet that is collocated with the target bean automatically accesses the JNDI properties for the node. Thus, accessing the EJB is simple: no JNDI properties are required.
//Get the Initial Context for the JNDI lookup for a local EJB InitialContext ic = new InitialContext(); //Retrieve the Home interface using JNDI lookup Objecthello
Object = ic.lookup("java:comp/env/ejb/HelloBean
");
This is also true if the target bean is in the same application or an application that has been deployed as this application's parent. See the Oracle Application Server Containers for J2EE User's Guide for more information on how to set the parent of the application.
If setting the JNDI properties within the jndi.properties
file, set the properties as follows. Make sure that this file is accessible from the CLASSPATH
.
Factory
See "When Do You Use the Different Initial Context Factory Classes?" for discussion on the initial context factory to use.
java.naming.factory.initial= com.evermind.server.ApplicationClientInitialContextFactory
Location
All ports, including the RMI port, are dynamically set by OPMN when each OC4J instance starts. When you specify the following URL in the client JNDI properties, the client-side OC4J retrieves the dynamic ports for the instance, and chooses one from the list for communication.
java.naming.provider.url= opmn:ormi://<opmn_host>:<opmn_port>:<oc4j_instance>/<application-name>
The OPMN host name and port number is retrieved from the opmn.xml
file. In most cases, OPMN is located on the same machine as the OC4J instance. However, you must specify the host name in case it is located on another machine. The OPMN port number is optional; if excluded, the default is port 6003. The OPMN port is specified in opmn.xml
.
The OC4J instance name is defined in the Enterprise Manager. The ORMI default port number is 23791, which can be modified in . Thus, set the URL in the , in one of the two ways:
Security
When you access EJBs in a remote container, you must pass valid credentials to this container. Stand-alone clients define their credentials in the jndi.properties
file deployed with the client's code.
java.naming.security.principal=<username> java.naming.security.credentials=<password>
Set the properties with the same values, only with a different syntax. For example, JavaBeans running within the container pass their credentials within the InitialContext
, which is created to look up the remote EJBs.
In the java.naming.provider.url
, the "opmn:ormi
" location string is provided. Both OPMN and OC4J are located on the same host. The OPMN default port is used, so the port number is not specified.
In the java.naming.factory.initial
, the ApplicationClientInitialContextFactory
object is used.
To pass JNDI properties within the Hashtable
environment, set these as shown below:
Hashtable env = new Hashtable(); env.put("java.naming.provider.url", "opmn:ormi://opmnhost:oc4j_inst1/ejbsamples"); env.put("java.naming.factory.initial", "com.evermind.server.ApplicationClientInitialContextFactory"); env.put(Context.SECURITY_PRINCIPAL, "guest"); env.put(Context.SECURITY_CREDENTIALS, "welcome"); Context ic = new InitialContext (env); Object homeObject = ic.lookup("java:comp/env/ejb/HelloBean
"); // Narrow the reference to aHello
Home.Hello
Home empHome = (Hello
Home) PortableRemoteObject.narrow(homeObject,Hello
Home.class);
The rules for which initial context factory are the same for OC4J standalone applications. However, since OC4J standalone does not use OPMN, the location URL cannot use the opmn:ormi://
prefix. Instead, the ormi://
prefix is used.
The ORMI default port number is 23791, which can be modified in config/rmi.xml. Thus, set the URL in the jndi.properties, in one of the two ways:
java.naming.provider.url=ormi://<hostname>/<application-name>
or
java.naming.provider.url=ormi://<hostname>:23791/<application-name>\
When you access EJBs in a remote container, you must pass valid credentials to this container. Stand-alone clients define their credentials in the jndi.properties file deployed with the client's code.
java.naming.security.principal=<username> java.naming.security.credentials=<password>
If you set the properties within the bean implementation, then set them with the same values, just with different syntax. For example, JavaBeans running within the container pass their credentials within the InitialContext, which is created to look up the remote EJBs.
To pass JNDI properties within the Hashtable environment, set these as shown below:
Hashtable env = new Hashtable();
env.put("java.naming.provider.url", "ormi://myhost/ejbsamples
");
env.put("java.naming.factory.initial",
"com.evermind.server.ApplicationClientInitialContextFactory");
env.put(Context.SECURITY_PRINCIPAL, "guest");
env.put(Context.SECURITY_CREDENTIALS, "welcome");
Context ic = new InitialContext (env);
Object homeObject = ic.lookup("java:comp/env/ejb/HelloBean");
// Narrow the reference to a HelloHome.
HelloHome helloHome =
(HelloHome) PortableRemoteObject.narrow(homeObject,
HelloHome.class);
The type of initial context factory that you use depends on who the client is. The initial context factory creates the initial context class for the client.
If the client is a pure Java client outside of the OC4J container, use the ApplicationClientInitialContextFactory
class.
If the client is an EJB or servlet client within the OC4J container, use the ApplicationInitialContextFactory
class. The ApplicationInitialContextFactory
class is the default class; thus, each time you create a new InitialContext
without specifying any initial context factory class, your client uses the ApplicationInitialContextFactory
class.
If the client is an administrative class that is going to manipulate or traverse the JNDI tree, use the com.evermind.server.RMIInitialContextFactory
class.
If the client is going to use DNS load balancing, use the RMIInitialContextFactory
class.
For example, if you have a pure Java client, then you set the initial context factory class ("java.naming.factory.initial
") to ApplicationClientInitialContextFactory
. The following example sets the initial context factory in the environment, but you could also put this in the JNDI properties file.
env.put("java.naming.factory.initial", "com.evermind.server.ApplicationClientInitialContextFactory");
If the client is an EJB or a servlet calling an EJB in the same application, you can use the default by not setting the JNDI properties with a initial context factory and uses the ApplicationInitialContextFactory
object by executing the following:
InitialContext ic = new InitialContext();
If you decide to use the RMIInitialContextFactory
class, you must use the JNDI name in the lookup and not a logical name defined in the <ejb-ref>
in your XML configuration file.
To use DNS for load balancing, you must do the following:
Within DNS, map a single host name to several IP addresses. Each of the port numbers must be the same for each IP address. Set up the DNS server to return the addresses either in a round-robin or random fashion.
Turn off DNS caching on the client. For UNIX machines, you must turn off DNS caching as follows:
Kill the NSCD daemon process on the client.
Start the OC4J client with the -Dsun.net.inetaddr.ttl=0
option.
Within each client, use any initial context factory to create an initial context. You can use either the opmn:ormi://
or the ormi://
prefix in the provider URL. Use opmn:ormi://
syntax for Oracle9iAS applications and the ormi://
for standalone OC4J applications.
Set the dedicated.rmicontext
property to true.
Each time the lookup occurs on the DNS server, the DNS server hands back a one of the many IP addresses that are mapped to it.
Before you start implementing your call to the EJB in your client, you should consider the following for the JNDI retrieval of the EJB reference of the bean:
Within your client code, you retrieve an EJB reference to the target bean in order to execute methods on that bean. Do you want to set up a logical name for the target bean or use the JNDI name?
Use the logical name: Modify the client XML configuration file to set up the <ejb-ref>
element with the target bean information. The logical name specified in the <ejb-ref-name>
element is used in the JNDI lookup. See "Configuring the EJB Reference Information" for more information on the <ejb-ref>
and <ejb-ref-name>
elements.
Use the actual name: The actual name of the bean is used in the JNDI lookup. This name has been specified in the target bean's ejb-jar.xml
XML deployment descriptors in the <ejb-name>
element.
The method for accessing EJBs depends on where your client is located relative to the bean it wants to invoke.
Is the client is collocated with the target bean? Deployed in the same application? Or is the target bean part of an application that is this client's parent? You do not need to set up any JNDI properties.
Otherwise, you must set up JNDI properties. There are two methods for setting up JNDI properties. See "Setting JNDI Properties" for more information
Specify the EJB reference information for the remote EJB in the <ejb-ref>
or <ejb-local-ref>
elements in the client's XML file:
application-client.xml
: The client is a pure-Java client, invoking the bean outside of the container.
ejb-jar.xml
: The client is another EJB.
web.xml
: The client is a servlet or JSP.
For example, if a client wants to access the remote interface of the Hello
example, then the client's XML would define the following:
<ejb-ref> <ejb-ref-name>ejb/HelloBean</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <home>hello.HelloHome</home> <remote>hello.Hello</remote> </ejb-ref>
If the client wants to access the local interface of the Hello
example, then the client's XML would define the following:
<ejb-ref> <ejb-ref-name>ejb/HelloBean</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <local-home>hello.HelloLocalHome</local-home> <local>hello.HelloLocal</local> </ejb-ref>
OC4J maps the logical name to the actual JNDI name on the client-side. The server-side receives the JNDI name and resolves it within its JNDI tree.
All EJB clients implement the following steps to instantiate a bean, invoke its methods, and destroy the bean:
Look up the home interface through a JNDI lookup. Follow JNDI and the EJB specification conventions for retrieving the bean reference, including setting up JNDI properties if the bean is remote to the client. See "How to Lookup the EJB Reference".
Narrow the returned object from the JNDI lookup to the home interface, as follows:
Create instances of the bean in the server through the returned object. Invoking the create
method on the home interface causes a new bean to be instantiated and returns a bean reference.
Note: For entity beans that are already instantiated, you can retrieve the bean reference through one of its finder methods. |
Invoke business methods, which are defined in the component (remote or local) interface.
After you are finished, invoke the remove
method. This will either remove the bean instance or return it to a pool. The container controls how to act on the remove
method.
These steps are demonstrated in Example 2-2.
Example 2-2 A Servlet Acting as a Local Client
The following example is executed from a servlet that is collocated with the Hello bean. Thus, the session bean uses the local interface, and the JNDI lookup does not require JNDI properties.
Note: The JNDI name is specified in the<ejb-local-ref> element in this session bean EJB deployment descriptor as follows:
|
package hello; import javax.servlet.http.*; import javax.servlet.*; import javax.ejb.*; import javax.naming.*; import java.io.IOException; public class HelloServlet extends HttpServlet { HelloLocalHome helloHome; HelloLocal hello; public void init() throws ServletException { try { // 1. Retreive the Home Interface using a JNDI Lookup // Retrieve the initial context for JNDI. // No properties needed when local Context context = new InitialContext(); // Retrieve the home interface using a JNDI lookup using // the java:comp/env bean environment variable // specified in web.xml helloHome = (HelloLocalHome) context.lookup("java:comp/env/ejb/HelloBean"); //2. Narrow the returned object to be an HelloHome object. // Since the client is local, cast it to the correct object type. //3. Create the local Hello bean instance, return the reference hello = (HelloLocal)helloHome.create(); } catch(NamingException e) { throw new ServletException("Error looking up home", e); } catch(CreateException e) { throw new ServletException("Error creating local hello bean", e); } } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); ServletOutputStream out = response.getOutputStream(); try { out.println("<html>"); out.println("<body>"); //4. Invoke a business method on the local interface reference. out.println(hello.sayHello("James Earl")); out.println("</body>"); out.println("</html>"); } catch(EJBException e) { out.println("EJBException error: " + e.getMessage()); } catch(IOException e) { out.println("IOException error: " + e.getMessage()); } finally { out.close(); } } }
Note:: You can download this example on OTN from the OC4J sample code page athttp://www.oracle.com/technology/tech/java/oc4j/demos/ on the OTN Web site.
|
Example 2-3 A Java Client as a Remote Client
The following example is executed from a pure Java client that is a remote client. Any remote client must set up JNDI properties before retrieving the object, using a JNDI lookup.
Note: The JNDI name is specified in the<ejb-ref> element in the this client's application-client.xml file—as follows:
|
The jndi.properties
file for this client is as follows:
java.naming.factory.initial= com.evermind.server.ApplicationClientInitialContextFactory java.naming.provider.url=opmn:ormi://opmnhost:oc4j_inst1/helloworld java.naming.security.principal=admin java.naming.security.credentials=welcome
The pure Java client that invokes Hello
remotely is as follows:
package hello; import javax.ejb.*; import javax.naming.*; import javax.rmi.PortableRemoteObject; import java.io.*; import java.util.*; import java.rmi.RemoteException; /* * A simple client for accessing an EJB. */ public class HelloClient { public static void main(String[] args) { System.out.println("client started..."); try { // Initial context properties are set in the jndi.properties file //1. Retrieve remote interface using a JNDI lookup*/ Context context = new InitialContext(); // Lookup the HelloHome object. The reference is retrieved from the // application-local context (java:comp/env). The variable is // specified in the application-client.xml). Object homeObject = context.lookup("java:comp/env/Helloworld"); //2. Narrow the reference to HelloHome. Since this is a remote // object, use the PortableRemoteObject.narrow method. HelloHome home = (HelloHome) PortableRemoteObject.narrow (homeObject, HelloHome.class); //3. Create the remote object and narrow the reference to Hello. Hello remote = (Hello) PortableRemoteObject.narrow(home.create(), Hello.class); //4. Invoke a business method on the remote interface reference. System.out.println(remote.sayHello("James Earl")); } catch(NamingException e) { System.err.println("NamingException: " + e.getMessage()); } catch(RemoteException e) { System.err.println("RemoteException: " + e.getMessage()); } catch(CreateException e) { System.err.println("FinderException: " + e.getMessage()); } } }
Note: You can download this example on OTN from the OC4J sample code page athttp://www.oracle.com/technology/tech/java/oc4j/demos/ on the OTN Web site.
|
A multi-tier situation exists when you have the servlets executing in one server which are to connect and communicate with EJBs in another server. Both the servlets and EJBs are contained in the same application. When you deploy the application to two different servers, the servlets normally look for the local EJB first.
In Figure 2-1, the HelloBean
application is deployed to both server 1 and 2. In order to ensure that the servlets only call out from server 1 to the EJBs in server 2, you must set the remote
attribute appropriately in the application before deploying on both servers.
The remote
attribute in the <ejb-module>
element in orion-application.xml
for the EJB module denotes whether the EJBs for this application are deployed or not.
In server 1, you must set remote=true
in the <ejb-module>
element of the orion-application.xml
file and then deploy the application. The EJB module within the application will not be deployed. Thus, the servlets will not look for the EJBs locally, but will go out to the remote server for the EJB requests.
In server 2, you must set remote=false
in the <ejb-module>
element of the orion-application.xml
file and then deploy the application. The application, including the EJB module, is deployed as normal. The default for the remote
attribute is false; thus, simply ensure that the remote
attribute is not true and redeploy the application.
In the <server>
element of the rmi.xml
file of server 1, configure the location of server 2, which is the remote server. Provide the hostname, port number, username, and password of the remote server, as follows:
<server host=<remote_host> port=<remote_port> username=<username> password=<password> />
If multiple remote servers are configured, the OC4J container searches all remote servers for the intended EJB application.
Example 2-4 Servlet Accessing EJB in Remote OC4J Instance
The following servlet uses the JNDI name for the target bean: HelloBean
. This servlet provides the JNDI properties in an RMIInitialContext
object. The environment is initialized as follows:
The INITIAL_CONTEXT_FACTORY
is initialized to a RMIInitialContextFactory
.
Instead of creating a new InitialContext
, it is retrieved.
The actual JNDI name is used in the lookup.
The remote location URL is opmn:ormi://host:oc4j_inst/application
. The OPMN port number uses the default and is omitted.
Hashtable env = new Hashtable(); env.put(Context.PROVIDER_URL, "opmn:ormi://theirhost:oc4j_inst/myapp"); env.put(Context.SECURITY_PRINCIPAL, "admin"); env.put(Context.SECURITY_CREDENTIALS, "welcome"); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.evermind.server.rmi.RMIInitialContextFactory"); Context ic = new com.evermind.server.rmi.RMIInitialContextFactory(). getInitialContext(env); Object homeObject = ic.lookup("ejb/HelloBean"); // Narrow the reference to a HelloHome. HelloHome helloHome = (HelloHome) PortableRemoteObject.narrow(homeObject, HelloHome.class);
Normally, you cannot have EJBs communicating across EAR files, that is, across applications that are deployed in separate EAR files. The only way for an EJB to access an EJB that was deployed in a separate EAR file is to declare it to be the parent of the client. Only children can invoke methods in a parent.
For example, there are two EJBs, each deployed within their EAR file, called sales
and inventory
, where the sales
EJB needs to invoke the inventory
EJB to check to see if enough widgets are available. Unless the sales
EJB defines the inventory
EJB to be its parent, the sales
EJB cannot invoke any mehtods in the inventory
EJB, because they are both deployed in separate EAR files. So, define the inventory
EJB to be the parent of the sales
EJB and the sales
EJB can now invoke any method in its parent.
You can only define the parent during deployment with the deployment wizard. See the "Deploying Applications" section in the "Configuration and Deployment" chapter in Oracle Application Server Containers for J2EE User's Guide on how to define the parent application of a bean. For broader issues on how to package your classes for method invocation, see "Directory Structure Recommendations for EJB Development".
If you invoke any EJB from an application client outside the EJB container, then Java Authentication and Authorization Service (JAAS) is not supported for the EJB. However, if you call the EJB from a servlet within the OC4J instance, then JAAS is supported.
If you access an EJB in an application from an EJB in a different application, then you cannot use the RMIInitialContextFactory
object. In this scenario, you must use a parent-child relationship between these applications, and you must use the default initial context factory object.
If the call sequence of several beans cause a deadlock scenario, the OC4J container notices the deadlock condition and throws a Remote
exception that details the deadlock condition in one of the offending beans.
If you are trying to remotely access an EJB and you receive an javax.naming.NamingException
error, your JNDI properties are probably not initialized properly.
When accessing a remote EJB from a Web application, you receive the following error: "java.lang.NullPointerException: domain was null
". In this case, you must set an environment property in your client while accessing the EJB set dedicated.rmicontext to true.
The following demonstrates how to use this additional environment property:
Hashtable env = new Hashtable( ); env.put (Context.INITIAL_CONTEXT_FACTORY, "com.evermind.server.rmi.RMIInitialContextFactory"); env.put (Context.SECURITY_PRINCIPAL, "admin"); env.put (Context.SECURITY_CREDENTIALS, "admin"); env.put (Context.PROVIDER_URL, "ormi://myhost-us/ejbsamples"); env.put ("dedicated.rmicontext", "true"); // for 9.0.2.1 and above Context context = new InitialContext (env);
See "Load Balancing Options" for more information on dedicated.rmicontext.