Wednesday, December 29, 2010

Dynamic forms, LazyList and transparent items removal


Browsing internet you can easily find a common method of implementing dynamic forms in JEE world. This is usually done using LazyList from apache commons-collections, or AutoPopulatingList from Spring. I don't want to repeat these descriptions, you can find nice examples here (using LazyList) or here (using AutoPopulatingList).

In a nutshell, you can implement such list in your object representing form and dynamically add items using javascript:

public class MyFormObject {
  private List elements = 
    LazyList.decorate(new ArrayList(),
      FactoryUtils.instantiateFactory(MyElement.class));
}

Then you can create the form:

<input name="myFormObject.elements[0].property" />
<input name="myFormObject.elements[1].property" />

And in JavaScript add dynamically new elements, for example:

<input name="myFormObject.elements[0].property" />
<input name="myFormObject.elements[1].property" />
<input name="myFormObject.elements[2].property" /> <!-- dynamically added element--!>
<input name="myFormObject.elements[3].property" /> <!-- dynamically added element--!>

After submitting this form, using whatever Ultimate Binding JEE Engine your're using, you will have new items auto populated into your list.

This example shows LazyList usage, but looking in the AutoPopulatingList source it seems that Spring guys chose approach, what I can call "we want to have LazyList but let's rewrite it from the scratch in the same way using different name".

The problem with items removal

Anyway, both implementations don't assume that items could be removed by the user. For example, if you have an empty form, and then:

In step 1 user is adding two fields:

<input name="myFormObject.elements[0].property" />
<input name="myFormObject.elements[1].property" />

In step 2 user is removing first (index=0) field:

<input name="myFormObject.elements[1].property" />

We have situation in which our automatic list, after first call to (index=1) element is populating both (index=0) and (index=1) elements to assure that we have list with requested size.

The solution

There are many solutions for this problem, from reindexing forms in js before submitting (a hard one, having for example 3 levels of nested dynamic forms in single form) or playing around collections on server side. It is the same as solving any other programmatic problem. But here I'd like to present the most simple working solution I figured out.

It's based on two observations. First is that our lists are filled first by null-s, and after that the factory creates items of our type. In the beginning unused elements are just null-s.

Second one is about what we usually are doing next with the list. The most frequently we are using iterator to access list items to do something with them:

1) by explicit iteration using iterator:

for (Iterator i=collection.iterator(); i.hasNext(); ) {}

2) by implicit iteration using iterator:

for (Object o: collection) {}

3) or, for example, using <c:foreach> jsp tag:

<c:foreach items="${collection}" var="item">
 ...
</c:foreach>

4) and even doing:

myOtherCollection.addAll(collection);

Each of these methods use the same collection iterator. Thus, because this is common way to use collections after binding, the most simple idea I had is to clean each collection before access it with iterator. It's very simple to achieve with LazyList, and I believe that it's simple with AutoPopulatingList as well.

The implementation

First we will write our own list decorator class:

public class ShrinkableLazyList extends LazyList {

  protected ShrinkableLazyList(List list, Factory factory) {
    super(list, factory);
  }

  /**
  * Decorates list with shrinkable lazy list.
  */
  public static List decorate(List list, Factory factory) {
    return new ShrinkableLazyList(list, factory);
  }

  public void shrink() {
    for (Iterator i=getList().iterator(); i.hasNext();)
      if (i.next()==null)
        i.remove();
  }

  @Override
  public Iterator iterator() {
    shrink();
    return super.iterator();
  }

}

Then we just need to have a little change in our original code:

public class MyFormObject {
  private List elements = 
    ShrinkableLazyList.decorate(new ArrayList(),
       FactoryUtils.instantiateFactory(MyElement.class));
}

Using this solution I refactored existing code very quickly and got working collection items removal instantly.

[EDIT] If you like to check how to apply the dynamic form binding to any object (even for Hibernate entities) check my another post.

Tuesday, December 14, 2010

SaxParser example

Today I've had to analyse pretty big XML file, that I couldn't open in any editor without a hassle. Finally I've used comprehensive vim editor, which can handle anything without any problems to view it, but it was useless anyway, because the XML was completely not in human readable format. Then I've been looking for some solution to format it or just to display its structure and I've found this class from Oracle.

This is nice SaxParser example, but it has one drawback. It's completely useless for analysis, because it just prints the XML human unreadable as well. But after a little change we can have a better tool to display formatted data, writing the tree structure with values to the output:

