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 4
Page 4 of 7

Getting children on a path

In the case of Managers, Developers, and Projects, the logic is simple: grab all the instances of each type and wrap them in AjaxTreeNodes. Things get more interesting when we start dealing with the pathing. We want to show the managers who are on the project, and also the developers working for each manager on each project. You can see this in Listing 4.

Listing 4. AjaxTreeBean.getChildNodes() - part 2

// ... Continued from Listing 3
  } else if (path.getSize(true) > 0){

            Project clickedProject = (Project)path.getEntity(0); // Get the project, which is the first object in the path

            // In this system, only a project instance is alone in a path
            if (path.getSize(true) == 1) { // A project instance
                if (path.getPathAsString().indexOf("managersOnProject") == -1) {
                    if (log.isTraceEnabled()) { log.trace("clickedProject: " + clickedProject); }
                    int manOnProjectCount = clickedProject.getManagers().size();
                    if (log.isTraceEnabled()) { log.trace("manOnProject: " + manOnProjectCount); }

                    PathHeader header = new PathHeader("Managers On Project");

                    children.add(new AjaxTreeNode(nodeString + EntityPath.NODE_STRING_SEPARATOR + EntityPath.getObjectAsNodeString(header),
                            manOnProjectCount <= 0, header.getLabel()));
                }
            } else if (path.getSize(true) == 2) {
                if (log.isTraceEnabled()) { log.trace("Opening managersOnProject node."); }
                for (Manager manager : clickedProject.getManagers()){
                    String manOnProjectNodeString = nodeString + EntityPath.NODE_STRING_SEPARATOR + EntityPath.getObjectAsNodeString(manager);
                    // Notice we just add the new child node (manager) to the path to get the child developers, to determine the leafs status of the node
                    List<Object> projectManagerDev = pathProxyService.getPathChildren(path.addChild(manager), Developer.class.getName());
                    children.add(new AjaxTreeNode(manOnProjectNodeString, projectManagerDev.size() <= 0,
                            manager.getFirstName(), Manager.class.getName(), manager));
                }

            } else if (path.getSize(true) == 3) { // project->header->manager
                List<Object> projectManagerDev = pathProxyService.getPathChildren(path, Developer.class.getName());
                for (Object dev : projectManagerDev){
                    Developer developer = (Developer)dev;
                    String devNodeString = nodeString + EntityPath.NODE_STRING_SEPARATOR + EntityPath.getObjectAsNodeString(developer);
                    List<Object> projectManagerDevTask = pathProxyService.getPathChildren(path.addChild(developer), Task.class.getName());
                    children.add(new AjaxTreeNode(devNodeString, projectManagerDevTask.size() <= 0,
                            developer.getFirstName(), Developer.class.getName(), developer));
                }
            } else if (path.getSize(true) == 4){ // Again, we know a three node path is project->manager->developer because the system is so simple
                List<Object> tasksOnDev = pathProxyService.getPathChildren(path, Task.class.getName());
                children.addAll(this.getMoNodeList(tasksOnDev));
            }
        } else {
            throw new IllegalArgumentException("Unknown node type: " + nodeString);
        }
        if (log.isInfoEnabled()) { log.info("RETURNING children: " + children); }
        return children;
    }

Listing 4 continues the handling of the nodeStrings. It operates by checking how many nodes are in the nodeString. This is a very simple strategy for figuring out what node has been clicked on. In a more complex system, you'd need more complex rules. This method is very clean and neat; the challenge is to keep the logic that way as your relationships grow more complex and involved. A couple of tips:

  • Use the nodeString to keep your logic neat, you can define it however you want.
  • Push complexity into the EntityPath class. The way I'm doing it here is a great template, but I encourage you to find strategies that suit your situation.

After determining what node has been clicked on, the method delegates to the service layer to get the children. You'll notice that when we need to get the developers that are assigned to a manager on a particular project, we make a call to PathProxyService. We'll take a look at that class in just a moment.

The EntityPath class

You might also have noticed the class EntityPath in Listing 4. This class basically encapsulates a path in the system, which has a number of advantages:

  • It holds all the logic for manipulating paths (like adding a child in Listing 4).
  • It allows you to pass the object around the layers of your app without leaking any information.
  • In a large system, you would probably want to hide all information regarding the string representation of paths inside the EntityPath.
  • Anytime you want a string path, you get it from the EntityPath.

Note that the EntityPath implementation here does not do everything listed above, but it does show you the role it can play in your persistence architecture.

A quick note about how we're getting instances of EntityPath in Listing 4. This technique is called Lookup method injection in Spring terminology (check out section 3.3.4.1 of the Spring docs). It's a little funky, but it suits our purposes here.

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