Serializing with JAXB and the Any (without ElementNS) - jaxb

I'm looking a best practices to marshal a XMLAnyElement that can handle String, Long etc... I found a Serializing with JAXB and the Any, but I need avoid ElementNS and resolve the case attached
Is DOMHandler the best way?
public static void main(String[] args) throws JAXBException {
JAXBContext jc = JAXBContext.newInstance(Payload.class, Foo.class, ObjectFactory.class);
Payload payload = new Payload();
payload.any = new ArrayList<>();
payload.any.add(new Bar());
payload.any.add(new Foo());
payload.any.add("pepe");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(payload, System.out);
}

Resolved, see my PoC on github..
https://github.com/franciscophilip/jaxb-payload-poc/

Related

Unmarshalling the XML using JAXB

Actually I am very new at spring and currently due to some requirement, I am working with spring-integration, I have made few JAXB classes to convert the data into XML and have to send it through webservices but in response I am getting the XML back with some new element, I want to know how to unmarshall the new XML with same JAXB classes that I have made?
I use the following component to do that (java configuration):
#Bean
public Jaxb2Marshaller jaxb2Marshaller() throws Exception {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
/* Packages with root elements (#XmlRootElement). Your JAXB classes */
marshaller.setContextPaths("...");
return marshaller;
}
#Bean
#ServiceActivator(inputChannel = "toWebServiceChannel")
public MessageHandler wsGateway() throws Exception {
ConfigWebServiceURLProvider provider = new ConfigWebServiceURLProvider(isHttps, host, port, endpoint);
/* marshaller and unmarshaller could be the same */
MarshallingWebServiceOutboundGateway gw = new MarshallingWebServiceOutboundGateway(url, jaxb2Marshaller(), jaxb2Marshaller());
gw.setOutputChannelName( "fromWebServiceChannel" );
return gw;
}

Spring integeration DefaultSoapHeaderMapper - getStandardRequestHeaderNames - override

in previous si versions (si 2.11 to be specific and spring 3.1.1) getStandardRequestHeaderNames could be overrided to include Additional Application specific objects in the si message header. Our application relied on this ability (may be wrongfully so) to override this method and supply a custom POJO to be carried downstream consisting of many splitters, aggregators etc. The app used an ws inbound gateway and used the header-mapper attribute to specify the custom soap header mapper.
Any clues on the reasoning behind why getStandardRequestHeaderNames cannot be overriden?
Need some advise on how I can migrate this to the current spring release.
The requirement is to extract elements from soapHeader and map them to an SI message headers as an POJO and send it down stream.
All help appreciated.
Code Snippet: Works with older versions of spring
<int-ws:inbound-gateway id="webservice-inbound-gateway"
request-channel="input-request-channel"
reply-channel="output-response-channel"
header-mapper="CustomSoapHeaderMapper"
marshaller="marshaller"
unmarshaller="marshaller" />
#Component("CustomSoapHeaderMapper")
public class CustomSoapHeaderMapper extends DefaultSoapHeaderMapper {
private static final Logger logger = Logger.getLogger("CustomSoapHeaderMapper");
public static final String HEADER_SEARCH_METADATA = SearchMetadata.HEADER_ATTRIBUTE_NAME;
public static final String HEADER_SERVICE_AUDIT = "XXXXXXXX";
// Use simulation if security token is set to this value
public static final String SECURITY_TOKEN_SIMULATION = "XXXX";
private static final List<String> CUSTOM_HEADER_NAMES = new ArrayList<String>();
static {
CUSTOM_HEADER_NAMES.add(WebServiceHeaders.SOAP_ACTION);
CUSTOM_HEADER_NAMES.add(HEADER_SEARCH_METADATA);
}
private int version =SearchMetadata.VERSION_CURRENT;
public void setVersion(int version) {
this.version = version;
}
#Override
protected List<String> getStandardRequestHeaderNames() {
return CUSTOM_HEADER_NAMES;
}
#Override
protected Map<String, Object> extractUserDefinedHeaders(SoapMessage source) {
// logger.log(Level.INFO,"extractUserDefinedHeaders");
// call base class to extract header
Map<String, Object> map = super.extractUserDefinedHeaders(source);
Document doc = source.getDocument();
SearchMetadata searchMetadata = new SearchMetadata();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
source.writeTo(baos);
baos.flush();
searchMetadata.setRequestXML(baos.toString());
baos.close();
} catch (IOException e1) {
}
//logger.log(Level.WARNING, "Incoming Message " + baos.toString());
SOAPMessage soapMessage = ((SaajSoapMessage) source).getSaajMessage();
// generate TransactionID with UUID value
String transactionID = UUID.randomUUID().toString();
// logger.log(Level.WARNING, "TransactionID=" + transactionID);
Date now = new Date();
searchMetadata.setTransactionID(transactionID);
searchMetadata.setRequestType(SearchMetadata.REQUEST_TYPE_SYNCHRONOUS);
searchMetadata.setRequestTime(now);// initialize the request time
searchMetadata.setReceivedTime(now);// mark time system receives request
searchMetadata.setVersion(version);
Map<String, Object> finalHeaders = new HashMap<String, Object>();
finalHeaders.put(HEADER_SEARCH_METADATA, searchMetadata);
if (!CollectionUtils.isEmpty(map)) {
// copy from other map
finalHeaders.putAll(map);
// check if ServiceAudit is available
SoapHeaderElement serviceAuditElement = null;
for (String key : map.keySet()) {
// logger.log(Level.WARNING, "SoapHeader.{0}", key);
if (StringUtils.contains(key, HEADER_SERVICE_AUDIT)) {
serviceAuditElement = (SoapHeaderElement) map.get(key);
break;
}
}
}
return finalHeaders;
}
// GK Key Thing here for performance improvement is avoiding marshalling
public gov.dhs.ice.ess.schema.ServiceAudit ExtractAuditHeader(Document doc) {
....
}
return serviceAudit;
}
}
Share, please, some code how would you like to see that.
Maybe you can just implement your own SoapHeaderMapper and inject it into WS Inbound Gateway?
You can still reuse your logic and copy/paste the standard behavior from the DefaultSoapHeaderMapper.
UPDATE
The test-case to demonstrate how to add user-defined header manually:
#Test
public void testCustomSoapHeaderMapper() {
DefaultSoapHeaderMapper mapper = new DefaultSoapHeaderMapper() {
#Override
protected Map<String, Object> extractUserDefinedHeaders(SoapMessage source) {
Map<String, Object> headers = super.extractUserDefinedHeaders(source);
headers.put("foo", "bar");
return headers;
}
};
mapper.setRequestHeaderNames("*");
SoapMessage soapMessage = mock(SoapMessage.class);
Map<String, Object> headers = mapper.toHeadersFromRequest(soapMessage);
assertTrue(headers.containsKey("foo"));
assertEquals("bar", headers.get("foo"));
}

How can I unlock a file locked by JAXB's unmarhsaller

I'm unmarshalling an XML file with JAXB w/Java 1.7.0_03 on Windows 7 x64 using the following code:
try (InputStream xsdStream = ConfigurationService.class.getClassLoader().getResourceAsStream(CONFIG_XSD_FILE_NAME)) {
configFile = new File(configFilePath);
if (configFile.exists()) {
context = JAXBContext.newInstance(Config.class);
Unmarshaller unMarshaller = context.createUnmarshaller();
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
StreamSource xsdStreamSource = new StreamSource(xsdStream);
Schema schema = sf.newSchema(xsdStreamSource);
unMarshaller.setSchema(schema);
Object xmlObject = Config.class.cast(unMarshaller.unmarshal(configFile));
myConfig = (Config) xmlObject;
} else {
log.severe(configFile.getAbsolutePath() + " does not exist, can not parse configuration info from it.");
}
}
Code which calls this method subsequently deletes the XML file.
The XML file will properly delete if unmarhalling is successful. However, if the above code throws and Exception, eg. a SAXException, the XML file remains locked indefinitely and the calling code is not able to delete it with File.delete().
This feels like JAXB is not closing the resource/file in this case. Is it my responsibility to do that somehow or is this a bug?
Reviewing the javadoc for Unmarshaller did not shed any light on this and Googling this issue revealed this old, unanswered question from 2008.
SHORT ANSWER
The behaviour you have described sounds like a bug in the JAXB reference implementation. You can open a ticket using the link below:
http://java.net/jira/browse/JAXB/
Work Around
Instead of unmarshalling from a File you can unmarshal from a FileInputStream and control that it is closed correctly yourself after unmarshalling.
LONG ANSWER
I have not been able to reproduce the issue that you are seeing. I have included what I have tried below. I am using JDK 1.7.0_07 x64 for the Mac.
Configuration Service
Most of the code below is copied from your question. I have added the call to delete the input file and then output if the file still exists.
package forum14765898;
import java.io.*;
import javax.xml.XMLConstants;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.*;
public class ConfigurationService {
private static final String CONFIG_XSD_FILE_NAME = "forum14765898/schema.xsd";
public static void main(String[] args) throws Exception {
File configFile = null;
String configFilePath = "src/forum14765898/input.xml";
JAXBContext context;
Config myConfig;
try (InputStream xsdStream = ConfigurationService.class.getClassLoader().getResourceAsStream(CONFIG_XSD_FILE_NAME)) {
configFile = new File(configFilePath);
if (configFile.exists()) {
context = JAXBContext.newInstance(Config.class);
Unmarshaller unMarshaller = context.createUnmarshaller();
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
StreamSource xsdStreamSource = new StreamSource(xsdStream);
Schema schema = sf.newSchema(xsdStreamSource);
unMarshaller.setSchema(schema);
Object xmlObject = Config.class.cast(unMarshaller.unmarshal(configFile));
myConfig = (Config) xmlObject;
} else {
//log.severe(configFile.getAbsolutePath() + " does not exist, can not parse configuration info from it.");
}
} catch(Exception e) {
e.printStackTrace(System.out);
}
configFile.delete();
System.out.println(configFile.exists());
}
}
schema.xsd
Below is the simple XML schema that I am using.
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
<element name="config">
<complexType>
<sequence>
<element name="bar" type="int"/>
</sequence>
</complexType>
</element>
</schema>
input.xml
Below is the XML input. The bar element is not valid according to the XML schema. When a Schema is set on the Unmarshaller this document will be enough to cause an Exception to be thrown while performing an unmarshal operation.
<?xml version="1.0" encoding="UTF-8"?>
<config>
<bar>INVALID</bar>
</config>
Config
package forum14765898;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Config {
public int bar;
}
Output
Below is output from running the demo code. It shows both the validation exception and on the last line we see that the XML file was successfully deleted as it no longer exists.
javax.xml.bind.UnmarshalException
- with linked exception:
[org.xml.sax.SAXParseException; systemId: file:/Users/bdoughan/Scratch/src/forum14765898/input.xml; lineNumber: 3; columnNumber: 23; cvc-datatype-valid.1.2.1: 'INVALID' is not a valid value for 'integer'.]
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.createUnmarshalException(AbstractUnmarshallerImpl.java:335)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.createUnmarshalException(UnmarshallerImpl.java:512)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:209)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:175)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:157)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:162)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:171)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:189)
at forum14765898.ConfigurationService.main(ConfigurationService.java:31)
Caused by: org.xml.sax.SAXParseException; systemId: file:/Users/bdoughan/Scratch/src/forum14765898/input.xml; lineNumber: 3; columnNumber: 23; cvc-datatype-valid.1.2.1: 'INVALID' is not a valid value for 'integer'.
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:198)
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:134)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:437)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:368)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:325)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator$XSIErrorReporter.reportError(XMLSchemaValidator.java:453)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.reportSchemaError(XMLSchemaValidator.java:3232)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.elementLocallyValidType(XMLSchemaValidator.java:3147)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.processElementContent(XMLSchemaValidator.java:3057)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleEndElement(XMLSchemaValidator.java:2135)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.endElement(XMLSchemaValidator.java:854)
at com.sun.org.apache.xerces.internal.jaxp.validation.ValidatorHandlerImpl.endElement(ValidatorHandlerImpl.java:579)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.ValidatingUnmarshaller.endElement(ValidatingUnmarshaller.java:91)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.SAXConnector.endElement(SAXConnector.java:143)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(AbstractSAXParser.java:606)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(XMLDocumentFragmentScannerImpl.java:1742)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2900)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:607)
at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:116)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:489)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:835)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:764)
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:123)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1210)
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:568)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:203)
... 6 more
false
public CashCountCompleted CashDeposit(String path) throws Exception {
// TODO Auto-generated method stub
CashCountCompleted cashCountCompleted = null;
File file = null;
FileInputStream inputStram = null;
try {
file = new File(path);
inputStram = new FileInputStream(file);
JAXBContext jaxbContext = JAXBContext.newInstance(CashCountCompleted.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
cashCountCompleted = (CashCountCompleted) jaxbUnmarshaller.unmarshal(inputStram);
}catch (JAXBException e) {
//throw new...
} catch (FileNotFoundException e) {
//throw new...
}finally{
try{
if(inputStram !=null){
inputStram.close();
}
}catch(Exception exception){
//throw new...
}
}
return cashCountCompleted;
}

How to force a JAXBException when marshalling for a JUnit test

I haven't been able to find a way to force a JAXBException when marshalling for a JUnit test. Does anyone have any ideas?
Here is my marshalling code:
public String toXml() {
log.debug("Entered toXml method");
String result = null;
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Config.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter writer = new StringWriter();
jaxbMarshaller.marshal(this, writer);
result = writer.toString();
} catch (JAXBException e) {
log.error(e);
}
log.debug("Exiting toXml method");
return result;
}
There are different ways to create a JAXBException during a marshal operation:
1 - Marshal an Invalid Object
You can generate a JAXBException during a marshal operation by marshalling an instance of a class that the JAXBContext isn't aware of (i.e. Take your example and use it to marshal an instance of Foo). This will produce the following exception.
Exception in thread "main" javax.xml.bind.JAXBException: class forum13389277.Foo nor any of its super class is known to this context.
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getBeanInfo(JAXBContextImpl.java:594)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:482)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:315)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:244)
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:95)
at forum13272288.Demo.main(Demo.java:27)
2 - Marshal to Invalid Output
If you try to marshal to an invalid output such as an OutputStream that has been closed:
FileOutputStream closedStream = new FileOutputStream("src/foo.xml");
closedStream.close();
jaxbMarshaller.marshal(this, closedStream);
Then you will get a MarshalException which is a subclass of JAXBException.
Exception in thread "main" javax.xml.bind.MarshalException
- with linked exception:
[java.io.IOException: Stream Closed]
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:320)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:244)
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:95)
at forum13272288.Demo.main(Demo.java:27)
Caused by: java.io.IOException: Stream Closed
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:318)
at com.sun.xml.bind.v2.runtime.output.UTF8XmlOutput.flushBuffer(UTF8XmlOutput.java:413)
at com.sun.xml.bind.v2.runtime.output.UTF8XmlOutput.endDocument(UTF8XmlOutput.java:137)
at com.sun.xml.bind.v2.runtime.output.IndentingUTF8XmlOutput.endDocument(IndentingUTF8XmlOutput.java:165)
at com.sun.xml.bind.v2.runtime.XMLSerializer.endDocument(XMLSerializer.java:852)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.postwrite(MarshallerImpl.java:369)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:316)
... 3 more

Making jaxb lines generic to avoid writing them repeatedly

Unmarshalling from a File:
JAXBContext jc = JAXBContext.newInstance( "com.acme.foo" );
Unmarshaller u = jc.createUnmarshaller();
Object o = u.unmarshal( new File( "nosferatu.xml" ) );
Unmarshalling from an InputStream:
InputStream is = new FileInputStream( "nosferatu.xml" );
JAXBContext jc = JAXBContext.newInstance( "com.acme.foo" );
Unmarshaller u = jc.createUnmarshaller();
Object o = u.unmarshal( is );
The JAXB (JSR-222) APIs are already quite generic.
JAXBContext
This object is thread safe so you create this once and create all your instances of Marshaller, Unmarshaller, etc from it.
Marshaller/Unmarshaller
These objects are not thread safe, so you need to ensure that they are not being used by more than one thread at the same time. Unless you are setting any properties on then you could always do:
JAXBContext jc = JAXBContext.newInstance("com.acme.foo");
Object o = jc.createUnmarshaller().unmarshal(new File("nosferatu.xml"));

Resources