Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Discover Seam and Sew Up Your Java Projects Faster than Ever : Page 2

In the tradition of Spring, JBoss offers Seam, which uses a declarative state model, extensive use of annotations, and two-way dependency injection to make automation of huge portions of your complex Java EE apps not just possible, but downright sensible.


advertisement
An Unseamly Experiment Begins
In his book, "Beyond Java," Bruce Tate uses an example in which he builds an application in J2EE in several weeks and then builds the same application in a weekend with Ruby on Rails. My goal in this article was similar: I will show an example of the application that I have been working on for the last 3.5 years—a Web-based car rental application for a major car rental company, refactored from COBOL to J2EE—and then the same functionality written using Seam. And I'm challenging myself to do it in a 48- to 72-hour time frame.

The example application, of course, is not the exact commercial app my team has been working on for so long but rather a scaled down version. However, it uses the general concepts that most layered large J2EE architectures are based on.

This architecture uses a 5+1 layer pattern. Each Layer has certain responsibilities assigned to them. Layered architectures manage dependencies by ensuring that each layer depends only on the layer immediately below it. Upward dependencies are not valid. It is an effective way to manage dependencies, manage the proper assignment of responsibility in a large development team, and decompose the problem set in a common way across the team.



Each layer needs to transfer data and that data tends to look pretty much the same. For this reason I've created a +1 layer that allows all components in all layers to have dependencies against it. This layer contains data files that have no real logic in them (see Figure 3).

Figure 3. Layered Architecture: The 5+1 layered architecture is shown with each layer identified. Components are placed in their layers and dependencies are identified between components.

As you can see in Figure 3, the top layer is the presentation layer, which is based on Struts. The second layer is the application coordination layer and contains the integration with the security system. Session EJBs manage interaction with this layer. This layer also contains the application-specific logic and manages interaction between the business domain entities. The third layer is the business layer and it contains the major domain entities within the enterprise with a set of reusable logic and functionality. The fourth layer manages communication between the rest of the system and its external resources. This layer is based on the DAO pattern and each external resource has one or more DAOs to manage the communication. The fifth layer is the external resources needed. Some of our applications have a single database as its external resource and others use as many as 18 different external resources. We have multiple applications based on this architecture which reuse several components and DAOs.

The database layer comprises four tables: the Reservation, the Location, the Customer and the CarClass. This database schema is used for the Seam application.

The application flow begins with the user associating a Customer record with the Reservation Record. The user then associates a pick-up and drop-off Location Record and the date and time for both with the reservation. A CarClass Record is associated with the Reservation Record. Based on the car class rate and the duration of the reservation, an estimated charge is calculated and put into the reservation table.

Once the reservation entry is persisted and completed it should contain a customer reference, a car class reference, a drop-off and pick-up location, the drop-off date and time, the pick-up date and time and finally the estimated charges.

The Weekend of Seam Arrives
As I sat down to get started on my 48- to 72-hour Seam experiment, I decided to use the Hibernate code generator to create the beginning of my code base. This tool has the option of creating a Seam skeleton application and is part of the JBoss IDE JEMS product. The generated components for each database table are:

  • the Hibernate object containing the actual data from the row in the table
  • a Finder Component
  • an Editor Component
  • a Selector Component
In the case of my Location Table, the Reverse Engineering tool generated two JSF pages, four classes, and two generated interfaces (see Figure 4). I'll walk through the various generated components as an example of what you can expect:
  • Location.java is the Hibernate component
  • LocationFinder.java is an interface for the LocationFinderBean.
  • LocationFinderBean.java is the session bean that contains database lookup code and page flow logic for the finder JSP page.
  • LocationEditor.java is an interface for the LocationEditorBean
  • LocationEditorBean.java is the session bean that contains the logic to create or modify a location and manages page flow for the edit JSP page
  • LocationSelector.java contains an interface and static inner classes that implement the interface allowing multiple rows to be listed and then selected. It also contains logic for screen titles, button labels and text labels.
  • editLocation.jsp and findLocation.jsp are the JSF pages
  • editLocation.jsp allows you to create or update a specific database table row/specific instance of Location.java.
  • findLocation.jsp allows you to provide search criteria, page through a list, and select a specific database table row/ specificinstance of Location.java from the list.


