eclipse-ee4j / jaxb-ri

Jaxb RI
https://eclipse-ee4j.github.io/jaxb-ri/
BSD 3-Clause "New" or "Revised" License
206 stars 114 forks source link

Custom namespace marshalling using JAXB RI results in property "org.glassfish.jaxb.runtime.marshaller.NamespacePrefixMapper" is not supported #1795

Open Aravinda93 opened 9 months ago

Aravinda93 commented 9 months ago

For convience I have added the sample code repo here: https://github.com/Aravinda93/test/tree/main

I am using JAXB RI to marshall the Java objects into XML but during the marshalling I need to provide some of the custom namespaces to my Marshaller and JAXBContext. I referred the documentation and answer here to do the same but its resulting in the error:

Exception in thread "main" jakarta.xml.bind.JAXBException: property "org.glassfish.jaxb.runtime.marshaller.NamespacePrefixMapper" is not supported
    at org.glassfish.jaxb.runtime.v2.ContextFactory.createContext(ContextFactory.java:126)
    at org.glassfish.jaxb.runtime.v2.ContextFactory.createContext(ContextFactory.java:246)
    at org.glassfish.jaxb.runtime.v2.JAXBContextFactory.createContext(JAXBContextFactory.java:58)
    at jakarta.xml.bind.ContextFinder.find(ContextFinder.java:324)
    at jakarta.xml.bind.JAXBContext.newInstance(JAXBContext.java:392)
    at io.test.convert.MainXML.main(MainXML.java:27)

Following is the completed code I have: Child1.class:

package io.test.convert;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;

@XmlRootElement(name = "Child1")
@XmlType(
        name = "Child1",
        propOrder = {
                "name",
                "age",
                "originalName"
        },
        factoryClass = ObjectFactory.class,
        factoryMethod = "createChild1")
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@XmlAccessorType(XmlAccessType.FIELD)
public class Child1 extends Parent {
    private String originalName;
}

Parent.class:

package io.test.convert;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlTransient;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
@XmlAccessorType(XmlAccessType.FIELD)
@XmlTransient
@Builder
public class Parent implements Serializable {
    @XmlTransient
    private String type;
    private String name;
    private String age;
}

CustomNamespacePrefixMapper.class:

package io.test.convert;

import org.glassfish.jaxb.runtime.marshaller.NamespacePrefixMapper;
import java.util.HashMap;
import java.util.Map;

public class CustomNamespacePrefixMapper extends NamespacePrefixMapper {
    public static final Map<String, String> NAMESPACE_MAP = Map.of(
            "http://www.w3.org/2001/XMLSchema-instance", "xsi",
            "https://example1.com/", "example1",
            "https://example2.com/", "example2");

    private Map<String, String> namespaceMap;
    public CustomNamespacePrefixMapper(final Map<String, String> namespaceMap) {
        this.namespaceMap = namespaceMap;
    }
    public CustomNamespacePrefixMapper() {
        this(new HashMap<>(NAMESPACE_MAP));
    }
    @Override
    public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
        return namespaceMap.getOrDefault(namespaceUri, suggestion);
    }
}

MainXML.class:

package io.test.convert;

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.UnmarshalException;
import jakarta.xml.bind.Unmarshaller;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

public class MainXML {
    public static void main(String[] args) throws Exception {
        final Map<String, String> myNamespaces = new HashMap<>();
        myNamespaces.put("test", "https://test.com");
        myNamespaces.put("test2", "https://test2.com");

        final InputStream xmlInputStream = MainXML.class.getResourceAsStream("/SampleXML.xml");

        final XMLInputFactory inputFactory = XMLInputFactory.newInstance();
        inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
        inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
        final XMLStreamReader xmlStreamReader = inputFactory.createXMLStreamReader(xmlInputStream);

        final JAXBContext jaxbContext = JAXBContext.newInstance("io.test.convert", Thread.currentThread().getContextClassLoader(),
                new HashMap<>() {
                    {
                        put(
                                "org.glassfish.jaxb.runtime.marshaller.NamespacePrefixMapper",
                                new CustomNamespacePrefixMapper());
                    }
                });
       //final JAXBContext jaxbContext = JAXBContext.newInstance("io.test.convert", Thread.currentThread().getContextClassLoader());
        final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        xmlStreamReader.next();

        final Marshaller marshaller = jaxbContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
        marshaller.setProperty("org.glassfish.jaxb.runtime.marshaller.NamespacePrefixMapper", new CustomNamespacePrefixMapper());

        try {
            while (xmlStreamReader.hasNext()) {
                Object event = unmarshaller.unmarshal(xmlStreamReader, Child1.class).getValue();
                if (event instanceof Child1) {
                    Child1 child1 = (Child1) event;
                    marshaller.marshal(child1, System.out);
                }
            }
        } catch (UnmarshalException e) {
            // Handle any unmarshalling exceptions
            e.printStackTrace();
        }
    }
}

