Saturday, October 27, 2007

WebWork style controllers in Spring

A long time ago (or so almost everything seems nowadays), I worked with Webwork 1 for a while. One thing I liked about it was its ActionSupport class, where you could set it up to accept certain request parameters by setting up the setters in this class, and specifying what to do with the data in its execute() or doDefault() method. Something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class FooAction extends ActionSupport {

  private String bar;
  public void setBar(String bar) { this.bar = bar; }
  public String getBar() { return bar; }

  private String baz;
  public void setBaz(String baz) { this.baz = baz; }
  public String getBaz() { return baz; }

  public void doDefault() {
    // ... do something with bar and baz, these are automatically
    // available to the view through the OGNL stack
    return SUCCESS;
  }
}

I like the way that one can "set" in the code what parameters are going to be used. With Spring Controllers, so far, I had been using something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class FooController implements Controller {
  public ModelAndView handle(HttpServletRequest request, HttpServletResponse response) {
    String bar = ServletRequestUtils.getStringParameter(request, "bar");
    String baz = ServletRequestUtils.getStringParameter(request, "baz");
    // ... do something with bar and baz
    ModelAndView mav = new ModelAndView();
    // ... populate the ModelAndView
    return mav;
  }
}

What I like about the above approach is its relative simplicity. However, the problem with this approach is that if you have multiple controllers which work off the same base set of parameters (which is true in most applications), the code is repeated (or moved out to a helper method). Also, if there is going to be significant computation based on the values of multiple parameters, then this logic will probably have to be repeated or put in a helper method as well. Putting the code into a helper method is preferable for maintenance but makes the code less readable.

In my case, I intended to use request parameters to define a very lightweight query language. A combination of certain values would trigger off certain actions and so on. Multiple controllers would have to work with the same basic set of request parameters. Also, the parameters in my case would have more numerous and have more interdependencies, since the parameters were meant to be set by human users rather than a computer program. So clearly, the Webwork style approach of being able to declaratively set parameters would be preferable to using Spring's ServletRequestUtils.getRequestParameter() calls.

One seemingly obvious way would have been to have setters in the Spring Controller implementation, and then use reflection to access them in the handle() method. However, as obvious as this sounds, this is a bad idea; since that the controller itself is a singleton by default, this is not thread-safe.

Looking a little further, however, I found Spring's AbstractCommandController, which provides pretty much the functionality I am looking for. To keep things thread-safe, AbstractCommandController exposes a setCommandClass() method, which allows you to set a bean class. Your controller class, which extends AbstractCommandController, will instantiate the bean once per request, and bind the request variables (parameters and attributes at least, AFAIK) to the instantiated bean using its exposed setters. In code, thus, for the above example, this is what our bean would look like:

1
2
3
4
5
6
7
8
9
public class Parameter {
  private String bar;
  public void setBar(String bar) { this.bar = bar; }
  public String getBar() { return bar; }

  private String baz;
  public void setBaz(String baz) { this.baz = baz; }
  public String getBaz() { return baz; }
}

Our controller class will look something like that shown below, and uses a different signature for its handle method as shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class FooController extends AbstractCommandController {
  protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response, 
      Object command, BindException errors) throws Exception {
    Parameter param = (Parameter) command;
    // ... request parameters are all available in param, do something with them
    ModelAndView mav = new ModelAndView();
    // ... populate the ModelAndView and return it
    return mav;
  }
}

The controller will be configured with its command object in configuration like so:

1
2
3
4
  <bean id="fooController" class="com.mycompany.myapp.controllers.FooController">
    <property name="commandClass" value="com.mycompany.myapp.beans.Parameter"/>
    ...
  </bean>

So this way we get the best of both worlds. In my case, there were situations where I would let the client use a set of input parameters and based on that, derive some other parameter, or alternatively let the client specify this derived parameter directly. In these situations, the controller would call a computeXXX() method in the Parameter class, rather than put logic to handle these cases in the getters for the parameters. I thought I'd share this, since my first approach was to modify the getters, but then I ran into a race condition with Spring first calling the getters inside its framework code. So I figured that its best to have default functionality on the getters and setters. That way, Spring, which assumes default semantics, can use it safely, and so can other application code. If application code needs values other than the default semantics, it can call the appropriate computeXXX() method instead.

This is probably old hat to a lot of you Spring MVC folks out there, but I had never needed this particular functionality so far, so just implementing Controller and grabbing the parameters with ServletRequestUtils worked fine for me. This was one case where this approach would have gotten too complicated, and happily Spring had something that worked out better.

Hot on the heels of my successful use of AbstractCommandController, and in the general spirit of learning new things, I have also been looking at the SimpleFormController for another part of the same application. SimpleFormController seems to be quite powerful but is anything but simple. However, its pretty easy to use once you know how...

By the way, if you would like to know more about Webwork 1, check out this nice tutorial on Joe Ottinger's blog, which by the way is an interesting read in itself.

Friday, October 19, 2007

Extending ROME to do RSS 2.0

In my previous post, I mentioned that I was trying to use ROME to convert an existing RSS 2.0 feed. ROME provides an abstraction of the various RSS and Atom flavors using its SyndFeed object. A Java programmer using the ROME library would use the SyndFeed object to build up a feed, then write it out into a WireFeed object of the appropriate type, using code like the one shown below:

1
2
3
4
5
  SyndFeed myfeed = new SyndFeedImpl();
  ... // populate the feed object
  WireFeedOutput outputter = new WireFeedOutput();
  WireFeed wirefeed = myfeed.createWireFeed("rss_2.0");
  System.out.println(outputter.output(wirefeed));

My problem was that I had a //rss/channel/item/source tag in the feed I was trying to convert. There did not seem to be any way to set anything in the SyndEntryImpl object (the ROME abstraction of the RSS item and the Atom entry elements) that would translate to a RSS item/source element. This seems strange, since the source element is valid RSS 2.0 according to the RSS 2.0 specifications. It may just be an oversight or a conscious decision by the ROME developers to exclude this element, but I needed it. Luckily, ROME was designed with extension in mind, and with the help of Dave Johnson's RAIA book, it was pretty simple to do. This post describes what I needed to do so that I could set a source attribute into the SyndEntryImpl object which would render and parse to and from a RSS 2.0 WireFeed object.

I chose the SyndEntry.setContributors() method which takes a List of SyndPerson objects as the method to set my source attribute from the SyndEntry object. The contributors object is used in Atom, so since I was not going to use Atom in this particular case, it was safe to use this method.

First I built a custom converter by extending the built in RSS 2.0 converter. The job of the converter is to translate between a SyndFeed and an RSS or Atom Feed object. However, since all I wanted to do was to be able to push a source object into my SyndEntry object and get it out when it was converted to an Item object, I extended protected methods of the superclass that does just that. Here is the code for my converter.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// ConverterForMyRss20.java
package com.mycompany.myapp.mymodule;

import java.util.Arrays;
import java.util.List;

import com.sun.syndication.feed.rss.Item;
import com.sun.syndication.feed.rss.Source;
import com.sun.syndication.feed.synd.Converter;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndPerson;
import com.sun.syndication.feed.synd.SyndPersonImpl;
import com.sun.syndication.feed.synd.impl.ConverterForRSS20;

public class ConverterForMyRss20 extends ConverterForRSS20 {

  /**
   * Default Ctor. Must be implemented and delegates to type ctor.
   */
  public ConverterForMyRss20() {
    this("myrss_2.0");
  }

  /**
   * Type Ctor. Must be implemented and delegates to superclass type ctor.
   * @param feedType the feed type to convert.
   */
  public ConverterForMyRss20(String feedType) {
    super(feedType);
  }
  
  /**
   * Called from {@link Converter#createRealFeed(com.sun.syndication.feed.synd.SyndFeed)}
   * Delegates to the superclass to create a partially populated Item object, then
   * provides additional logic to pull extra elements from the SyndEntry object into
   * the Item object and returns it.
   * @param syndEntry the SyndEntry to populate from.
   * @return the Item object.
   */
  @Override
  @SuppressWarnings("unchecked")
  protected Item createRSSItem(SyndEntry syndEntry) {
    Item item = super.createRSSItem(syndEntry);
    List<SyndPerson> contributors = syndEntry.getContributors();
    if (contributors != null && contributors.size() > 0) {
      Source source = new Source();
      source.setValue(contributors.get(0).getName());
      item.setSource(source);
    }
    return item;
  }
  
  /**
   * Called from {@link Converter#copyInto(com.sun.syndication.feed.WireFeed, com.sun.syndication.feed.synd.SyndFeed)}
   * Delegates to the superclass to create a partially populated SyndEntry object, then
   * adds the extra elements from the Item object to the SyndEntry object.
   * @param item the Item object to convert to a SyndEntry object.
   */
  protected SyndEntry createSyndEntry(Item item) {
    SyndEntry syndEntry = super.createSyndEntry(item);
    Source source = item.getSource();
    if (source != null) {
      SyndPerson syndPerson = new SyndPersonImpl();
      syndPerson.setName(source.getValue());
      syndEntry.setContributors(Arrays.asList(new SyndPerson[] {syndPerson}));
    }
    return syndEntry;
  }
}

I also needed to write a custom WireFeedParser and WireFeedGenerator to parse RSS 2.0 feeds to and from ROME objects. I really only needed to build the generator, but I built the parser too, just in case I need to provide my clients with tools to parse the RSS feeds I generate. Here is the code for my custom WireFeed parser and generator. As before, I override the RSS 2.0 parser and generator, so I only need to override a single protected method in each superclass.

Here is the code for my custom WireFeed parser. As in the case of the custom converter, the default and type constructors are required, and class just overrides the parseItem() method in the built in WireFeed parser for RSS 2.0 to grab the source JDOM Element from the XML and add it to the RSS Item object.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// MyRss20Parser.java
package com.mycompany.myapps.mymodule;

import org.jdom.Element;

import com.sun.syndication.feed.rss.Item;
import com.sun.syndication.feed.rss.Source;
import com.sun.syndication.io.impl.RSS20Parser;

public class MyRss20Parser extends RSS20Parser {
  
  /**
   * Default Ctor. Must be implemented and delegates to type ctor.
   */
  public MyRss20Parser() {
    this("myrss_2.0");
  }
  
  /**
   * Type Ctor. Must be implemented and delegates to superclass type ctor.
   * @param feedType the feed type for this parser.
   */
  public MyRss20Parser(String feedType) {
    super(feedType);
  }
  
  /**
   * Called from {@link com.sun.syndication.io.WireFeedParser#parse(org.jdom.Document, boolean)}
   * Delegates to the superclass to build an Item object, then adds the source to the
   * returned Item if it exists in the Element itemElement.
   * @param rssRoot the root Element of the RSS feed.
   * @param itemElement the Element representing the Item object.
   * @return the Item object with the source added if it exists.
   */
  @Override
  public Item parseItem(Element rssRoot, Element itemElement) {
    Item item = super.parseItem(rssRoot, itemElement);
    Element sourceElement = itemElement.getChild("source", getRSSNamespace());
    if (sourceElement != null) {
      Source source = new Source();
      source.setValue(sourceElement.getTextTrim());
      item.setSource(source);
    }
    return item;
  }
}

And here is the code for the custom WireFeedGenerator. As before, the default and the type constructors are required by the framework, and all this class does is to override the superclass's populateItem() method, to populate an Item JDOM Element object with the source Element if the Item.getSource() value is not null.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// MyRss20Generator.java
package com.mycompany.myapp.mymodule;

import org.jdom.Element;

import com.sun.syndication.feed.rss.Item;
import com.sun.syndication.io.WireFeedGenerator;
import com.sun.syndication.io.impl.RSS20Generator;

public class MyRss20Generator extends RSS20Generator {

  /**
   * Default Ctor. Must be implemented and delegates to type ctor.
   */
  public MyRss20Generator() {
    this("myrss_2.0", "2.0");
  }
  
  /**
   * Type Ctor. Must be implemented and delegates to superclass type ctor.
   * @param feedType the feed type for this generator.
   * @param version the feed version for this generator.
   */
  public MyRss20Generator(String feedType, String version) {
    super(feedType, version);
  }
  
  /**
   * Called from {@link WireFeedGenerator#generate(com.sun.syndication.feed.WireFeed)}
   * Delegates to the superclass to partially populate an Item element from
   * an Item object. Adds on a source element to the item element if the Item
   * object has a non-null source.
   * @param item the Item object from which to populate.
   * @param itemElement the Element being populated from the Item.
   * @param index the index of the object, not used here.
   */
  @Override
  public void populateItem(Item item, Element itemElement, int index) {
    super.populateItem(item, itemElement, index);
    if (item.getSource() != null) {
      Element sourceElement = new Element("source", getFeedNamespace());
      sourceElement.setText(item.getSource().getValue());
      itemElement.addContent(sourceElement);
    }
  }
}

To let ROME know that I will be using my dialect of RSS 2.0 (myrss_2.0) which supports the source element, I need to update the rome.properties file with the mapping to the above classes. I also need to change the mapping for the Module parser and generator to use this dialect. My complete rome.properties looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
myrss_2.0.item.ModuleParser.classes=\
com.mycompany.myapp.mymodule.MyModuleParser

myrss_2.0.item.ModuleGenerator.classes=\
com.mycompany.myapp.mymodule.MyModuleGenerator

WireFeedParser.classes=\
com.mycompany.myapp.mymodule.MyRss20Parser

WireFeedGenerator.classes=\
com.mycompany.myapp.mymodule.MyRss20Generator

Converter.classes=\
com.mycompany.myapp.mymodule.ConverterForMyRss20

Once this is all done, the Java code to set a source element inside a SyndEntry object looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  SyndEntry entry = new SyndEntryImpl();
  ...
  SyndPerson source = new SyndPersonImpl();
  source.setName(mydomainObject.getSourceName());
  entry.setContributors(Arrays.asList(new SyndPerson[] {source}));
  ...
  feed.getEntries().add(entry);

  WireFeedOutput outputter = new WireFeedOutput();
  WireFeed wirefeed = myfeed.createWireFeed("myrss_2.0");
  System.out.println(outputter.output(wirefeed));

And I am happy to say that the resulting item elements in the RSS 2.0 feed did contain the source as expected. However, it is very possible that I am doing something wrong and there is some settable field in SyndEntry that will allow the source to be populated without going through all this trouble. If there is, I would appreciate being corrected.

Update

I found that once I put in my custom WireFeed generator and parser, and the custom converter to convert between SyndEntry and Item objects, some of my old feeds began to fail. Specifically, it was not generating the OpenSearch element within the channel, even though they were being set in the code. Prior to the change, the OpenSearch elements were showing up fine. I also noticed that some namespace declarations were not showing up. The upshot was that I ended up changing my rome.properties file to explicitly declare all the properties for RSS 2.0 classes in addition to my custom myrss_2.0 dialect. Here is my complete rome.properties.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# Copied from the base rome.properties for rss_2.0 since we are now using
# our own dialect of rss_2.0
myrss_2.0.feed.ModuleGenerator.classes=\
  com.sun.syndication.io.impl.DCModuleGenerator \
  com.sun.syndication.io.impl.SyModuleGenerator \
  com.sun.syndication.feed.module.opensearch.impl.OpenSearchModuleGenerator

myrss_2.0.feed.ModuleParser.classes=\
  com.sun.syndication.io.impl.DCModuleParser \
  com.sun.syndication.io.impl.SyModuleParser \
  com.sun.syndication.feed.module.opensearch.impl.OpenSearchModuleParser

myrss_2.0.item.ModuleParser.classes=\
  com.mycompany.myapp.mymodule.MyModuleParser

myrss_2.0.item.ModuleGenerator.classes=\
  com.mycompany.myapp.mymodule.MyModuleGenerator

WireFeedParser.classes=\
  com.sun.syndication.io.impl.RSS090Parser \
  com.sun.syndication.io.impl.RSS091NetscapeParser \
  com.sun.syndication.io.impl.RSS091UserlandParser \
  com.sun.syndication.io.impl.RSS092Parser \
  com.sun.syndication.io.impl.RSS093Parser \
  com.sun.syndication.io.impl.RSS094Parser \
  com.sun.syndication.io.impl.RSS10Parser  \
  com.sun.syndication.io.impl.RSS20wNSParser  \
  com.sun.syndication.io.impl.RSS20Parser  \
  com.sun.syndication.io.impl.Atom10Parser \
  com.sun.syndication.io.impl.Atom03Parser \
  com.mycompany.myapp.mymodule.MyRss20Parser

WireFeedGenerator.classes=\
  com.sun.syndication.io.impl.RSS090Generator \
  com.sun.syndication.io.impl.RSS091NetscapeGenerator \
  com.sun.syndication.io.impl.RSS091UserlandGenerator \
  com.sun.syndication.io.impl.RSS092Generator \
  com.sun.syndication.io.impl.RSS093Generator \
  com.sun.syndication.io.impl.RSS094Generator \
  com.sun.syndication.io.impl.RSS10Generator  \
  com.sun.syndication.io.impl.RSS20Generator  \
  com.sun.syndication.io.impl.Atom10Generator \
  com.sun.syndication.io.impl.Atom03Generator \
  com.mycompany.myapp.mymodule.MyRss20Generator

Converter.classes=\
  com.sun.syndication.feed.synd.impl.ConverterForAtom10 \
  com.sun.syndication.feed.synd.impl.ConverterForAtom03 \
  com.sun.syndication.feed.synd.impl.ConverterForRSS090 \
  com.sun.syndication.feed.synd.impl.ConverterForRSS091Netscape \
  com.sun.syndication.feed.synd.impl.ConverterForRSS091Userland \
  com.sun.syndication.feed.synd.impl.ConverterForRSS092 \
  com.sun.syndication.feed.synd.impl.ConverterForRSS093 \
  com.sun.syndication.feed.synd.impl.ConverterForRSS094 \
  com.sun.syndication.feed.synd.impl.ConverterForRSS10  \
  com.sun.syndication.feed.synd.impl.ConverterForRSS20 \
  com.mycompany.myapp.mymodule.ConverterForMyRss20

ROME uses a hierarchy of rome.properties files to configure itself, which works fine when we are adding modules as I did in my last post, but breaks down when we are overriding. Its like overriding a superclass method without calling super.method(), I guess. So we basically have to do the equivalent of the super method call by copying the classes for RSS 2.0 for the overriden properties.

Saturday, October 13, 2007

Custom Modules with ROME

I have been looking at ROME lately. ROME is a very popular open-source Java based RSS/Atom Feed parsing and generation library, originally developed by a group of Sun developers. I originally looked at ROME as a way to parse external feeds. Although the feeds were either in RSS or Atom, there are various versions of both RSS and Atom, which are quite different from each other in subtle ways. ROME abstracts away all the differences, allowing you to treat them as high level Java objects. ROME uses JDOM, my favorite XML parsing library, to do the parsing and building of XML behind the scenes.

My first application using ROME was to parse and aggregate a bunch of external feeds, and was ridiculously simple, about half a page of Java code. As Mark Woodman says in his article "Rome in a Day: Parse and Publish Feeds in Java" on XML.com, the sheer variety of RSS and Atom flavors are enough to make a grown programmer cry. However, the way in which ROME abstracts away all these variations and the simplicity of my resulting code almost made me cry... tears of joy.

However, given that most of the "smarts" of the application would be in the selection of the feeds themselves, and since that required domain expertise I did not have, I decided to put that project aside for a while and explore the other part of ROME, trying to use it to build a feed instead. In any case, in retrospect, I would probably be looking at using the rome-fetcher subproject instead to build the aggregator, since that already provides code to build "well-behaved" feed fetchers.

The feed I choose to rebuild was an existing RSS 2.0 feed. It was generated using JSP XML templates powered by Java services. As such, there were many custom extensions built in, which were not part of any of the standard modules that ROME uses. So I basically had to build a custom extension module for ROME to support the custom tags that this feed used. This post describes the (pretty simple) process.

I could not find any place where this process was described in sufficient newbie detail for me, so after quickly looking through the RSS/Atom/ROME books on Amazon, I settled on "RSS and Atom in Action" (RAIA) by Dave Johnson. One reason for my choice was that it was published by Manning and they provide downloadable PDF versions free with their printed book, so I got my book about 15 minutes after I ordered from my home computer. As it turned out, I was done reading the PDF version by the time the print edition arrived about 5 days later. The print version sits unopened on my desk for now, but I am sure I will need it some day.

But enough idle chatter. Lets get right down to building a custom module that supports three of my custom tags, called my:tag, my:tagDate and my:isTagged respectively. The first is a String, the second a Date and the third a Boolean.

Each module has four components that needs to be built and hooked up with ROME. An interface that describes the URI for the namespace in which the custom tags will live and the getters and setters for each custom tag supported, an implementation of that interface, a parser and a generator. They are shown below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// MyModule.java
package com.mycompany.feeds.modules.mymodule;

import java.util.Date;

import com.sun.syndication.feed.module.Module;

public interface MyModule extends Module {
  
  public static final String URI = "http://www.my.com/spec";

  public String getTag();
  public void setTag(String tag);
  public Date getTagDate();
  public void setTagDate(Date tagDate);
  public Boolean getIsTagged();
  public void setIsTagged(Boolean isTagged);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// MyModuleImpl.java
package com.mycompany.feeds.modules.mymodule;

import java.util.Date;

import com.sun.syndication.feed.module.ModuleImpl;

public class MyModuleImpl extends ModuleImpl implements MyModule {

  private static final long serialVersionUID = -8275118704842545845L;

  private Boolean isTagged;
  private Date tagDate;
  private String tag;
  
  // boilerplate code. Eclipse will generate all but the constructor but
  // will keep reporting an error until you do it.
  public MyModuleImpl() {
    super(MyModule.class, MyModule.URI);
  }

  public void copyFrom(Object obj) {
    MyModule module = (MyModule) obj;
    setTag(module.getTag());
    setTagDate(module.getTagDate());
    setIsTagged(module.getIsTagged());
  }

  public Class getInterface() {
    return MyModule.class;
  }

  // getter and setter impls for MyModule interface
  public Boolean getIsTagged() {
    return isTagged;
  }

