Search code examples
javajaxbjaxb2

JAXB complex mapping


I have the following code:

@XmlRootElement(name = "repository")
@XmlAccessorType(XmlAccessType.FIELD)
public class Repository
{
    @XmlElement (name = "id")
    private String id;

    @XmlElement (name = "policy")
    private String policy;
    ...
}

@XmlRootElement(name = "storage")
@XmlAccessorType(XmlAccessType.FIELD)
public class Storage 
{

    @XmlElement (name = "id")
    private String id;

    /**
     * K: repository.id
     * V: Repository
     */
    @XmlElement (name = "repositories")
    @XmlJavaTypeAdapter(RepositoryMapAdapter.class)
    private Map<String, Repository> repositories = new LinkedHashMap<String, Repository>();
    ...
}

public class DataCenter
{
    /**
     * K: storageId
     * V: storage
     */
    @XmlElement(name = "storages")
    @XmlJavaTypeAdapter(StorageMapAdapter.class)
    //@XStreamAlias(value = "storages")
    private Map<String, Storage> storages = new LinkedHashMap<String, Storage>();

    ...
}

I have the following two adapters:

public class RepositoryMapAdapter
        extends XmlAdapter<RepositoryMapAdapter.RepositoryMap, Map<String, Repository>>
{

    public static class RepositoryMap
    {
        @XmlVariableNode("id")
        List<RepositoryMapEntry> entries = new ArrayList<RepositoryMapEntry>();
    }

    public static class RepositoryMapEntry
    {
        @XmlAttribute
        public String id;

        @XmlValue
        public Repository repository;
    }

    @Override
    public RepositoryMap marshal(Map<String, Repository> map)
            throws Exception
    {
        RepositoryMap repositoryMap = new RepositoryMap();
        for (Map.Entry<String, Repository> entry : map.entrySet())
        {
            RepositoryMapEntry repositoryMapEntry = new RepositoryMapEntry();
            repositoryMapEntry.id = entry.getKey();
            repositoryMapEntry.repository = entry.getValue();

            System.out.println("Writing repository " + entry.getValue().getId());

            repositoryMap.entries.add(repositoryMapEntry);
        }

        return repositoryMap;
    }

    @Override
    public Map<String, Repository> unmarshal(RepositoryMap repositoryMap)
            throws Exception
    {
        List<RepositoryMapEntry> adaptedEntries = repositoryMap.entries;

        Map<String, Repository> map = new LinkedHashMap<String, Repository>(adaptedEntries.size());
        for (RepositoryMapEntry repositoryMapEntry : adaptedEntries)
        {
            System.out.println("Reading repository " + repositoryMapEntry.id);
            map.put(repositoryMapEntry.id, repositoryMapEntry.repository);
        }

        return map;
    }

}

public class StorageMapAdapter
        extends XmlAdapter<StorageMapAdapter.StorageMap, Map<String, Storage>>
{

    public static class StorageMap
    {
        @XmlVariableNode("id")
        List<StorageMapEntry> entries = new ArrayList<StorageMapEntry>();
    }

    public static class StorageMapEntry
    {
        @XmlAttribute
        public String id;

        @XmlValue
        public Storage storage;
    }

    @Override
    public StorageMap marshal(Map<String, Storage> map)
            throws Exception
    {
        StorageMap storageMap = new StorageMap();
        for (Map.Entry<String, Storage> entry : map.entrySet())
        {
            StorageMapEntry storageMapEntry = new StorageMapEntry();
            storageMapEntry.id = entry.getKey();
            storageMapEntry.storage = entry.getValue();

            System.out.println("Writing storage " + entry.getValue().getId());

            storageMap.entries.add(storageMapEntry);
        }

        return storageMap;
    }

    @Override
    public Map<String, Storage> unmarshal(StorageMap storageMap)
            throws Exception
    {
        List<StorageMapEntry> adaptedEntries = storageMap.entries;

        Map<String, Storage> map = new LinkedHashMap<String, Storage>(adaptedEntries.size());
        for (StorageMapEntry storageMapEntry : adaptedEntries)
        {
            System.out.println("Reading storage " + storageMapEntry.id);
            map.put(storageMapEntry.id, storageMapEntry.storage);
        }

        return map;
    }

}

