I am using the JaxB Marshalling
to create the XML
. My XML I have few custom fields which I am creating using the JAXB XmlAdapter
option. The custom fields are created using the JAXBElement
, in which QName
is one of the parameters.
As per the QName
documentation it takes 3 parameters NamespaceURI
, LocalPart
and Prefix
. I am passing all these parameters. But for some reason, the created XML
takes the default namespace prefix ns0, ns1, ns2
etc rather than the provided one in the QName
creation parameter.
Everything is working as expected without any issue. I just want to know how can I make the QName
take up the custom prefix
value that I am passing as a parameter rather than the default namespace prefix
it's adding automatically. I am aware that if I do not pass the prefix
value then it would take the default namespace prefix
but in my case, even after passing the custom prefix
value, it's assigning the default namespaces prefix
to XML
which I want to avoid. I tried many things but still, nothing worked out.
Note: I am not using the javax Jaxb
libraray rather than that I am using the EclipseLink Moxy
which is based on Jaxb
implementation.
Currently, the created XML would look something like this: (Please note that these do not root elements or XML header rather these are chunks taken from a certain part of the XML).
<ns0:MainField xmlns:ns0="https://google.com?xsd=google.xsd">
<ns1:SubField1 xmlns:ns1="https://fb.com?xsd=fb.xsd">
<ns2:Field1 xmlns:ns2="https://fb.com?xsd=fb.xsd">Value1</ns2:Field1>
</ns1:SubField1>
<ns3:SubField3 xmlns:ns3="https://google.com?xsd=google.xsd">SubValue3</ns3:SubField3>
</ns0:MainField>
<ns4:MainField2 xmlns:ns4="https://google.com?xsd=google.xsd">MainValue2</ns4:MainField2>
Where as I am expecting it to look something like this based on the values I have provided to the QName
field:
<google:MainField xmlns:google="https://google.com?xsd=google.xsd">
<fb:SubField1 xmlns:fb="https://fb.com?xsd=fb.xsd">
<fb:Field1 xmlns:fb="https://fb.com?xsd=fb.xsd">Value1</fb:Field1>
</fb:SubField1>
<google:SubField3 xmlns:google="https://google.com?xsd=google.xsd">SubValue3</google:SubField3>
</google:MainField>
<google:MainField2 xmlns:google="https://google.com?xsd=google.xsd">MainValue2</google:MainField2>
Following is my Java
class which is creating the QName
. I have a Map<String, Object>
based on the type of value it's creating the QName
. Everything is working as expected except the part of the QName namespace prefix
.
import io.openepcis.service.jaxb.DefaultJsonSchemaNamespaceURIResolver;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.annotation.XmlAnyElement;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.xml.namespace.QName;
import org.apache.commons.lang3.NotImplementedException;
public class MapAdapter extends XmlAdapter<MapWrapper, Map<String, Object>> {
DefaultJsonSchemaNamespaceURIResolver uriResolver = new DefaultJsonSchemaNamespaceURIResolver();
@Override
public Map<String, Object> unmarshal(MapWrapper valueType) throws Exception {
throw new NotImplementedException();
}
@Override
public MapWrapper marshal(Map<String, Object> extensions) throws Exception {
if (extensions == null) {
return null;
}
MapWrapper wrapper = new MapWrapper();
List elements = new ArrayList();
//Loop through the Extensions MAP
for (Map.Entry<String, Object> property : extensions.entrySet()) {
final Optional<String> xmlNamespace = uriResolver.namespaceURIFromSchema(extension.getKey());
String namespaceURI = xmlNamespace.get();
String localPart = getLocalPart(property.getKey());
String prefix = getPrefix(property.getKey());
String label = getCleanLabel(property.getKey());
System.out.println(" namespaceURI : " + namespaceURI + " localPart : " + localPart + " prefix : " + prefix);
//If the Value type is MAP then recurse through the loop
if (property.getValue() instanceof Map) {
elements.add(new JAXBElement<MapWrapper>(new QName(namespaceURI, localPart, prefix), MapWrapper.class, marshal((Map) property.getValue())));
} else if (property.getValue() instanceof String) {
// If the Value type is String then directly create JAXBElement
elements.add(new JAXBElement<String>(new QName(namespaceURI, localPart, prefix), String.class, property.getValue().toString()));
}
}
wrapper.elements = elements;
return wrapper;
}
// Formats the complete Label for the XML tag
public static String getCleanLabel(String label) {
label = label.replaceAll("[()]", "").replaceAll("[^\\w\\s]", "_").replaceAll(" ", "_");
return label;
}
//Formats the LocalPart of the XML Tag (after ":")
public static String getLocalPart(String localPart) {
localPart = localPart.substring(localPart.indexOf(":") + 1);
return localPart;
}
//Formats the Prefix of the XML Tag (before ":")
public static String getPrefix(String prefix) {
prefix = prefix.substring(0, prefix.indexOf(":"));
return prefix;
}
}
class MapWrapper {
@XmlAnyElement
List elements;
}
class DefaultJsonSchemaNamespaceURIResolver{
private static Map<String, String> xmlNamespaces = new HashMap<String, String>();
static {
xmlNamespaces.put("google", "https://google.com");
xmlNamespaces.put("fb", "https://fb.com");
xmlNamespaces.put("insta", "https://instagram.com");
}
public Optional<String> namespaceURIFromSchema(String schemaURI) {
if (xmlNamespaces.containsKey(schemaURI)) {
return Optional.of(xmlNamespaces.get(schemaURI));
}
return Optional.empty();
}
}
Following are the dependencies I am using in my Maven pom.xml
:
<!-- JAXB Annotations Dependencies START -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>3.0.0</version>
</dependency>
<!-- JAXB Annotations Dependencies END -->
After trying a lot of things and referring to many things I was able to get it. I am using the Moxy
dependency instead of Jaxb
as it provides various additional benefits on top of the existing features in Jaxb
. Posting this answer as it can be helpful to someone in the future:
Remove the package-info.java
and all of its content (if you have added it while trying something because I see a lot of answers here are based on it).
Since you are using the Moxy
you can create a Map with all of the required NamespcaeURI
and Prefix
. Something like this:
Map<String, String> urisToPrefixes = new HashMap<String, String>();
urisToPrefixes.put("http://www.root.com", "rootNS");
urisToPrefixes.put("http://sub.root.com", "subNS");
marshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, urisToPrefixes);
This will ensure that whenever a Namespace
is encountered it would check for the respective prefix
and add it to the XML header so in this way it would replace all the default prefix ns0,ns1, etc to the corresponding prefix from the map.
JAXBContext ctx = JAXBContext.newInstance(new Class[] { TestObject.class, SubObject.class });
Map<String, String> urisToPrefixes = new HashMap<String, String>();
urisToPrefixes.put("http://www.root.com", "rootNS");
urisToPrefixes.put("http://sub.root.com", "subNS");
Marshaller m = ctx.createMarshaller();
m.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, prefixesToUris);
If you would like to read more about it then follow the official documentation here.