Enterprise Java Beans (EJB)

Practice for Week 8

In this exercise, we will create a very simple EJB that returns a greeting.

In previous lab exercises, we began by creating a "Web Application" project. This exercise is different. You will be creating an "Enterprise Application".

Create an Enterprise Application

In NetBeans, create a new project:

  1. In the "Java EE" category, select the type "Enterprise Application".
  2. Click "Next >".
  3. Enter the project name, "Week8" (without the quotes).
  4. Click "Next >".
  5. Use GlassFish as the Server and allow NetBeans to create EJB and web application modules (i.e., leave the checkboxes checked).
  6. Click "Finish".

You should have three projects appear in NetBeans:

  1. Week8. This project is your complete Enterprise Application. This project is used to create an EAR file that is deployed to your application server. An EAR file combines a WAR file (i.e., you web application) with a JAR file (i.e., your EJB application) into a single file.
  2. Week8-ejb. This project contains your Enterprise JavaBeans.
  3. Week8-war. This project contains your web application (i.e., Servlets, JSP, JSF and backing beans). This project depends on Week8-ejb.

In this exercise, you will be using JavaServer Faces, so you should add that framework to the Week8-war project:

  1. Right click on the "Week8-war" project and select "Properties".
  2. Click on the "Frameworks" category.
  3. Add JavaServer Faces to the project.

Also, remember to add the Java EE 7 API Library to the Week8-war project:

  1. While still in properties, select the "Libraries" category.
  2. Click on "Add Library..." (to add a compile-time library)
  3. Choose the "Java EE 7 API Library" and click "Add Library"
  4. Click OK to close the project properties

Important Tip

Java EE 7 allows you to deploy EJBs using a WAR or an EAR file. In NetBeans, this means that even though your Week8-war is only part of your Enterprise Application, you are still able to run Week8-war directly.

This can result in problems when/if the same EJB is deployed twice. I recommend only deploying or running your application via the Week8 enterprise application.

This means that if you want to run your project, you should NOT run individual JSF (xhtml) files. Instead, you should right click on, and run, the Week8 Enterprise Application. Avoid using the large green "play" button in NetBeans as it may not run the project that you are intending to run.

If you are experiencing problems with NetBeans and/or GlassFish, here are some steps you might try to resolve the problem:

  • Deploy the Enterprise Application again. Right click on the Enterprise Application (e.g., Week8) and then click "Deploy".
  • Undeploy all applications. In the Services tab, locate Servers > GlassFish Server > Applications. Right click on Applications to refresh the list. Select all the Applications with your mouse (hold down the shift key). Then right click and select "Undeploy".
  • Restart GlassFish. If you are seeing error messages about NetBeans being unable to delete JAR files, then you should undeploy all applications and restart GlassFish. To restart GlassFish, in the Services tab locate Servers > GlassFish Server. Then, right click on GlassFish Server and select Restart (or Stop and then Start).

Create a UniqueIdGenerator Helper Class

As with the Week 7 lab exercises, you can use a UniqueIdGenerator class to understand when new objects are created.

Right click on the "Week8-ejb" project and select "New..." and "Other...".

Select the "Java Class" file type in the "Java" category. Name the class UniqueIdGenerator and use the package "au.edu.uts.aip.greeting.domain"

Enter the following Java code:

package au.edu.uts.aip.greeting.domain;

public class UniqueIdGenerator {

    private static int counter = 0;

    public static synchronized int generate() {
        counter++;
        return counter;
    }

}

Create an Enterprise JavaBean

Now you can create your first Enterprise JavaBean.

Right click on the "Week8-ejb" project and select "New..." and "Other...".

An EJB is an ordinary Java class with appropriate annotations or deployment descriptors. We will create an EJB manually.

Select the "Java Class" file type in the "Java" category. Name the class GreetingBean and use the package "au.edu.uts.aip.greeting.domain"

You could have automatically created a bean using the "Session Bean" type in the "Enterprise JavaBeans" category. However, for the purposes of this exercise we will create it manually.

Enter the following Java code:

package au.edu.uts.aip.greeting.domain;

import javax.ejb.*;

@Stateless
public class GreetingBean {

    private int uniqueId = UniqueIdGenerator.generate();

    public int getUniqueId() {
        return uniqueId;
    }

    public String getGreeting() {
        return "Hello, World!";
    }

}

Now, you can create a client for that EJB.

