carlspring
carlspring

Reputation: 32607

How to use XStream with variants of the same data?

In my scenario I'm using XStream and I need to be able to store/parse the same data in different variants.

Consider the following classes:

User:

public class User implements Serializable
{

    @XStreamAlias(value = "roles")
    private List<Role> roles = new ArrayList<Role>();

    ...
}

Role:

public class Role implements Serializable
{

    @XStreamAlias(value = "name")
    private String name;

    @XStreamAlias(value = "description")
    private String description;

    ...
}

I need to be able to produce the following outputs:

users.xml

<users>
    <user>
        <username>admin</username>
        <password>password</password>
        <roles>
            <role>administrator</role>
        </roles>
    </user>
    <user>
        <username>deployer</username>
        <password>password</password>
        <roles>
            <role>deploy</role>
        </roles>
    </user>
</users>

roles.xml:

<roles>
    <role>
        <name>admin<name>
        <description>Administrative role</description?
    </role>
    <role>
        <name>deploy<name>
        <description>Deployment role</description?
    </role>
    ...
</roles>

However, I am stuck with:

<users>
    <user>
        <username>admin</username>
        <password>password</password>
        <roles>
            <role>
                <name>admin</name>
            </role>
        </roles>
    </user>
    <user>
        <username>deployer</username>
        <password>password</password>
        <roles>
            <role>
                <name>deploy</name>
            </role>
            <role>
                <name>read</name>
            </role>
            <role>
                <name>delete</name>
            </role>
        </roles>
    </user>
</users>

... Whereas, I would like to be getting:

<users>
    ...
    <user>
        <username>deployer</username>
        <password>password</password>
        <roles>
            <role>
                <name>deploy</name>
                <name>read</name>
                <name>delete</name>
            </role>
        </roles>
    </user>
    ...
</users>

This is understandable. The data in the User DTO is structured differently. So, I figured I needed a Converter. So, I knocked up the following:

public class RoleListConverter
        implements Converter
{

    public boolean canConvert(Class clazz)
    {
        return AbstractList.class.isAssignableFrom(clazz);
    }

    public void marshal(Object value,
                        HierarchicalStreamWriter writer,
                        MarshallingContext context)
    {
        if (value instanceof List)
        {
            //noinspection unchecked
            List<Role> roles = (List<Role>) value;

            for (Role role : roles)
            {
                writer.startNode("role");
                writer.setValue(role.getName());
                writer.endNode();
            }
        }
    }

    public Object unmarshal(HierarchicalStreamReader reader,
                            UnmarshallingContext context)
    {
        List<Role> roles = new ArrayList<Role>();

        while (reader.hasMoreChildren())
        {
            reader.moveDown();

            Role role = new Role();

            final String nodeName = reader.getNodeName();
            if (nodeName.equals("role"))
            {
                role.setName(reader.getValue().trim());
                roles.add(role);
            }

            reader.moveUp();
        }

        return roles;
    }

}

My parser class looks like this:

public class UserParser
        extends GenericParser<User>
{
    ...
    public XStream getXStreamInstance()
    {
        XStream xstream = new XStream();
        xstream.autodetectAnnotations(true);
        xstream.alias("user", User.class);
        xstream.alias("users", List.class);
        xstream.alias("role", Role.class);
        xstream.alias("credentials", Credentials.class);
        xstream.registerConverter(new RoleListConverter());

        return xstream;
    }
    ...
}

This fails when trying to store the XML with the following error:

java.lang.ClassCastException: org.foo.User cannot be cast to org.foo.Role
    at org.foo.RoleListConverter.marshal(RoleListConverter.java:35)

Apparently, when I register this converter, it tries to intercept all lists and screws up. What am I doing wrong here and how could achieve my goal?

Thanks in advance!

Upvotes: 0

Views: 126

Answers (1)

lvr123
lvr123

Reputation: 584

Probably a bit late as answer... Any way.

You could define

private class UserList extends ArrayList<User> {};
private class RoleList extends ArrayList<Role> {};

xstream.alias("users", UserList.class); 
xstream.alias("roles", RoleList.class);

Upvotes: 1

Related Questions