The PathProxy pattern: Persisting complex associations

PathProxy offers an easier way to persist complex relationships without so many lookup tables

1 2 3 4 5 6 7 Page 3
Page 3 of 7

Service and DAO layers

The application's service and DAO layers are also mostly vanilla flavored, except for the PathProxy stuff. Notice that I've gathered the common operations that can be used on any object into the CommonService interface. That way every persistent object doesn't require its own version of every operation, like the findById() method.

The PathProxyDAO class contains the heart of the logic that manages the PathProxy instances. This class provides us the CRUD capabilities for objects that rely on the PathProxy. Using it, we can add an object to a path, get objects of a type from a path, and remove objects from a path.

The Tree and EntityPath classes

The simple application has only a handful of backing beans. For getting into the PathProxy, the AjaxTreeBean is the most important. The AjaxTreeBean supplies data to the tree on the main page of the application.

As you know, there is a real issue with HTTP, which is that everything has to go over the wire as strings. Don't get me wrong, I love HTTP for what it's given us (simplicity, universal adoption, and a standard protocol). It's just ... well, everything is strings. That includes the paths we are representing in our system.

The AjaxTree used in the sample application was built using concepts explained in a previous JavaWorld article (see "The AjaxComponent strategy for JSF"). It represents every node with a nodeString. It is up to the application builder to decide how the nodeString is marshalled and unmarshalled.

When the AjaxTree is rendering, the first thing it needs to do is output the root nodes of the tree. You can see this in Listing 2.

Listing 2. AjaxTreeBean.getRootNodes()

public List<AjaxTreeNode> getRootNodes(){
        rootNodes = new ArrayList<AjaxTreeNode>();
        rootNodes.add(new AjaxTreeNode(Constant.PEOPLE, false, "People", Constant.PEOPLE));
        Long projectCount = commonService.getCount(Project.class);
        rootNodes.add(new AjaxTreeNode(Constant.PROJECTS, projectCount <= 0, "Projects", Constant.PROJECTS));
        return rootNodes;
    }

Listing 2 shows how the AjaxTreeBean provides the data necessary to render the root nodes of the tree. The AjaxTreeNode constructor's first argument is the NodeString. In the case of the root nodes, it's very simple. Those constants are strings. When someone clicks on the first root node, the AjaxTree will send back the string "people". It then falls to the method getChildNodes() to take that string and return a new list of nodes, representing the children of the people node.

Listing 3. AjaxTreeBean.getChildNodes() - part 1

public List getChildNodes(String nodeString){
        if (log.isInfoEnabled()) { log.info("BEGIN getChildNodes(): " + nodeString); }

        EntityPath path = this.getNewPath().setPathAsString(nodeString); // Get an EntityPath object representing the path

        ArrayList<AjaxTreeNode> children = new ArrayList<AjaxTreeNode>();
        if (path.equals(Constant.PEOPLE)){
            Long devCount = commonService.getCount(Developer.class);
            children.add(new AjaxTreeNode(Developer.class.getName(), devCount <= 0, "Developers"));

            Long managerCount = commonService.getCount(Manager.class);
            children.add(new AjaxTreeNode(Manager.class.getName(), managerCount <= 0, "Managers", "normal", "managers"));
        } else if (path.equals(Developer.class.getName())
                || path.equals(Manager.class.getName()) || path.equals(Project.class.getName())){
            try {
                children.addAll(this.getMoNodeList(commonService.getAll(Class.forName(path.toString()))));
            } catch (Exception e){
                throw new RuntimeException("Unknown type (classname expected): " + path);
            }
        // } else if...

        // Continued in Listing 4 ...

Listing 3 shows the first part of getChildNodes. The first thing we've done is to wrap the nodeString in an EntityPath object. You'll learn more about the EntityPath class in a bit. We've also tested whether the path is Constant.PEOPLE ("people"). If so, we create a couple more nodes, each with their own nodeStrings. The process can then repeat on the newly added nodes.

1 2 3 4 5 6 7 Page 3
Page 3 of 7