Skip to main content

EJB Programming with snippets in WebSphere Studio V5.1.2

Ritchie Schacher (schacher@us.ibm.com), Advisory Software Engineer, J2EE Tools, IBM Research Triangle Park Lab, North Carolina
Ritchie Schacher is a software engineer at IBM Research Triangle Park Lab in Durham, North Carolina. He is a developer of J2EE tools for WebSphere Studio Application Developer. You can reach Ritchie at schacher@us.ibm.com.
Daniel Berg (danberg@us.ibm.com), Senior Software Engineer, J2EE Tools Team Lead, IBM Research Triangle Park Lab, North Carolina
Daniel Berg is a software engineer at IBM Research Triangle Park Lab in Durham, North Carolina. At the time of writing this article he was the team lead architect for J2EE tools in WebSphere Studio Application Developer. You can reach Dan at danberg@us.ibm.com.

Summary:  EJBs provide a powerful mechanism for distributed computing and container-managed persistence. The programming model, however, can be complex. This article shows you how to use WebSphere Studio to automate EJB programming by generating the client code and hiding the complexity. Along the way, it explains how the code works and shows best practices for simplicity and portability.

Date:  06 Oct 2004
Level:  Introductory
Activity:  532 views

Introduction

Enterprise Java™Beans (EJBs) provide a powerful mechanism for distributed computing as well as container-managed persistence. The programming model, however, can be complex, cumbersome, and difficult to remember. This article shows you how to use IBM® WebSphere® Studio Application Developer V5.1.2 (hereafter called Application Developer) to automate the task of programming with EJBs, by generating the client code and hiding the complexity.


EJB lookups the hard way

While automated code generation is great for productivity, it is always beneficial for the programmer to understand what is going on behind the scenes. This article will show you a few different ways to program with EJBs by hand, as you've had to do it in the past. Unfortunately, not all methods for EJB lookup are created equal, and there are real-world production applications that use all of the methods described, even though some of them are not recommended. The examples below progress from bad to better to even better. Finally, the article walks you through the best and easiest method for EJB lookup and optimization.

You can download the example code in the form of a Project Interchange ZIP file, which you can then import into your Application Developer workspace. Doing so will enable you to follow along with the article and evaluate the sample code. In Application Developer V5.1.2, the Project Interchange feature, which lets you import the content for this article, is now included in the base product. For information on how to use the Project Interchange import wizard and a feature download for earlier versions of WebSphere Studio, see Share and share alike: A new Project Interchange feature for Eclipse and WebSphere Studio. In order to take advantage of the new Snippets feature, you need Application Developer V5.1.2 or later.

The example project interchange ZIP file contains four projects: SnippetsEAR and SnippetsEJB, which together compose a server based EJB application, and SnippetClientEAR and SnippetAppClient, which together compose an application client that will use the EJBs from the first application. After you import the two projects, your view of the workspace from the J2EE Hierarchy view should look like this:

J2EE Hierarchy view

The SnippetsEJB project contains one entity bean, and four session beans. The entity bean is a very simple example, named Person, which has attributes firstName and lastName, and a key field named id. The Person bean also contains a custom create method on the home interface, which we will use for the example lookups. Each of the four session beans demonstrates a slightly different way to lookup the Home interface for the Person bean and then to invoke the create method.

Lookup 1: Using the JNDI name

The first example uses a direct lookup on the JNDI name of the referenced bean. This is how you would perform the lookup without EJB references (discussed later).

All EJBs must be bound to a JNDI name on the application server on which they are hosted. As of the EJB specification V1.1 or later, the JNDI name itself is not part of the standard ejb-jar.xml deployment descriptor that is part of the EJB specification. Instead, it is stored in a vendor-specific file that is used by the EJB container (application server) at run time. On WebSphere Application Server, the JNDI names of EJBs are stored in the bindings file named ibm-ejb-jar-bnd.xmi, under the directory META-INF. The file is a peer to the ejb-jar.xml deployment descriptor (DD), and contains references to the EJB objects defined in the DD. Under normal circumstances you should not need to hand edit this file. The JNDI names for the EJBs can be added using any of the following methods:

  • Let Application Developer create a default JNDI name whenever a new EJB is created.
  • Use the Deployment Descriptor Editor to set or change the JNDI name.
  • Set the JNDI names as part of installing a J2EE application on the server.