To the Week8-war project, add a new JavaServer Faces file named greeting (i.e., greeting.xhtml):

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h:head>
        <title>Greeting</title>
    </h:head>
    <h:body>
        <p>
            The greeting is: <h:outputText value="#{greetingController.greeting}"/>.
        </p>
        <p>
            The unique id is: <h:outputText value="#{greetingController.uniqueId}"/>.
        </p>
    </h:body>
</html>

In the Week8-war project, also add a new Java class named GreetingController in the package au.edu.uts.aip.greeting.web:

package au.edu.uts.aip.greeting.web;

import au.edu.uts.aip.greeting.domain.*;
import javax.ejb.*;
import javax.enterprise.context.*;
import javax.inject.*;

@Named
@RequestScoped
public class GreetingController {

    @EJB
    private GreetingBean greeting;

    public String getGreeting() {
        return greeting.getGreeting();
    }

    public int getUniqueId() {
        return greeting.getUniqueId();
    }

}

Now, run the Enterprise Application. Do this by right clicking on the Week8 project (NOT Week8-war) and clicking Run.

Change the URL of your browser to http://localhost:8080/Week8-war/faces/greeting.xhtml and check the output.

Reflect

Obviously, we could have achieved the same effect without the complexity of EJBs. What might be some advantages to this complexity? Why might it be useful to have our EJBs and web application in separate projects?

Recalling the the Week 8 lecture, are we accessing the EJB via a "Local" or "Remote" invocation?

Have we created a Session Bean or a Message Driven Bean?

Is the bean Stateful or Stateless? What does this mean?

Why does the Unique Id stay the same when you refresh? What happens if you use a second browser, clear your cookies or launch "incognito mode" to start a new browser session? Why does the Unique Id still stay the same?

How could you "force" GlassFish to create new instances of the Session bean? (i.e., how can you get the UniqueId to change?).

How do things change if you use Singleton beans?

Hint

GlassFish will create additional session beans if the existing beans are "busy".

You can make the beans busy by slowing them down. Slow down the request handling by adding the following code to getGreeting of your GreetingBean:

try {
    Thread.sleep(5000); // wait for 5000 milliseconds (i.e., 5 seconds)
} catch (InterruptedException ie) {
    // do nothing
}

You should make this change and save the file. If you just refresh your browser window, you may receive an exception: "Exception attempting to inject ...". You will need to redeploy the Enterprise Application. Do this by right clicking on Week8 and selecting "Deploy".

Now that your Stateless Session bean runs slower, you can open two tabs at once and refresh both quickly. What does this tell us?

To change the session bean from stateless to singleton, modify your GreetingBean class as follows...

Change this:

