Step 2: Coding the EJB

For this exercise we’ll make a simple SLSB, a Calculator Service, to illustrate how the various pieces all fit together. In fact, we’ll spin up two EJBs—one to show the path of least resistance, and another to expose all business, home, and component views. The full source and deployment instructions for this example are available in Appendix A.

The Contract

This is a great starting point, regardless of whether you’re building EJBs. The contract, implemented as interfaces in Java, defines what our service will do, and leaves it up to the implementation classes to decide how it’s done. Remember that the same interface cannot be used for both @Local and @Remote, so we’ll make some common base that may be extended.

public interface CalculatorCommonBusiness
{
   /**
    * Adds all arguments
    *
    * @return The sum of all arguments
    */
   int add(int... arguments);
}

public interface CalculatorRemoteBusiness extends CalculatorCommonBusiness{}

As you can see, we’ve created CalculatorRemoteBusiness by extending the contract of CalculatorCommonBusiness. This will come in handy later when we want to add more views exposing the same method (so we don’t have to rewrite its definition). Our remote business interface meets the requirement that our session bean will have at least one view, so we may now write the bean implementation class.

The Bean Implementation Class

Again we’ll make a common base to contain the logic, and extend it to add our metadata that will define the SLSB.

public class CalculatorBeanBase implements CalculatorCommonBusiness
{
   /**
    * {@link CalculatorCommonBusiness#add(int...)}
    */
   @Override
   public int add(final int... arguments)
   {
      // Initialize
      int result = 0;

      // Add all arguments
      for (final int arg : arguments)
      {
         result += arg;
      }

      // Return
      return result;
   }
}

This contains the required implementation of CalculatorCommonBusiness.add(int...). The bean implementation class therefore has very little work to do.

import javax.ejb.LocalBean;
import javax.ejb.Stateless;

@Stateless
@LocalBean
public class SimpleCalculatorBean extends CalculatorBeanBase

{
   /*
    * Implementation supplied by common base class
    */
}

The function of our bean implementation class here is to bring everything together and define the EJB metadata. Compilation will embed two important bits into the resultant .class file. First, we have an SLSB, as noted by the @Stateless annotation. And second, we’re exposing a no-interface view, new to EJB 3.1, courtesy of the @LocalBean annotation. Alternatively, we could have used an XML descriptor to hold this information, making an EJB out of CalculatorBeanBase. The EJB Container, upon deployment, will pick up on this information and take the necessary actions.

Before we use our new classes as an EJB, let’s leverage the pure POJO programming model to make some quick tests.

Out-of-Container Testing

Unit testing, as contrasted with integration testing, aims to isolate small bits of functionality and assert that they’re working as expected. The addition implementation of our CalculatorEJB is an excellent candidate for this kind of approach. By treating our EJB Implementation class as a POJO, we can keep test setup and execution time light and quick.

