I'm currently trying to unmarshal some XML into a java object using JAXB and I'm getting a strange Null Pointer Exception. This is only an issue while unmarshalling. I can marshal with these classes just fine. Here are the relevant pieces of code (irrelevant portions are denoted by "..."):
The JAXB Root element:
...
#XmlRootElement(name="assets")
public class Assets {
String product;
Images images = new Images();
...
Public Assets() {}
...
public String getProduct() { return this.product; }
#XmlAttribute(name="product")
public void setProduct(String newProduct) { this.product = newProduct; }
public Images getImages() { return this.images; }
#XmlElement(name="images")
public void setImages(Images newImages) { this.images = newImages; }
...
}
The Images sub-element of the root element:
...
#XmlRootElement(name="images")
#XmlSeeAlso(Image.class)
#XmlType(propOrder = {"mainAsset", "image"})
public class Images {
List<Image> images;
Image mainAsset;
private static char counter = 'a';
private static final String prefix = "product-image-";
// Here's the part that causes the NPE
#XmlAnyElement(lax=true)
public List<JAXBElement<Image>> getImage() {
final List<JAXBElement<Image>> imageList = new ArrayList<JAXBElement<Image>>();
for (final Image g : this.images) {
imageList.add(new JAXBElement(new QName(prefix + counter++), Image.class, g));
}
counter = 'a';
return imageList;
}
#XmlElement(name="renditions")
public void setImage(List<Image> newImages) { this.images = newImages; }
public Image getMainAsset() { return this.mainAsset; }
#XmlElement(name="main-asset-name")
public void setMainAsset(Image newMainAsset) { this.mainAsset = newMainAsset; }
}
The logic for unmarshalling the XML:
...
public void modifyXML() {
try {
JAXBContext context = JAXBContext.newInstance(Assets.class, Images.class, Image.class);
Unmarshaller um = context.createUnmarshaller();
File f = new File("/path/to/my.xml");
Assets assets = (Assets) um.unmarshal(f);
...
} catch (JAXBException e) {
e.printStackTrace();
}
}
...
Finally, the XML I'm trying to unmarshal (it might help to know that this xml file was actually generated using the JAXB marshaller, which runs without any problems):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assets product="TEST PRODUCT">
<images>
<main-asset-name>
<path>stuff.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</main-asset-name>
<product-image-a>
<path>48x48.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</product-image-a>
<product-image-b>
<path>140x140.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</product-image-b>
<product-image-c>
<path>1280x1280.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</product-image-c>
<product-image-d>
<path>319x319.jpg</path>
<title>theTitle</path>
<alt>theAlt</path>
<description>theDescription</description>
</product-image-d>
</images>
</assets>
Okay, so that's all the relevant code (I think). When I run my program, I get the following error right after invoking the unmarshaller:
java.lang.NullPointerException
at com.my.companys.name.Images.getImage(Images.java:25)
And the line number referenced is the line where the for loop starts in my Images.java class file.
Does anyone have any ideas why this.images might be null here? Any help is greatly appreciated. Thanks ahead of time.
Here's the solution I've been using for now. It's a bit of a hack, but hell... It does the job.
First, I get the raw xml as a string instead of reading it as a file:
public void modifyXML() {
try {
JAXBContext context = JAXBContext.newInstance(Assets.class, Image.class);
Unmarshaller um = context.createUnmarshaller();
// This xml should be formatted in a way that JAXB can work with
String rawXML = getRawXML("/path/to/my.xml");
// Hand the modified xml to the unmarshaller
Assets umAssets = (Assets) um.unmarshal(new StreamSource(new StringReader(rawXML)));
// Do stuff with umAssets
...
} catch (Exception e) {
e.printStackTrace();
}
}
private String getRawXML(String path) throws IOException {
String xml = readFile(path, StandardCharsets.UTF_8);
// This should make it so JAXB can handle the xml properly
xml = xml.replaceAll("<main-asset-name>", "<image>");
xml = xml.replaceAll("</main-asset-name>", "</image>");
xml = xml.replaceAll("<product-image.*>", "<image>");
return xml.replaceAll("</product-image.*>", "</image>");
}
private String readFile(String path, Charset encoding) throws IOException {
// A simple file reader
byte[] encoded = Files.readAllBytes(Paths.get(path));
return new String(encoded, encoding);
}
This seems to do the trick, so I'm going to use this for now. Of course, I'm still open to better solutions if anyone has one.
Related
<soapenv:Fault xmlns:m="http://schemas.xmlsoap.org/soap/envelope/">
<faultcode>m:Server</faultcode>
<faultstring>someFaultString</faultstring>
<detail>
<NS1:confirmIdentityErrorInfo xmlns:NS1="example.com">
<faultInfo>
<faultType>Business Exception</faultType>
<faultCode>100</faultCode>
<message>some message</message>
<faultState>fault state</faultState>
</faultInfo>
</NS1:confirmIdentityErrorInfo>
</detail>
</soapenv:Fault>
I would need to generate the fault object as above. I am able to generate 'confirmIdentityErrorInfo' but not able to add child elements. I tried this option, but I couldn't generated nested object structure.
public class WebServiceConfig extends WsConfigurerAdapter {
#Bean
public SoapFaultMappingExceptionResolver exceptionResolver(){
SoapFaultMappingExceptionResolver exceptionResolver = new DetailSoapFaultDefinitionExceptionResolver();
SoapFaultDefinition faultDefinition = new SoapFaultDefinition();
faultDefinition.setFaultCode(SoapFaultDefinition.SERVER);
exceptionResolver.setDefaultFault(faultDefinition);
Properties errorMappings = new Properties();
errorMappings.setProperty(Exception.class.getName(), SoapFaultDefinition.SERVER.toString());
errorMappings.setProperty(BusinessException.class.getName(), SoapFaultDefinition.SERVER.toString());
exceptionResolver.setExceptionMappings(errorMappings);
exceptionResolver.setOrder(1);
return exceptionResolver;
}
}
protected void customizeFault(Object endpoint, Exception ex, SoapFault fault) {
if (ex instanceof BusinessException) {
SoapFaultDetail detail = fault.addFaultDetail();
try
{
QName entryName = new QName("namespace", "confirmIdentityErrorInfo", "NS1");
detail.addFaultDetailElement(entryName);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
But I am not sure how to add child elements to QName. Can someone help me how to product the fault detail as shown above?
This is the solution.
fault.addFaultDetail();
this.marshaller.marshal(((AJAXB2MarshalledBusinessException)ex).getMyJAXB2MarshallableComplexMessage(), fault.getFaultDetail().getResult());
More detailed in this link:
public class DetailSoapFaultDefinitionExceptionResolver extends SoapFaultMappingExceptionResolver {
private static final ObjectFactory FACTORY = new ObjectFactory();
private final Marshaller marshaller;
public DetailSoapFaultDefinitionExceptionResolver() throws JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance("your.schema");
this.marshaller = jaxbContext.createMarshaller();
}
#Override
protected void customizeFault(Object endpoint, Exception e, SoapFault fault) {
YourFault xsdFault = new YourFault();
xsdFault.setCode("UNKNOWN_FAILURE");
xsdFault.setDescription(e.getMessage());
SoapFaultDetail faultDetail = fault.addFaultDetail();
try {
marshaller.marshal(FACTORY.createSubscriptionManagementFault(xsdFault), faultDetail.getResult());
} catch (JAXBException e1) {
log.error("Could not marshall SubscriptionManagementFault", e);
}
}
Here is detailed explanation
https://maarten.mulders.it/2018/07/custom-soap-faults-using-spring-ws/
I need to unmarshall nested soap response to java object using jaxb. But i am always getting a null pointer exception.
I have checked almost all links out there like the following:
jaxb unmarshalling returns null
jaxb unmarshalling giving NULL to anytype element in xsd
But nothing worked out for me.
Here is my soap response message
<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsap.org/sap/env/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
<soapenv:Body>
<ns1:subscribeProductResponse xmlns:ns1='http://www.csapi.org/schema/parlayx/subscribe/manage/v1_0/local'>
<ns1:subscribeProductRsp>
<result>22007233</result>
<resultDescription>Temporary Order saved successfully! DataSendStep finish end.</resultDescription>
</ns1:subscribeProductRsp>
</ns1:subscribeProductResponse>
</soapenv:Body>
</soapenv:Envelope>
Here are my Java Pojo Classes
#XmlRootElement (name="subscribeProductResponse",namespace="http://www.csapi.org/schema/parlayx/subscribe/manage/v1_0/local")
#XmlAccessorType(XmlAccessType.FIELD)
public class SubscribeProductResponse {
// #XmlElementWrapper
#XmlElement(name="subscribeProductRsp")
private SubscribeProductRsp subscribeProductRsp;
public SubscribeProductRsp getSubscribeProductRsp() {
return subscribeProductRsp;
}
public void setSubscribeProductRsp(SubscribeProductRsp subscribeProductRsp) {
this.subscribeProductRsp = subscribeProductRsp;
}
}
#XmlRootElement(name="subscribeProductRsp")
public class SubscribeProductRsp {
private String result;
private String resultDescription;
public String getResult() {
return result;
}
#XmlElement(name = "result", required = true)
public void setResult(String result) {
this.result = result;
}
public String getResultDescription() {
return resultDescription;
}
#XmlElement(name = "resultDescription", required = true)
public void setResultDescription(String resultDescription) {
this.resultDescription = resultDescription;
}
#Override
public String toString() {
return "ClassPojo [result = " + result + ", resultDescription = "
+ resultDescription + "]";
}
}
Below is the code to unmarshal the response message
JAXBContext jc = JAXBContext.newInstance(SubscribeProductResponse.class);
Unmarshaller um = jc.createUnmarshaller();
SubscribeProductResponse output = (SubscribeProductResponse)um.unmarshal(soapResponse.getSOAPBody().extractContentAsDocument());
System.out.println(output.getSubscribeProductRsp().getResult());
JAXBContext jc = JAXBContext.newInstance(SubscribeProductResponse.class);
Unmarshaller um = jc.createUnmarshaller();
SubscribeProductResponse output = (SubscribeProductResponse)um.unmarshal(soapResponse.getSOAPBody().extractContentAsDocument());
System.out.println(output.getSubscribeProductRsp().getResult());
I am getting output.getSubscribeProductRsp() as null
Can anyone please tell me what i am doing wrong.
I have JAXB objects created from a schema. While marshalling, the xml elements are getting annotated with ns2. I have tried all the options that exist over the net for this problem, but none of them works. I cannot modify my schema or change package-info.java. Please help
After much research and tinkering I have finally managed to achieve a solution to this problem. Please accept my apologies for not posting links to the original references - there are many and I wasn't taking notes - but this one was certainly useful.
My solution uses a filtering XMLStreamWriter which applies an empty namespace context.
public class NoNamesWriter extends DelegatingXMLStreamWriter {
private static final NamespaceContext emptyNamespaceContext = new NamespaceContext() {
#Override
public String getNamespaceURI(String prefix) {
return "";
}
#Override
public String getPrefix(String namespaceURI) {
return "";
}
#Override
public Iterator getPrefixes(String namespaceURI) {
return null;
}
};
public static XMLStreamWriter filter(Writer writer) throws XMLStreamException {
return new NoNamesWriter(XMLOutputFactory.newInstance().createXMLStreamWriter(writer));
}
public NoNamesWriter(XMLStreamWriter writer) {
super(writer);
}
#Override
public NamespaceContext getNamespaceContext() {
return emptyNamespaceContext;
}
}
You can find a DelegatingXMLStreamWriter here.
You can then filter the marshalling xml with:
// Filter the output to remove namespaces.
m.marshal(it, NoNamesWriter.filter(writer));
I am sure there are more efficient mechanisms but I know this one works.
For me, only changing the package-info.java class worked like a charm, exactly as zatziky stated :
package-info.java
#javax.xml.bind.annotation.XmlSchema
(namespace = "http://example.com",
xmlns = {#XmlNs(prefix = "", namespaceURI = "http://example.com")},
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package my.package;
import javax.xml.bind.annotation.XmlNs;
You can let the namespaces be written only once. You will need a proxy class of the XMLStreamWriter and a package-info.java. Then you will do in your code:
StringWriter stringWriter = new StringWriter();
XMLStreamWriter writer = new Wrapper((XMLStreamWriter) XMLOutputFactory
.newInstance().createXMLStreamWriter(stringWriter));
JAXBContext jaxbContext = JAXBContext.newInstance(Collection.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
jaxbMarshaller.marshal(books, writer);
System.out.println(stringWriter.toString());
Proxy class (the important method is "writeNamespace"):
class WrapperXMLStreamWriter implements XMLStreamWriter {
private final XMLStreamWriter writer;
public WrapperXMLStreamWriter(XMLStreamWriter writer) {
this.writer = writer;
}
//keeps track of what namespaces were used so that not to
//write them more than once
private List<String> namespaces = new ArrayList<String>();
public void init(){
namespaces.clear();
}
public void writeStartElement(String localName) throws XMLStreamException {
init();
writer.writeStartElement(localName);
}
public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
init();
writer.writeStartElement(namespaceURI, localName);
}
public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
init();
writer.writeStartElement(prefix, localName, namespaceURI);
}
public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
if(namespaces.contains(namespaceURI)){
return;
}
namespaces.add(namespaceURI);
writer.writeNamespace(prefix, namespaceURI);
}
// .. other delegation method, always the same pattern: writer.method() ...
}
package-info.java:
#XmlSchema(elementFormDefault=XmlNsForm.QUALIFIED, attributeFormDefault=XmlNsForm.UNQUALIFIED ,
xmlns = {
#XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi")})
package your.package;
import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
You can use the NamespacePrefixMapper extension to control the namespace prefixes for your use case. The same extension is supported by both the JAXB reference implementation and EclipseLink JAXB (MOXy).
http://wiki.eclipse.org/EclipseLink/Release/2.4.0/JAXB_RI_Extensions/Namespace_Prefix_Mapper
Every solution requires complex overwriting or annotations which seems not to work with recent version. I use a simpler approach, just by replacing the annoying namespaces. I wish Google & Co would use JSON and get rid of XML.
kml.marshal(file);
String kmlContent = FileUtils.readFileToString(file, "UTF-8");
kmlContent = kmlContent.replaceAll("ns2:","").replace("<kml xmlns:ns2=\"http://www.opengis.net/kml/2.2\" xmlns:ns3=\"http://www.w3.org/2005/Atom\" xmlns:ns4=\"urn:oasis:names:tc:ciq:xsdschema:xAL:2.0\" xmlns:ns5=\"http://www.google.com/kml/ext/2.2\">", "<kml>");
FileUtils.write(file, kmlContent, "UTF-8");
I write manually a KML file trying to import some polygons in MyMaps. This way works fine:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.0">
<Document>
<Placemark>
<Style>
<PolyStyle>
<color>#a00000ff</color>
<outline>0</outline>
</PolyStyle>
</Style>
<Polygon>
<outerBoundaryIs>
<LinearRing>
<coordinates>9.184254,45.443636 9.183379,45.434288 9.224836,45.431499 9.184254,45.443636</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
</Document>
</kml>
I try to write a java program using JAK that generate a most possibile equal file, but it doesn't work with Maps
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns3:kml xmlns:atom="http://www.w3.org/2005/Atom" xmlns:ns3="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:xal="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0">
<ns3:Document>
<ns3:Placemark>
<ns3:Style>
<ns3:PolyStyle>
<ns3:color>#EABCFF</ns3:color>
<ns3:outline>0</ns3:outline>
</ns3:PolyStyle>
</ns3:Style>
<ns3:Polygon>
<ns3:innerBoundaryIs>
<ns3:LinearRing>
<ns3:coordinates>9.184254,45.443636 9.183379,45.434288 9.224836,45.431499 9.184254,45.443636</ns3:coordinates>
</ns3:LinearRing>
</ns3:innerBoundaryIs>
</ns3:Polygon>
</ns3:Placemark>
</ns3:Document>
</ns3:kml>
That's program:
public static void main(String[] args) throws IOException {
// Style
PolyStyle polystyle = KmlFactory.createPolyStyle();
polystyle.setColor("#EABCFF");
// polystyle.setFill(true);
polystyle.setOutline(false);
//
Kml kml = KmlFactory.createKml();
Document document = kml.createAndSetDocument();
Placemark pm = document.createAndAddPlacemark();
LinearRing linearRing = pm.createAndSetPolygon().createAndAddInnerBoundaryIs().createAndSetLinearRing();
linearRing.addToCoordinates(9.184254, 45.443636, 0);
linearRing.addToCoordinates(9.183379, 45.434288, 0);
linearRing.addToCoordinates(9.224836, 45.431499, 0);
linearRing.addToCoordinates(9.184254, 45.443636, 0);
pm.createAndAddStyle().setPolyStyle(polystyle);
//
kml.marshal(new FileWriter("D:/prova.kml"));
}
I view <ns3: in your kml this make the kml invalid for google maps
Try to correct the file
I had the same problem.
Instead of using kml.marshal(new FileWriter("D:/prova.kml")); I did this...
String name = kml.getClass().getSimpleName();
if ("Kml".equals(name)) {
name = name.toLowerCase();
}
JAXBContext jaxbContext = JAXBContext.newInstance(Kml.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
// output pretty printed
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NameSpaceBeautyfier());
JAXBElement<Kml> jaxbKml = new JAXBElement<>(new QName("http://www.opengis.net/kml/2.2", name), (Class<Kml>) kml.getClass(), kml);
jaxbMarshaller.marshal(jaxbKml, file);
With a NameSpaceBeautifier like this ...
private static final class NameSpaceBeautyfier extends NamespacePrefixMapper {
private static final String KML_PREFIX = ""; // DEFAULT NAMESPACE
private static final String KML_URI= "http://www.opengis.net/kml/2.2";
#Override
public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
if(KML_URI.equals(namespaceUri)) {
return KML_PREFIX;
}
return suggestion;
}
#Override
public String[] getPreDeclaredNamespaceUris() {
return new String[] { KML_URI };
}
private NameSpaceBeautyfier() {
}
}
Hope this helps..
We are parsing an XML file with the SAX parser. Is it possible to get the schema location from the XML?
<view id="..." title="..."
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="{schema}">
I want to retrieve the {schema} value from the XML. Is this possible? And how to I access this value of noNamespaceSchemaLocation? I'm using the default SAX Parser.
#Override
public void startElement(String uri, String localName,
String name, Attributes attributes)
{ .... }
Thank you.
It all depends with what kind of tool/library you are working (a basic SAXParser? Xerces? JDom? ...) But what you want is the value of the attribute "noNamespaceSchemaLocation" in the namspace defined by the URI "http://www.w3.org/2001/XMLSchema-instance"
in JDom, it would be something like:
Element view = ...; // get the view element
String value = view.getAttributeValue("noNamespaceSchemaLocation", Namespace.getNamespace("http://www.w3.org/2001/XMLSchema-instance"));
Here is how I get the XSD's name using XMLStreamReader:
public static String extractXsdValueOrNull(#NonNull final InputStream xmlInput)
{
final XMLInputFactory f = XMLInputFactory.newInstance();
try
{
final XMLStreamReader r = f.createXMLStreamReader(xmlInput);
while (r.hasNext())
{
final int eventType = r.next();
if (XMLStreamReader.START_ELEMENT == eventType)
{
for (int i = 0; i <= r.getAttributeCount(); i++)
{
final boolean foundSchemaNameSpace = XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI.equals(r.getAttributeNamespace(i));
final boolean foundLocationAttributeName = SCHEMA_LOCATION.equals(r.getAttributeLocalName(i));
if (foundSchemaNameSpace && foundLocationAttributeName)
{
return r.getAttributeValue(i);
}
}
return null; // only checked the first element
}
}
return null;
}
catch (final XMLStreamException e)
{
throw new RuntimeException(e);
}
}
Actually XMLStreamReader does all the magic, namely:
only parses the XML's beginning (not the whole XML)
does not assume a particular namespace alias (i.e. xsi)