A working example

In this section, we shall examine Java EE 7 from a sample project management application. The name of the application is XenTracker. It is a web application with EJB, the RESTful web services, and Persistence.

The development version of the XenTracker application, which has minimal styling and user interface enhancement, is shown in the following screenshot:

A working example

Entities

Our web application manages a couple of entities (also known as persistence capable objects), named Project and Task. The entities are mapped to the database table using the JPA annotations. We start with the entities in our Java EE 7 application, because it helps model the business requirements, and the domain of our boundaries. A project has zero or more task entries.

The definition for the Project entity is as follows:

package je7hb.intro.xentracker.entity;

import org.hibernate.validator.constraints.NotEmpty;
import javax.persistence.*;
import javax.validation.constraints.Size;
import java.util.*;

@Entity
public class Project {
  @Id @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "PROJECT_ID") private Integer id;
  
  @NotEmpty @Size(max = 64)
  private String name;
  
  @OneToMany(cascade = CascadeType.ALL, mappedBy = "project", fetch = FetchType.EAGER)
  private List<Task> tasks = new ArrayList<>();
  
  public Project() {/* Required for JPA */}
  public Project(String name) {this.name = name;}
  
  public Integer getId() {return id;}
  public void setId(Integer id) {this.id = id;}
  public String getName() {return name;}
  public void setName(String name) {this.name = name;}
  
  public List<Task> getTasks() {return tasks;}
  public void setTasks(List<Task> tasks) {this.tasks = tasks;}
  
  public boolean addTask(Task task) {
    if (!tasks.contains(task)) {
      Project oldProject = task.getProject();
      if (oldProject != null) {
        removeTask(task);
        }
      tasks.add(task);
      return true;
      } else {return false;}
    }
  
  public boolean removeTask(Task task) {
    if (tasks.contains(task)) {
      tasks.remove(task);
      task.setProject(null);
      return true;
      } else {return false;}
    }
  
  // hashCode(), equals(), toString() omitted
  }

Tip

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you. Alternatively, you can download the code from the author's GitHub account at https://github.com/peterpilgrim.

We observe that the class Project is declared with several annotations. @Entity marks this type as a JPA entity object and it has a default mapping to a database table called PROJECT. The @Id annotation designates the id field as a primary key. The @Column annotation overrides the default object-relational mapping and changes the table column name to PROJECT_ID instead of ID. The @GeneratedValue annotation informs JPA to automatically generate the primary key value for the project from the database connection provider.

The @NotNull annotation and @Size are from Bean Validation and Hibernate Validator frameworks respectively. They provide constraint checking at the source on the Project entity bean and they validate that the project's name field is not null and that the length of the field must be less than or equal to 64 characters. Incidentally, the Java EE 7 application servers will automatically invoke Bean Validation when this entity is inserted into, or updated to, the database.

Lastly, the @OneToMany annotation declares that the Project entity has a one-to-many relationship with another entity Task. Chapter 5, Object-Relational Mapping with JPA, is dedicated fully to the entity relationships, the database cardinalities, and the fetch types.

The definition for the Task entity is as follows:

package je7hb.intro.xentracker.entity;

import org.hibernate.validator.constraints.NotEmpty;
import javax.persistence.*;
import javax.validation.constraints.*;
import java.util.Date;

@Entity
public class Task {
  @Id @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "TASK_ID") private Integer id;

  @NotEmpty @Size(max = 256)
  private String name;

  @Temporal(TemporalType.DATE)
  @Column(name = "TARGET_NAME") @Future
  private Date targetDate;

  private boolean completed;

  @ManyToOne(cascade = CascadeType.ALL)
  @JoinColumn(name = "PROJECT_ID")
  private Project project;

  public Task() {/* Required by JPA */}
  public Task(String name, Date targetDate, boolean completed) {
    this.name = name;
    this.targetDate = targetDate;
    this.completed = completed;
    }

  // getters and setters omitted()
  public Project getProject() {return project;}
  public void setProject(Project project) {
    this.project = project;
    }

  // hashCode(), equals(), toString() omitted
  }

The entity Task represents a task in the business domain. The Task entity has a primary key too, mapped by the @Id annotation, which is also automatically generated on a database insertion. We also override the database column name to TASK_ID.

We declare Bean Validation constraints on the Task entities name field in exactly the same way as we do on Project, except a task has a longer length of string.