Figure 4. Generating Code: The code generator begins with a basic database table represented by the "Location Table." Once executed against this table I get two JSF pages and Seam code to support the JSF pages. The editLocation.jsp provides a web page that can create or modify a row on the "Location Table." The findLocation.jsp provides a web page that can search for rows in the Location Table, page through those rows, and select one of those rows.
 
Figure 5. Picking a Car: The screen shot shows the user selecting a car class from the Create Reservation page. This launches the findCarClass.jsp page. Click on the "Find" button to execute a search, then click on the "Select" button beside the car class desired.

Once the creation was created, compiled and deployed in JBoss, the application could now search, page, add, delete, and update the Location, CarClass, Customer, and Reservation tables. This took less than an hour to complete.

I didn't like everything. On the Reservation table I had to enter the primary key of the pick-up and drop-off location, the car class, and the customer in order to associate them with the reservation. Also my reservation object used integers rather than objects as references to these classes. I wanted to generate code that would manage that relationship well. I had seen examples where this was generated, therefore I knew it could be done. I decided to use foreign keys in the database.

I created the foreign key to CarClass, generated the code, and was impressed with the results. At that point I had a button attached to my create reservation table that took me to the findCarClass.jsp page. From that page I could search for CarClass objects and select the CarClass I wanted to associate with my Reservation object (see Figure 5). Again this was all accomplished in less than an hour.

At this point I'm thinking all I have to do now is add the three additional foreign keys (one for the customer table and two for the pick-up and drop-off locations) and I'm 95 percent done. That's where I ran into my first snag: After adding my new foreign keys, the generated code wouldn't compile.

As I dug into the errors, I first discovered that because I had a foreign key to the location table for the pick-up and drop-off location, I had duplicate methods on several objects managing the interaction between location and reservation. It seemed like a simple enough error to fix—just comment out the extra methods and everything should work--but going through the flow of the generated code was slow work. After four hours of debugging the code compiled and I called it a night.

The next morning I deployed the code but found that I got an exception every time I added a location to the reservation. By lunch, about four more hours, it was clear that my attempts to improve it were actually making it worse.

I decided to abandon my current approach and try something more agile. First I would remove one of the foreign keys from the location table and generate code that compiled and deployed. It took less than 10 minutes to generate the site and provide the integration of the CarClass, the Customer, and one of the Locations. With just the one location left to integrate, I decided to start in the jsp and add the functionality by hand, but without changing any back end code at this point.

This action resulted in the following JSF code:

<div class="rvgResults"> <h2><h:outputText value="#{msg.Reservation_PickUplocation}"/></h2> <h:outputText value="#{msg.No} #{msg.Reservation_location}" rendered="#{ reservationEditor.instance.pickUpLocation == null}"/> <h:dataTable var="parent" value="#{ reservationEditor.instance.pickUpLocation}" rendered="#{ reservationEditor.instance.pickUpLocation != null}" rowClasses="rvgRowOne,rvgRowTwo"> <h:column> <h:column> <f:facet name="header"><h:outputText value="#{msg.Location_street}"/></f:facet> <h:outputText value="#{parent.street}"/> </h:column> <h:column> . . . <h:column> <f:facet name="header"><h:outputText value="#{msg.Location_closetime}"/></f:facet> <h:outputText value="#{parent.closetime}"/> </h:column> <h:column> <f:facet name="header"><h:outputText value="#{msg.Action}"/></f:facet> <h:commandButton action="#{ reservationEditor.pickUpLocation}" value="#{msg.View} #{msg.Location}"/> </h:column> </h:dataTable> <span class="rvgPage"> <h:commandButton type="submit" value="#{msg.Select} #{msg.Location}" action="#{reservationEditor. selectPickUpLocation}" /> </span> </div>

I had the hooks in the front end to display the pick-up location so now I needed to add methods to the Reservation components to define a pick-up location. I modified Reservation.java so that the pick-up and drop-off location variables were now both of type Location because one of them was int which represented the primary key of the location table. And the getter and setter methods for these variables were modified from int to Location type.

I added methods selectPickUpLocation(), pickUpLocation(), selectDropoffLocation() and dropOffLocation() to ReservationEditor.java, all of which were essentially copies of their original methods selectLocation() and location(). While they were named differently, they still had the same implementation. I modified the JSF code to work with the new methods and deployed successfully.

