Dynamic Value Objects using CMP2 and XML

Java Development News:

Dynamic Value Objects using CMP2 and XML

By Meeraj Kunnumpurath

01 Mar 2002 | TheServerSide.com

Before the advent of EJB 2.0 local references and container managed relations, entity EJBs were predominantly used to model coarse grained domain objects. This was mainly due to the overheads associated with remote communication and to discourage client tier objects from making fine grained access to the enterprise tier. The performance of a coarse grained design was further improved by implementing value objects that encapsulated all the data that was transferred between client tier and enterprise tier. Eventhough this provided an elegant and high performing design, with complex systems with a lot of domain objects, this design led to a plethora of value objects within the system. This also created tight coupling between the enterprise and the client tiers.

Also, in the pre-EJB world, bean providers had to explicitly provide the logic for maintaining the associations between the domain objects. In a situation where there were complex relations between the domain objects, the design of value objects became very complicated.

The advent of container managed relations and local references has opened exciting new avenues in enterprise application development using EJBs. In this article I will take you through a powerful way of using EJB 2.0 in conjunction with bean introspection and JAXP to create dynamic XML based data structures that can be transferred between your enterprise tier and presentation tier. The use of XML for transferring data from the enterprise tier to the client tier helps you implement loose coupling between the various tiers in your applications; however, as and when you add new domain objects to your entity model, you may need to add classes responsible for creating new DOM structures for the added entities. In this article we will develop a framework that can dynamically traverse the container managed persistent and related fields of a given local EJB and create an XML document that can be transferred between the various tiers in the application. This method will provide the following advantages:

  • Promotion of loose coupling between the enterprise and client tiers

  • Easier to manage relations between domain objects

  • Removal of complex value objects from the system

  • Since the XML is generated by dynamically traversing the CMP and CMR fields, the bean providers need not create new classes responsible for creating new DOM structures when they add new objects to their domain model

EJB 2.0 local references promote fine grained access of bean components; beans with local references can be involved in relations with other beans that are managed by the container. For example, in a helpdesk system, a UserEJB may have a one-to-many bi-directional relationship with a ServiceRequestEJB which in turn may have a one-to-one unidirectional relationship with a ProductEJB and a one-to-many bi-directional relationship with a ServiceRequestHistoryEJB. The UserEJB may also have a one-to-many bi-directional relationship with a PhoneEJB. Thus, using EJB 2.0 local references and container managed relations, you can design a complex set of related entities. The container managed persistent and relationed fields are defined using abstract accessor methods in the bean class. The accessor methods for relationed fields return either a collection or the local interface of the bean to which the relation is defined, depending on the cardinality of the relation. These accessor methods may be exposed through the local interfaces of the bean components. An in-depth coverage of EJB 2.0 is beyond the scope of this article. Please refer to the EJB 2.0 specification for an exhaustive coverage.

One efficient design pattern would be to expose the coarse grained use cases of your applications through facade components and disallow direct access to your entity components from the client tier. This can be achieved by implementing your use case facades as remote session bean components that interact with the complex hierarchy of collaborating local entities to provide the business services. Going back to our helpdesk example, one use case would be to get the details of a given user. The facade component may find the required User entity component, and navigate through the container managed persistent and relationship fields to get the required data and return it to the presentation tier.

An obvious choice for the data transfer object is plain Java beans. A UserBean may have properties representing the persistent and relationship fields. The types of relationship fields may be either java.util.Collection or other bean components depending on the cardinality of the relations. The UserBean may have collections of ServiceRequestBean and PhoneBean. A ServiceRequestBean may have a ProductBean and a collection of ServiceRequestHistory beans etc. In addition, these beans may also have simple String or primitive properties representing the container managed persistent fields. The main disadvantage of this choice is that the more complex your entity model, the more complicated your data transfer object bean hierarchy, which will also create tight coupling between your service (enterprise) tier and the consumer (presentation) tier. Looking at the complex hierarchy of the relations, a better choice is to use XML DOM objects as data transfer objects. Your facade components will produce objects of type oeg.w3c.dom.Document and your presentation components will consume them using XML aware JSP custom tags or XSLT files.

