This development article is intended for Java and ActionScript developers who want to create applications for LiveCycle Data Services.
Intermediate
You can programmatically create a LiveCycle Data Services application to manage data located in a relational database. The Adobe LiveCycle Data Services Data Management service automates data synchronization between an Adobe Flex client application and a middle tier. You can build applications that provide data synchronization, on-demand data paging, and occasionally connected application services. Additionally, you can manage large collections of data and nested data relationships, such as one-to-one and many-to-one associations.
The application developed in this article manually creates the server-side Java classes. That is, classes such as the Assembler class are created by using a Java IDE and then deployed to the application server hosting LiveCycle Data Services. Instead of manually creating server-side classes, you can use Adobe Modeling technology. When using Adobe Modeling technology, the server-side Java classes are created without the need to manually create them using a Java IDE. The generated Java classes are based on the model. A benefit of using modeling technology is that it reduces the amount of Java programming that you have to do. Likewise, the benefit of manually creating a data management application is you have finer control over the server-side classes. For example, you can use custom Java DAO application logic that exists.
In addition to creating server-side Java classes, you also create application logic for the client application build by using Flash Builder 4. The following illustration provides a visual representation of the server-side Java classes and the client application built using Flash Builder.
Step | Description |
1 | A client application built using Flash Builder uses operations exposed by the Assembler class. For example, the Fill method of the Assembler class returns all data in a specific table. The data can be displayed in a control such as the Data Grid control. (An illustration of this client application is shown after this table.) |
2 | Server-side Java classes are deployed on the application server hosting LiveCycle Data Services. You can manually create the Data Service classes that are deployed to the server hosting Data Services. |
3 | The Java server-side classes perform database operations on a relational database. For example, data can be retrieved and pushed to all client applications. |
The database used in this article is based on the Product table. This table is located in the sample database that is available with LiveCycle Data Services. The Product table consists of the following fields:
- PRODUCT_ID: Specifies a unique identifier value for this item (this field serves as the PK)
- NAME: Specifies the product name
- CATEGORY: Specifies the category in which the product belongs
- PRICE: Stores the price of the item
- IMAGE: Specifies an image file that displays the item
- DESCRIPTION: Specifies a description of the item
- QTY_IN_STOCK: Specifies the number of items that are in stock
The following illustration shows a client application built by using Flash Builder displaying records that are located in the Product table. This development article provides a step by step description of how to create this client application.
To create a LiveCycle Data Service application by manually creating the server-side Java classes, perform the following tasks:
You create the Java server-side classes in a Java IDE such as Eclipse. Add the LiveCycle Data Service JAR files to your project’s class path. Include the flex-messaging-*.jar files that are located in the following directory:
[Install Directory]/lcds/tomcat/webapps/lcds-samples/WEB-INF/lib
where [Install Directory] is the LiveCycle Data Services installation location. The following server-side Java classes are created:
- Product: Represents data located in the Product table
- ProductAssembler: Represents the Assembler class, which is a required LiveCycle Data Service class
- Connection: Represents a connection to the database
- DAOException: Represents an exception that is thrown if a database error occurs
- ProductDAO: Represents application logic that performs database operations.
Note:
All of these classes are created and deployed to the server hosting LiveCycle Data Services in this development article.
The Product class represents the data located in the Product table. The Product class contains a data member for each field in the Product table. The data value of a data member matches the data type of the field. That is, consider the Description field whose data type is VarChar.
The data type of the description data member in the Product class is string, as shown in the following example.
private String description ;
The rest of the Product class consists of setter and getter methods for each data member. The Product class implements
java.io.Serializable
The following code example represents the Product class.
package flex.samples.product; import java.io.Serializable; public class Product implements Serializable { static final long serialVersionUID = 103844514947365244L; private int productId; private String name; private String description; private String image; private String category; private double price; private int qtyInStock; public Product() { } public Product(int productId, String name, String description, String image, String category, double price, int qtyInStock) { this.productId = productId; this.name = name; this.description = description; this.image = image; this.category = category; this.price = price; this.qtyInStock = qtyInStock; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getImage() { return image; } public void setImage(String image) { this.image = image; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public int getProductId() { return productId; } public void setProductId(int productId) { this.productId = productId; } public int getQtyInStock() { return qtyInStock; } public void setQtyInStock(int qtyInStock) { this.qtyInStock = qtyInStock; } }
The ProductAssembler class extends the flex.data.assemblers.AbstractAssembler class and contains methods used by the client application. The assembler class contains at least the fill method. This method implements queries, each of which returns a collection of objects to the client application. If your client application updates objects, your assembler class must include a sync method or individual create, update, and delete methods.
The client application created in the development article invokes the fill method that is located in the ProductAssembler class. Notice that the fill method accepts a java.util.Listinstance as an input parameter. It returns a java.util.Collection that represents the data located in the Product table.
Typically it is not necessary to directly invoke a method located in the Assembler class. However, the fill method is an exception; that is, a client application invokes this method. While you can invoke a create, update, or delete method located in this class, it is not required because LiveCycle Data Services manages the collection of items. That is, you can operate on the items in a collection by performing an update operation. The changes are either automatically committed (if autoCommit=true, the default) or changes are sent to the server when the commit method is invoked.
LiveCycle Data Services tracks the changes to the items in the collection. The changes are committed to the server or rolled back in response to the revertChanges method. This action is dependent on the setting of the autoCommit flag (which is true by default).
The following code example represents the ProductAssembler class.
package flex.samples.product; import java.util.List; import java.util.Map; import java.util.Collection; import flex.data.DataSyncException; import flex.data.assemblers.AbstractAssembler; public class ProductAssembler extends AbstractAssembler { public Collection fill(List fillArgs, PropertySpecifier ps) { try { ProductDAO service = new ProductDAO(); return service.getProducts(); } catch (Exception e) { e.printStackTrace(); } return null; } public Object getItem(Map identity) { ProductDAO service = new ProductDAO(); return service.getProduct(((Integer) identity.get("productId")).intValue()); } public void createItem(Object item) { ProductDAO service = new ProductDAO(); service.create((Product) item); } public void updateItem(Object newVersion, Object prevVersion, List changes) { ProductDAO service = new ProductDAO(); boolean success = service.update((Product) newVersion); if (!success) { int productId = ((Product) newVersion).getProductId(); throw new DataSyncException(service.getProduct(productId), changes); } } public void deleteItem(Object item) { ProductDAO service = new ProductDAO(); boolean success = service.delete((Product) item); if (!success) { int productId = ((Product) item).getProductId(); throw new DataSyncException(service.getProduct(productId), null); } } }
The Connection class represents a connection to the database. This class uses Java classes located in the java.sql.* package. The following code represents the Connection class. Notice that this class is located in a package named flex.samples. An instance of this class is created in the ProductDAO class to create a connection to the database.
package flex.samples; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class ConnectionHelper { private String url; private static ConnectionHelper instance; private ConnectionHelper() { try { Class.forName("org.hsqldb.jdbcDriver"); url = "jdbc:hsqldb:hsql://localhost:9002/flexdemodb"; } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { if (instance == null) { instance = new ConnectionHelper(); } try { return DriverManager.getConnection(instance.url); } catch (SQLException e) { throw e; } } public static void close(Connection connection) { try { if (connection != null) { connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } }
The DAOException class represents an exception that is thrown if an error is encountered. Notice that this class is located in a package named flex.samples. The following Java code represents the DAOException class.
package flex.samples; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class ConnectionHelper { private String url; private static ConnectionHelper instance; private ConnectionHelper() { try { Class.forName("org.hsqldb.jdbcDriver"); url = "jdbc:hsqldb:hsql://localhost:9002/flexdemodb"; } catch (Exception e) { e.printStackTrace(); }
The ProductDAO class contains Java application logic that performs database operations. Methods in the Assembler class invoke methods located in this class to perform the operations. You can use SQL commands in this class to perform database operations. For example, the following Java application logic represents a method named getProducts that retrieves data from the Product table.
public List getProducts() throws DAOException { List list = new ArrayList(); Connection c = null; try { c = ConnectionHelper.getConnection(); Statement s = c.createStatement(); ResultSet rs = s.executeQuery("SELECT * FROM product ORDER BY name"); while (rs.next()) { list.add(new Product(rs.getInt("product_id"), rs.getString("name"), rs.getString("description"), rs.getString("image"), rs.getString("category"), rs.getDouble("price"), rs.getInt("qty_in_stock"))); } } catch (SQLException e) { e.printStackTrace(); throw new DAOException(e); } finally { ConnectionHelper.close(c); } return list;
The ConnectionHelper.getConnection method returns an instance of Connection, which represents a connection to the database. Notice that a SQL statement is used to retrieve all fields from the Product table. The records are ordered by name and the results are placed in a java.util.Listinstance. The fill method located in the ProductAssembler class invokes the getProducts method.
The ProductDAO class contains the following methods:
- getProducts: Returns data located in the Product table.
- getProductsByName: Returns data based on the name of an item.
- getProduct: Returns data based on the unique identifier value of the item.
- create: Creates a record.
- update: Updates an existing record.
- remove: Removes an item from the database.
The following code example represents the ProductDAO class.
package flex.samples.product; import java.util.ArrayList; import java.util.List; import java.sql.*; import flex.samples.ConnectionHelper; import flex.samples.DAOException; public class ProductDAO { public List getProducts() throws DAOException { List list = new ArrayList(); Connection c = null; try { c = ConnectionHelper.getConnection(); Statement s = c.createStatement(); ResultSet rs = s.executeQuery("SELECT * FROM product ORDER BY name"); while (rs.next()) { list.add(new Product(rs.getInt("product_id"), rs.getString("name"), rs.getString("description"), rs.getString("image"), rs.getString("category"), rs.getDouble("price"), rs.getInt("qty_in_stock"))); } } catch (SQLException e) { e.printStackTrace(); throw new DAOException(e); } finally { ConnectionHelper.close(c); } return list; } public List getProductsByName(String name) throws DAOException { List list = new ArrayList(); Connection c = null; try { c = ConnectionHelper.getConnection(); PreparedStatement ps = c.prepareStatement("SELECT * FROM product WHERE UPPER(name) LIKE ? ORDER BY name"); ps.setString(1, "%" + name.toUpperCase() + "%"); ResultSet rs = ps.executeQuery(); while (rs.next()) { list.add(new Product(rs.getInt("product_id"), rs.getString("name"), rs.getString("description"), rs.getString("image"), rs.getString("category"), rs.getDouble("price"), rs.getInt("qty_in_stock"))); } } catch (SQLException e) { e.printStackTrace(); throw new DAOException(e); } finally { ConnectionHelper.close(c); } return list; } public Product getProduct(int productId) throws DAOException { Product product = new Product(); Connection c = null; try { c = ConnectionHelper.getConnection(); PreparedStatement ps = c.prepareStatement("SELECT * FROM product WHERE product_id=?"); ps.setInt(1, productId); ResultSet rs = ps.executeQuery(); if (rs.next()) { product = new Product(); product.setProductId(rs.getInt("product_id")); product.setName(rs.getString("name")); product.setDescription(rs.getString("description")); product.setImage(rs.getString("image")); product.setCategory(rs.getString("category")); product.setPrice(rs.getDouble("price")); product.setQtyInStock(rs.getInt("qty_in_stock")); } } catch (Exception e) { e.printStackTrace(); throw new DAOException(e); } finally { ConnectionHelper.close(c); } return product; } public Product create(Product product) throws DAOException { Connection c = null; PreparedStatement ps = null; try { c = ConnectionHelper.getConnection(); ps = c.prepareStatement("INSERT INTO product (name, description, image, category, price, qty_in_stock) VALUES (?, ?, ?, ?, ?, ?)"); ps.setString(1, product.getName()); ps.setString(2, product.getDescription()); ps.setString(3, product.getImage()); ps.setString(4, product.getCategory()); ps.setDouble(5, product.getPrice()); ps.setInt(6, product.getQtyInStock()); ps.executeUpdate(); Statement s = c.createStatement(); // HSQLDB Syntax to get the identity (company_id) of inserted row ResultSet rs = s.executeQuery("CALL IDENTITY()"); // MySQL Syntax to get the identity (product_id) of inserted row // ResultSet rs = s.executeQuery("SELECT LAST_INSERT_ID()"); rs.next(); // Update the id in the returned object. This is important as // this value must get returned to the client. product.setProductId(rs.getInt(1)); } catch (Exception e) { e.printStackTrace(); throw new DAOException(e); } finally { ConnectionHelper.close(c); } return product; } public boolean update(Product product) throws DAOException { Connection c = null; try { c = ConnectionHelper.getConnection(); PreparedStatement ps = c.prepareStatement("UPDATE product SET name=?, description=?, image=?, category=?, price=?, qty_in_stock=? WHERE product_id=?"); ps.setString(1, product.getName()); ps.setString(2, product.getDescription()); ps.setString(3, product.getImage()); ps.setString(4, product.getCategory()); ps.setDouble(5, product.getPrice()); ps.setInt(6, product.getQtyInStock()); ps.setInt(7, product.getProductId()); return (ps.executeUpdate() == 1); } catch (SQLException e) { e.printStackTrace(); throw new DAOException(e); } finally { ConnectionHelper.close(c); } } public boolean remove(Product product) throws DAOException { Connection c = null; try { c = ConnectionHelper.getConnection(); PreparedStatement ps = c.prepareStatement("DELETE FROM product WHERE product_id=?"); ps.setInt(1, product.getProductId()); int count = ps.executeUpdate(); return (count == 1); } catch (Exception e) { e.printStackTrace(); throw new DAOException(e); } finally { ConnectionHelper.close(c); } } public boolean delete(Product product) throws DAOException { return remove(product); } }
After you create the Java server-side classes, deploy them to the server hosting LiveCycle Data Services. Compile the Java classes in your Java IDE and then deploy the .CLASS files. The deployment location is dependant upon the name of the application. In this development article, it is assumed that the name of the application is lcds-samples. Because the Product.class, ProductAssembler.class, and ProductDAO.class are all part of the flex.samples.product package, place these class files in the following folder:
[Install Directory]\lcds\tomcat\webapps\lcds-samples\WEB-INF\classes\flex\samples\product
Because the Connection.class and DAOException.class belong to the flex.samples package, place these classes in the following folder:
[Install Directory]\lcds\tomcat\webapps\lcds-samples\WEB-INF\classes\flex\samples
To successfully use the Java server-side classes, you configure the data-management-config.xml file. Within this file, you create a Data Management Service destination. A destination is the endpoint that you use to exchange data to provide data distribution and synchronization functionality in your client application. A destination is referenced in the client application.
By default, the data-management-config.xml file is located in the WEB_INF/flex directory of the web application that contains LiveCycle Data Services. The data-management-config.xml file is referenced in the top-level services-config.xml file. The following example shows a destination named inventory. Notice that the source element references flex.samples.product.ProductAssembler. Likewise, the item-class element references flex.samples.product.Product.
<destination id="inventory"> <properties> <source>flex.samples.product.ProductAssembler</source> <scope>application</scope> <item-class>flex.samples.product.Product</item-class> <metadata> <identity property="productId"/> </metadata> <network> <paging enabled="false" pageSize="10"/> </network> </properties> </destination>
Application scope means there is only one instance of the assembler for all client applications. The identity property element maps to a data member in the Product class. When you use data management, all persistent entities must have one or more identity properties that uniquely identify each managed instance.
Paging can improve performance of applications by decreasing query times and reducing the amount of consumer memory. LiveCycle Data Services supports three types of paging: client-to-server paging, server-to-data-source paging, and association paging. In this example, paging is not enabled.
Start the J2EE application server hosting LiveCycle Data Services.
- Click Start, All Programs, Adobe, LiveCycle Data Services ES 3.1, Start LiveCycle Data Services Server.
Run startdb.bat to start the sample database. This file is located in the following directory: [Install directory]lcds3/sampledb.
Create a Flash Builder project that is used to create the client application. This project references the J2EE application server hosting LiveCycle Data Services. That is, when you create the project, select J2EE as the Application Server type and LiveCycle Data Services as the application server. Ensure that the project references the context root where you created the data source. After you create the project, all of the client libraries required to interact with the J2EE application server are automatically added to your project’s class path.
The following MXML code creates a data grid. When the user clicks the Get Data button, the fill method defined in the ProductAssembler class is invoked. Notice that the destination property is assigned inventory, which references the destination defined in the data-management-config.xml file.
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*" backgroundColor="#FFFFFF"> <mx:ArrayCollection id="products"/> <mx:DataService id="ds" destination="inventory"/> <mx:DataGrid dataProvider="{products}" editable="true" width="100%" height="100%"> <mx:columns> <mx:DataGridColumn dataField="name" headerText="Name"/> <mx:DataGridColumn dataField="category" headerText="Category"/> <mx:DataGridColumn dataField="price" headerText="Price"/> <mx:DataGridColumn dataField="image" headerText="Image"/> <mx:DataGridColumn dataField="description" headerText="Description"/> </mx:columns> </mx:DataGrid> <mx:Button label="Get Data" click="ds.fill(products)"/> </mx:Application>
You also have to create a Product ActionScript class that represents the data in the Product table. The Product.as file can be located in the same package as the main MXML file. The following ActionScript class represents the Product.as file.
package { [Managed] [RemoteClass(alias="flex.samples.product.Product")] public class Product { public function Product() { } public var productId:int; public var name:String; public var description:String; public var image:String; public var category:String; public var price:Number; public var qtyInStock:int; } }
Notice that the RemoteClass tag references flex.samples.product.Product. All data members that are defined in the flex.samples.product.Product class are defined in the Product.as class. Also notice that the data types defined in Product.as are consistent with the data members defined in flex.samples.product.Product.
To create a client application by using Flash Builder 4, perform the following steps:
- Start Flash Builder 4 by clicking Start, All Programs, Adobe Flash Builder 4.
- In the Project Name box, specify a name for your project.
- Under Application Type, select Web.
- Specify version 3.5 for the Flex SDK version.
- In the Application Server list, select J2EE.
- Select the Use Remote Access Service check box.
- Select LiveCycle Data Services check box.
- In the Root folder box, specify the root folder value. For example, specify C:\lcds\tomcat\webapps\lcds-samples.
- In the Root URL box, specify the root URL folder value. For example, specify http://localhost:8400/lcds-samples/.
- In the Content root box, specify the Context root value. For example, specify /lcds-samples.
- Accept the Output folder default value.
- Click Finish.
- Create the Product.as class.
- Add the application logic shown at the beginning of this section to the MXML file.