The targetDate is the due date for the task, which is optional, meaning that its value can be null. We use Bean Validation's @Future to constrain the target date to any date in the future. Finally, JPA requires us to explicitly define the temporal type whenever we map java.util.Date. In this case, we map targetDate to only SQL date types with the @TemporalType annotation.

A Task entity has a reverse mapping back to Project. In other words, a task is aware of the project that it belongs to; we call this a bi-directional relationship. We declare the project field with the annotation @ManyToOne.

Business logic

In order to be operational, we write business logic for our web application XenTracker. We require methods to create, update, and delete Projects and for Tasks. We also want to retrieve the list of projects and tasks for each project.

We shall use the session EJB for this purpose, because we want the lifecycle of business logic to be available as soon as our application is deployed to a Java EE 7 application server product or web container. EJBs support the transactions by default, they can be pooled by the server, their methods can be declared as asynchronous, and they are normally participants in monitoring with Java Management Extensions (JMX). EJBs are discussed in detail in Chapter 3, Enterprise Java Beans.

First, we add some named queries to one of the entity beans. A named query is a way of storing a persistence query with the domain object. JPA only allows named queries to be declared with the entities.

Let us modify the Project entity as follows:

@NamedQueries({
  @NamedQuery(name = "Project.findAllProjects", query = "select p from Project p order by p.name"), @NamedQuery(name = "Project.findProjectById", query = "select p from Project p where p.id = :id"), @NamedQuery(name = "Project.findTaskById", query = "select t from Task t where t.id = :id"),})
@Entity
public class Project {/* ... */}

The annotation @NameQueries declares a set of @NamedQuery annotations attached to the Project entity bean. Each named query must have a distinct name and a Java Persistence Query Language (JPQL) statement. JPQL can have named parameters, which are denoted with the prefix of a colon character (:id).

In order to define a stateless session EJB, all we need to do is annotate a concrete class with the type @javax.ejb.Stateless. Our ProjectTaskService stateless session EJB is as follows:

package je7hb.intro.xentracker.boundary;
import je7hb.intro.xentracker.entity.*;
import javax.ejb.*;
import javax.persistence.*;
import java.util.List;

@Stateless
public class ProjectTaskService {
  @PersistenceContext(unitName = "XenTracker")
  private EntityManager entityManager;
  
  public void saveProject(Project project) {
    entityManager.persist(project);
    }
  
  public void updateProject(Project project ) {
    Project projectToBeUpdated = entityManager.merge(project);
    entityManager.persist(projectToBeUpdated);
    }
  
  public void removeProject(Project project) {
    Project projectToBeRemoved = entityManager.merge(project);
    entityManager.remove(projectToBeRemoved);
    }
  
  public List<Project> findAllProjects() {
    Query query = entityManager.createNamedQuery("Project.findAllProjects");
    return query.getResultList();
    }
  
  public List<Project> findProjectById(Integer id) {
    Query query = entityManager.createNamedQuery("Project.findProjectById").setParameter("id", id );
    return query.getResultList();
    }
  
  public List<Task> findTaskById(Integer id) {
    Query query = entityManager.createNamedQuery("Project.findTaskById").setParameter("id", id );
    return query.getResultList();
    }
  }

Our session EJB depends upon persistence objects, so we inject an EntityManager object into it with the special annotation @PersistenceContext. The entity manager operation represents a resource connection to the database.

The methods saveProject(), updateProject(), and removeProject() respectively create, update, and delete the project entities from the database. The entity manager operations are covered in Chapter 4, Essential Java Persistence API 3.2. Because of the CascadeType.ALL definitions on the actual entities themselves, the dependent detail entity Task is looked after with the changes on the Project entity. You will learn about the cascade operations in Chapter 5, Object-Relational Mapping with JPA. So do we retrieve data back from the database?

The methods findAllProjects(), findProjectById(), and findTaskById() are so-called finder operations, the R in the acronym CRUD (Create Retrieve Update Delete). Each of the methods accesses a particular named query, which we attach to the Project entity. The findTaskById() method, for example, gets the JPQL command named Project.findTaskById as a query instance. Notice that we can invoke the methods on that instance by chaining, so that the local variable is in fact unnecessary, and just serves as an education artifact.

The ProjectTaskService session has all of the operations to allow users to add, edit, and remove projects, and also to add, update, and remove tasks to and from projects. So now that we have our business logic, we can go forward and add a controller endpoint for web clients.

The service endpoints

Let's dive straight into the Java EE 7 pool and show off a Java-based WebSocket endpoint for our XenTrack application. We shall create a straightforward server-side endpoint that accepts a text message, which simply is the project ID, and returns the results as a JSON.

