Elvis Meets GlassFish

Cay S. Horstmann

This article gives a brief introduction into basic EJB3 concepts from the point of a programmer (Elvis) who wants to write business applications and has no interest in Einstein-level knowledge of EJB. We assume that Elvis has basic knowledge of Java and relational databases, and that he can run tools from the command line. 

The article includes complete code for a skeletal EJB3 implementation that has been tested on GlassFish build 40. The code demonstrates

Most importantly, all pieces are included and it actually works :-) No exotic tools are required, just Java 5 and GlassFish.

??? Check out Elvis meets NetBeans. That tutorial shows how to develop the same example using the NetBeans 5.5 development snapshot. Very slick.

??? Internet Explorer 6 can't render the CSS2 in this document. Get a real browser if you want to see the eye candy.

Meet Elvis

When Microsoft designs tools for developers, they personify the tool users as Mort, Elvis, or Einstein. Mort is happiest if he can click through wizards instead of writing code. Performance or future maintainability aren't an issue for him--he just wants to get the job done now. Einstein is on the other extreme--he wants complete control and maximum performance, even if that requires wading through endless complexity. Elvis is in the middle. He wants efficient and maintainable code, but he is pragmatic. He won't learn a complex technology if he can get the job done almost as well with a much simpler alternative.

Elvis stamp

In the past, the Enterprise Java Beans technology was definitely Einstein territory. Just look at Head First EJB, a book that tries valiantly to make EJB comprehensible. Here is a random margin note (from page 139): “A handle is a Serializable object that knows how to get back to the original Remote EJB object. It's not a stub but it can GET the stub. It has just one method,getEJBObject(), that returns type EJBObject. So you have to narrow and cast the stub you get back!” This is not what Elvis cares about. Elvis wants to implement a UI, execute business methods, and access databases. He doesn't want to narrow and cast stubs.

Fortunately, Einstein is about to lose his territorial claims on EJB. EJB3 is much simpler than its predecessor, and it is well within the grasp of Elvis. I am working on a book "Enterprise Java for Elvis", to be published by Sun Microsystems Press in 2006. 

Meet GlassFish

GlassFish is an open-source Java EE container that aims to implement the Java EE 5 standard, including EJB3.  

glassfish logo

GlassFish is based upon the Sun Application Server 8, Sun's J2EE 1.4 compliant application server (J2EE 1.4 is the predecessor of Java EE 5--don't ask me to explain the version numbering) and Oracle's TopLink.

From Elvis' point of view, GlassFish gives one-stop shopping for web presentation (with JavaServer Faces), transactions and security for business logic, and persistent storage.

Eventually, every Java EE 5 compliant application server should work just fine, but as I write these notes, GlassFish seems to  furthest along. JBoss is a serious competitor.

GlassFish is pretty easy to install and use. It is not an IDE, but the NetBeans IDE has a Java EE 5 branch that works with GlassFish. However, that is still in early development. In these notes, I just use Eclipse to write the code and Ant to deploy it.

Installation of GlassFish is straightforward:

  1. Download the JAR file for your OS
  2. Run:
    java -Xmx256M -jar filename.jar -console
  3. Move the resulting directory into a convenient location. I'll refer to that directory as glassfish/.
  4. Change to that directory and run:
    glassfish/bin/asant -f setup.xml

asant is just a version of Ant that is included in GlassFish. If you have the regular Ant installed and on the path, simply use ant instead of asant.

To start the app server, run

glassfish/bin/asadmin start-database
glassfish/bin/asadmin start-domain

That's start-domain, not start-server.

You don't need to start the database if you use another database with GlassFish, but for now we'll use the Derby database that comes bundled with it.

Here, we use the Unix (also Linux and Mac OS X) conventions for directory separators. On Windows, use \ instead of /.

Point your browser to http://localhost:8080 to see that the app server has started.

.

Application Layering

The standard JavaEE architecture has three layers:

.

The presentation layer is the user interface. Ultimately, we'll use JavaServer Faces for the presentation layer, but that's yet another thing to learn. For now, a command-line client will do.

The business logic is handled by session beans. That's just EJB speak for classes whose methods can supply transaction, security and failover.

Persistence is handled by entity beans. That's EJB speak for classes whose instances can be stored in a database. A key feature of EJB3 is object-relational mapping: automatic (or semi-automatic) mapping between Java objects and rows in a relational database. Ideally, Elvis won't have to write any SQL at all. (He still needs to know SQL for troubleshooting, though.)

Persistence AKA Entity Beans

