metal7
metal7

Reputation: 265

Can a part of XML be marshalled using JAXB (or JAXB + StAX)?

I have an XML structure which is very huge. I want to update the parts of this XML, by unmarshalling one element and then applying business logic.

I am able to unmarshal a child element into a POJO. I want to make changes to this POJO in Java and then update it back to the XML at the same location.

Is this possible in JAXB? Or by using the combination of JAXB + StAX.

Example Structure :

 <folder id="c5718b36-bab1-4c08-8f75-8e2f9aee42c5" name="Folder-1">
        <description> folder Desc</description>
        <createdBy>User2</createdBy>
        <hidden>false</hidden>

        <file id="4f2efb42-0604-4878-9e1e-ae90d66fb836" name="File-1">
            <description>file desc</description>
            <createdBy>User1</createdBy>
            <hidden>false</hidden>
        </file>
 </folder>

In the above example, I am able to unmarshal a 'file' element into a POJO. I want to make changes to this POJO and then update the same in the XML file at its correct location.

How can I accomplish this ?

Please help me. Thanks.

Upvotes: 6

Views: 8941

Answers (3)

Zhenyria
Zhenyria

Reputation: 447

I only want to add that you can apply partionally unmarshalling by JAXB, but it can be accosiated with some problems.

For example i used the same partionally unmarshalling (but i parsed my XML with XMLStreamReader, not XMLEventReader) with the follow XSD:

<xs:element name="Payload">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="Cities">
                <xs:complexType>
                    <xs:sequence minOccurs="1" maxOccurs="unbounded">
                        <xs:element name="City">
                            <xs:complexType>
                                <xs:simpleContent>
                                    <xs:extension base="xs:string">
                                        <xs:attribute type="xs:ID" name="id" use="required"/>
                                    </xs:extension>
                                </xs:simpleContent>
                            </xs:complexType>
                        </xs:element>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:element name="Users">
                <xs:complexType>
                    <xs:sequence minOccurs="0" maxOccurs="unbounded">
                        <xs:element name="User">
                            <xs:complexType>
                                <xs:simpleContent>
                                    <xs:extension base="xs:string">
                                        <xs:attribute name="email"/>
                                        <xs:attribute type="xs:IDREF" name="city" use="required"/>
                                    </xs:extension>
                                </xs:simpleContent>
                            </xs:complexType>
                        </xs:element>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

And i used partionally unmarshalling by JAXB for User objects which are related with cities. But there was not city in the unmarshalled user:

User user = unmarshaller.unmarshal(xsr, User.class);
user.getCity().getId() // throws NullPointerException

Apparently JAXB just does not see earlier cities objects in the parsing XML so i should use the follow workaround:

String cityId = xsr.getAttributeValue(null, "city")
User user = unmarshaller.unmarshal(xsr, User.class);

At last i had read the javadoc for the method Unmarshaller#unmarshal(javax.xml.stream.XMLStreamReader reader, Class<T> declaredType):

Unmarshal root element to JAXB mapped declaredType and return the resulting content tree.
This method implements unmarshal by declaredType.
This method assumes that the parser is on a START_DOCUMENT or START_ELEMENT event. 
Unmarshalling will be done from this start event to the corresponding end event.
If this method returns successfully, the reader will be pointing at the token right after the end event.

Apparently the method is not supposed for such using and you should pay much attention when you use the methoud by this way.

Upvotes: 0

bdoughan
bdoughan

Reputation: 148987

You could do the following with JAXB and StAX:

import javax.xml.bind.*;
import javax.xml.stream.*;
import javax.xml.stream.events.XMLEvent;
import java.io.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        XMLInputFactory xif = XMLInputFactory.newFactory();
        XMLOutputFactory xof = XMLOutputFactory.newFactory();

        try(
           FileInputStream in = new FileInputStream("in.xml");
           FileOutputStream out = new FileOutputStream("out.xml");
        ) {
            XMLEventReader xer = xif.createXMLEventReader(in);
            XMLEventWriter xew = xof.createXMLEventWriter(out);

            JAXBContext jc = JAXBContext.newInstance(File.class);
            Unmarshaller unmarshaller = jc.createUnmarshaller();
            Marshaller marshaller = jc.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);

            while(xer.hasNext()) {
                if(xer.peek().isStartElement() && xer.peek().asStartElement().getName().getLocalPart().equals("file")) {
                    // Unmarshal the File object from the XMLEventReader
                    File file = (File) unmarshaller.unmarshal(xer);

                    // Modify the File object
                    file.description = "NEW DESCRIPTION";

                    // Marshal the File object to the XMLEventWriter
                    marshaller.marshal(file, xew);
                } else {
                    // Copy node from reader to writer
                    xew.add(xer.nextEvent());
                }
            }
        }
    }

}

Upvotes: 7

lexicore
lexicore

Reputation: 43651

Try the Binder, see the tutorial.

If you want StAX, see the methods:

  • javax.xml.bind.Unmarshaller.unmarshal(XMLStreamReader, Class<T>)
  • javax.xml.bind.Marshaller.marshal(Object, XMLStreamWriter)

You can unmarshal a known klass from stream, process it and marshal back. This can even be done in a "filter"-like process. I.e.:

  • You parse your XML via StAX.
  • You check the coming events until you see someting you want to process.
  • Irrelevant events are passed further on.
  • When you get a relevant event, you unmarshal your class with JAXB, process the pojo and marshal it back to the stream writer.

Upvotes: 5

Related Questions