Now the next question is: how will we create the XML documents from the CMP local bean references? We may use a factory based approach for creating different types of DOM components; however, this will create a plethora of factory components and you may need to add new factory components as your entity model expands. What we need is a utility that can introspect entity local objects, navigate container managed relations and create dynamic DOM structures. This utility should take care of circular references in bi-directional relationships to avoid infinite recursion and should also have configurable drill-down depth in navigating relationship elements.

The next section shows a utility class that can provide this functionality.

package ws.business.service.util;

Import the JAXP classes.

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;

Import the DOM classes.

import org.w3c.dom.Document;
import org.w3c.dom.Element;

Import the EJB classes.

import javax.ejb.EJBLocalObject;

Import the Collection classes.

import java.util.Collection;
import java.util.ArrayList;
import java.util.Iterator;

Import the bean introspection classes.

import java.beans.Introspector;
import java.beans.BeanInfo;
import java.beans.PropertyDescriptor;
import java.beans.IntrospectionException;

Import the reflection classes:

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
public class DOMGenerator {

The document that is generated:

    private Document doc;

Drill-down depth defined by the user:

    private int drillDownDepth;

A private instance variable to store circular references:

    private ArrayList _circularRef = new ArrayList();

A private instance variable to store the current depth of the drilldown:

    private int _currentDepth;

The methods getEJBLocalHome, getLocalHandle, getClass and getPrimaryKey need not be processed.

    private static String RESERVED =
    "EJBLocalHome^localHandle^class^primaryKey";

    public DOMGenerator(String docElementName, int drillDownDepth) {

        try {

Use standard JAXP calls to create a DOM and store it as an instance variable.

            DocumentBuilderFactory fact =
            DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = fact.newDocumentBuilder();
            doc = builder.newDocument();
        }catch(ParserConfigurationException ex) {
            throw new RuntimeChainedException(ex);
        }

Store the drilldown depth.

        this.drillDownDepth = drillDownDepth;

Create the document element by the name defined by the user.

        doc.appendChild(doc.createElement(docElementName));

    }

This method passes the local reference for which the DOM is to be created.

    public Document getDOM(EJBLocalObject local) {

        try {

Call the method to populate the element with container managed persistent and relationship fields for the passed local reference.

            populateElement(doc.getDocumentElement(), local);

Return the DOM.

            return doc;
        }catch(IntrospectionException ex) {
            throw new RuntimeException(ex.getMessage());
        }catch(IllegalAccessException ex) {
            throw new RuntimeException(ex.getMessage());
        }catch(InvocationTargetException ex) {
            throw new RuntimeException(ex.getMessage());
        }

    }

    private void populateElement(Element parent, EJBLocalObject local) throws
    IntrospectionException, IllegalAccessException, InvocationTargetException
    {

Add the current local reference to the list of linked references.

        _circularRef.add(local);

Increment the current drilldown depth.

        _currentDepth++;

Get the bean information for the current local references.

        BeanInfo info = Introspector.getBeanInfo(local.getClass());

Get the list of property descriptors and iterate through the array.

        PropertyDescriptor properties[] = info.getPropertyDescriptors();

        for(int i = 0;i < properties.length;i++) {

Get the property read method.

            Method propReadMethod = properties[i].getReadMethod();

Get the property name.

            String propName = properties[i].getName();

Get the property value using reflection.

            Object prop = propReadMethod.invoke(local, null);

Skip the reserved properties.

            if(RESERVED.indexOf(propName) >= 0)
     continue;

            try {

Try casting the property to an EJB local reference. Please note that you can't use instanceof because the implementation may vary from container to container.

                EJBLocalObject locProp = (EJBLocalObject)prop;

If the local reference is already available in the list of link references or the current drilldown depth is greater than the set drilldown depth, skip processing.

                if(isCircularRef(locProp) || _currentDepth >= drillDownDepth)
                    continue;

Create an element by the property.

                Element child = doc.createElement(propName);

Recursively populate the newly created element with the persistent and the relationship fields of the child local reference.

                populateElement(child, locProp);

Add the newly created child to the parent.

                parent.appendChild(child);

            }catch(ClassCastException ex1) {

If a ClassCastException is thrown, try casting the property to a collection of local references. Please note that you can't use instanceof because the implementation may vary from container to container.

                try {

                    Collection colProp = (Collection)prop;

If the current drilldown depth is greater than the set drilldown depth, skip processing.

                    if(_currentDepth >= drillDownDepth) continue;

Create a child element that will encapsulate the collection.

                    Element child = doc.createElement(propName);
                    Iterator it = colProp.iterator();

                    while(it.hasNext()) {

Get each local reference in the collection.

                        EJBLocalObject locProp = (EJBLocalObject)it.next();

If the local reference is already available in the list of link references or the current drilldown depth is greater than the set drilldown depth, skip processing.

                        if(isCircularRef(locProp)) continue;

Create an element that will contain the persistent and relationship information for the current local reference in the collection, add it to the element representing the collection and populate it.

                        Element grandChild = doc.createElement(propName +
                        "-child");
                        child.appendChild(grandChild);

                        populateElement(grandChild, locProp);

                    }

Populate the parent with the child.

                    parent.appendChild(child);

                }catch(ClassCastException ex2)  {

If a ClassCastException is thrown, the property is a persistent field and add its value as an attribute to the current node.

                    parent.setAttribute(propName, prop.toString());
                }

            }

        }

Remove the current local reference from the list to track circular references.

        _circularRef.remove(local);

Decrement the current drilldown depth.

        _currentDepth--;

    }

This is a utility method to check whether a reference is already there in the list of linked references.