The new methods in ReservationEditor used the LocationSelector interface and its static inner classes. One inner class helped integrate the Reservation and Location relationship. I made two copies of that inner class and renamed them for use in selecting the pick-up and drop-off locations. I used annotation @Name to provide a way to identify the class when injecting an instance.

@Stateless @Name("reservationPickUpLocationSelector") @LocalBinding(jndiBinding = "com.devx.res.example.ReservationPickUpLocationSelector") @JndiName("com.devx.res.example.ReservationPickUpLocationSelector") @Interceptors(SeamInterceptor.class) public static class ReservationPickUpLocationSelector implements LocationSelector { @Stateless @Name("reservationDropOffLocationSelector") @LocalBinding(jndiBinding = "com.devx.res.example.ReservationDropOffLocationSelector") @JndiName("com.devx.res.example.ReservationDropOffLocationSelector") @Interceptors(SeamInterceptor.class) public static class ReservationDropOffLocationSelector implements LocationSelector {

Experiencing the power of annotations and how Seam uses them is where I began to really find Seam cool! I had to modify the methods I added in the ReservationEditor.java to work with the selection inner classes I had just created. I changed:

@Begin(join = true) public String selectPickUpLocation() { CONVERSATION.getContext().set("locationSelector", Component.getInstance("reservationLocationSelector", true)); return "selectLocation"; } @Begin(join = true) public String selectDropOffLocation() { CONVERSATION.getContext().set("locationSelector", Component.getInstance("reservationLocationSelector", true)); return "selectLocation"; }

to:

@Begin(join = true) public String selectPickUpLocation() { CONVERSATION.getContext().set("locationSelector", Component.getInstance("reservationPickUpLocationSelector", true)); return "selectLocation"; } @Begin(join = true) public String selectDropOffLocation() { CONVERSATION.getContext().set("locationSelector", Component.getInstance("reservationDropOffLocationSelector", true)); return "selectLocation"; }

With the simple change shown above I'm using a completely different instance of locationSelector for the findLocation.jsp and ReservationEditor. The different instance was put into the conversational state context depending on which button I selected on the editReservation.jsp. Whenever ReservationEditor or its client, createReservation.jsp, referenced "locationSelector" they would get the correct instance. The Selector managed things like button labels and page titles so that the jsp page was reused but could identify which location was selected and for what reason.

I had one problem left to solve. The screens titles for the selectLocation.jsp would change based on which button was selected on the createReservation.jsp page. I needed one line in the whole class to change based on whether it is a pick-up, drop-off, or general location. I took the easy route and generated two new classes with a one-line change between the two files. I had to modify the reservation editor to use the two new classes. In ReservationEditor.java, I changed:

@In(value="locationEditor",create = true) private transient LocationEditor locationEditor; public String pickUpLocation() { locationEditor.setNew(false); locationEditor.setInstance(instance.getPickUpLocation()); locationEditor.setDoneOutcome("editReservation"); return "editLocation"; } public String dropOffLocation() { locationEditor.setNew(false); locationEditor.setInstance(instance.getDropOffLocation()); locationEditor.setDoneOutcome("editReservation"); return "editLocation"; }

to:

@In(value="pickUpLocationEditor",create = true) public String pickUpLocation(LocationEditor locationEditor) { locationEditor.setNew(false); locationEditor.setInstance(instance.getPickUpLocation()); locationEditor.setDoneOutcome("editReservation"); return "editLocation"; } @In(value="dropOffLocationEditor",create = true) public String dropOffLocation(LocationEditor locationEditor) { locationEditor.setNew(false); locationEditor.setInstance(instance.getDropOffLocation()); locationEditor.setDoneOutcome("editReservation"); return "editLocation"; }

While Spring has the ability to inject into a method, it isn't recommended. With Seam you can. With that I could inject a specific instance of the location editor that I wanted into the method signature. In the "before" code you can see injection into a class variable; in the "after" code, injection into a variable on the method signature.

By now my Seam tally was up to 16 hours. In that time I was able to generate an application that could CRUD, search, and page through four database tables. It could also manage object-level references from the reservation object to three other objects—one of which held two references. The equivalent portion of the original application was developed in 50 hours with less functionality than my 16-hour investment in Seam.



Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap