Search code examples
xmljaxbspring-batchadapter

Get Attribute name and value form XML in StaxEventItemReader


I am going around in circles for a problem that seems simple.

I have an XML which contains only tags with no values (only with attributes).

I want to iterate over this XML with a StaxEventItemReader but I can't.

<?xml version='1.0' encoding='UTF-8'?>
    <!DOCTYPE dataset>
    <dataset>
      <table_row id="3944552" code_a="N" code_b="232" num_abc="[NULL]" sexe="M" date_nais="1959-11-15" />
      <table_row id="3944553" code_a="[NULL]" code_b="[NULL]" num_abc="[NULL]" sexe="F" date_nais="1920-05-10" />
    </dataset>

And the reader :

private StaxEventItemReader<TableRow> createXmlReader(Resource source) throws Exception {
    StaxEventItemReader<TableRow> reader = new StaxEventItemReader<>();

    reader.setResource(source);
    reader.setFragmentRootElementName("table_row");

    Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
    jaxb2Marshaller.setClassesToBeBound(TableRow.class);
    reader.setUnmarshaller(jaxb2Marshaller);

    reader.afterPropertiesSet();

 
    return reader;
}

I am indeed trying to obtain at the output of my reader (and at the input of my processor) an object in the image of this content with a MAP which contains each attribute / value

@Setter
@Getter
public class TableRow {
    private Map<String, String> mapChampValeurs; 
}

I try to use an adapte but I cant' see how to implement it (I use @XmlJavaTypeAdapter() but I never enter in my adapter)

Do you know how I can do this?

EDIT 1

I try to add an adapter for convert all attributes in a map

public class TableRowXmlAdapter extends XmlAdapter<Object, TableRow> {

    private static final Logger LOGGER = LoggerFactory.getLogger(TableRowXmlAdapter.class);

    @Override
    public TableRow unmarshal(Object v) throws Exception {
        LOGGER.info("***** INTO ROW ADAPTER ****");

        Element element = (Element) v;

        Map<String, String> properties = new LinkedHashMap<>();
        NamedNodeMap attributes = element.getAttributes();

        for (int i = attributes.getLength() - 1; i >= 0; i--) {
            Node node = attributes.item(i);
            properties.put(node.getNodeName(), node.getNodeValue());
        }

        TableRow tableRow = new TableRow();
        tableRow.setMapChampValeurs(properties);

        return tableRow;

    }

I declare it in StaxEventItemReader :

 private StaxEventItemReader<TableRow> createXmlReader(Resource source) throws Exception {
        StaxEventItemReader reader = new StaxEventItemReader();

        reader.setResource(source);
        reader.setFragmentRootElementName(ROOT_ELEMENT);

        Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();

        TableRowXmlAdapter tableRowXmlAdapter = new TableRowXmlAdapter();
        jaxb2Marshaller.setAdapters(tableRowXmlAdapter);
        jaxb2Marshaller.setClassesToBeBound(TableRow.class);

        reader.setUnmarshaller(jaxb2Marshaller);
        reader.afterPropertiesSet();

        return reader;
    }

But when I debug this, I never enter in my adapter and there is an Exception :

Caused by: com.sun.istack.internal.SAXParseException2; lineNumber: 4; columnNumber: 94; élément inattendu (URI : "", local : "table_row"). Les éléments attendus sont (none)

Both of lineNumber and columnNumber represents the position of the last characters juste after "/>" self-closing tag


Solution

  • For information, I found the solution with the @XmlAnyAttribute annotation

    @XmlAnyAttribute
    private Map<QName, String> mapChampValeurs;
    

    With that annotation, no need more action like Adapter