Home > Development > features
Anatomy of an Enterprise Flex RIA Part 12: Building the Java Service Layer
Last time we looked at testing the Java part of the application so far. In this section of the series from Tony Hillerson's Enterprise Application Development with Flex, we'll look at Maven building our service layer, and be introduced to the powerful LiveCycle Data Services assemblers, which deal with the managed data flowing in and out of Flex.
Maven Configuration
Here is the Maven configuration:
<?xml version="1.0"?>
<project ...>
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>bookieProject</artifactId>
<groupId>lcds.examples.bookie</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>lcds.examples.bookie</groupId>
<artifactId>bookie-ui</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>Bookie Example WAR Project</name>
<build>
<finalName>bookie</finalName>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<warSourceDirectory>
${basedir}/src/main/bookie.war
</warSourceDirectory>
</configuration>
</plugin>
...
<dependencies>
<dependency>
<groupId>lcds.examples.bookie</groupId>
<artifactId>bookie-data</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
...
</project>
There’s nothing too new to announce here. First we have to tell Maven that this project has a parent project, and then we have to identify the project. Next, we set the
One extra step I take is to reconfigure the name of the WAR source directory to be bookie.war instead of the default webapp. I think it’s more descriptive.
The dependencies include some files we put directly into our repository, as well as the dependency on the bookie-data project.
Services
Let’s review the use cases we discussed when Iintroduced the project. First, we’ll look at the user use cases. We’re going to define an API (a set of methods that we’ll expose as a simple service for other clients—in this case, Flex) in a simple class called lcds.examples.bookie.service.BookSearchService. As you’ll see as you look at the class, it’s a set of methods that simply invoke the session beans we defined in the preceding section. The benefits of consolidating these calls into one class are as follows:
- This is a good Service-Oriented Architecture (SOA) practice whereby a service ties together classes that support a certain category of business need into one service.
- We get a single point of access to retrieve the important methods of all the beans, which allows for better control over concerns such as role-based security.
- We can define one RemoteObject for Flex to access as a service.
The single point of access is important, because here we can do anything that needs to be done for every remote call. We don’t want to do anything special for our sample, but it’s a good practice to follow.
This service API will get us through most of the use cases for users and allow us to look at how to access Plain Old Java Objects (POJOs) from Flex. We’ll also use a feature of LCDS called an “assembler” to let us take advantage of some of the managed data features and how they can make our lives easier.
Note that I’ve split the implementations of the use cases in this way only to highlight the different ways we can have Flex talk to Java. Depending on the features you need for your application, you may want to stick with RemoteObjects or go whole-hog and access data through a DataService with an assembler.
Table 7 lists our user use cases and where they’re implemented.
| Table 7: User use cases and where they’re implemented | |
|---|---|
| User use case | Service method or assembler |
| Sign In | BookSearchService.findPersonByCardNumber |
| Find Book by Subject | BookSearchService.findBooksBySubject |
| Find Book by Title | BookSearchService.findBooksByTitle |
| Find Book by Author | BookSearchService.findBooksByAuthor |
| Find Author | PersonAssembler |
| Find Subject | SubjectAssembler |
| Browse Titles | BookAssembler |
| Browse Authors | PersonAssembler |
| Browse Subjects | SubjectAssembler |
All of the administrator use cases are taken care of by the assemblers shown in Table 8.
| Table 8: Administrator use cases and where they’re implemented | |
|---|---|
| Administrator use case | Service method or assembler |
| Create, Update, Delete Subjects | SubjectAssembler |
| Create, Update, Delete Books | BookAssembler |
| Create, Update, Delete Authors | PersonAssembler |
| Create, Update, Delete Users | PersonAssembler |
| Check Books Out | BookAssembler |
Figure 15 shows the location of all of the ui project’s Java service classes.
Managing Data with LCDS
Data management is one of the coolest features of LCDS. By using a data service in Flex, you can skip having to write the explicit calls to the backend service. When Flex manages a collection of data, which can be an arbitrarily nested set of objects, any changes made to that collection can be automatically sent to the service. That means any views using managed data collections only have to hook up to the collection. Once the collection is managed, that’s all the work we have to do. We do not have to explicitly read properties into DTOs to send, figure out which properties changed, or watch for changes to know when to save. This is an incredible timesaver for data-driven application development.
But wait, there’s more! Not only can all of one client’s changes be automatically sent to the service, but also changes from other clients sent into the service can be automatically sent to other clients who are using the same managed data sources. As it becomes more critical for your application to avoid stale data issues, which occur when one client saves changes to some set of data that’s also in another client, only to have those changes overwritten by that second client’s next save, this feature becomes very interesting.
Of course, there’s no magic here. LCDS can’t automatically know in all situations that the data from the second save overwriting the first client’s changes represents the correct changes. What LCDS can do, however, is to provide a conflict management framework. As part of a data-sync operation, Flex can receive notification that a certain change message conflicts with data that has already been merged into the managed collection. This lets the Flex developer decide what the application can do automatically, whether it should show the conflicts to the user to let her choose to continue with the change, or which other actions should be taken. This lets an application work well when lots of users are editing the same data, but also allows offline applications to sync up data from a disconnected session using a framework such as Adobe’s AIR. A user could work with a set of data that had previously been downloaded, and then, when a network is available, sync that data with the service. The service, using LCDS, can take the client through the syncing process, letting the user know which data was changed since the last time the user was online.
As the service developer for these types of applications, you’ll hook into different parts of the data management process to provide an implementation of what should happen when data is saved, updated, queried, or deleted. The classes that fit into this part of the application’s life cycle are called assemblers.
Assemblers
LCDS provides an API to the data management system for Java. One way to get the data we want Flex to manage is to use custom assemblers. Assemblers are classes we can write to meet situations in which we may find our managed data, such as new objects added, objects updated, initial selection, and so on.
Assemblers can be simple Java classes that have a method or two which fit a certain part of the data management life cycle, such as querying data (fills) or syncing data. In this case, a POJO provides the implementation and the developer provides LCDS with the configuration to hook up these methods.
We’re going to take the more formal approach of implementing the assembler interface provided by LCDS to provide the data from any of our DAOs to the data management framework. Table 9 lists the methods of the class we’re going to extend, AbstractAssembler, which implements the Assembler interface.
| Table 9: Methods of the AbstractAssembler class | |
|---|---|
| Method signature | Intended use |
| Object getItem(Map identity); | This is for getting one object of a certain type. The map argument should contain key/value pairs of the data needed to uniquely identify the object. |
| List getItems(List identityList); | This is the same as getItem, but for a list of objects. The argument should be a list of maps like those described for getItem. |
| void createItem(Object newItem); | This is called when a new item is added to a managed collection. The object could be typed by LCDS (we’ll see how), or it could be a map of keys and values that should be used to create an object of the assembler’s type. |
| void updateItem(Object newVersion, Object previousVersion, List changes); | This is called when an item is changed on the client. The object or map is the first argument, the object in its previous state is the second argument, and a list of properties changed is the third argument. The third argument may be null to indicate unknown changes. Your assembler is responsible for throwing a flex.data.DataSynchException if it’s determined that any of the changes conflict with the persisted object. |
| void deleteItem(Object item); | This is called if an item is deleted. |
| int count(List countParameters); | This method works the same way as the fill method, but it is meant to return the count of the items matching the parameters. |
| void addItemToFill(List fillParameters, int position, Map identity); | This method is called when a client inserts an item in a certain position in a managed collection. You generally don’t make use of this method if your collections are set to automatically refresh. |
| void removeItemFromFill(List fillParameters, int position, Map identity); | This is the same as refreshFill, but for removing items. |
| boolean autoRefreshFill(List fillParameters); | TYou can use this method to decide whether a certain managed collection, based on parameters, should be refreshed automatically. |
In addition to the methods in Table 9, with configuration you can also set up custom fill methods and, along with them, custom methods to be called each time objects are added, modified, or deleted to let the assembler decide whether the changes should be added to the custom fill.
Let’s look at one of our assemblers, the SubjectAssembler, which is typical of all of our assemblers. We’ll just go method by method:
package lcds.examples.bookie.assemblers;
...
public class SubjectAssembler extends AbstractAssembler {
@Override
public Object getItem(Map identity) {
Integer id = (Integer)identity.get("id");
try {
SubjectDAO dao = getSubjectDAO();
return dao.findById(id);
} catch (Exception e) {
throw new RuntimeException("Unable to get Subject", e);
}
}
...
As I mentioned, we extend AbstractAssembler, which implements Assembler. AbstractAssembler provides default implementations of one or two of the methods mentioned earlier. For some of the others, it does nothing, and for yet others it throws an UnsupportedOperationException to indicate that a method of that type must be overridden. One thing to note is that AbstractAssembler isn’t actually marked abstract, so it can be the implementation class, although there’s not much it can do.
We’ll override getItem, which takes a map of properties that should uniquely identify an object. All we need to uniquely identify a subject is an ID. We just pass that ID into the DAO and return the result. If there’s an exception, we’ll throw a RuntimeException.
...
public Collection getAllSubjects() {
try {
SubjectDAO dao = getSubjectDAO();
return dao.getAll();
} catch (Exception e) {
throw new RuntimeException("Unable to get all Subjects", e);
}
}
public Collection findByName(String name) {
try {
SubjectDAO dao = getSubjectDAO();
return dao.findByName(name);
} catch (Exception e) {
throw new RuntimeException("Unable to get Subjects by name", e);
}
}
...
These next two methods aren’t overridden; they’ll be configured to different fill methods, as we’ll see in a bit. getAllSubjects does what it says, no surprises there. findByName takes a string as an argument and passes that on to the DAO’s findByName method.
public List<ChangeObject> syncSubjects(List<ChangeObject> changes) {
try {
SubjectDAO dao = getSubjectDAO();
Subject subject;
for (ChangeObject change : changes) {
if (change.isCreate() || change.isUpdate()) {
subject = (Subject)change.getNewVersion();
change.setNewVersion(dao.merge(subject));
} else if (change.isDelete()) {
subject = (Subject)change.getPreviousVersion();
if (subject.getId() > 0)
dao.removeDetached(subject);
}
}
return changes;
...
The syncSubjects method is a sync method, which means it’s called when there are changes to a managed collection on the client side in Flex. If your assembler uses this type of method, it’s called instead of the createItem, updateItem, and deleteItem methods. Notice that in the delete case, the syncMethod uses the removeDetached method on the DAO to avoid the problem of the client calling the method with an object that’s not in the entity manager yet.
The method’s arguments are a list of ChangeObjects which identify changes to a specific member of a managed data collection. If the change is a create change or an update change, we’ll merge that object into the entity manager and then set the new version on the change object that goes back to the client. If the change is a delete, the object is stored in the previousVersion property, so we’ll grab that, make sure it’s actually saved (make sure it has an ID), and then use the removeDetached method to delete it.
...
public boolean shouldAddSubjectToAllCollection(Object[] params,
Object item) {
return true;
}
public boolean shouldAddSubjectToNameCollection(Object[] params,
Object item) {
String name = (String) params[0];
Subject subject = (Subject) item;
if (subject.getName().contains(name)) return true;
return false;
}
...
The first method, shouldAddSubjectToAllCollection, should have a question mark after it (too bad Java doesn’t allow that, like Ruby does). Basically, it’s asking “should this new subject be added to the collection of all subjects?”, and of course, the answer is “yes”; because it’s a subject, it belongs to the collection of all subjects.
The next method asks “should this new subject be added to the collection of subjects matched by the name in parameter one?” In that case, we need to check that the subject’s name contains the name and, if so, return true. LCDS manages a collection of subjects for each distinct name searched through the findByName method, and if a new subject is created, it calls this method to see whether it should be added to one of those collections.
Assembler Configuration
Let’s look at the configuration of the LCDS services to see how assemblers are configured. Figure 16 shows the LCDS installation with a description of the important configuration files.
Assembler definitions are in data-management-config.xml. Here’s a look at the SubjectAssembler definition from that file:
<destination id="subjectService" >
<properties>
<metadata>
<identity property="id" />
</metadata>
<source>
lcds.examples.bookie.assemblers.SubjectAssembler
</source>
<scope>application</scope>
<network>
<paging enabled="true" pageSize="10" />
</network>
...
This part of the document tells LCDS how to provide the subject assembler as a data service destination to the Flex service code. The ID of the destination is subjectService; the adapter is the java-dao adapter, which basically means we’re using a Java assembler. The channel is defined in services-config.xml.
The
...
<server>
<fill-method>
<name>getAllSubjects</name>
<ordered>ordered</ordered>
<fill-contains-method>
<name>
shouldAddSubjectToAllCollection
</name>
</fill-contains-method>
</fill-method>
<fill-method>
<name>findByName</name>
<params>java.lang.String</params>
<ordered>ordered</ordered>
<fill-contains-method>
<name>
shouldAddSubjectToNameCollection
</name>
</fill-contains-method>
</fill-method>
<sync-method>
<name>syncSubjects</name>
</sync-method>
</server>
</properties>
</destination>
In the <server> tag is where we keep information about the assembler’s data access methods. a
Our getAllSubjects fill should bring back all subjects, so it doesn’t need arguments. It should be kept ordered. Also, remember the addSubjectToAllCollection method? Here’s where we tell LCDS to use this method for this particular fill to check whether a new subject should be added to the fill.
The findByName fill method is used if there is a single string parameter. It should be kept ordered as well, and the method to choose if a new subject should be added to the fill is addSubjectToNameCollection.
The sync method, syncSubjects, which we just saw, is the one that’s called when there are changes to the managed collection of subjects.
Look at the other assemblers and the configuration files to see what’s going on in there, but note that the configuration and assembler for the subject collections are typical of what you’ll find in the others.
Pushing Data with JMS over RTMP
One thing is left: we want to be able to notify an administrator in real time whenever there’s a new reservation.
There are a few ways to do this type of thing. The obvious way is to have a timer running every few seconds to make a call to a service method to check whether there are new updates. I call this the “poor man’s push” method. If we didn’t have LCDS, we’d be stuck with this type of solution.
Luckily, with LCDS we have a better solution. The HyperText Transfer Protocol (HTTP), the protocol that runs the Web as we know it, creates a channel for communication with a client, and with Version 1.1 that channel can remain “alive” or ready to connect again, but it carries no information about the state of the client, and sends information only in response to a request.
Adobe has a protocol that powers the Flash media server: the Real Time Media Protocol (RTMP). RTMP connections stay open to allow two-way communication and streaming of media and data. LCDS makes use of RTMP to allow data and messages to be sent from the server to the client without prompting from the client.
The Java Messaging Service (JMS) is an API for message-oriented software development. JMS supports two messaging models: point-to-point or queue messaging, and publish/subscribe messaging.
Queue messaging works something like the client/server model that browsers and web servers follow, except that the server, called the producer in this case, can send messages to the client, or consumer.
Publish/subscribe messaging doesn’t have a one-to-one relationship between the producer and consumer. Many subscribers can subscribe to one publisher and all will receive the messages that the publisher sends.
We’re going to use the publish/subscribe model because that model fits so well with the case that we want to support: the server will send a message that some books have been reserved and any administration client can subscribe and get those messages. In JBoss, we can set up a topic, which is something about which messages will be sent, and then we can publish messages to that topic from anywhere.
Here’s how we set up the topic, in bookie-data/service-config/jboss/bookie-message-service.xml, which you need to copy to the JBoss deploy directory:
<?xml version="1.0" encoding="UTF-8" ?>
<server>
<mbean
code="org.jboss.mq.server.jmx.Topic"
name="jboss.mq.destination:service=Topic,
name=outstandingReservationTopic">
<depends
optional-attribute-name="DestinationManager">
jboss.mq:service=DestinationManager
</depends>
</mbean>
</server>
If this application were to be deployed to production, we’d have this configuration file in the deploy directory already, but during development we have the root project’s build.xml copy this file over in the dev-deploy task.
In the ReservationDAOBean, when a reservation is saved, we’ll publish a message with the reservation in it to the topic. Later, we’ll see how Flex can subscribe the admin client to receive these messages and alert the administrator. Look at the message publishing code:
package lcds.examples.bookie.dao.beans;
...
@Stateless
@Local(value={ReservationDAO.class})
public class ReservationDAOBean implements ReservationDAO {
@PersistenceContext
public EntityManager entityManager;
@Resource(mappedName="TopicConnectionFactory")
private ConnectionFactory connectionFactory;
@Resource(mappedName="topic/outstandingReservationTopic")
private Topic outstandingReservationTopic;
First, under the @PersistenceContext, we set up two @Resources, which point to the TopicConnectionFactory, where we get a connection to send messages to the topic, and then a reference to the topic itself.
...
public void persist(Reservation transientInstance) throws Exception {
entityManager.persist(transientInstance);
sendReservationMessage(transientInstance);
}
...
public Reservation merge(Reservation detachedInstance) throws Exception {
Boolean isNew = (detachedInstance.getId() == 0);
Reservation result = entityManager.merge(detachedInstance);
if (isNew) {
sendReservationMessage(result);
}
return result;
}
...
private void sendReservationMessage(Reservation reservation) throws Exception {
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(true, 0);
MessageProducer producer = session.createProducer(outstandingReservationTopic);
ObjectMessage message = session.createObjectMessage();
message.setObject(reservation);
producer.send(message);
connection.close();
}
...
Now, whenever we get a new reservation or a reservation is somehow changed (in the persist or merge method of the ReservationDAO), we should send out a notification. The logic that does that is in the sendReservationMessage method. To do that, we get a connection from the factory, create a session, and get a MessageProducer using the topic that was injected into outstandingReservationTopic.
We then create an “object message,” which means that we intend to send a typed object with the message: the reservation. Then we set the reservation as the object, send the message, and close the connection. It’s fairly straightforward, if not exactly simple. The next thing you know, there’s an alert message in the administrative UI.
Next time we're going to look at the Flex service layer and be introduced to Cairngorm, the micro-architecture for Flex. You can always find the entire series here.




Comments
Leave a comment