highsource / jaxb-tools

The most advanced JAXB2 Maven Plugin for XML Schema compilation.
Other
434 stars 99 forks source link

[BUG] JAXB2 Namespace Prefix Plugin creating duplicated package info namespace #577

Open mathisgauthey opened 4 days ago

mathisgauthey commented 4 days ago

Hey there.

My usage of jaxb-tools

I'm using jaxb-tools in a factur-x API for :

Context

Basically, my process involves :

  1. Sending a json file with the appropriate serialized object along with a pdf
  2. The json is mapped into an object from the jaxb-generated classes
  3. I then marshal this object into a proper xml file with the correct namespaces
  4. I incorporate the xml into the pdf to create a factur-x file

My issue

My issue is related to namespaces associations in the package-info file.

I don't understand why my generated package-info would be different if my executions in the pom.xml file are identical, same thing for the bindings.xjb files. But here they are :

image

Hence, the minimal profile is working fine with proper generation of factur-x compliant file.

The 4 other profiles don't work well at all.

Reproducible example

Here is my plugin config inside my pom.xml :

<plugin>
                <groupId>org.jvnet.jaxb</groupId>
                <artifactId>jaxb-maven-plugin</artifactId>
                <version>4.0.8</version>
                <configuration>
                    <extension>true</extension>
                    <args>
                        <arg>-Xnamespace-prefix</arg>
                    </args>
                    <plugins>
                        <plugin>
                            <groupId>org.jvnet.jaxb</groupId>
                            <artifactId>jaxb-plugins</artifactId>
                            <version>4.0.8</version>
                        </plugin>
                    </plugins>
                </configuration>
                <executions>
                    <execution>
                        <id>xjc-minimum</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <schemaDirectory>${project.basedir}/src/main/resources/schemas/minimum</schemaDirectory>
                            <generateDirectory>${project.build.directory}/generated-sources/jaxb/minimum</generateDirectory>
                            <generatePackage>REDACTED.entity.generated.minimum</generatePackage>
                            <cleanPackageDirectories>false</cleanPackageDirectories>
                        </configuration>
                    </execution>
                    <execution>
                        <id>xjc-basicwl</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <schemaDirectory>${project.basedir}/src/main/resources/schemas/basicwl</schemaDirectory>
                            <generateDirectory>${project.build.directory}/generated-sources/jaxb/basicwl</generateDirectory>
                            <generatePackage>REDACTED.entity.generated.basicwl</generatePackage>
                        </configuration>
                    </execution>
                    <execution>
                        <id>xjc-basic</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <schemaDirectory>${project.basedir}/src/main/resources/schemas/basic</schemaDirectory>
                            <generateDirectory>${project.build.directory}/generated-sources/jaxb/basic</generateDirectory>
                            <generatePackage>REDACTED.entity.generated.basic</generatePackage>
                        </configuration>
                    </execution>
                    <execution>
                        <id>xjc-en16931</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <schemaDirectory>${project.basedir}/src/main/resources/schemas/en16931</schemaDirectory>
                            <generateDirectory>${project.build.directory}/generated-sources/jaxb/en16931</generateDirectory>
                            <generatePackage>REDACTED.entity.generated.en16931</generatePackage>
                        </configuration>
                    </execution>
                    <execution>
                        <id>xjc-extended</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <schemaDirectory>${project.basedir}/src/main/resources/schemas/extended</schemaDirectory>
                            <generateDirectory>${project.build.directory}/generated-sources/jaxb/extended</generateDirectory>
                            <generatePackage>REDACTED.entity.generated.extended</generatePackage>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

Here you can find the approporiate filestructure using the xsd files from fnfe-mpe.org zip :

image

And finally, here is a bindings.xjb file example, only the schemaLocation is changing :

<?xml version="1.0"?>
<jxb:bindings xmlns:jxb="https://jakarta.ee/xml/ns/jaxb"
              xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
              xmlns:namespace="urn:jaxb.jvnet.org:plugin:namespace-prefix"
              version="3.0">
    <jxb:globalBindings>
        <xjc:simple/>
    </jxb:globalBindings>
    <jxb:bindings schemaLocation="Factur-X_1.0.07_MINIMUM.xsd">
        <jxb:schemaBindings>
            <jxb:package name="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"/>
        </jxb:schemaBindings>
        <jxb:bindings>
            <namespace:prefix name="rsm"/>
            <namespace:prefix name="ram" namespaceURI="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"/>
            <namespace:prefix name="udt" namespaceURI="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"/>
            <namespace:prefix name="qdt" namespaceURI="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"/>
        </jxb:bindings>
    </jxb:bindings>