A WebSocket endpoint

The definition of the class ProjectWebSocketServerEndpoint is as follows:

package je7hb.intro.xentracker.control;
import je7hb.intro.xentracker.boundary.ProjectTaskService;
import je7hb.intro.xentracker.entity.*;

import javax.ejb.*;
import javax.inject.Inject;
import javax.json.Json;
import javax.json.stream.*;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.*;

@ServerEndpoint("/sockets")
@Stateless
public class ProjectWebSocketServerEndpoint {
  static SimpleDateFormat FMT = new SimpleDateFormat("dd-MMM-yyyy");
  @Inject ProjectTaskService service;
  
  @OnMessage
  public String retrieveProjectAndTasks(String message) {
    int projectId = Integer.parseInt(message.trim());
    List<Project> projects = service.findProjectById(projectId);
    StringWriter swriter = new StringWriter();
    
    JsonGeneratorFactory factory = Json.createGeneratorFactory(new HashMap<String, Object>(){{put(JsonGenerator.PRETTY_PRINTING, true);}});
    JsonGenerator generator = factory.createGenerator(swriter);
    
    generator.writeStartArray();
    for (Project project: projects) {
      generator.writeStartObject()
      .write("id", project.getId())
      .write("name", project.getName())
      .writeStartArray("tasks");
      
      for (Task task: project.getTasks()) {
        generator.writeStartObject()
        .write("id", task.getId())
        .write("name", task.getName())
        .write("targetDate", task.getTargetDate() == null ? "" : FMT.format(task.getTargetDate()))
        .write("completed", task.isCompleted())
        .writeEnd();
        }
      generator.writeEnd().writeEnd();
      }
    generator.writeEnd().close();
    
    return swriter.toString();
    }
  }

Although the ProjectWebSocketServerEndpoint class looks complicated, it is actually easy to write. We declare POJO with the @ServerEndpoint annotation, which annotates it as a Java WebSocket endpoint, and it becomes available as a server. The WebSocket clients can interact with this endpoint, by sending text data to a web context defined URL. For example, on my test this is http://localhost:8080/xentracket/sockets. The @ServerEndpoint annotation accepts a URL pattern value.

In Java EE 7 we must also declare ProjectWebSocketServerEndpoint as a stateless EJB with @Stateless in order to inject the ProjectTasksService EJB as a dependency. (This is a consequence of Java for WebSocket 1.0 specification.) Note that we can use @javax.annotation.Inject from CDI.

The unit test ProjectRESTServerEndpointTest running in the IDE is as shown in the following screenshot:

A WebSocket endpoint

Next, we annotate the method retrieveProjectAndTasks() with the WebSocket annotation @OnMessage, which declares this method as the reception point for the incoming requests. There can be only one message per class per web application deployment.

Our method retrieveProjectAndTasks() accepts a text message, which we parse into a project ID integer. We then invoke the ProjectTasksService session to retrieve a list collection of the Project entities. Afterwards, we immediately turn that list into a JSON array output string using a StringWriter as a text buffer, and two new classes from the JSON-P streaming API namely: JsonGeneratorFactory and JsonGenerator.

We instantiate a JsonGeneratorFactory class with the literal Java HashMap trick to set up JSON output that is prettily printed. With the factory, we can write the JSON output using the fluent API JsonGenerator. The method call writeStartArray() starts the output stream for a JSON array. The method call writeStartObject() starts the output stream for a JSON object. We just call the generator's write(String, X) to send the JSON name and value pair. When we finish writing the JSON object and array, we must call writeEnd() to properly close JsonValue.

Finally, once we finish writing the JSON output, we call the generator's close() method. The target java.io.StringWriter now contains the JSON text value. The Java EE 7 WebSocket provider takes the return value and sends that data, the JSON array, to the other WebSocket peer. We shall quickly look at a JAX-RS example.

A RESTful endpoint

JAX-RS 2.0 is the RESTful standard framework for Java EE 7. We shall use JAX-RS to create the beginnings of a web application with a POJO that serves as a RESTful resource. In the interest of space in this book, we only consider the HTTP GET and POST methods. The GET method accepts a project ID and returns a JSON object that represents a project. The POST method accepts a JSON object and creates a new Project with or without dependent Task instances. The new Project instance returns as JSON.

The POJO class ProjectRESTServerEndpoint with a definition of a RESTful endpoint in the class called is as follows:

