writing mvc framework in cq4 (part 3)

In the first part of this little tutorial I’ve shown three major problems I encountered when building a CQ 4 framework. Two of them were solved in the previous post, leaving the persistence one for today. Once again, what it’s about: let’s assume you’ve got your shiny new project with Java controller and JSP view, the data you’re about to use to create your model are stored in pages as a set of atoms, containers and container lists. The simplest way of accessing them is by using e.g.:

Container pageContent = page.getContent();
ContainerList list = pageContent.getContainerList("myData");
Container content = list.getContainer("Single");
Atom atom = content.getAtom("myAtom");
String data = atom.getString();

Though it’s scary, it works just fine. Though it works, it’s too damn easy to hide an error here. For pages storing multiple containers with many atoms inside, managing such a mapping has proven to be really tricky.

We at Cognifide have been struggling with this one for quite a while. The credit for the solution I am about to present goes mainly to Albert Cenkier who invented an automated mapper from a CQ container to a Java object. In order to build it we did the following assumptions:

  • one CQ container is mapped to one java object (DTO),
  • an atom is mapped to the object field of the same name as atom’s label (case insensitive),
  • the above requires that all labels used are valid Java identifiers - we use camel case names.

The mechanism we came up with uses reflection mechanism to find all the properties of a DTO class. It then tries to fetch atoms with the same labels as the fields from the given container. This approach is called “standard over configuration” - instead of the excessive layer enabling the most flexible and elaborate mapping (that you’re not ever going to use anyway) it introduces a standard solution at the cost of a few restrictions (that you most often abide to already).

Time for more implementation details. Here are some example code listings - not a complete solution, but should be enough to get you started. First - get all setters of a given class:

private static Collection/* <Method> */getSetters(Class clazz) {
  Method[] methods = clazz.getMethods();
  Pattern pattern = Pattern.compile("(set)([A-Z_]\\w+)");
  Collection/* <Method> */setters = new ArrayList/* <Method> */();
 
  for (int i = 0; i < methods.length; i++) {
    String methodName = methods[i].getName();
    if (matcher.matches()) {
      setters.add(methods[i]);
    }
  }
}

Now, to get those from a container and execute the setter:

public static Object mapContainerToObject(Container content, Collection/* <Method> */setters)
    throws Exception {
  Pattern pattern = Pattern.compile("(get|set)([A-Z_]\\w+)");
  Object result = clazz.newInstance();
 
  for (int i = 0; i < setters.size(); i++) {
    Method setter = (Method) setters.get(i);
    Matcher matcher = pattern.matcher(method.getName());
    String propertyName = matcher.group(2);
    propertyName = propertyName.substring(0, 1).toLowerCase()
        + propertyName.substring(1);
 
    if (content.hasElement(propertyName)) {
      String value = content.getAtom(propertyName).getString();
      setter.invoke(result, new Object[] { value });
    }
  }
}

Note - the above doesn’t implement case insensitivity and assumes that every value is a string. The implementation of those complicates the code significantly and I wanted to show the basic idea here. From this point however, it shouldn’t be much of a challenge to implement the following:

  • mapping to any basic data type (string, integer, boolean, floating-point, date, etc.) based on Java field type and pre-defined parsing rules,
  • mapping container label to one specific field (essential for list’s parNum),
  • mapping a container list to a list of objects.

In fact, it is possible to implement a complete hibernate-like solution for Communiqué. Just remember - do not over-complicate. This piece of code lies at the very basis of all your projects. If it’s clean and simple, anything built on it will be so as well.

Leave a Reply