    private boolean isCircularRef(EJBLocalObject local) {

        Iterator it = _circularRef.iterator();
        while(it.hasNext())
            if(local.isIdentical((EJBLocalObject)it.next())) return true;
                return false;

    }

}

The snippet below shows how to use this class.

User local = userHome.findByPrimaryKey("1");
return new DOMGenerator("user", 4).getDOM(local);

The generated DOM may look as shown below depending on your entity model:

<?xml version="1.0" encoding="UTF-8"?>
<user firstName="Meeraj" id="1" lastName="Kunnumpurath">

    <requests>
        <requests-child description="Outlook not working" id="2" status="P">
            <product description="Install Laptop" id="1" name="SVC01"/>
        </requests-child>
        <requests-child description="PC not booting" id="1" status="O">
            <histories>
                <histories-child description="Informtion requested" id="2"
                    loggedAt="2000-12-12 12:12:12.0"/>
                <histories-child description="Request logged" id="1"
                    loggedAt="2000-12-12 00:00:00.0"/>
            </histories>
            <product description="Install Laptop" id="1" name="SVC01"/>
        </requests-child>
    </requests>

    <phones>
        <phones-child id="2" number="0771 8210586" type="M"/>
        <phones-child id="1" number="01908 251575" type="W"/>
    </phones>

</user>

In this article we have seen an elegant and flexible model for transferring complex data between the enterprise and presentation tiers of a J2EE application. The presentation tier can render the XML data that is generated by the enterprise tier on to the client devices using XSLT and/or XML aware JSP custom tags.

When you add new definitions to your domain model, the only thing you need to do is define those definitions using fine grained local entity beans and define associations using container managed relations. The DOM generator provides a generic way of navigating those relations to generate dynamic XML documents.

However, as with any other design solutions, this approach also has its weaknesses and shortcomings. One obvious aspect developers often worry about regarding XML based value objects is performance. On a Windows ME machine with 1GHz and 256MB, running both the app server (WLS 6.1) and data server (Adaptive SQL Anywhere), the above operation took less than 0.5 seconds for a record set of a thousand, with complex relationships.

A second obvious aspect is type safety. Since XML provides the highest level of data abstraction, the node values for attributes and text nodes are always treated as string literals. If you intend to perform further business operations on the value objects that are retrieved from the enterprise tier, it is better to adopt a value object pattern that uses Java bean components. However, XML schemas provide a powerful mechanism for imposing type safety on XML documents. Also, in the approach I have explained in this document, most of the business logic involving an entity and its dependent entities will be implemented in the entity itself and those related to independent entities will be implemented in a session facade. In either case, you use the local entity objects as your domain objects.

One of the biggest shortcomings I can see with this approach is that there is no well-defined technique for modeling XML based data. I haven't come across any UML notation or popular stereotypes for modeling domain object relations using XML.