In the example, we have chosen the first method for creating default JNDI names. No action was required on our part; the values were set by the EJB creation wizard. To see the JNDI name for the Person bean, double-click on the bean in the J2EE Hierarchy view, which takes you to the Beans page of the editor. Notice the WebSphere bindings section on the right side of the page:

WebSphere bindings section

By convention, the JNDI names for all EJBs should start with the ejb segment.

The following method exists in the session bean implementation class LookupWithJNDIBean.java. This code uses a hard-coded reference to the JNDI name to perform the EJB lookup:

public void createNewPerson(Integer id, String firstName, String lastName) {
    Object obj = null;
        try {
            InitialContext ctx = new InitialContext();
            obj = ctx.lookup("ejb/sample/ejb/PersonHome");
        } catch (NamingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        PersonHome aPersonHome =(PersonHome) PortableRemoteObject.narrow(obj, PersonHome.class);
        Person aPerson = null;
        try {
            aPerson = aPersonHome.create(id, firstName, lastName);
         } catch (RemoteException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (CreateException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
}

This code first creates a new InitialContext using the default constructor. The InitialContext will be constructed with all its properties preset by the application server run time. It then uses the InitialContext to perform a JNDI lookup for the object registered with the JNDI name ejb/sample/ejb/PersonHome. In this case, that object will be the remote home interface for the Person bean. We then must cast the interface using the PortableRemoteObject#narrow API. Lastly, we can use the casted home interface to invoke our custom create method.

Here are reasons not to use the above method for looking up EJBs:

  • The JNDI name is hard-coded in the Java code. This makes the Java code much less portable. If you need to change the JNDI name of a bean to eliminate a conflict, or because you are deploying to another app server, then you must also update and recompile the Java code. This can be tedious and expensive.
  • Using this implementation code, only "remote" interfaces can be used. This adds quite a performance penalty for clients running on the same server (such as other EJBs in the same EAR, Web applications, and so on). For more information, see "Client View of an Entity" in the EJB specification.

Lookup 2: Factoring the lookups

The next example introduces a slight improvement over the first method, in that we factor out the common lookup code into a separate method. The following code is illustrated in the Session bean class LookupWithServiceMethodBean.java:

private Object lookupRemoteHome(Class aHomeClass, String jndiName) {
    Object obj = null;
     try {
        InitialContext ctx = new InitialContext();
        obj = ctx.lookup(jndiName);
    } catch (NamingException e) {
         // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return PortableRemoteObject.narrow(obj, PersonHome.class);
}

This cleans up the code in the method that actually retrieves and uses the bean, thus making the code a bit less cluttered and more readable, as shown below:

public void createNewPerson(Integer id, String firstName, String lastName) {
    PersonHome aPersonHome = (PersonHome)lookupRemoteHome(PersonHome.class,
            "ejb/sample/ejb/PersonHome");
    Person aPerson = null;
    try {
        aPerson = aPersonHome.create(id, firstName, lastName);
    } catch (RemoteException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    } catch (CreateException e1) {
         // TODO Auto-generated catch block
        e1.printStackTrace();
    }
}

Moreover, now the lookup method is available for reuse within this class for lookups of other referenced beans. However, like the previous example, the same disadvantages remain:

  • The JNDI name is a hard-coded.
  • Only remote interfaces can be used.

Lookup 3: Using an EJB reference

This example shows you how to use EJB references to look up the client interface for the Person bean and invoke the custom create method. EJB references let us use an extra level of indirection between the code and the actual JNDI name of the referenced bean. For illustrative purposes, we will show how to use this pattern for both local and remote client views. Whenever possible, however, use local client views and EJB local references.

Create an EJB reference from a session bean to the Person CMP bean:

  1. From the J2EE Hierarchy view, in the J2EE Perspective, select the session bean LookupWithReference.
  2. Select New -> Reference.
    Selecting the session bean
  3. Select EJB Reference, and select Next.
  4. Select Enterprise bean in current EJB project, and then select the Person bean. The EJB reference name defaults automatically to ejb/Person.
    Adding an EJB reference
  5. Select Finish.

The following code has been added to the file ejb-jar.xml:

<ejb-ref id="EjbRef_1097813639528">
    <ejb-ref-name>ejb/Person</ejb-ref-name>
    <ejb-ref-type>Entity</ejb-ref-type>
    <home>sample.ejb.PersonHome</home>
    <remote>sample.ejb.Person</remote>
    <ejb-link>Person</ejb-link>
</ejb-ref>

The JNDI name of the referenced EJB was defaulted for you. From the J2EE Hierarchy View, double-click on the bean LookupWithReference, then turn to the References page of the editor:

References page showing WebSphere bindings

This JNDI name is the JNDI name of the linked EJB, Person. Now we have a mechanism for looking up a referenced bean without hard coding the JNDI name in Java code. Here is the improved implementation of the lookup example in the class LookupWithReferenceBean.java:

public void createNewPersonUsingRemote(Integer id, String firstName, String lastName) {
    PersonHome aPersonHome = (PersonHome)lookupRemoteHome(PersonHome.class, "ejb/Person");
    Person aPerson = null;
    try {
        aPerson = aPersonHome.create(id, firstName, lastName);
    } catch (CreateException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    } catch (RemoteException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}		 
private Object lookupService(String referenceName) {
    try {
        Context aContext = new InitialContext();
        return aContext.lookup("java:comp/env/" + referenceName);
    } catch (NamingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return null;
}
		 
private EJBHome lookupRemoteHome(Class aHomeClass, String refName) {
    Object remote = lookupService(refName);
    return (EJBHome)PortableRemoteObject.narrow(remote, aHomeClass);
}

Notice that instead of passing in the JNDI name from the caller, we now use the EJB reference name ejb/Person to perform the lookup. As in the previous example, we have factored out the service lookup code, in order to keep the createNewPerson method, which does the real work, as simple as possible. Factoring out the lookup code also lets us reuse it for other EJB references.

Similarly, you can use the EJB reference wizard to create an EJB local reference, in which case the code looks like this:

private EJBLocalHome lookupLocalHome(String refName) {
    return (EJBLocalHome) lookupService(refName);
}
		 
public void createNewPersonUsingLocal(Integer id, String firstName, String lastName) {
    PersonLocalHome aPersonLocalHome = (PersonLocalHome) lookupLocalHome("ejb/PersonLocal");
    PersonLocal aPerson = null;
    try {
        aPerson = aPersonLocalHome.create(id, firstName, lastName);
    } catch (CreateException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

In this example, it is not necessary to narrow the result to the home class, since no RMI calls are made when using local interfaces. Here are two improvements in this method of lookup vs. using JNDI names directly:

  • We are coding to the EJB reference name as opposed to the JNDI name of the target bean. This allows for late binding of the JNDI names in an EJB application without having to recompile the Java code. Notice the use of the java:comp/env/ scheme, which is appended to the front of the EJB reference name to construct the lookup string. This is a special scheme which is used by the Naming service of the EJB container to resolve the reference by name to the actual JNDI object.
  • The second example takes advantage of the local client view of the target (Person) EJB. We can do that since the client in this case is another EJB running on the same server. This eliminates any RMI transfer and is a significant advantage of EJB 2.0 or later.

Here are two disadvantages that still remain in this example:

  • Unless you write this code frequently, it can be hard to remember the format of the java:comp/env/ scheme. We've mitigated that to some extent by factoring out the lookupService() method and passing just the reference name.
  • The common lookup code that we factored must be duplicated in every client where we want to use this pattern.

Now that you've seen all the hard ways to code EJBs, the next section will show you how to turbo-charge your productivity and optimize your application.


Using snippets to simplify EJB lookups

So far we have seen methods for doing the lookup of the home interface of an EJB. Some of these methods were not very good and others were a bit better, but all required a good bit of hand coding. The better solutions required changes to the EJB deployment descriptor to add an EJB reference. Even though these approaches are better, they require you to remember to make those deployment descriptor changes. Ideally, you'd like to have the EJB reference created when it was needed -- when a method is coded that uses the EJB reference to look up the home interface.

This is where EJB snippets make it much easier to call the create and find methods for a home interface of an EJB. The EJB snippet support builds on top of EJB references by providing the following benefits:

  • EJB reference creation while adding the required code to look up the home interface
  • Wizard-driven approach to add the necessary lookup code, which can be hard to remember
  • Use of the service locator design pattern to simplify lookup code
  • Optional EAR-level caching of home interfaces (more on this topic below).

Just by using a handful of mouse clicks, you can insert well-factored code that provides simplified lookups of home interfaces as well as invoking a selected method. The following sections describe a similar example as the previous session beans, except that we will invoke the EJB snippet framework to insert the necessary code in the LookupWithSnippets session bean class.

The Snippets view

Application Developer adds a Snippets view that contains several compartments for the types of snippets that can be invoked. The J2EE perspective has this view in the lower left tabbed area as shown below. To open this view, select Window => Show View => Other and then select Basic => Snippets.

Snippets view

All of the compartments shown in this view are used in HTML files except for the EJB compartment, which can only be used from within .java files that are contained within a project that is associated with an enterprise application project. The Snippets view provides function similar to the Java template support within Eclipse except that the Snippets view provides a means to visually represent the available templates. Also, the snippets that are invoked can invoke an advanced wizard that allows for object selections to drive the template creation which can span methods and fields to support the inserted code snippet. Currently the advanced snippet support is only invoked from the EJB compartment actions.

Using the EJB snippet support

The EJB Snippet support makes it easy to invoke either create or find methods on a home interface. We will walk through an example of creating the same createNewPerson(...) method that was created using other well known schemes for doing EJB home lookups.

Call EJB create method Snippet wizard

  1. Open the Java editor on the LookupWithSnippetsBean Java file.
  2. Create the following method signature with the cursor at the first line of the method body.
    public void createNewPerson(Integer id, String firstName, String lastName) {
    }

  3. Expand the EJB compartment of the Snippets view and double click on the Call an EJB create method.
    EJB compartment of the snippets viewThis will launch the EJB snippet wizard.
  4. The first page of the wizard requires you to select the appropriate EJB reference that refers to the EJB home interface that you would like to invoke. The tree displayed shows all of the available EJB reference owners within the workspace (e.g., EJBs, Web applications, and Application Clients). Based on the Java file that was selected a good guess at the proper owner is made, thus setting the selection to this guessed owner. In our example the LookupWithSnippets EJB is selected. It is your responsibility to select an EJB reference from the proper owner.
    Insert EJB Create window
  5. Since there is not an EJB reference to the Person CMP bean, click New to invoke the EJB Reference creation wizard. Walk through this wizard to create an EJB Local Reference to the Person bean, as outlined in the above section Using an EJB reference.
  6. The new reference will now appear in the tree. Double click on this EJB reference.
    EJB reference
  7. You will now be taken to the methods page which displays the available create methods. Double click on the create(Integer, String, String) method.
    Tree showing new reference
  8. The last page lets you modify the text that will be inserted for parameter values of the method. By default, the values are the same names as the parameters of the method itself but you are free to change them. The values entered are just simple strings that are inserted into the code snippet as entered on this page, so you can enter any code that you wish.
    Insert EJB Create screen
  9. Click Finish to insert the code.
  10. The inserted code may have compile errors due to missing import statements. To remove these errors select Ctrl + shift + o to organize imports.

EJB create method snippet wizard output

Generated code

The following code is inserted at the cursor location. The PersonLocalHome lookup is done first and then the create(...) method is invoked to create a new PersonLocal. The inserted code is formatted based on the formatting rules that are defined in the Java preferences. If the code is not formatted as you wish, change your Java formatting preferences to suit your needs.

PersonLocalHome aPersonLocalHome =
    (PersonLocalHome) ServiceLocatorManager.getLocalHome(
        STATIC_PersonLocalHome_REF_NAME,
        STATIC_PersonLocalHome_CLASS);
PersonLocal aPersonLocal = null;
try {
    if (aPersonLocalHome != null)
        aPersonLocal = aPersonLocalHome.create(id, firstName, lastName);
} catch (CreateException ce) {
    // TODO Auto-generated catch block
    ce.printStackTrace();
}

In order to support the lookup of the PersonLocalHome interface, two new static fields have been added to the class as well. The first field holds the EJB reference name for the EJB reference that was selected in the EJB snippet wizard.

private final static String STATIC_PersonLocalHome_REF_NAME = 
        "ejb/Person";

The second static field holds the Class of the PersonLocalHome interface.

private final static Class STATIC_PersonLocalHome_CLASS = 
        PersonLocalHome.class;

Addition of the serviceLocatorMgr.jar

One item in this code snippet that should raise some eyebrows is the reference to the ServiceLocatorManager class. Before we get into a detailed explanation of this class let's first explore how the LookupSnippetsBean.java file has a reference to it in its build path. This class is not a hidden class in one of the WebSphere Application Server JARs -- it is actually found within a serviceLocatorMgr.jar file, which is located in the SnippetsEAR project. The steps below spell out what happened when you clicked Finish in the EJB Snippet wizard in addition to inserting the code snippet and fields:

  1. The serviceLocatorMgr.jar is added to each enterprise application project in which the current Java file is a member.
  2. A classpath entry is added to the Java project's build path of the current Java file, which resolves to the serviceLocatorMgr.jar so the inserted code will compile.
  3. The file MANIFEST.MF is modified for the Java project in Step 2 to include the following entry: Class-Path: serviceLocatorMgr.jar This ensures that the ServiceLocatorManager reference will be available at run time.

Since the initial release of Snippets feature, important updates have been made to the serverLocatorMgr.jar file. These include an option to turn caching on and off (the default is now off), and the ability to set default properties for all Contexts. You can download the updated JAR file below and apply it to your installation of application developer. Download serviceLocatorMgrUpdate.zip and unzip it in the root installation directory for Application Developer.

The ServiceLocatorManager

The samples at the beginning of this article presented different alternatives for doing EJB home interface lookups. All of these samples shared one thing in common: they all used the same basic logic to perform the lookup. The EJB snippet sample clearly does not have this basic logic. The ServiceLocatorManager encapsulates the logic necessary for looking up an EJB home interface and presents simple APIs for looking up both local and remote home interfaces. The APIs for looking up home interfaces are shown below:

public static EJBHome getRemoteHome(String
ejbReferenceName, Class homeClass)

public static EJBHome getRemoteHome(String ejbReferenceName, Class
homeClass, String providerURL, String nameserviceType)

public static EJBLocalHome getLocalHome(String
localEjbReferenceName, Class homeClass)

public static EJBLocalHome getLocalHome(String
localEjbReferenceName, Class homeClass, String providerURL, String
nameserviceType)

Next we will walk through the API code path within the ServiceLocatorManager that was used in our code snippet.

Code walk-through

  1. First we start with ServiceLocatorManager.getLocalHome( STATIC_PersonLocalHome_REF_NAME, STATIC_PersonLocalHome_CLASS).
  2. It calls getLocalHome(localEjbReferenceName, homeClass, null, null), where the last two parameters are the provider URL and the Name Service type, respectively. In this case null is used for both, because we want to use the default InitialContext. The default InitialContext will be initialized with no environment properties, unless the default environment properties are set using the setDefaultProperties(Hashtable) method.
  3. We then call getHome(localEjbReferenceName, homeClass, providerURL, nameserviceType, false), where the last boolean indicates whether it is a remote call (in this example it is not).
  4. The getHome(...) method determines if synchronization is required. The test for synchronization depends on whether the returned Homes are being cached. Caching is discussed below.
  5. The doGetHome(...) method is the most interesting method, because it is where most of the work is done. The first check is to see if the home interface is already cached in the static HOMES Map only if caching is turned on and the default Context is being used. If the Home is found in the cache, it is returned; if not, the home interface must be looked up and then cached, (again, only if caching is enabled):
    private static Object doGetHome(String ejbReferenceName, Class homeClass, 
        Context ctx, boolean isRemote, boolean useCache) {
            String homeName = null;
            Object home = null;
            if (useCache) {
                homeName = homeClass.getName();
                home = HOMES.get(homeName);
            }
            if (home == null) {
                if (isRemote)
                    home = lookupRemoteHome(ejbReferenceName, homeClass, ctx);
                else
                    home = lookupLocalHome(ejbReferenceName, ctx);
                if (useCache && home != null)
                    HOMES.put(homeName, home);
                }
                return home;
    }

  6. The IntialContext is obtained in Step 4 by calling the getInitialContext(String, String) method. If no providerURL or nameserviceType is provided, then the default InitialContext will be obtained. If caching is enabled the cached default InitialContext is obtained:
    /**
     * Return a Context based on only the Provide URL and the InitialContext factory Type 
     * environment properties that are being passed.
     * Note, if the parameters passed are null, the default Context will be
     * returned; otherwise, a new InitialContext will be created with these
     * properties.
     * @param providerURL
     * @param nameserviceType
     * @return
     * 
     * @see #getInitialContext()
     * @see Context#PROVIDER_URL
     * @see Context#INITIAL_CONTEXT_FACTORY
     */
    public static Context getInitialContext(String providerURL, String nameserviceType) {
            Hashtable environment = setupEnvironmentHashtable(providerURL, nameserviceType);
         if (environment == null)
            return getInitialContext();
        return getInitialContext(environment);
    }

  7. After the InitialContext is obtained, the method called from Step 5 does the lookup. In our code snippet, lookupLocalHome(...) is called, since we are looking up a local home interface:
    private static EJBLocalHome 
            lookupLocalHome(String localEjbReferenceName, 
            Context ctx) {
                if (localEjbReferenceName != null)
                    return (EJBLocalHome) lookupService(localEjbReferenceName, ctx);
                return null;
    }

    It then calls the following to determine if synchronization is required. This is the same test used in Step 4. If caching is required, we synchronize the actual lookup block of code:

    private static Object lookupService(String referenceName, Context aContext) {
        if (shouldUseHomeCache(aContext)) {
            synchronized (aContext) {
                return doLookupService(referenceName, aContext);
            }
        } else
            return doLookupService(referenceName, aContext);
    }

    The actual lookup of the home is then performed in the doLookupService(...) method:

    private static Object doLookupService(String referenceName, Context aContext) {
        try {
            return aContext.lookup("java:comp/env/" + referenceName);
        } catch (NamingException e) {
            getErrorHandler().handleLookupFailure(e, referenceName);
        }
        return null;
    }

  8. If the lookupRemoteHome(...) method was called, we would have gone down the lookupRemoteHome(...) method, which is the same as Step 7, except it does the narrowing for you, as shown below:
    private static EJBHome lookupRemoteHome(String ejbReferenceName, 
            Class homeClass, Context context) {
        if (ejbReferenceName != null && homeClass != null)
            return (EJBHome) 
        PortableRemoteObject.narrow(lookupService(ejbReferenceName, context), 
                homeClass);
        return null;
    }

ServiceLocatorManager error handling

In order to keep the code that uses the ServiceLocatorManager simple, none of the APIs have exceptions in their throws clause, so that all calls are not required to catch the exceptions and perform the same logic to tell the user that a problem has occurred. This does not mean that the ServiceLocatorManager catches and ignores all exceptions. On the contrary, it catches exceptions and pass the handling of them to a registered ServiceLocatorErrorHandler instance.

The ServiceLocatorErrorHandler interface has the following two APIs. The first method is called when there is a problem creating an InitialContext. The second method is called when a lookup call on the InitialContext fails to find a home interface:

/**/**
 * @param e
 * @param properties
 */
void handleInitialContextFailure(NamingException e, Hashtable properties);
/**
 * @param e
 * @param referenceName
 */
void handleLookupFailure(NamingException e, String referenceName);

The ServiceLocatorErrorHandler has to be registered with the ServiceLocatorManager prior to using any APIs on the ServiceLocatorManager. The following method is used to register the ServiceLocatorErrorHandler. If no ServiceLocatorErrorHandler is registered when an exception occurs, a default ServiceLocatoryErrorHandler is constructed that simply prints out the stack trace of the exception:

/**
 * Set the ServiceLocatorErrorHandler for this manager.
 * @param handler
 */
public static void setErrorHandler(ServiceLocatorErrorHandler handler) {
    ERROR_HANDLER = handler;
}

Using EJB snippets from an application client

The basic usage of the EJB snippet support is the same no matter where it is called from. However, some unique changes take place when using the EJB snippet wizard to refer to an EJB within another enterprise application. The key difference here is that you cannot create an EJB reference with an EJB link defined, because this is allowed only for EJB references within the same enterprise application.

EJB client JAR project preference

Before starting, turn on a preference that allows the creation of an EJB client JAR project:

  1. Select Window > Preferences > J2EE.
  2. Select the options Create EJB client JAR projects for new EJB projects and the Use EJB Client JAR, if it exists:
    Preferences screen
  3. Select OK.

Using the EJB Snippet wizard from application client

We will create the same reference that was used in the Call EJB create method Snippet wizard, except that we will do it from the snippet.example.MyMain.java file within the SnippetAppClient module.

  1. Open a Java editor on snippet.example.MyMain.java and put your cursor in the main method.
  2. From the Snippets view, double-click on the Call an EJB create method snippet action .
  3. Select the SnippetAppClient from the tree and click New .
  4. This launches the Add EJB Reference wizard, as shown in the above section Using an EJB reference. On the first page, select the Enterprise bean in different EAR option. Then select the Person CMP bean and click Finish:
    Add EJB Reference screen
  5. You will see the following prompt to have an EJB client JAR project created for the SnippetEJB project. Select Yes to have the client project created:
    Prompt for creating project
  6. Double-click on the new ejb/Person reference.
  7. This advances you to the EJB Lookup Properties page, where you can enter the provider URL and name service type class that will be used when creating the InitialContext. These properties may be necessary to look up the EJB home from the application client. This page appears because the EJB reference that was selected does not contain an ejb-link attribute. For this example, we will select the Use default context properties for EJB home interface lookups. option, which indicates that we are running this application client on the same server as the target EJB module's EAR file. Therefore a default InitialContext can be used. If this was not the case, then you would set the Provider URL, which is a URL to the service provider (for example ldap://somehost:389) and the Name Service Type, which is the class name of the initial context factory class (for example, com.ibm.websphere.naming.WsnInitialContextFactory):
    Insert EJB Create screen
  8. Select Next.
  9. Follow Steps 7 through 10 from the above section Using an EJB reference.

Application client-based EJB Snippet wizard output

Before we explain the output from this wizard, we need to explain what happened after Step 5 above. At this point, an EJB client JAR project is created for the SnippetsEJB project called SnippetsEJBClient. The client-specific files (EJB client interfaces and their references) are then copied from the SnippetsEJB project to the SnippetsEJBClient project. This new project is then added to SnippetsEAR application as a project utility JAR. EJB client JAR files must be placed in the EAR with the EJB module if the EJB module has a reference to it. You can see this on the Modules tab of the EAR Deployment Descriptor of SnippetsEAR, as shown below:

Project Utility JARs

The EJB deployment descriptor for the SnippetsEJB module project is also updated to have the ejb-client-jar reference to this new SnippetsEJBClient.jar from the EAR file.

<ejb-client-jar>SnippetsEJBClient.jar</ejb-client-jar>

The SnippetClientEAR also has a new project utility JAR entry to the SnippetsEJBClient project, since it is now needed by the SnippetClient module project.

Lastly, a JAR dependency is set up for both the SnippetsEJB project and the SnippetClient project to point to the SnippetsEJBClient.jar entry. This ensures that the SnippetEJBClient project is added to the Java build path of both of these projects, and and that the SnippetEJBClient.jar entry is added to the MANIFEST.MF file for both projects. This makes the contents of SnippetsEJBClient.jar available at run time.

Java JAR Dependencies

Generated code

After selecting Finish in the Snippet wizard from the MyMain.java file, the output is virtually the same as it was the first time that we ran the wizard. First, ServiceLocatorMgr.jar is added to SnippetClientEAR, and a Java JAR dependency is added to it from the SnippetClient module project, just as it was in the section Addition of the serviceLocatorMgr.jar.

The following code is inserted at the cursor location. The PersonHome remote home lookup is done first, and then the create(...) method is invoked to create a new Person:

PersonHome aPersonHome =
    (PersonHome) ServiceLocatorManager.getRemoteHome(
        STATIC_PersonHome_REF_NAME,
        STATIC_PersonHome_CLASS);
Person aPerson = null;
try {
    if (aPersonHome != null)
        aPerson = aPersonHome.create(id, firstName, lastName);
} catch (CreateException ce) {
    // TODO Auto-generated catch block
    ce.printStackTrace();
} catch (RemoteException re) {
    // TODO Auto-generated catch block
    re.printStackTrace();
}

In order to support the lookup of the PersonHome interface, two new static fields have been added to the class as well. The first field holds the EJB reference name for the EJB reference that was selected in the EJB Snippet wizard:

private final static String STATIC_PersonHome_REF_NAME = "ejb/Person";

The second static field holds the class of the PersonHome interface:

private final static Class STATIC_PersonHome_CLASS = PersonHome.class;

If you require additional variability with the environment properties, you can modify the generated snippet code to pass an instance of a hashtable that includes the context properties that you wish to set. For example, you can update the snippet above to look like this:

Hashtable properties = new Hashtable();
properties.put(Context.SECURITY_AUTHENTICATION, "strong");
PersonHome aPersonHome =
    (PersonHome) ServiceLocatorManager.getRemoteHome(
        STATIC_PersonHome_REF_NAME,
        STATIC_PersonHome_CLASS,
        properties);
Person aPerson = null;
try {
    if (aPersonHome != null)
        aPerson = aPersonHome.create(id, firstName, lastName);
} catch (CreateException ce) {
    // TODO Auto-generated catch block
    ce.printStackTrace();
} catch (RemoteException re) {
    // TODO Auto-generated catch block
    re.printStackTrace();
}


Caching within the ServiceLocatorManager

The idea of caching within the ServiceLocatorManager was touched on earlier in this article. Before explaining how to enable caching, a brief warning is required. Caching in ServiceLocators has been popular in the past with J2EE 1.2, because you could cache on the global JNDI name. This article has already described the shortcomings of using the global JNDI name, but now that reference names are used for doing lookups of EJBs, it is no longer possible to cache based on this reference name. Because reference names are scoped to application clients, Web applications, and individual EJBs, the names are no longer global and are therefore not safe for caching. For more information on why most service locators fail, see Eliminate caching in service locator implementations in J2EE 1.3.

The ServiceLocatorManager employed in the EJB Snippet support does not have caching by default, but you can enable caching by calling:

ServiceLocatorManager.setEnableDefaultHomeCaching(true)

So what happens when caching is enabled? The default InitialContext is cached to avoid obtaining it each time, and any lookup call done via this default context is synchronized. Also, each Home instance is cached once found, so that subsequent lookups will obtain it from the cache, and this cache access is protected with a synchronized block. This caching happens only when the default Context is used. When other Contexts are used, no caching of the Homes is performed. Each Home instance is cached by the fully qualified name of the Home interface. This is safe for most applications, but there is one case in which the caching will not return the proper results and that is when there is a reference to two separate EJBs that share client interfaces. For example:

Client1
    ejbs/A  -> A
 Client2
    ejbs/B  -> B
  
 A
   ABean
   A
   AHome
 
 B
   BBean
   A
   AHome

In this situation, if caching is on, when the client does a look-up using ejbs/A it will obtain the correct AHome from bean A. But then when Client2 does a look-up for ejbs/B, it will get the cached A bean's Home instance, because it was in the cache first with the AHome class name. This situation is rare, but if it does exist, you cannot enable caching. Also, only Home instances are being cached and not other types of references, such as resource references. The ServiceLocatorManager is intended to support the EJB Snippets and thus only deals with EJB references. To flush the cache:

ServiceLocatorManager.flushCache()

Leave caching off unless you have a performance issue with the Home lookups and you don't have the limiting case described above. If many lookups are being performed, enabling caching will yield a noticeable performance gain; otherwise performance gains are negligible.

If you wish to enable caching, profile your application in your particular environment to determine the maximum benefit. Many application servers, including WebSphere Application Server, support caching of references at the server level, thus making the performance gains of caching at the application level less dramatic than in the past.


Conclusion

This article has explored the basic programming idioms for looking up enterprise beans in an EJB application. It showed you some not-so-good practices and explained why to avoid them. It compared a few methods for lookup and explained why each was better than the previous one. Next the article introduced you to the Snippets view in Application Developer and showed you how to automate the generation of best practice coding idioms, as well as create EJB references "on the fly" as they are needed. It presented a pattern-based solution for lookups via the "service locator manager", and explained how this code works. Finally, the article showed you a simplified way to reference EJBs in another EAR file and automate the task of the EJB client JAR creation for referencing applications. Use of the techniques presented in this article should yield the following benefits for your development team:

  • Improved productivity in developing EJB applications
  • Better run-time performance for EJB applications
  • Simplified, easy-to-read, easy-to-understand code
  • Well-structured application packaging for multiple enterprise applications


Downloads

NameSizeDownload method
snippets_examples.zip20 KBFTP|HTTP
serviceLocatorMgrUpdate.zip9 KBFTP|HTTP

Information about download methods


Resources

About the authors

Ritchie Schacher is a software engineer at IBM Research Triangle Park Lab in Durham, North Carolina. He is a developer of J2EE tools for WebSphere Studio Application Developer. You can reach Ritchie at schacher@us.ibm.com.

Daniel Berg is a software engineer at IBM Research Triangle Park Lab in Durham, North Carolina. At the time of writing this article he was the team lead architect for J2EE tools in WebSphere Studio Application Developer. You can reach Dan at danberg@us.ibm.com.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere, Java technology
ArticleID=23608
ArticleTitle=EJB Programming with snippets in WebSphere Studio V5.1.2
publish-date=10062004
author1-email=schacher@us.ibm.com
author1-email-cc=
author2-email=danberg@us.ibm.com
author2-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers