views:

291

answers:

2

Hello All,

Attempting to use XStream's JavaBeanConverter and running into an issue. Most likely I'm missng something simple, or not understanding XStream's converter handling well enough.

@XStreamAlias("test")
public class TestObject
{
    private String foo;

    public String getFoo()
    {
        //return foo; -- Adjusted for EDIT#2
        return foo.toLowerCase();
    }

    public void setFoo(String foo)
    {
        this.foo = foo;
    }
}

public void test() throws Exception
{
    XStream x = new XStream(new XppDriver());
    x.autodetectAnnotations(true);
    x.processAnnotations(TestObject.class);

    x.registerConverter(new JavaBeanConverter(x.getMapper()));

    TestObject o = new TestObject();
    //o.setFoo("bar"); -- Adjusted for EDIT#2
    o.setFoo("BAR");

    String xml = x.toXML(o);

    System.out.println(xml);

    /*
      Expecting...
        <test>
          <foo>bar</foo>
        </test>

      But instead getting...
        <test>
          <foo/>
        </test>         
    */
}

I tried adding a trace on the TestObject.getFoo() method and it appears it is being called by XStream, but the data isn't being written to the output stream.

After looking at the source for JavaBeanConverter, it looks like my implementation should work, which leads me to believe I haven't configured something correctly during the XStream setup.

Am I just missing something simple?

Thanks!


Edit 1

Also, if it helps, I'm using the following Maven deps for this...

<dependency>
    <groupId>org.apache.servicemix.bundles</groupId>
    <artifactId>org.apache.servicemix.bundles.xstream</artifactId>
    <version>1.3_3</version>
</dependency>
<dependency>
    <groupId>org.apache.servicemix.bundles</groupId>
    <artifactId>org.apache.servicemix.bundles.xpp3</artifactId>
    <version>1.1.4c_3</version>
</dependency>


Edit 2

I altered the TestObject.getFoo() method to better explain my objective.

I'm trying to use the getters/setters to do "sanitation" of the internal object's data. Specifically, I'm trying to clean up incoming deserialized data, while trying to avoid having to implement Serializable's readResolve() method.

If I implemented readResolve's method I would have to do the cleanup in both the setter method (for any non-deserialized incoming data set elsewhere) and also in the readResolve (for XML data I deserialized).

That's the reason why I'm using JavaBeanConverter instead of XStream's normal field based practices... to force data through the getters/setters.

Hopefully this explains a little better.

Thanks.

+1  A: 

If you comment out the line

x.registerConverter(new JavaBeanConverter(x.getMapper()));

it works fine :) I think you are doing both annotation processing and registering converters when just one of them would be enough

http://xstream.codehaus.org/annotations-tutorial.html

Calm Storm
Hi, I updated the original question to better explain what I am trying to achieve. Sorry for not explaining my end goals beforehand.
Steve Foster
+2  A: 

Stepping through XStream's source you can find this in com.thoughtworks.xstream.core.DefaultConverterLookup.

public Converter lookupConverterForType(Class type) {
    Converter cachedConverter = (Converter) typeToConverterMap.get(type);
    if (cachedConverter != null) return cachedConverter;
    Iterator iterator = converters.iterator();
    while (iterator.hasNext()) {
        Converter converter = (Converter) iterator.next();
        if (converter.canConvert(type)) {
            typeToConverterMap.put(type, converter);
            return converter;
        }
    }
    throw new ConversionException("No converter specified for " + type);
}

You will notice for the String it returns JavaBeanConverter and the reason it does that is that JavaBeanConverter will be used for any object with a default public constructor (which String has).

One way to fix the problem is to give the JavaBeanConverter a lower priority.

x.registerConverter(new JavaBeanConverter(x.getMapper()), -10);

Then JavaBeanConverter is used for your Bean and SingleValueConverterWrapper is used for the String value.

Randy Simon
Hi Randy, thanks for the info. I updated my original question to help better reflect my end goal. If I read this right, I'm trying to do the opposite, and force XStream to use the getters/setters (eg. via JavaBeanConverter). Does all of that make sense? (I'm not very good at explaining sometimes)
Steve Foster
I think the above accomplishes that. If you change your code how I suggest you will see that your getter is called. The problem is that first XStream tries to marshal TestObject and it determines to use the JavaBeanConverter class to do it, then it calls the getter and gets a String back ("bar") and now has to marshall that String too. Since String has a default public constructor XStream thinks that it can use the JavaBeanConverter again, but it shouldn't. Instead it should use the SingleValueConverterWrapper to marshall the String. Attach the XStream source and step through to see.
Randy Simon
Yep, your right. That works perfect. Also, it works on unmarshalling too (eg. from XML with setter).
Steve Foster