Most business applications load data from a relational database, present the data to the user, and update the database as a result of the user actions.  Administration, performance optimization, and backup of relational databases are well understood, and programmers can largely treat the database as a black box.

In the past, one had to use SQL to talk to the database. That is an annoyance since the business logic is expressed in terms of objects, and relational databases are not very object-oriented. Programmers who don't want to fuss with SQL will want to use an object-relational mapping (ORM) tool. These tools have matured greatly in recent years. EJB3 requires an ORM tool as part of the application server, and it standardizes the programming interface. Under the hood, different EJB3 implementations can choose different tools. For example, GlassFish uses TopLink and JBoss uses Hibernate. Elvis won't care.

In this note, I will start developing a very simple application that allows users to take quizzes. This might be for an online course such as a college course, corporate training, or even traffic school.

We'll start out with three classes.

A Quiz has a title and a collection of questions. A Question has a text (such as "What is the maximum blood alcohol level to operate a fork lift", an answer ("0.1 percent") and several other choices that are not correct answers. A Choice simply contains the choice text.

In our model, a question can occur in multiple quizzes. (This could happen if a particular quiz is generated by randomly choosing questions from a pool.) In other words, there is a many-to-many relationship between questions and quizzes. But for simplicity, we'll have a one-to-many relationship between Question and Choice. That is, a particular Choice object will only be contained in a single Question.

The following UML diagram shows the relationships.

.

It is straightforward to implement these classes in Java. Here are the first two:

public class Choice { 
    private String text; 
    public String getText() { return text; } 
    public void setText(String text) { this.text = text; }
}

public class Question { 
    private String text; 
    private Collection<Choice> choices; 
    private String answer; 
    public String getText() { return text; } 
    public void setText(String text) { this.text = text; } 
    public String getAnswer() { return answer; } 
    public void setAnswer(String answer) { this.answer = answer; } 
    public Collection<Choice> getChoices() { return choices; } 
    public void setChoices(Collection<Choice> choices) { this.choices = choices; }
}

Elvis hates the getters and setters. Sure, Eclipse can write them automatically, but they clutter up the code. Why can't Java support properties as a language feature?

EJB3 makes it phenomenally easy to map these classes to a database. First, annotate each class with @Entity and include an ID field:

package elvis.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Choice implements Serializable { 
    private int id; 
    private String text; 
    @Id
    @GeneratedValue
    public int getId() { return id; } 
    public void setId(int id) { this.id = id; } 
    public String getText() { return text; } 
    public void setText(String text) { this.text = text; }
}

Annotations are a feature of Java SE 5. Writing tools that process annotations is not an easy task--see Horstmann and Cornell, Core Java 7th ed. vol. 2, ch. 13. However, using annotations is straightforward. Think of @Entity and @Id as modifiers with syntax similar to public or static.

Here, we use two annotations. @Entity marks the class as an "entity bean"--a persistent class. The @Id method marks the id property of the class as the ID or primary key.

Recall that Java properties are expressed as getter/setter pairs (e.g. getId/setId or getText/setText). Place the annotation before the getter method.

To annotate the Question class, we need to establish the one-to-many relationship:

package elvis.entity;
import java.util.Collection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;

@Entity
public class Question implements Serializable { 
    private int id; 
    private String text; 
    private Collection<Choice> choices; 
    private String answer; 

    @Id 
    @GeneratedValue
    public int getId() { return id; } 
    public void setId(int id) { this.id = id; } 
    public String getText() { return text; } 
    public void setText(String text) { this.text = text; } 
    public String getAnswer() { return answer; } 
    public void setAnswer(String answer) { this.answer = answer; } 
    @OneToMany 
    public Collection<Choice> getChoices() { return choices; } 
    public void setChoices(Collection<Choice> choices) { this.choices = choices; }
}

The Quiz class is annotated in the same fashion, except that we use @ManyToMany. Look at the complete example code for details.

It now becomes very simple to store any of these objects into the database. Get an EntityManager and call persist:

EntityManager em = . . .; // see below
em.persist(myQuiz);

It is up to the ORM layer to establish a set of tables in the database, and to save the object into the database. You'll see later how you can spy on these activities.

To get an object back from the database, you can make a query, using the EJB Query Language, a language that is similar to SQL but deals with objects instead of database rows. We won't discuss EJB QL in detail now but just show how to execute a simple query:

Query q = em.createQuery("SELECT x FROM Quiz x WHERE x.id = :id").
    setParameter("id", id);
Quiz quiz = (Quiz) q.getSingleResult();

Business Logic AKA Session Beans

The methods of a session bean implement business logic, such as "pay an invoice", "submit a questionnaire", etc. As is customary in object-oriented design, related methods are put into the same class. To enable distributed processing, EJB forces you do define an interface with the business method as well as a class that implements the interface. But that's ok--it is good practice anyway.

In this article, we just want to test some EJB persistence features, so our "business logic" is contained in a single interface with a couple of test methods:

package elvis.session;import javax.ejb.Remote;
@Remote
public interface Test { 
    int makeQuiz(); 
    String getQuiz(int id);
}

Note the @Remote annotation. It passes information to the application server. When the interface is compiled, the EJB annotations remain in the class file. When the application server loads the class, it processes the annotations. 

We need a class that implements the interface:

package elvis.session;

@Stateless
public class TestImpl implements Test { 
    public int makeQuiz() { . . . } 
    public String getQuiz(int id) { . . . }
} 

This class is annotated as @Stateless.  This is useful information for the server. For example, if a client requests a makeQuiz method call, then any instance of the class that happens to be available can fulfill the request.

In our test application, the method bodies are pretty simple. The makeQuiz method builds up a Quiz object and persists it. The getQuiz method runs the EJB QL query that you saw at the end of the preceding query.

However, to carry out these tasks, the methods require an entity manager. We will use dependency injection to obtain it. 

The TestImpl class declares a field

@PersistenceContext(unitName="defaultPersistenceUnit") private EntityManager em;

When the application server loads the class, it notes the annotation for the em field and initializes it with an appropriate EntityManager object.

Now the em field can be used in any of the methods without worrying how it was initialized. For example,

public int makeQuiz() { 
    Quiz q = new Quiz(); 
    q.setTitle("A Java Trivia Quiz"); 
    . . . 
    em.persist(q); 
    return q.getId();
}

public String getQuiz(int id) { 
    Query q = em.createQuery("SELECT x FROM Quiz x WHERE x.id = :id")
        .setParameter("id", id); 
    return q.getSingleResult().toString();
}

An application can have multiple entity managers, and each of them must be given a separate name, both in the @PersistenceContext annotation and the persistence.xml configuration file that you will see in the next section.

Packaging and Deploying an EJB Application

You put the class files of your session and entity beans into an EAR (Enterprise ARchive) file. An EAR file is similar to a JAR file: a ZIP file with a special META-INF directory that holds information about the archive.

In the bad old days, it was mandatory to add an application.xml file into the META-INF directory of an EAR file, but that is no longer required. (You still can do this if you have additional configuration information, but we don't need that in our simple example.)

We follow the advice of Sahoo and structure the EAR file as follows:

If our application contained a presentation layer (as a WAR file), then that too would be a part of the EAR file. 

Altogether, the EAR file has the following layout:

.

The lib subdirectory of an EAR file is special: any JAR files in it are accessible to all other parts of the web application. We place the first two JAR files into the lib subdirectory so that the entities and session bean interfaces are visible everywhere.

The entity JAR file requires a persistence.xml file in its META-INF directory. That file has the following content:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"> 
    <persistence-unit name="defaultPersistenceUnit"/> 
</persistence>

You can see the defaultPersistenceUnit name that matches the name of the @PersistenceContext annotation in the preceding section.

Here is the structure of quiz-entity.jar.

.

Elvis hates configuration file busywork. Why can't there be an anonymous default entity manager?

If you add the property 
<persistence-unit name="defaultPersistenceUnit">
   <properties>
      <property name="toplink.logging.level" value="FINEST"/>
   </properties>
</persistence-unit>

to the persistence.xml properties, then you get more information about the Toplink ORM activities in the GlassFish logs.  That is helpful for debugging.

It is possible (but tedious) to make these various JAR and EAR files by hand, with the jar command. We use an Ant script for this purpose.

Now we are ready to deploy the EAR to the application server. First log in so the app server knows who you are.

glassfish/bin/asadmin login

You will be prompted for the app server username and password.

The default username/password is admin and adminadmin. You know that it is unsafe to use these in a production setting :-)

Then deploy.

glassfish/bin/asadmin deploy --dropandcreatetables quiz.ear

You will see in the next section how to access the TestImpl class from a command-line client.

Accessing Session Beans

To access a session bean, you need to get a proxy object--an object of some class that implements the bean interface. If your client code is in a managed environment, that is, inside the application server, then it is very easy to get such a proxy.

Make a field whose type is the remote interface, and annotate it with @EJB.

public class MyClient { 
    @EJB public static Test test; 
    public myMethod() { 
        test.makeQuiz(); 
        . . . 
    }
}

When the application server loads this class, it initializes the field with a proxy instance. This is again dependency injection in action.

This is the approach of choice when calling a session bean from a JSF 1.2 managed bean.

However, we don't yet want to fuss with a web-based user interface. We just want to run a standalone command-line Java program. That program runs outside the application server, so we have to look up the proxy. The code is still pretty straighforward.

package elvis.client;
import javax.naming.InitialContext;
import elvis.session;

public class Client { 
    public static void main(String[] args) throws Exception { 
        InitialContext ctx = new InitialContext(); 
        Test test = (Test) ctx.lookup(Test.class.getName()); 
        int id = test.makeQuiz(); 
        . . . 
    }
}

To run the client code with GlassFish, you need to include the JAR files glassfish/lib/appserv-rt.jar in the class path and use the -javaagent option to invoke the TopLink agent, like this:
java -classpath .:glassfish/lib/appserv-rt.jar -javaagent:glassfish/lib/toplink-essentials-agent.jar elvis.client.Client
The TopLink agent is required to process the annotations of the entity classes as they are loaded.

This code obtains the default naming context and looks up the stub object that is associated with the string Test.class.getName() (i.e. "elvis.client.Client"). When you deploy the EJB classes to the app server, it registers the stub object with the name of the interface.

Try For Yourself

  1. Make sure you have Java 5 installed. Run java -version and check that you get a version number >= 1.5.0. (Internally, Java 5 knows itself as 1.5. Don't ask why...)
  2. Install GlassFish, as described at the beginning of this article.
  3. Download and unzip this example
  4. Change into the directory into which you unzipped it. Edit the file build.properties and supply the location of your GlassFish installation.
  5. Run glassfish/bin/asant run-client

If all is well, you will get a message such as this one:

[java] elvis.entity.Quiz[id=1626,title=A Java Trivia Quiz,questions={IndirectList: not instantiated}]

As you can see, the questions collection is lazy--its values aren't fetched from the database until they are requested.

Change the program so that the elements of the questions collection are printed out as well.

The Ant script of the sample application automates the deploy command. It contains a workaround to a bug in password handling when you invoke the deploy command from Ant.

Spying on the App Server

Browsing JNDI Bindings

Once you deployed the application, you can use the JNDI Browsing module of the app server to verify that the name exists:

.

When you look at the Client class, it is not at all clear how the client finds the naming service. The client uses a bootstrapping mechanism that is a part of the JNDI specification. It locates a file jndi.properties on the class path. That file is contained inside . Unzip the JAR file to find it. The file contains an entry that defines the class responsible for initializing the naming service.
java.naming.factory.initial=com.sun.enterprise.naming.SerialInitContextFactory
By default, the Sun implementation uses a URL of 
java.naming.provider.url=iiop://localhost:1050
to locate the naming service.

Spying on the Database

You will also want to see that the database contains the appropriate tables and the application data. You could use the command-line client of the Derby database, but that's rather hard-core. Fortunately, there are a number of Java-powered GUI clients for databases. One of the prettier ones is SQuirreL.

.

You may have your own favorite, perhaps an Eclipse plugin such as Quantum. Whichever one it is, you need to give it these pieces of information:

These are the GlassFish default settings for the bundled Derby database. It is configured in glassfish/domains/domain1/config/domain.xml. 

Once you have your SQL viewer configured, you can see the tables that the ORM layer produced, as well as their contents. 

Viewing the Logs

So far, I have described the "Happy Day" scenario. All planets are properly aligned, and fortune smiles upon Elvis.

However, not every day is a happy day. More often than not, a program run produces a stack trace such as this one:

[java] Exception in thread "main" javax.ejb.EJBException: nested exception is: java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: 
[java] java.rmi.RemoteException: Transaction aborted; nested exception is: javax.transaction.RollbackException: Transaction marked for rollback.; neste d exception is: 
[java] javax.transaction.RollbackException: Transaction marked for roll back. 
[java] java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: 
[java] java.rmi.RemoteException: Transaction aborted; nested exception is: javax.transaction.RollbackException: Transaction marked for rollback.; neste d exception is: 
[java] javax.transaction.RollbackException: Transaction marked for roll back. 
[java] at com.sun.corba.ee.impl.javax.rmi.CORBA.Util.mapSystemException (Unknown Source) 
[java] at javax.rmi.CORBA.Util.mapSystemException(Util.java:67) 
[java] at com.sun.corba.ee.impl.presentation.rmi.StubInvocationHandlerI mpl.privateInvoke(Unknown Source) 
[java] at com.sun.corba.ee.impl.presentation.rmi.StubInvocationHandlerI mpl.invoke(Unknown Source) 
[java] at com.sun.corba.ee.impl.presentation.rmi.bcel.BCELStubBase.invo ke(Unknown Source) 
[java] at elvis.session.__Test_Remote_DynamicStub.makeQuiz(__Test_Remot e_DynamicStub.java) 
[java] at elvis.session._Test_Wrapper.makeQuiz(elvis.session._Test_Wrap per.java) 
[java] at elvis.client.Client.main(Client.java:13) 
[java] Caused by: java.rmi.RemoteException: Transaction aborted; nested exc eption is: javax.transaction.RollbackException: Transaction marked for rollback. ; nested exception is: 
[java] javax.transaction.RollbackException: Transaction marked for roll back. 
[java] at com.sun.enterprise.iiop.POAProtocolMgr.mapException(POAProtoc olMgr.java:232) 
[java] at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer .java:1252) 
[java] at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBO bjectInvocationHandler.java:195) 
[java] at com.sun.ejb.containers.EJBObjectInvocationHandlerDelegate.inv oke(EJBObjectInvocationHandlerDelegate.java:44) 
[java] at $Proxy104.makeQuiz(Unknown Source) 
[java] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
[java] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAcces sorImpl.java:39) 
[java] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMet hodAccessorImpl.java:25) 
[java] at java.lang.reflect.Method.invoke(Method.java:585) 
[java] at com.sun.corba.ee.impl.presentation.rmi.ReflectiveTie._invoke( Unknown Source) 
[java] at com.sun.corba.ee.impl.protocol.CorbaServerRequestDispatcherIm pl.dispatchToServant(Unknown Source) 
[java] at com.sun.corba.ee.impl.protocol.CorbaServerRequestDispatcherIm pl.dispatch(Unknown Source) 
[java] at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handl eRequestRequest(Unknown Source) 
[java] at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handl eRequest(Unknown Source) 
[java] at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handl eInput(Unknown Source) 
[java] at com.sun.corba.ee.impl.protocol.giopmsgheaders.RequestMessage_ 1_2.callback(Unknown Source) 
[java] at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handl eRequest(Unknown Source) 
[java] at com.sun.corba.ee.impl.transport.SocketOrChannelConnectionImpl .dispatch(Unknown Source) 
[java] at com.sun.corba.ee.impl.transport.SocketOrChannelConnectionImpl .doWork(Unknown Source) 
[java] at com.sun.corba.ee.impl.orbutil.threadpool.ThreadPoolImpl$Worke rThread.run(Unknown Source) 
[java] Caused by: javax.transaction.RollbackException: Transaction marked f or rollback. 
[java] at com.sun.enterprise.distributedtx.J2EETransaction.commit(J2EET ransaction.java:392) 
[java] at com.sun.enterprise.distributedtx.J2EETransactionManagerOpt.co mmit(J2EETransactionManagerOpt.java:355) 
[java] at com.sun.ejb.containers.BaseContainer.completeNewTx(BaseContai ner.java:3633) 
[java] at com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContain er.java:3411) 
[java] at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer .java:1219) 
[java] ... 18 more 
[java] javax.ejb.EJBException: nested exception is: java.rmi.ServerExceptio n: RemoteException occurred in server thread; nested exception is: 
[java] java.rmi.RemoteException: Transaction aborted; nested exception is: javax.transaction.RollbackException: Transaction marked for rollback.; neste d exception is: 
[java] javax.transaction.RollbackException: Transaction marked for roll back. 
[java] at elvis.session._Test_Wrapper.makeQuiz(elvis.session._Test_Wrap per.java) 
[java] at elvis.client.Client.main(Client.java:13) 
[java] Java Result: 1

It is quite impressive to see how hard the application server must be working, but it is also completely useless for solving the problem. The next step is to inspect the logs.

There are two ways of doing this. You can simply view the file glassfish/domains/domain1/logs/server.log. Patient people can find valuable nuggets of information among all the chaff, like this:

.

The app server console offers a slightly more comfortable alternative. Log into http://localhost:4848/asadmin. (Default user name = admin, password = adminadmin).

.

Click on "Search Log Files", then the Search button. The most recent log messages are displayed:

.

Click on the various (details) links for more information.

Elvis hates this part. Can't there be an easier way of telling him what he did wrong?

Comments? Suggestions?

Your name:

Your email address:

Your comment:

To protect against spam robots, please answer this simple math problem:

*