Following is my sampleXML.xml:

<Child1>
    <type>ChildType</type>
    <name>Batman</name>
    <age>30</age>
    <originalName>Bruce</originalName>
</Child1>

ObjectFactory.class:

package io.test.convert;

import jakarta.xml.bind.annotation.XmlRegistry;

@XmlRegistry
public final class ObjectFactory {
    private ObjectFactory() {}
    public static Child1 createChild1() {
        return new Child1();
    }
}

When I run I get the error:

Exception in thread "main" jakarta.xml.bind.JAXBException: property "org.glassfish.jaxb.runtime.marshaller.NamespacePrefixMapper" is not supported

How to provide the custom namespaces to the JAXBContext/Marshaller so it can be used during the marshalling of the Java objects to XML?

Following are my dependencies:

  <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>4.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>4.0.1</version>
        </dependency>

        <dependency>
            <groupId>jakarta.xml.bind</groupId>
            <artifactId>jakarta.xml.bind-api</artifactId>
            <version>4.0.1</version>
        </dependency>
    </dependencies>
antoniosanct commented 9 months ago

@Aravinda93

  1. The correct name of the property is "org.glassfish.jaxb.namespacePrefixMapper".
  2. This property is exclusive for marshalling. You can not use at unmarshalling process.

Regards, Antonio.

Aravinda93 commented 9 months ago

@antoniosanct Thanks a lot for the response. For your convience I have added the sample code repo here: https://github.com/Aravinda93/test/tree/main

I was trying couple of things thats the reason I added the org.glassfish.jaxb.runtime.marshaller.NamespacePrefixMapper but I have tried the org.glassfish.jaxb.namespacePrefixMapper and that did not work either for me.

As you mentioned I have removed the Unmarshaller part although I was not using it and kept only the Marshaller but still I get the same error:

Exception in thread "main" jakarta.xml.bind.JAXBException: property "org.glassfish.jaxb.namespacePrefixMapper" is not supported

Following is my main class updated:

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Marshaller;

import java.util.HashMap;
import java.util.Map;

public class MainXML {
    public static void main(String[] args) throws Exception {
        final Map<String, String> myNamespaces = new HashMap<>();
        myNamespaces.put("test", "https://test.com");
        myNamespaces.put("test2", "https://test2.com");

        final JAXBContext jaxbContext = JAXBContext.newInstance("io.test.convert", Thread.currentThread().getContextClassLoader(),
                new HashMap<>() {
                    {
                        put(
                                "org.glassfish.jaxb.namespacePrefixMapper",
                                new CustomNamespacePrefixMapper());
                    }
                });
        //final JAXBContext jaxbContext = JAXBContext.newInstance("io.test.convert", Thread.currentThread().getContextClassLoader());

        final Marshaller marshaller = jaxbContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
        marshaller.setProperty("org.glassfish.jaxb.namespacePrefixMapper", new CustomNamespacePrefixMapper());

        final Child1 child1 = new Child1();
        child1.setName("Batman");
        child1.setAge("30");
        child1.setType("Superhero");
        child1.setOriginalName("Bruce Wayne");

        marshaller.marshal(child1, System.out);
    }
}
antoniosanct commented 8 months ago

@Aravinda93 You can not set this property at context initialization. See constants defined here to check what properties you can initialize. Lines 23 and 28 of your class are correct (delete hashmap initialization in 15 and uncomment 23 to run fine).

Regards, Antonio.