Juru
Juru

Reputation: 1629

Jaxb2Marshaller in StaxEventItemReader causing UnmarshalException

So I'm trying to parse an xml and unmarshal it into a program. This is the example xml:

<MaintenanceTransaction:provideCommunicationEvents_BatchRequest
        xmlns:MaintenanceTransaction="http://www.pwx.com/Interface/CRS/MaintenanceTransaction_v02"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <MaintenanceTransaction:maintenanceTransaction>
        <MaintenanceTransaction:eventInitiatedBy>
            <MaintenanceTransaction:identifier>FINACLE</MaintenanceTransaction:identifier>
        </MaintenanceTransaction:eventInitiatedBy>
        <MaintenanceTransaction:transactionDate>2014-06-21T19:00:32.356+01:00</MaintenanceTransaction:transactionDate>
        <MaintenanceTransaction:maintenanceTransactionType>PRE-NOTIFICATION
        </MaintenanceTransaction:maintenanceTransactionType>
        <MaintenanceTransaction:maintenanceEntries>
            <MaintenanceTransaction:hasNewValues xsi:type="MaintenanceTransaction:DepositArrangement">
                <MaintenanceTransaction:enterpriseId xsi:type="MaintenanceTransaction:ArrangementIdentifier">
                    <MaintenanceTransaction:identifier>222000000322</MaintenanceTransaction:identifier>
                    <MaintenanceTransaction:enterpriseIdType>Term Deposit</MaintenanceTransaction:enterpriseIdType>
                </MaintenanceTransaction:enterpriseId>
                <MaintenanceTransaction:maturityDate>2014-10-08</MaintenanceTransaction:maturityDate>
            </MaintenanceTransaction:hasNewValues>
        </MaintenanceTransaction:maintenanceEntries>
    </MaintenanceTransaction:maintenanceTransaction>
</MaintenanceTransaction:provideCommunicationEvents_BatchRequest>

The xsd is defined as:

<xsd:schema xmlns:MaintenanceTransaction="http://www.pwx.com/Interface/CRS/MaintenanceTransaction_v02" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.pwx.com/Interface/CRS/MaintenanceTransaction_v02" elementFormDefault="qualified" attributeFormDefault="qualified">
    <xsd:include schemaLocation="./commonIFWxsd/IFWXML.xsd" />
    <xsd:include schemaLocation="./commonIFWxsd/Event.xsd" />
    <xsd:include schemaLocation="./commonIFWxsd/Arrangement.xsd" />
    <!-- end of TYPES REQUIRED FOR PARAMETERS -->
    <xsd:complexType name="provideCommunicationEvents_BatchRequest">
        <xsd:sequence>
            <xsd:element name="maintenanceTransaction" type="MaintenanceTransaction:MaintenanceTransaction" maxOccurs="unbounded" />
        </xsd:sequence>
    </xsd:complexType>
    <!-- TYPES REQUIRED FOR PARAMETERS -->
    <xsd:element name="provideCommunicationEvents_BatchRequest" type="MaintenanceTransaction:provideCommunicationEvents_BatchRequest" />
</xsd:schema>

With the following setup of my StaxEventItemReader and Jaxb2Marshaller:

<bean id="uploadEventMessageReader" parent="abstractUploadEventMessageReader" scope="step">
    <property name="resource" value="file:#{jobExecutionContext['fileToProcess']}"/>
    <property name="fragmentRootElementName" value="maintenanceTransaction"/>
    <property name="unmarshaller" ref="maintenanceTransactionUnmarshaller"/>
</bean>

<bean id="maintenanceTransactionUnmarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller" scope="step">
    <property name="classesToBeBound">
        <list>
            <value>com.pwx.crs.informationcollection.ifw.process.model.mt.MaintenanceTransaction</value>
        </list>
    </property>
</bean>

The problem however is that I get the following exception.

 * [javax.xml.bind.UnmarshalException: unexpected element (uri:"http://www.pwx.com/Interface/CRS/MaintenanceTransaction_v02", local:"maintenanceTransaction"). Expected elements are (none)] on step uploadEventMessages, with message: uploadEventMessagesStep. 
 * --stacktrace:com.pwx.crs.frwk.exp.technical.CRS2UnexpectedBatchException: An unexpected exception occurred in batch job JAXB unmarshalling exception; nested exception is javax.xml.bind.UnmarshalException

Any idea what this is about?

When I slightly change the maintenanceTransaction opening tag of the input xml like this:

<MaintenanceTransaction:maintenanceTransaction xsi:type="MaintenanceTransaction:MaintenanceTransaction">

It does work. But that is not a solution as the clients won't deliver the input xml's like that. So why is the error occuring? There seems to be a problem with determining which class maintenanceTransaction is part of.

Also tried different approaches on the marshaller bean definition:

<bean id="maintenanceTransactionUnmarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller" scope="step">
    <property name="contextPath" value="com.pwx.crs.informationcollection.ifw.process.model.mt"/>
</bean>

and

<bean id="maintenanceTransactionUnmarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller" scope="step">
    <property name="classesToBeBound">
        <list>
            <value>com.pwx.crs.informationcollection.ifw.process.model.mt.ObjectFactory</value>
        </list>
    </property>
</bean>

The results for both are the same and similar to the first error

javax.xml.bind.UnmarshalException: unexpected element (uri:"http://www.pwx.com/Interface/CRS/MaintenanceTransaction_v02", local:"maintenanceTransaction"). Expected elements are <{http://www.pwx.com/Interface/CRS/MaintenanceTransaction_v02}AccessTokenLifecycleStatus> ... and here the whole list of classes in the package.

But the xml is perfectly valid against the xsd, as that is done beforehand. Idea's, suggestions, ... that I could try?

Upvotes: 3

Views: 2726

Answers (3)

Juru
Juru

Reputation: 1629

So after researching through the code and debugging and much more labour I ended up with something that works. I don't exactly know why, as it was a guess, but it works.

<bean id="maintenanceTransactionUnmarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
    <property name="classesToBeBound">
        <list>
            <value>com.pwx.crs.informationcollection.ifw.process.model.mt.MaintenanceTransaction</value>
        </list>
    </property>
    <property name="mappedClass" value="com.pwx.crs.informationcollection.ifw.process.model.mt.MaintenanceTransaction"/>
</bean>

As you can see it was not necessary to use the ObjectFactory as classesToBeBound (But it has to be noted that it would have worked as well but in my opinion this is more readable). The thing that really fixes it is adding a mappedClass.

From what I have seen in the code while debugging (I could not check the code f UnmarshallingImpl as this is part of rt.jar and this is not public even though it is part of the jre ...) The classesToBeBound parameter is used to create the jaxbContext for the unmarshaller and the mappedClass is passed to this unmarshaller as expectedType. But when this is not provided and the ObjectFactory is used as parameter the error clearly shows a list of expected classes and the one applicable is present ... Maybe a bug in the unmarshaller, maybe something that I don't understand. But my root problem is solved.

Upvotes: 5

lexicore
lexicore

Reputation: 43709

Here's my theory.

What mappedClass actually does is switches the unmarshalling method from unmarshaller.unmarshal(source) to unmarshaller.unmarshal(source, this.mappedClass). This is called "partial unmarshalling", i.e. you can unmarshal a known class from some element regardless of the element name.

So, as you say, this works for you. And it does not without mappedClass. This would mean that you are missing an element declaration for your root element. This also alignes well with the error you have reported:

javax.xml.bind.UnmarshalException: unexpected element
(uri:"http://www.pwx.com/Interface/CRS/MaintenanceTransaction_v02",
local:"maintenanceTransaction"). Expected elements are (none)

Which says basically the same thing.

This is also correct. As your schema does not declare a global element for the maintenanceTransaction, only for provideCommunicationEvents_BatchRequest.

So, basically you're unmarshalling the wrong element. And as your schema does not have a declaration for that, this fails. But if you specify exactly which type you want (thus providing the declaration "explicitly"), then it works.

Next, the question is, why do you you unmarshal the wrong element. My guess is that this is because you have

<property name="fragmentRootElementName" value="maintenanceTransaction"/>

This probably points the unmarshaller to use the maintenanceTransaction element as the root element. So the unmarshaller is applied to the wrong element.

Using contextPath, adding more bound classes etc. does not help as none of it adds the element declaration for the maintenanceTransaction.

Now, how to fix this. I see the following options:

  • Add a global element for maintenanceTransaction to your schema
  • (OR) Customize your schema to generate an additional @XmlRootElement for MaintenanceTransaction
  • (OR) Don't use the fragmentRootElementName, unmarshal provideCommunicationEvents_BatchRequest
  • (OR) Leave it as is with mappedClass

If you handle a specific case here, namely maintenanceTransaction then I'd use the fragmentRootElementName/mappedClass combo. Just as you do now.

Upvotes: 5

bdoughan
bdoughan

Reputation: 149047

When creating a JAXBContext on a model generated from an XML Schema you should do one of the following to get all the necessary metadata:

Option #1 - Create it on the Generated ObjectFactory Class

<bean id="maintenanceTransactionUnmarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller" scope="step">
    <property name="classesToBeBound">
        <list>
            <value>com.pwx.crs.informationcollection.ifw.process.model.mt.ObjectFactory</value>
        </list>
    </property>
</bean>

Option #2 - Create it on the Package Name(s) of the Generated Model

<bean id="maintenanceTransactionUnmarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller" scope="step">
    <property name="contextPath" value="com.pwx.crs.informationcollection.ifw.process.model.mt"/>
</bean>

Upvotes: 0

Related Questions