@Stateful
public class GreetingBean {

into this:

@Singleton
public class GreetingBean {

(i.e., @Stateful becomes @Singleton)

NetBeans is able to automatically deploy your Enterprise Application to your local GlassFish server.

However, if you want to deploy your application on another server, you would need to use an EAR file.

EAR stands for "Enterprise ARchive".

An EAR file has been built for you by NetBeans.

Open the local filesystem browser (e.g., Windows Explorer, Mac Finder or browse your home directory in the Labs). Find your NetBeansProjects folder and open the Week8 project folder. Inside, you will find another folder called "dist".

There will be a number of files, including an EAR file.

An EAR file is simply a ZIP file that has been renamed. Inside an EAR file is a special file structure.

You should unzip the EAR file (depending on your operating system, you may need to first rename it to a ZIP file) and examine the contents. If there are any JAR (Java ARchive) or WAR (Web ARchive) files, you can also unzip those files (again, JAR and WAR files are simply ZIP files that have been renamed).

Even though NetBeans has automatically created the EAR file, it is possible to create an EAR file manually. You simply need to ensure you have the right files in the correct directory structure. You compress the directories using zip and then rename the archive into EAR.

Reflect

What is the structure of an EAR file? Can you describe the structure in words?

In this exercise, we will create remote interface for the GreetingBean EJB.

If you have added code to slow down your EJB (i.e., Thread.sleep), you should comment that code out. (If you don't comment it out, it might become confusing trying to decide if your code has crashed or if it is just running slow.)

If you made your session bean @Singleton, you should change it back to be @Stateless.

Create a Remote Interface

To your Week8-ejb project, add a new file of type "Java Interface" in the category "Java". Name the file GreetingRemote and use the package au.edu.uts.aip.greeting.domain.

Enter the following code:

package au.edu.uts.aip.greeting.domain;

import javax.ejb.*;

@Remote
public interface GreetingRemote {

    public String getGreeting();
    public int getUniqueId();

}

Now you can modify GreetingBean so that it implements this interface. In other words, change these lines in GreetingBean.java:

@Stateless
public class GreetingBean {

into these lines:

@Stateless
public class GreetingBean implements GreetingRemote {

Before deploying and/or testing the application, there is one more thing we need to do.

By default, a Session bean with no interfaces only has a local, no-interface view.

However, once you have implemented interfaces for the bean, the local no-interface is no longer assumed.

The rules work something like this:

  1. (Default rule) If a bean has no interfaces: the bean exposes a no-interface view only
  2. (Default rule) If a bean has interfaces: the bean exposes local interface views only
  3. If a bean has interfaces and @Remote or @Local is annotated on the bean only: all interfaces are remote or local accordingly
  4. If a bean and its interfaces are explicitly annotated: no default rules apply, only those views that are annotated are exposed

(The exact rules can be found in Section 4.9.7 of the JSR 345 Enterprise JavaBeans 3.2 specification)

In other words, we previously relied on the default no-interface view. However, now we have used a @Remote interface, the default rule does not apply.

If we wish to continue using the EJB in our JavaServer Faces backing bean (i.e., in GreetingController we had: @EJB private GreetingBean greeting), we need to either:

  • Annotate the EJB with @LocalBean, or
  • Create a local business interface and annotate that with @Local, or
  • Modify the Faces backing bean to use GreetingRemote (i.e., @EJB private GreetingRemote greeting).

For now, you can use the first strategy. Annotate your EJB with @LocalBean.

i.e., Change this:

@Stateless
public class GreetingBean implements GreetingRemote {

into this:

@Stateless
@LocalBean
public class GreetingBean implements GreetingRemote {

Save all your files, and redeploy the Week8 project (i.e., Right click on the Week8 project, and click on "Deploy"). Check that your web application still works (it should still work - we aren't using the Remote interface yet).

Creating a Remote Client

Now that you have a remote interface for your EJB, you can use access this bean remotely (i.e., using a network connection to send/receive data).

Create a new project of type "Enterprise Application Client" in the "Java EE" category:

  1. Name the project Week8-app-client. For the project location, I suggest creating it inside the project folder for your Week8 Enterprise Application. i.e., create it in NetBeansProjects/Week8 instead of just NetBeansProjects.
  2. Set the main class to be au.edu.uts.aip.greeting.client.Main and make sure that "Week8" is selected in the "Add to Enterprise Application" drop down list.

Make Week8-ejb a library for Week8-app-client:

  1. Inside the Week8-app-client project, right click on the "Libraries" subfolder and click on "Add Project...".
  2. Expand Week8 and select Week8-ejb.
  3. Click on "Add Project JAR Files" to add the project's jar files to the client application.

Open the main class and enter the following code:

package au.edu.uts.aip.greeting.client;

import au.edu.uts.aip.greeting.domain.*;
import javax.naming.*;

public class Main {

    public static void main(String[] args) throws Exception {
        System.out.println("Starting Week8 Client");
        GreetingRemote remote = InitialContext.doLookup("java:app/Week8-ejb/GreetingBean!au.edu.uts.aip.greeting.domain.GreetingRemote");
        System.out.println(remote.getGreeting());
    }

}

In the above, we have used JNDI to look up an EJB in the same application.

The JNDI name can be understood as follows:

java:app/Week8-ejb/GreetingBean!au.edu.uts.aip.greeting.domain.GreetingRemote
  • java:app/ Lookup within the application namespace
  • /Week8-ejb The module name
  • /GreetingBean The name of the EJB
  • !au.edu.uts.aip.greeting.domain.GreetingRemote The business interface to use (if there were only one interface, this would not be needed)

We could have also used the global name:

java:global/Week8/Week8-ejb/GreetingBean!au.edu.uts.aip.greeting.domain.GreetingRemote

Note that this is slightly different in that the global name uses java:global and also includes the name of the application (/Week8/).

You may have noticed that when you deploy your application, the GlassFish server output shows the global name of every bean you deploy.

See if you can find these global names in the GlassFish server logs (i.e., the output from GlassFish that you see inside NetBeans).

Run the Application Client

Once again, I recommend running this application via the Enterprise Application, rather than attempting to run an individual project.

To do this, right click on your Week8 project (i.e., your Enterprise Application project) and select "Properties". In the "Run" category, change the client module to "Week8-app-client" and then click OK.

Now, to run the application client, you can right click on the Week8 project and click run.

The application will be redeployed and you can see the output of the console application.

You should see the greeting, "Hello, World!" appear in the output of the console application (after the compiler/build information).

Reflect

What happens if you change the JNDI reference to use the name of the local interface? (You can find the name of the local interface by examining the GlassFish server output when your EJB is deployed. It should be shown alongside the name of the remote interface).
It should cause an exception. Why?

You added the entire EJB project as a library for the application client. This could cause problems when deploying the application. This is because the classes for the entire application are included in the project (i.e., all of the domain logic, not just the remote interfaces). Can you think of any way to address this problem? (Note, you don't need to do it - this is just a discussion point).

Using Dependency Injection

Finally, you can replace the JNDI lookup with dependency injection.

When using the application client container, you can use the @EJB annotation on static methods:

@EJB
private static GreetingRemote greeting;

Now, you can change your application client to use this injected bean instead of a JNDI lookup.

(Additional note: the GlassFish 4 application client container does not yet support injection of EJBs using @Inject.)

The GreetingBean EJB that you created was a Stateless Session Bean (SLSB). In this exercise, you will modify the bean to be a Stateful Session Bean (SFSB).

The bean will greet people by name.

You will create a method to set the current name, and then modify getGreeting to use the name that has been set.

  1. Replace the @Stateless annotation of your GreetingBean with @Stateful.
  2. Add a private field:
    private String name;
  3. Add a setter to the Remote interface and to the bean implementation:
    public void setName(String name)
  4. Finally, modify your getGreeting() method so that it will use the name that has been set.

i.e., You should be able to use your Stateful Session Bean, as follows:

remote.setName("Carol");
String greeting = remote.getGreeting();
System.out.println(greeting); // This should display Hello, Carol!

Now, modify your JavaServer Faces code and your application client code to use the Stateful Session Bean.

Reflect

Would the code still work if it was @Stateless? Why? What would happen? When could it cause problems?

Instead of using a stateful bean, we could have eliminated the state by creating following method on our stateless bean:

public String getGreeting(String name) {
  return "Hello, " + name + "!";
}

Would this be "better" or "worse"? What would be some advantages or disadvantages of this approach?

Removing a Stateful Bean

A Stateful bean will automatically be closed by the application server after a timeout. However, we should release a Stateful bean as soon as we are finished with it. To do this, you simply invoke a method that has been annotated with @Remove.

Add the following method to your GreetingBean:

@Remove
public void close() {
  // do nothing - our application doesn't need to do anything when it closes
}

The stateful session bean does not make use of any resources that need to be closed. As such, our close() method does not need to do anything. However, because the method is annotated with @Remove, when we call the method the application server will know that it can discard the bean.

To use this close method from the application client, you will also need to also expose it in the remote interface, GreetingRemote.

Now, modify your client code so that it creates several stateful session beans but does not close all of them.

Hints

NetBeans will redeploy your console application each time you run it. When your application is redeployed, stateful session beans may be lost. So, if you want to experiment with creating and closing stateful session beans, you will need to write one console program that does it all in one go.

It is possible to get around this, but it's not really relevant right now. (If you wanted to be able to run code multiple times, you could instead use your JavaServer Faces project.)

To monitor the creation and removal of session beans, enable EJB monitoring in GlassFish.

First log into the GlassFish Server Administration console. In the "server (Admin Server)" section, you'll find several tabs. Select "Monitor", and in the "Applications" section you can see the monitoring information if it is enabled. However, if it isn't enabled, then click on "Configure Monitoring".

Check monitoring

Then set the "Ejb Container" monitoring level to "HIGH" and click Save.

Enable EJB monitoring

Finally, return back to the "server (Admin Server)" section of the admin console and you will be able to view statistics about created EJBs:

View statistics

Previously, you created "Data Access Objects" and "Data Transfer Objects".

The name "Data Transfer Object" actually refers to an object used for communicating results to remote clients.

i.e., A Data Transfer Object is often used for "transferring" data over a network to a remote client.

The same pattern and name is perfectly appropriate in the way that we have been using it during previous weeks. With a Data Access Object on the same machine, you still "transfer" data to and from the Data Access Object.

For this final activity, you should create a Data Transfer Object, called "PersonName". You will use that Data Transfer Object to pass information to the session bean.

The PersonName should have two private properties (firstName and lastName) and corresponding getters and setters.

Modify your Stateful Session Bean:

Instead of setName accepting a String...

public void setName(String name) {

...modify the setName method so that it accepts a PersonName...

public void setName(PersonName name) {

Modify your JavaServer Faces client and your application client to use the new DTO.

Hints

If you are using PersonName in the remote interface, it needs to implement java.io.Serializable. You may get CORBA exceptions if your PersonName class does not implement Serializable.

Reflect

Why must PersonName implement Serializable?