</jxb:bindings>

Note that the globalBindings part is used for defining the XmlRootElement that would otherwise be missing when marshalling to xml :

jakarta.xml.bind.MarshalException<EOL> - with linked exception:<EOL>[com.sun.istack.SAXException2: unable to marshal type "REDACTED.entity.generated.extended.CrossIndustryInvoiceType" as an element because it is missing an @XmlRootElement annotation

Questions

Thanks in advance and have a great day !

laurentschoelens commented 4 days ago

Hi @mathisgauthey

Thanks for your detailed issue.

We'll check soon if it is a bug or a configuration problem.

Regards

laurentschoelens commented 2 days ago

Hi @mathisgauthey : you're mentionning "fnfe-mpe.org zip" but I can't find the mentionned zip file above. Could you share it please as MRE ? Thanks šŸ˜„

mathisgauthey commented 2 days ago

Hi @mathisgauthey : you're mentionning "fnfe-mpe.org zip" but I can't find the mentionned zip file above. Could you share it please as MRE ? Thanks šŸ˜„

Hey there, I just downloaded it in french and remove the unwanted files so that it fits the 25mo Github limit.

You'll find xsd files along with factur-x PDF examples.

factur-x.zip

mathisgauthey commented 1 day ago

I happened to find a workaround that could help locate the problem.

Removing this part from the binding files :

<jxb:globalBindings>
        <xjc:simple/>
    </jxb:globalBindings>

It allows me to use CrossIndustryInvoiceType.java instead of CrossIndustryInvoice.java and it removed the unusual duplicated package-info :

//
// This file was generated by the Eclipse Implementation of JAXB, v4.0.5 
// See https://eclipse-ee4j.github.io/jaxb-ri 
// Any modifications to this file will be lost upon recompilation of the source schema. 
//

@jakarta.xml.bind.annotation.XmlSchema(namespace = "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100", elementFormDefault = jakarta.xml.bind.annotation.XmlNsForm.QUALIFIED, xmlns = {
    @jakarta.xml.bind.annotation.XmlNs(namespaceURI = "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100", prefix = "ram"),
    @jakarta.xml.bind.annotation.XmlNs(namespaceURI = "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100", prefix = "udt"),
    @jakarta.xml.bind.annotation.XmlNs(namespaceURI = "urn:un:unece:uncefact:data:standard:QualifiedDataType:100", prefix = "qdt"),
    @jakarta.xml.bind.annotation.XmlNs(namespaceURI = "urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100", prefix = "rsm")
})
package REDACTED.entity.generated.extended;

And then, the only issue I had left was related to using an Object of CrossIndustryInvoiceType : There was an error with a missing @XmlRootElement which I solved by marshalling differently :

Old :

private static String getXmlString(Object invoice) throws JsonProcessingException {
        try {
            JAXBContext jaxbContext = JAXBContext.newInstance(invoice.getClass());
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            StringWriter stringWriter = new StringWriter();
            marshaller.marshal(invoice, stringWriter);
            return stringWriter.toString();
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }

New :

private static <T> String getXmlString(T invoice) throws JsonProcessingException {
        try {
            JAXBContext jaxbContext = JAXBContext.newInstance(invoice.getClass());
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            StringWriter stringWriter = new StringWriter();
            marshaller.marshal(new JAXBElement<>(new QName("urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100", "CrossIndustryInvoice", "rsm"), (Class<T>) invoice.getClass(), invoice), stringWriter);
            return stringWriter.toString();
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }

It now works fine, I needed to modify some of my jsons input files because removing the part from the bindings file renamed some attributes from plural to strictly singular. But it now works fine !

laurentschoelens commented 1 day ago

That's good news šŸ˜„

You can manually add the @XmlRootElement to the CrossIndustryInvoiceType by using the jaxb-annotate-plugin with appropriate binding file.