  public String getTag() {
    return tag;
  }

  public Date getTagDate() {
    return tagDate;
  }

  public void setIsTagged(Boolean isTagged) {
    this.isTagged = isTagged;
  }

  public void setTag(String tag) {
    this.tag = tag;
  }

  public void setTagDate(Date tagDate) {
    this.tagDate = tagDate;
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// MyModuleGenerator.java
package com.mycompany.feeds.modules.mymodule;

import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.jdom.Element;
import org.jdom.Namespace;

import com.sun.syndication.feed.module.Module;
import com.sun.syndication.io.ModuleGenerator;

public class MyModuleGenerator implements ModuleGenerator {

  // boilerplate code
  private static final Namespace NAMESPACE = Namespace.getNamespace("my", MyModule.URI);
  private static final Set NAMESPACES;
  static {
    Set<Namespace> namespaces = new HashSet<Namespace>();
    namespaces.add(NAMESPACE);
    NAMESPACES = Collections.unmodifiableSet(namespaces);
  }

  public String getNamespaceUri() {
    return MyModule.URI;
  }

  public Set getNamespaces() {
    return NAMESPACES;
  }

  // Implements the module generation logic
  private final SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");

  public void generate(Module module, Element element) {
    MyModule myModule = (MyModule) module;
    if (myModule.getTag() != null) {
      Element myElement = new Element("tag", NAMESPACE);
      myElement.setText(myModule.getTag());
      element.addContent(myElement);
    }
    if (myModule.getTagDate() != null) {
      Element myElement = new Element("tagDate", NAMESPACE);
      myElement.setText(dateFormat.format(myModule.getTagDate()));
      element.addContent(myElement);
    }
    if (myModule.getIsTagged() != null) {
      Element myElement = new Element("isTagged", NAMESPACE);
      myElement.setText(String.valueOf(myModule.getIsTagged()));
      element.addContent(myElement);
    }
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// MyModuleParser.java
package com.mycompany.feeds.modules.mymodule;

import java.text.ParseException;
import java.text.SimpleDateFormat;

import org.jdom.Element;
import org.jdom.Namespace;

import com.sun.syndication.feed.module.Module;
import com.sun.syndication.io.ModuleParser;

public class MyModuleParser implements ModuleParser {

  // boilerplate
  public String getNamespaceUri() {
    return MyModule.URI;
  }

  // implements the parsing for MyModule
  private final SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");

  public Module parse(Element element) {
    Namespace myNamespace = Namespace.getNamespace(MyModule.URI);
    MyModule myModule = new MyModuleImpl();
    if (element.getNamespace().equals(myNamespace)) {
      if (element.getName().equals("tag")) {
        myModule.setTag(element.getTextTrim());
      }
      if (element.getName().equals("tagDate")) {
        try {
          myModule.setTagDate(dateFormat.parse(element.getTextTrim()));
        } catch (ParseException e) {
          // don't set it if bad date format
        }
      }
      if (element.getName().equals("isTagged")) {
        myModule.setIsTagged(Boolean.valueOf(element.getTextTrim()));
      }
    }
    return myModule;
  }
}

To let ROME know that these modules should be used, we need to create a rome.properties file in our classpath. The ROME JAR file already has rome.properties files within it that controls its default configuration, and it will read our rome.properties in addition to its own configuration. Our three tags are all item level tags, so we will need to only map the parsers to the item level. The RAIA book shows you how to map feed level modules as well, but the process is quite similar. Here is my rome.properties file (it would be located in src/main/resources in a Maven2 project). I am building an RSS 2.0 feed, so I map it to that dialect here.

1
2
3
4
5
6
# rome.properties
rss_2.0.item.ModuleParser.classes=\
com.mycompany.feeds.modules.mymodule.MyModuleParser

rss_2.0.item.ModuleGenerator.classes=\
com.mycompany.feeds.modules.mymodule.MyModuleGenerator

Finally, here is how I would call it from within my code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  @Test
  public void testFeedWithMyModule() throws Exception {
    // create the feed object
    SyndFeed feed = new SyndFeedImpl();
    feed.setFeedType("rss_2.0");
    feed.setTitle("My test feed");
    ...
    // add the MyModule namespace to the feed
    feed.getModules().add(new MyModuleImpl());

    // create the item
    SyndEntry entry = new SyndEntryImpl();
    ...
    // create the module, populate and add to the item
    MyModule myModule = new MyModuleImpl();
    myModule.setTag("tagValue");
    myModule.setTagDate(new Date());
    myModule.setIsTagged(true);
    entry.getModules().add(myModule);
    ...
    // add entry(s) to the feed
    feed.getEntries().add(entry);
    // print it out
    WireFeedOutput output = new WireFeedOutput();
    WireFeed wireFeed = feed.createWireFeed("rss_2.0");
    log.debug(output.outputString(wireFeed));
  }

As you can see, adding custom modules to ROME is a bit involved, but its really quite simple. Before I started on these two projects, I did not know much about all the various flavors of ROME and about these custom extensions. In fact, this is the first time I have used Namespaces in JDOM. However, Dave Johnson's book provides a lot of background information and a lot of nice examples in Java and C#. I would highly recommend it to anyone who needs to get up to speed quickly with ROME and RSS/Atom. Another very informative article is the article "ROME: Escape syndication hell, a Java developer's perspective" written by two of the original developers of ROME.

Saturday, October 06, 2007

Converting XML to Badgerfish JSON

The Badgerfish convention defines a standard way to convert an XML document to a JSON object. Their website lists tools written in PHP and Ruby, and even a web service, but I needed one for Java. Since the conversion rules are nicely enumerated on their site, it did not seem terribly difficult to write one myself, so I did. The code for the converter is modeled after the JDOM XMLOutputter, and allows for outputting either the compact format (for computer programs) or the pretty format (for humans). Unlike the JDOM XMLOutputter, however, methods are only provided to work with a JDOM Document object. An additional convenience outputString() method is provided to work with an XML string, converting it to a JDOM Document internally. The code is shown below:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package com.mycompany.myapp.converters;

import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.Writer;
import java.util.List;

import net.sf.json.JSONObject;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;

/**
 * Provides methods to convert an XML string into an equivalent JSON string 
 * using the Badgerfish convention described in http://badgerfish.ning.com.
 *  
 * Conversion Rules copied from the website are enumerated below:
 * 
 * 1. Element names become object properties
 * 2. Text content of elements goes in the $ property of an object.
 *    <alice>bob</alice>
 *    becomes
 *    { "alice": { "$" : "bob" } }
 * 3. Nested elements become nested properties
 *    <alice><bob>charlie</bob><david>edgar</david></alice>
 *    becomes
 *    { "alice": { "bob" : { "$": "charlie" }, "david": { "$": "edgar"} } }
 * 4. Multiple elements at the same level become array elements.
 *    <alice><bob>charlie</bob><bob>david</bob></alice>
 *    becomes
 *    { "alice": { "bob" : [{"$": charlie" }, {"$": "david" }] } }
 * 5. Attributes go in properties whose names begin with @.
 *    <alice charlie="david">bob</alice>
 *    becomes
 *    { "alice": { "$" : "bob", "@charlie" : "david" } }
 * 6. Active namespaces for an element go in the element's @xmlns property.
 * 7. The default namespace URI goes in @xmlns.$.
 *    <alice xmlns="http://some-namespace">bob</alice>
 *    becomes
 *    { "alice": { "$" : "bob", "@xmlns": { "$" : "http:\/\/some-namespace"} } }
 * 8. Other namespaces go in other properties of @xmlns.
 *    <alice xmlns="http:\/\/some-namespace" xmlns:charlie="http:\/\/some-other-namespace">bob</alice>
 *    becomes
 *    { "alice": { "$" : "bob", "@xmlns": { "$" : "http:\/\/some-namespace", "charlie" : "http:\/\/some-other-namespace" } } }
 * 9. Elements with namespace prefixes become object properties, too.
 *    <alice xmlns="http://some-namespace" xmlns:charlie="http://some-other-namespace"> <bob>david</bob> <charlie:edgar>frank</charlie:edgar> </alice>
 *    becomes
 *    { "alice" : { "bob" : { "$" : "david" , "@xmlns" : {"charlie" : "http:\/\/some-other-namespace" , "$" : "http:\/\/some-namespace"} } , "charlie:edgar" : { "$" : "frank" , "@xmlns" : {"charlie":"http:\/\/some-other-namespace", "$" : "http:\/\/some-namespace"} }, "@xmlns" : { "charlie" : "http:\/\/some-other-namespace", "$" : "http:\/\/some-namespace"} } }
 *    
 * @author Sujit Pal
 */
public class JsonOutputter {

  private final Log log = LogFactory.getLog(getClass());

  private int indent = 0;
  
  /**
   * Set the format for the outputter. Default is compact format.
   * @param format the format to set.
   */
  public void setFormat(Format format) {
    String indentString = format.getIndent();
    if (indentString != null) {
      indent = format.getIndent().length();
    }
  }
  
  /**
   * Converts a JDOM Document into a JSON string and writes the result into
   * the specified OutputStream.
   * @param document the JDOM Document.
   * @param ostream the OutputStream.
   * @throws IOException if one is thrown.
   */
  public void output(Document document, OutputStream ostream) throws IOException {
    ostream.write(outputString(document).getBytes());
  }
  
  /**
   * Converts the JDOM Document into a JSON string and writes the result into 
   * the specified Writer.
   * @param document the JDOM Document.
   * @param writer the Writer.
   * @throws IOException if one is thrown.
   */
  public void output(Document document, Writer writer) throws IOException {
    writer.write(outputString(document));
  }
  
  /**
   * Convenience method that accepts an XML string and returns a String 
   * representing the converted JSON Object.
   * @param xml the input XML string.
   * @return the String representation of the converted JSON object.
   * @throws IOException if one is thrown.
   * @throws JDOMException if one is thrown.
   */
  public String outputString(String xml) throws IOException, JDOMException {
    SAXBuilder builder = new SAXBuilder();
    Document doc = builder.build(new StringReader(xml));
    return outputString(doc);
  }
  
  /**
   * Converts the JDOM Document into a JSON String and returns it.
   * @param document the JDOM Document.
   * @return the JSON String representing the JDOM Document.
   */
  public String outputString(Document document) {
    Element rootElement = document.getRootElement();
    JSONObject jsonObject = new JSONObject();
    JSONObject namespaceJsonObject = getNamespaceJsonObject(rootElement);
    processElement(rootElement, jsonObject, namespaceJsonObject);
    processChildren(rootElement, jsonObject, namespaceJsonObject);
    if (indent == 0) {
      return StringUtils.replace(jsonObject.toString(), "/", "\\/");
    } else {
      return StringUtils.replace(jsonObject.toString(indent), "/", "\\/");
    }
  }

  /**
   * Process the children of the specified JDOM element. This method is recursive.
   * The children for the given element are found, and the method is called for
   * each child.
   * @param element the element whose children needs to be processed.
   * @param jsonObject the reference to the JSON Object to update.
   * @param namespaceJsonObject the reference to the root Namespace JSON object.
   */
  private void processChildren(Element element, JSONObject jsonObject, JSONObject namespaceJsonObject) {
    List<Element> children = element.getChildren();
    JSONObject properties;
    if (jsonObject.has(getQName(element))) {
      properties = jsonObject.getJSONObject(getQName(element));
    } else {
      properties = new JSONObject();
    }
    for (Element child : children) {
      // Rule 1: Element names become object properties
      // Rule 9: Elements with namespace prefixes become object properties, too.
      JSONObject childJsonObject = new JSONObject();
      processElement(child, childJsonObject, namespaceJsonObject);
      processChildren(child, childJsonObject, namespaceJsonObject);
      if (! childJsonObject.isEmpty()) {
        properties.accumulate(getQName(child), childJsonObject.getJSONObject(getQName(child)));
      }
    }
    if (! properties.isEmpty()) {
      jsonObject.put(getQName(element), properties);
    }
  }

  /**
   * Process the text content and attributes of a JDOM element into a JSON object.
   * @param element the element to parse.
   * @param jsonObject the JSONObject to update with the element's properties.
   * @param namespaceJsonObject the reference to the root Namespace JSON object.
   */
  private void processElement(Element element, JSONObject jsonObject, JSONObject namespaceJsonObject) {
    JSONObject properties = new JSONObject();
    // Rule 2: Text content of elements goes in the $ property of an object.
    if (StringUtils.isNotBlank(element.getTextTrim())) {
      properties.accumulate("$", element.getTextTrim());
    }
    // Rule 5: Attributes go in properties whose names begin with @.
    List<Attribute> attributes = element.getAttributes();
    for (Attribute attribute : attributes) {
      properties.accumulate("@" + attribute.getName(), attribute.getValue());
    }
    if (! namespaceJsonObject.isEmpty()) {
      properties.accumulate("@xmlns", namespaceJsonObject);
    }
    if (! properties.isEmpty()) {
      jsonObject.accumulate(getQName(element), properties);
    }
  }
  
  /**
   * Return a JSON Object containing the default and additional namespace
   * properties of the Element. 
   * @param element the element whose namespace properties are to be extracted.
   * @return the JSON Object with the namespace properties.
   */
  private JSONObject getNamespaceJsonObject(Element element) {
    // Rule 6: Active namespaces for an element go in the element's @xmlns property.
    // Rule 7: The default namespace URI goes in @xmlns.$.
    JSONObject namespaceProps = new JSONObject();
    Namespace defaultNamespace = element.getNamespace();
    if (StringUtils.isNotBlank(defaultNamespace.getURI())) {
      namespaceProps.accumulate("$", defaultNamespace.getURI());
    }
    // Rule 8: Other namespaces go in other properties of @xmlns.
    List<Namespace> additionalNamespaces = element.getAdditionalNamespaces();
    for (Namespace additionalNamespace : additionalNamespaces) {
      if (StringUtils.isNotBlank(additionalNamespace.getURI())) {
        namespaceProps.accumulate(additionalNamespace.getPrefix(), additionalNamespace.getURI());
      }
    }
    return namespaceProps;
  }

  /**
   * Return the qualified name (namespace:elementname) of the element.
   * @param element the element to set.
   * @return the element name qualified with its namespace.
   */
  private String getQName(Element element) {
    if (StringUtils.isNotBlank(element.getNamespacePrefix())) {
      return element.getNamespacePrefix() + ":" + element.getName();
    } else {
      return element.getName();
    }
  }
}

The only dependencies for this code are commons-lang, JDOM and json-lib. I guess I could have just used the methods built into String, but I have gotten too used to StringUtils doing null-safe operations for me. JDOM happens to be my favorite XML parsing and generation toolkit by far, even though there are many toolkits that are more popular because they are faster. I also prefer using json-lib for JSON stuff than the more popular org.json module because of the way json-lib is architected.

Most of the rules have expected outputs for a given input, so testing the converter was simply a matter of writing a JUnit test case and making sure the inputs returned the expected outputs. Here is the JUnit test I wrote to test the converter.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package com.mycompany.myapp.converters;

import java.io.StringReader;
import java.io.StringWriter;

import junit.framework.Assert;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdom.Document;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.junit.Before;
import org.junit.Test;

/**
 * Test for XML to JSON conversion tool.
 * @author Sujit Pal
 */
public class JsonOutputterTest {

  private final Log log = LogFactory.getLog(getClass());
  
  private JsonOutputter jsonOutputter;
  
  @Before
  public void setUp() throws Exception {
    jsonOutputter = new JsonOutputter();
    // this call is redundant, really
    jsonOutputter.setFormat(Format.getCompactFormat());
  }
  
  /**
   * Rule 1: Element names become object properties
   * <foo><bar><baz>baztext</baz></bar></foo>
   * becomes:
   * {"foo":{"bar":{"baz":{"$":"baztext"}}}}
   */
  @Test
  public void testBadgerfishRule1() throws Exception {
    String xml = "<foo><bar><baz>baztext</baz></bar></foo>";
    String json = jsonOutputter.outputString(xml);
    log.debug("Rule 1:" + json);
    Assert.assertEquals("{\"foo\":{\"bar\":{\"baz\":{\"$\":\"baztext\"}}}}", json);
  }

  /**
   * Rule 2: Text content of elements goes in the $ property of an object.
   * <alice>bob</alice>
   * becomes
   * {"alice":{"$":"bob"}}
   */
  @Test
  public void testBadgerfishRule2() throws Exception {
    String xml = "<alice>bob</alice>";
    String json = jsonOutputter.outputString(xml);
    log.debug("Rule 2:" + json);
    Assert.assertEquals("{\"alice\":{\"$\":\"bob\"}}", json);
  }

  /**
   * Rule 3: Nested elements become nested properties
   * <alice><bob>charlie</bob><david>edgar</david></alice>
   * becomes
   * {"alice":{"bob":{"$":"charlie"},"david":{"$":"edgar"}}}
   */
  @Test
  public void testBadgerfishRule3() throws Exception {
    String xml = "<alice><bob>charlie</bob><david>edgar</david></alice>";
    String json = jsonOutputter.outputString(xml);
    log.debug("Rule 3:" + json);
    Assert.assertEquals("{\"alice\":{\"bob\":{\"$\":\"charlie\"},\"david\":{\"$\":\"edgar\"}}}", json);
  }

  /**
   * Rule 4: Multiple elements at the same level become array elements.
   * <alice><bob>charlie</bob><bob>david</bob></alice>
   * becomes
   * {"alice":{"bob":[{"$":"charlie"},{"$":"david"}]}}
   */
  @Test
  public void testBadgerfishRule4() throws Exception {
    String xml = "<alice><bob>charlie</bob><bob>david</bob></alice>";
    String json = jsonOutputter.outputString(xml);
    log.debug("Rule 4:" + json);
    Assert.assertEquals("{\"alice\":{\"bob\":[{\"$\":\"charlie\"},{\"$\":\"david\"}]}}", json);
  }
  
  /**
   * Rule 5: Attributes go in properties whose names begin with @.
   * <alice charlie="david">bob</alice>
   * becomes
   * {"alice":{"$":"bob","@charlie":"david"}}
   */
  @Test
  public void testBadgerfishRule5() throws Exception {
    String xml = "<alice charlie=\"david\">bob</alice>";
    String json = jsonOutputter.outputString(xml);
    log.debug("Rule 5:" + json);
    Assert.assertEquals("{\"alice\":{\"$\":\"bob\",\"@charlie\":\"david\"}}", json);
  }
  
  /**
   * Rule 6: Active namespaces for an element go in the element's @xmlns property.
   * Rule 7: The default namespace URI goes in @xmlns.$.
   * <alice xmlns="http://some-namespace">bob</alice>
   * becomes
   * {"alice":{"$":"bob","@xmlns":{"$":"http:\/\/some-namespace"}}}
   */
  @Test
  public void testBadgerfishRule6And7() throws Exception {
    String xml = "<alice xmlns=\"http://some-namespace\">bob</alice>";
    String json = jsonOutputter.outputString(xml);
    log.debug("Rule 6+7:" + json);
    Assert.assertEquals("{\"alice\":{\"$\":\"bob\",\"@xmlns\":{\"$\":\"http:\\/\\/some-namespace\"}}}", json);
  }

  /**
   * Rule 8: Other namespaces go in other properties of @xmlns.
   * <alice xmlns="http:\/\/some-namespace" xmlns:charlie="http:\/\/some-other-namespace">bob</alice>
   * becomes
   * {"alice":{"$":"bob","@xmlns":{"$":"http:\/\/some-namespace","charlie":"http:\/\/some-other-namespace"}}}
   */
  @Test
  public void testBadgerfishRule8() throws Exception {
    String xml = "<alice xmlns=\"http://some-namespace\" xmlns:charlie=\"http://some-other-namespace\">bob</alice>";
    String json = jsonOutputter.outputString(xml);
    log.debug("Rule 8:" + json);
    Assert.assertEquals("{\"alice\":{\"$\":\"bob\",\"@xmlns\":{\"$\":\"http:\\/\\/some-namespace\",\"charlie\":\"http:\\/\\/some-other-namespace\"}}}", json);
  }

  /**
   * Rule 9: Elements with namespace prefixes become object properties, too.
   * <alice xmlns="http://some-namespace" xmlns:charlie="http://some-other-namespace"> <bob>david</bob> <charlie:edgar>frank</charlie:edgar> </alice>
   * becomes
   * {"alice":{"bob":{"$":"david","@xmlns":{"$":"http:\/\/some-namespace","charlie":"http:\/\/some-other-namespace"}},"charlie:edgar":{"$":"frank","@xmlns":{"$":"http:\/\/some-namespace","charlie":"http:\/\/some-other-namespace"}},"@xmlns":{"$":"http:\/\/some-namespace","charlie":"http:\/\/some-other-namespace"}}}
   */
  @Test
  public void testBadgerfishRule9() throws Exception {
    String xml = "<alice xmlns=\"http://some-namespace\" xmlns:charlie=\"http://some-other-namespace\"> <bob>david</bob> <charlie:edgar>frank</charlie:edgar> </alice>";
    String json = jsonOutputter.outputString(xml);
    log.debug("Rule 9:" + json);
    Assert.assertEquals("{\"alice\":{\"bob\":{\"$\":\"david\",\"@xmlns\":{\"$\":\"http:\\/\\/some-namespace\",\"charlie\":\"http:\\/\\/some-other-namespace\"}},\"charlie:edgar\":{\"$\":\"frank\",\"@xmlns\":{\"$\":\"http:\\/\\/some-namespace\",\"charlie\":\"http:\\/\\/some-other-namespace\"}},\"@xmlns\":{\"$\":\"http:\\/\\/some-namespace\",\"charlie\":\"http:\\/\\/some-other-namespace\"}}}", json);
  }
}

I did not know much about the Badgerfish convention until quite recently. This move towards being able to generate XML into a standard JSON format seems really cool, and I wonder if it is widely used. Frankly, given that so many Java applications use XML and JSON, I was hoping to just snag the code from the net, rather than have to write it myself. If you use Java and generate JSON using the Badgerfish convention, I would love to know of alternative approaches you may be using for the conversion.