import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Stack;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class Echo01 extends DefaultHandler {
 static private Writer out;
 
 public Stack stack = new Stack();

 public static void main(String[] argv) {
  if (argv.length != 1) {
   System.err.println("Usage: cmd filename");
   System.exit(1);
  }

  // Use an instance of ourselves as the SAX event handler
  DefaultHandler handler = new Echo01();

  // Use the default (non-validating) parser
  SAXParserFactory factory = SAXParserFactory.newInstance();

  try {
   // Set up output stream
   out = new OutputStreamWriter(System.out, "UTF8");

   // Parse the input
   SAXParser saxParser = factory.newSAXParser();
   saxParser.parse(new File(argv[0]), handler);
  } catch (Throwable t) {
   t.printStackTrace();
  }

  System.exit(0);
 }

 public void startDocument() throws SAXException {
 }

 public void endDocument() throws SAXException {
  try {
   nl();
   out.flush();
  } catch (IOException e) {
   throw new SAXException("I/O error", e);
  }
 }

 public void startElement(String namespaceURI, String sName, String qName, 
    Attributes attrs) throws SAXException {
  stack.push(sName);

  nl();
  for (int i = 0; i < stack.size(); i++)
   emit("  ");

  StringBuilder sb = new StringBuilder();
        for (int i = 0; i < attrs.getLength(); i++) {
            String aName = attrs.getLocalName(i); // Attr name 

            if ("".equals(aName)) {
                aName = attrs.getQName(i);
            }

            sb.append(aName + "=\"" + attrs.getValue(i) + "\"");
        }
        
  emit(qName+" ["+sb.toString()+"]: ");
 }

 public void endElement(String namespaceURI, String sName, String qName) 
 throws SAXException {
  stack.pop();
 }

 public void characters(char[] buf, int offset, int len) throws SAXException {
  String s = new String(buf, offset, len);
  emit(s);
 }

 private void emit(String s) throws SAXException {
  try {
   out.write(s);
   out.flush();
  } catch (IOException e) {
   throw new SAXException("I/O error", e);
  }
 }

 private void nl() throws SAXException {
  String lineEnd = System.getProperty("line.separator");

  try {
   out.write(lineEnd);
  } catch (IOException e) {
   throw new SAXException("I/O error", e);
  }
 }
}

Monday, November 22, 2010

Using Eclipse with multi level Maven project structure

Yesterday I've been struggling with setting up Eclipse to work with some project having multi level Maven project structure. Eclipse uses its own flat project layout and isn't able to handle this properly. I've tried multiple approaches to achieve this project working, being built and debug in Eclipse. Finally I've found some workaround.

By multi level Maven project I mean following layout:

|- project
   |- pom.xml    <-- parent pom.xml
   |- project1   <-- subproject 1
      |- src
      |- pom.xml <-- subproject pom.xml using parent pom.xml
   |- project2   <-- subproject 2
      |- src
      |- pom.xml <-- subproject pom.xml using parent pom.xml

In such layout we cannot simply import subprojects into workspace separately and use maven 2 eclipse plugin, because they use upper level pom.xml. On the other hand - we cannot have subproject in Eclipse project subfolder. It seems to be no easy way to have it working.

There are also some other problems, for example I wanted to have everything in single subversion node to be transparently checked in and updated. My subprojects used some resources and other code from main project, and from each other. The additional fun was the requirement to have all projects run in single Tomcat instance working in IDE in debug mode. But after longer time I've found a soultion.

To do the job you need M2eclipse only. First start with checking out the repo into a single project. After this, you have the project in workspace with no sources, not even Java project. And this won't be your really used project, but will be useful to operate on svn.

Now you have to download somewhere appropriate maven version and setup in Window | Preferences | Maven | User Settings path to maven conf/settings.xml file. I don't know if it is really necessary because I haven't played without it.

We will use standard Dynamic Web Project with linked sources from our main project to configure whole env in following steps:
  1. Create Dynamic Web Project.
  2. Uncheck Use default location and select project1 dir from workspace in field below. Here I've had some NullPointerException with setting Dynamic Web Module facet on this project. If you have that too, after creation you need to enable Dynamic Web Module facet manually in project properties, Project Facets section. After these steps you should have Dynamic Web Project with Maven layout, linked to project1 dir. You probably have there single source folder.
  3. Click on project and select Maven | Enable dependency management from context menu. Maven should correct source paths and configure libs - you should have now few source dirs there. Now the Maven is enabled and after cleaning and building project you should have the project properly built. You can experience only some "missing artifact" problems with pom, but they're harmless. If your project is not built properly, check Java Build Path entries in project properties and fix the problems. Previously I run command line maven compilation to download all necessary libraries into repository, so if Eclipse can't found libs maybe you have to do it.
  4. When you can build project properly, the last thing is to setup server deployment policy. You can do it in Deployment Assembly section of project properties. On the beginning it's completely invalid and configured to work with standard eclipse Dynamic Web Project. Remove everything there, and add:
    • /src/config/YOURCONFIG deployment to /WEB-INF/classes (using Add | Folder)
    • /src/main/java -> /WEB-INF/classes
    • /src/main/resources -> /WEB-INF/classes
    • /src/main/webapp -> /
    • Maven Dependencies -> /WEB-INF/lib (using Add | Java build path entries)
  5. You can now remove unnecessary build and WebContent directories from your project.
Now, add your project as Server Runtime Module and try to rebuild everything, publish and run in the server. You should now be able to work with code from eclipse with the maven layout. You can now create other projects in workspace, linked with different subprojects in your super-project, and configure them as web modules too, to be working together. The last problem I encountered has been a fact, that Eclipse deploys all Maven Dependencies into /WEB-INF/lib directory, event those marked for "provided" scope. This can results with problems, when these libs conflict with those from server runtime. These problems can appear in various ways (I've had some JSP exception, since the jsp-api.jar has been overridden). Unfortunately there is no simple solution neither in Eclipse nor M2eclipse to exclude these files (at least I haven't been able to find something working). I've overcome this problem with another workaround - I've built the project manually using package maven goal, and the I joined libs from generated webapp to Deployment Assembly in project properties, instead of whole Maven Dependencies.