I'd like to have the following XML:

<storages>
    <storage id="storage0">
        <repositories>
            <repository id="repository1" policy="policy1"/>
            <repository id="repository2" policy="policy2"/>
        </repositories>
    </storage>
    <storage id="storage1">
        <repositories>
            <repository id="repository3" />
            <repository id="repository4" />
        </repositories>
    </storage>
</storages>

Based on the above XML, what more do I need to do in order to have my code read/write such XML using JAXB? As far as I understand, I need to use XmlAdapter, but I'm not quite sure how to apply it to this case.


Solution

  • Something in the lines of:

    @XmlRootElement(name = "datacenter")
    public class DataCenter {
        /**
         * K: storageId V: storage
         */
        @XmlElement(name = "storages")
        @XmlJavaTypeAdapter(StorageMapAdapter.class)
        private final Map<String, Storage> storages = new LinkedHashMap<String, Storage>();
    }
    
    @XmlJavaTypeAdapter(StorageMapAdapter.class)
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Storage {
    
        @XmlAttribute(name = "id")
        private String id;
    
        /**
         * K: repository.id V: Repository
         */
        @XmlElement(name = "repositories")
        @XmlJavaTypeAdapter(RepositoryMapAdapter.class)
        private final Map<String, Repository> repositories = new LinkedHashMap<String, Repository>();
    
    }
    
    public class StorageMap {
        @XmlElement(name = "storage")
        List<Storage> entries = new ArrayList<Storage>();
    
        public List<Storage> getEntries() {
            return entries;
        }
    }
    
    public class StorageMapAdapter extends XmlAdapter<StorageMap, Map<String, Storage>> {
    
        @Override
        public StorageMap marshal(Map<String, Storage> map) throws Exception {
            StorageMap storageMap = new StorageMap();
            for (Map.Entry<String, Storage> entry : map.entrySet()) {
                storageMap.getEntries().add(entry.getValue());
            }
            return storageMap;
        }
    
        @Override
        public Map<String, Storage> unmarshal(StorageMap storageMap) throws Exception {
            List<Storage> adaptedEntries = storageMap.entries;
            Map<String, Storage> map = new LinkedHashMap<String, Storage>(adaptedEntries.size());
            for (Storage storage : adaptedEntries) {
                map.put(storage.getId(), storage);
            }
            return map;
        }
    }
    
    @XmlRootElement(name = "repository")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class Repository {
        @XmlAttribute(name = "id")
        private String id;
    
        @XmlAttribute(name = "policy")
        private String policy;
    }
    
    public class RepositoryMap {
        @XmlElement(name = "repository")
        List<Repository> entries = new ArrayList<Repository>();
    
        public List<Repository> getEntries() {
            return entries;
        }
    }
    
    public class RepositoryMapAdapter extends XmlAdapter<RepositoryMap, Map<String, Repository>> {
    
        @Override
        public RepositoryMap marshal(Map<String, Repository> map) throws Exception {
            RepositoryMap repositoryMap = new RepositoryMap();
            for (Map.Entry<String, Repository> entry : map.entrySet()) {
                repositoryMap.getEntries().add(entry.getValue());
            }
            return repositoryMap;
        }
    
        @Override
        public Map<String, Repository> unmarshal(RepositoryMap repositoryMap) throws Exception {
            List<Repository> adaptedEntries = repositoryMap.entries;
            Map<String, Repository> map = new LinkedHashMap<String, Repository>(adaptedEntries.size());
            for (Repository repository : adaptedEntries) {
                System.out.println("Reading repository " + repository.getId());
                map.put(repository.getId(), repository);
            }
            return map;
        }
    }
    

    Running demo here: http://ideone.com/NyOGVQ Demo updated with marshalling here: http://ideone.com/NzvRzX