package je7hb.intro.xentracker.control;
import je7hb.intro.xentracker.boundary.ProjectTaskService;
import je7hb.intro.xentracker.entity.*;

import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.json.*;
import javax.json.stream.*;
import javax.ws.rs.*;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.*;
import static javax.ws.rs.core.MediaType.*;

@Path("/projects")
@Stateless
public class ProjectRESTServerEndpoint {
  static SimpleDateFormat FMT = new SimpleDateFormat("dd-MMM-yyyy");
  static JsonGeneratorFactory jsonGeneratorFactory = Json.createGeneratorFactory();
  
  @Inject ProjectTaskService service;
  
  @GET @Path("/item")
  @Produces(APPLICATION_JSON)
  public String retrieveProject(@PathParam("id") @DefaultValue("0") int projectId ) {
    List<Project> projects = service.findProjectById(projectId);
    StringWriter swriter = new StringWriter();
    JsonGenerator generator = jsonGeneratorFactory.createGenerator(swriter);
    generateProjectsAsJson(generator, projects).close();
    return swriter.toString();
    }
  /* ... */
  }

The ProjectRESTServerEndpoint is also a stateless session EJB and there really is no penalty in modern Java EE 7 products for how heavy a session EJB is. In fact, they are extremely lean; an application server for Java EE 7 can easily handle 10000 beans or more without issues. In the not too distant future, when we have Java EE standard for the cloud environment, the Java EE application server products will handle millions of EJBs and CDI managed beans.

We annotate the ProjectRESTServerEndpoint type with the JAX-RS annotation@Path, which notifies the JAX-RS provider that a POJO is an endpoint at the URL context path of /projects. For the JAX-RS projects, normally we also define an application path root, and it stands in front of the individual endpoints. For instance, on my test server the full URL context is http://localhost:8080/xentracker/rest/projects/item.

You have already seen the injection of the EJB ProjectTaskService, but we now see the @GET annotation from JAX-RS on the method retrieveProject(), which designates the endpoint for the HTTP GET requests from the client.

The @PathParam annotation identifies a parameter in the URL; it actually extracts a key parameter in the URL pattern. In this case, the parameter is called id in the input URL. For example, http://localhost:8080/xentracker/rest/projects/item/1234 maps to the JAX-RS URL pattern /projects/item/{id}. Meanwhile, the @DefaultValue annotation defines a default String value, just in case the client did not specify the parameter, which avoids an error inside the server.

We refactor out the JSON generator code from before and simply call a static method in order to generate the correct output. Finally, the @Produces annotation informs the JAX-RS provider that his RESTful resource endpoint produces JSON.

Let's examine one RESTful endpoint for the HTTP POST request, where we want to insert a new project into the database. The definition method createProject() is as follows:

@POST @Path("/item")
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
public String createProject(JsonObject projectObject)
throws Exception {
  Project project = new Project(projectObject.getString("name"));
  JsonArray tasksArray = projectObject.getJsonArray("tasks");
  if (tasksArray ! = null) {
    for (int j = 0; j<tasksArray.size(); ++j) {
      JsonObject taskObject = tasksArray.getJsonObject(j);
      Task task = new Task(taskObject.getString("name"), (taskObject.containsKey("targetDate") ?
      FMT.parse(taskObject.getString("targetDate")): null), taskObject.getBoolean("completed"));
      project.addTask(task);
      }
    }
  
  service.saveProject(project);
  StringWriter swriter = new StringWriter();
  JsonGenerator generator = jsonGeneratorFactory.createGenerator(swriter);
  writeProjectAsJson(generator, project).close();
  return swriter.toString();
  }

The method is annotated with @POST from JAX-RS. It consumes and produces JSON, so we annotate it with @Consumes and @Produces. JAX-RS knows about JSON-P in the Java EE 7 edition, so our method directly accepts a JsonObject instance. In other words, we get the conversion from a String to a JsonObject object for free!

Unfortunately, we must retrieve individually the name, value, and array pairs from the JSON input, and create our domain objects. There is no substitute for work. Given JsonObject, we build a new Project instance, and optionally the associated Task objects. JsonObject has a number of convenient calls such as getString(), getNumber(), and getBoolean(). Unfortunately, we must convert the formatted target date string and we must deal with the optional JSON tasks array, because it can be null. It is possible to check if the value exists in the JsonObject object by calling containsKey(), since a JsonObject is a type of java.util.Map.

Once we have the Project instance, we save it to the database using the ProjectTaskService boundary service. Afterwards, we use the refactored JSON generator method to write the Project instance to the client.

