How to add XML processing instructions during JAXB marshal
Asked Answered
H

1

7

I would like to add a processing instruction whenever a collection/array property is serialized to get something like

<alice>
  <? array bob ?>
  <bob>edgar</bob>
  <bob>david</bob>
</alice>

Is this possible with JAXB? Or at least with some specific JAXB implementation?

Hulk answered 3/8, 2011 at 18:40 Comment(0)
D
8

You could leverage an XMLStreamWriter and an XmlAdapter to do this:

BobAdapter

Things to note about the XmlAdapter:

  • It's stateful and references an XMLStreamWriter. We will later leverage JAXB's ability to set a stateful XmlAdapter on a Marshaller.
  • It converts from a List<String> to a List<String>, we're just using an XmlAdapter here to get an event.
  • The marshal method is where we call writeProcessingInstruction on the XMLStreamWriter:

 

package forum6931520;

import java.util.List;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.stream.XMLStreamWriter;

public class BobAdapter extends XmlAdapter<List<String>, List<String>> {

    private boolean first = true;
    private XMLStreamWriter xmlStreamWriter;

    public BobAdapter() {
    }

    public BobAdapter(XMLStreamWriter xmlStreamWriter) {
        this();
        this.xmlStreamWriter = xmlStreamWriter;
    }

    @Override
    public List<String> marshal(List<String> stringList) throws Exception {
        if(first) {
            xmlStreamWriter.writeProcessingInstruction("array", "bob");
            first = false;
        }
        return stringList;
    }

    @Override
    public List<String> unmarshal(List<String> stringList) throws Exception {
        return stringList;
    }

}

Alice

The @XmlJavaTypeAdapter annotation is used to link the XmlAdapter with the field/property:

package forum6931520;

import java.util.List;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
public class Alice {

    private List<String> bob;

    @XmlJavaTypeAdapter(BobAdapter.class)
    public List<String> getBob() {
        return bob;
    }

    public void setBob(List<String> bob) {
        this.bob = bob;
    }

}

Demo

package forum6931520;

import java.io.File;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Alice.class);

        File xml = new File("src/forum6931520/input.xml");
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Alice alice = (Alice) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        XMLOutputFactory xof = XMLOutputFactory.newFactory();
        XMLStreamWriter xmlStreamWriter = xof.createXMLStreamWriter(System.out);
        marshaller.setAdapter(new BobAdapter(xmlStreamWriter));
        marshaller.marshal(alice, xmlStreamWriter);
    }

}

input.xml

<?xml version="1.0" encoding="UTF-8"?>
<alice>
  <?array bob?>
  <bob>edgar</bob>
  <bob>david</bob>
</alice>

Output

<?xml version='1.0' encoding='UTF-8'?><alice><?array bob?><bob>edgar</bob><bob>david</bob></alice>

Note

This example needs to be run with EclipseLink JAXB (MOXy) as the JAXB RI will throw the following exception (I'm the MOXy lead):

Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 2 counts of IllegalAnnotationExceptions
java.util.List is an interface, and JAXB can't handle interfaces.
    this problem is related to the following location:
        at java.util.List
        at public java.util.List forum6931520.Alice.getBob()
        at forum6931520.Alice
java.util.List does not have a no-arg default constructor.
    this problem is related to the following location:
        at java.util.List
        at public java.util.List forum6931520.Alice.getBob()
        at forum6931520.Alice

For More Information


UPDATE

I have entered an enhancement request (https://bugs.eclipse.org/354286) to add native support for processing instructions. This would eventually allow you to specify the following on your property:

@XmlProcessingInstruction(target="array", value="bob")
public List<String> getBob() {
    return bob;
}
Deviate answered 9/8, 2011 at 18:17 Comment(8)
Thanks, I will try this with MOXy. The @ProcessingInstruction annotation would definitely nice.However, as I would like to add a PI for any list, it would be somewhat inconvenient to add an annotation for all list properties in a model. Is there a way to globally register an XmlAdapter?Hulk
@Hulk - You can register XmlAdapters at the package level with @XmlJavaTypeAdapters (see blog.bdoughan.com/2011/05/…). However since you will have Lists with different types of content, this won't work for you. Also I'm guessing the contents of the PI will be different per property as well meaning a per property solution would work better.Deviate
OK, thanks. Unfortunately, the marshal(...) method does not have any information about the property being/element. This would mean I had to implement an adapter class for every list property. Hmm... The @ProcessingInstruction annotation would be nice.Hulk
what do you think of some kind of MarshalListener which can be added to the Marshaller with a listener method that informs about the current marshal events?Hulk
@Hulk - I thought about that, but MarshalListener is at the domain object level, and you really need a field/property level event.Deviate
ried your code. Instead once for the list, BobAdapter is called for each list element which causes a ClassCastException.Hulk
Note that the PI is to appear once per array sequence.Hulk
thanks; all in all, I feel the XmlAdapter solution is not an option in practice. The ProcessingInstruction annotation would be OK, but it's not available. What I am really looking for is a way to intercept the marshalling process to add a PI for each and every list.Hulk

© 2022 - 2024 — McMap. All rights reserved.