Testing in this book will be implemented using the popular open source framework JUnit (http://www.junit.org/):

import junit.framework.TestCase;

import org.junit.Test;

public class CalculatorUnitTestCase
{
   /**
    * Ensures that the CalculatorEJB adds as expected
    */
   @Test
   public void testAddition()
   {
      // Initialize
      final CalculatorCommonBusiness calc = new SimpleCalculatorBean();
      final int expectedSum = 2+3+5;

      // Add
      final int actualSum = calc.add(2, 3, 5);

      // Test
      TestCase.assertEquals("Addition did not return the expected result",
        expectedSum, actualSum);
   }
}

Here we make a new instance of SimpleCalculatorBean and use it to add a few integers. Again, this is not an EJB. The example showcases how the EJB Programming Model does not tie you to EJB at all; the test has no knowledge of the specification APIs.

Integration testing is where we’ll ensure that the individual pieces play nicely together.

Integration Testing

There are three steps involved in performing integration testing upon an EJB. First, we must package the sources and any descriptors into a standard Java Archive (JAR; http://java.sun.com/javase/6/docs/technotes/guides/jar/jar.html) or Enterprise Archive (EAR; http://java.sun.com/javaee/5/docs/tutorial/doc/bnaby.html#indexterm-47). Next, the resultant deployable must be placed into the container according to a vendor-specific mechanism. Finally, we need a standalone client to obtain the proxy references from the Container and invoke upon them.

Packaging

The Sun Microsystems JDK, for example, ships with a standard jar tool that can be used to assemble classes, resources, and other metadata into a unified JAR file, which will both compress and encapsulate its contents. Typically your project’s build will handle this step, often by way of an Apache Ant (http://ant.apache.org/) task or Maven (http://maven.apache.org/) goal. Whichever route you choose in packaging, the result might look a little bit like this if we were to print out the contents:

shell$> $JAVA_HOME/bin/jar -tvf firstejb.jar
     0 Wed Mar 25 01:59:22 GMT-08:00 2009 META-INF/
   129 Wed Mar 25 01:59:20 GMT-08:00 2009 META-INF/ejb-jar.xml // < OPTIONAL
     0 Wed Mar 25 01:59:20 GMT-08:00 2009 com/
     0 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/
     0 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/
     0 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/
     0 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/
     0 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/firstejb/
   318 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/firstejb/CalculatorLocal
Home.class
   248 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/firstejb/CalculatorRemote
.class
   348 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/firstejb/CalculatorRemote
Home.class
  1473 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/firstejb/CalculatorBean
Base.class
   237 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/firstejb/CalculatorRemote
Business.class
   939 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/firstejb/ManyView
CalculatorBean.class
   251 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/firstejb/CalculatorLocal
.class
   235 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/firstejb/CalculatorLocal
Business.class
   189 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/firstejb/CalculatorCommon
Business.class
   643 Wed Mar 25 01:59:20 GMT-08:00 2009 com/mycompany/firstejb/SimpleCalculator
Bean.class

The ejb-jar.xml line represents our optional XML descriptor, which is excluded from this example.

Deployment into the Container

The EJB Specification intentionally leaves the issue of deployment up to the vendor’s discretion. Therefore, our examples will keep this out of scope until Part V, which will fully detail instructions for running examples.

The client

Again we’ll implement our client as a JUnit test. This time, instead of creating POJOs via the new operator, we’ll look up true EJB references via JNDI. For our purposes, JNDI is a simple store from which we may request objects keyed to some known address. The form of the address is supplied by the EJB 3.1 Portable Global JNDI Name Syntax, discussed in Chapter 16.

Let’s walk through this step-by-step. The process looks a bit like the following:

  1. Perform initialization of the test, and set up the JNDI Environment.

  2. Look up the proxy reference to the EJB via JNDI.

  3. Invoke upon the reference.

  4. Test the result.

First, we declare some imports to show where some nonobvious types are coming from. Assume that the client is in the same package namespace as the CalculatorEJB classes:

import javax.naming.Context;
import javax.naming.InitialContext;
import org.junit.BeforeClass;
import org.junit.Test;

The javax.naming classes are used in obtaining the proxy references from JNDI, and the others are part of the test framework. Now to define the class and make for some static members used in testing:

public class CalculatorIntegrationTestCase
{
   /**
    * The JNDI Naming Context
    */
   private static Context namingContext;

   /**
    * The EJB 3.1 no-interface view view of the CalculatorEJB
    */
   private static SimpleCalculatorBean calc;


   /**
    * JNDI Name of the no-interface view
    */
   private static final String JNDI_NAME_CALC =
    "java:global/myJarName/SimpleCalculatorBean";

Here we’ve declared a JNDI naming Context for the lookups, and the names of the target JNDI Addresses under which we’ll find the appropriate proxy reference. Our tests will check that the EJB 3.1 Business View works as expected, but before the test runs, a lifecycle method will obtain the reference and set the proxy reference accordingly:

@BeforeClass // Invoked by the test framework before any tests run
   public static void obtainProxyReferences() throws Throwable
   {
      // Create the naming context, using jndi.properties on the CP
      namingContext = new InitialContext();

      // Obtain EJB 3.1 Business Reference
      calc = (SimpleCalculatorBean)
        namingContext.lookup(JNDI_NAME_CALC);
   }

First we create the naming Context, using a standard jndi.properties file that we assume to be present on the classpath. The JNDI properties to be used are vendor-specific, so by pulling these out into a separate file, our test remains portable across EJB implementations.

From there, we obtain the business proxy by performing a simple JNDI lookup upon the portable Global JNDI name.

Let’s define a central point to perform assertions within our tests:

   private void assertAdditionSucceeds(CalculatorCommonBusiness calc)
   {
      // Initialize
      final int[] arguments = new int[]
      {2, 3, 5};
      final int expectedSum = 10;

      // Add
      final int actualSum = calc.add(arguments); // Real EJB Invocation!

      // Test
      TestCase.assertEquals("Addition did not return the expected result",
        expectedSum, actualSum);
   }

This time, the calc reference passed in will not be a POJO, but instead a true EJB Proxy.

   @Test
   public void testAdditionUsingBusinessReference() throws Throwable
   {
      this.assertAdditionSucceeds(calc);
   }
}

Our two tests will merely pass the appropriate EJB Proxy to the assertion function. Assuming everything goes well, we’ll get the all clear and may rest assured that the system is working as we expect.

The interesting thing to note about this setup is that nowhere have we declared the notion of remoting, transactions, or the like. Regardless, this test is a fully functional remote client to a distributed architecture. Client code has no idea that it’s crossing process and network boundaries. This is a powerful argument in favor of the EJB Architecture.

Get Enterprise JavaBeans 3.1, 6th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.