To complete our brief tour of JAX-RS, we shall add another HTTP GET method to our RESTful endpoint. This time, however, we will make it asynchronous in operation. The home page of our web application XenTracker always executes an AJAX request; whenever it loads in the browser, it queries all of the projects in the database. Let's say 1000 web users are simultaneously accessing the application in a couple of minutes and each user has say an average of 10 projects with an average of 25 tasks between them, how would we scale this query?

With a stateless session EJB such as ProjectRESTServerEndpoint, we can use the new Concurrency Utilities API in Java EE 7 to achieve an asynchronous output. Let us apply it to the method getProjectList() now as follows:

/* ... */
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;

/* ... */
public class ProjectRESTServerEndpoint {
  /* ... */
  @Resource(name = "concurrent/LongRunningTasksExecutor")
  ManagedExecutorService executor;
  
  @GET
  @Path("/list")
  @Produces(MediaType.APPLICATION_JSON)
  public void getProjectList(@Suspended final AsyncResponse asyncResponse) {
    executor.submit(new Runnable() {
      @Override
      public void run() {
        List<Project> projects = service.findAllProjects();
        StringWriter swriter = new StringWriter();
        JsonGenerator generator = jsonGeneratorFactory
        .createGenerator(swriter);
        generateProjectsAsJson(generator, projects)
        .close();
        Response response = Response.ok(swriter.toString()).build();
        asyncResponse.resume(response);
        }
      });
    }
  }

In JAX RS 2.0 we must also add a special class as a method parameter. AsyncResponse is the object context for an asynchronous response. The AsyncResponse object has all of the server-side information to enable the JAX-RS API to send data back to the client from another Java thread from the executor thread pool. Typically, the application retrieves a Concurrent Utility Task (new in Java EE 7, see Appendix D, Java EE 7 Assorted Topics), which is the responding thread, and for long-running operations it is not associated with the main JAX-RS request processing thread. The parameter asyncResource is also annotated with @Suspended in order to suspend the output of the response, because we want to halt the response until the application invokes the long-running task. Inside the task, given an AsyncResponse object, we call the resume method with the JAX-RS response to send back to the client. Note that in this example, because we are using an inner class Runnable, we must set the method parameter's modifier to final.

We take advantage of the Concurrency Utilities API; in particular, we inject a ManagedExecutorService into the EJB with the @Resource annotation. The @Resource annotation is from Common Annotation API such as, @Inject, but it injects the database connection, connectors, and now managed concurrency services. The method getProjectList() is exactly one statement long. It creates a Runnable task, an anonymous inner class, and then submits it to the executor pool running inside the Java EE 7 application server. At some point after the submission, the task is invoked, and it delivers a response to the RESTful client on a separate thread.

Tip

Ramping up on Java concurrency

The best book of multiple threads programming in Java with JVM at the time of writing is Java Concurrency in Practice by Brian Goetz, Oracle's Java language architect. The author strongly recommends this book to learn how to use the Java concurrency features such as synchronization primitives, executors as in Java EE 7, atomic wrappers, futures, and scheduled services.

The Runnable task instantiates a java.ws.rs.core.Response object instance in order to build a generic entity, which is the list of projects generated as JSON. Our most important responsibility is to cause the output to be sent back to the client by resuming an asynchronous response context; we invoke the method AsyncResponse.resume() with the generic response.

This is the end of the worked example tour of some new features of Java EE 7. The full application can be found with this book's source code. There are two variations of the XenTracker application: an initial edition, and a completely refined and finessed business example.

The Entity Control Boundary pattern

This worked example is based on a recent Java EE design pattern called the Entity Control Boundary (ECB) design pattern. It identifies a component part of a system from key perspectives: the entity, the control, and the boundary.

The Entity Control Boundary pattern

The boundary layer of the component represents the separation of the business and presentation code. In our worked example, we placed the ProjectTaskService EJB in the boundary subpackage, because effectively it is the business façade of the application.

The control layer of the component manages the flow of data to the internal data inside the component. We placed ProjectWebSocketServerEndpoint and ProjectRESTServerEndpoint in the control subpackage because these POJOs are manipulating the entities on behalf of the client side. Of course, it is a matter of debate and decent architectural sense whether business logic is placed either on the boundary or on the control layers in the application's architecture.

Finally, the entity layer of the component is reserved for the application's domain objects. We placed the entity beans Project and Task in the entity subpackage, because they are rich domain objects managed by JPA.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.17.18