I have a particular Java class like
public class JavaClass implements Serializable{
private static final long serialVersionUID = 1L;
private ElementC ElementC = null;
private ArrayList<ElementA> elementA = new ArrayList<ElementA>();
private ArrayList<ElementB> elementB = new ArrayList<ElementB>();
and an XSD generated along with WSDL for the particular class, something like below.
(XSD File)
<xs:complexType name="JavaClass">
<xs:sequence>
<xs:element name="ElementA" type="tns:elementA" nillable="true" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="ElementB" type="tns:elementB" nillable="true" minOccurs="1" maxOccurs="unbounded"/>
<xs:element name="ElementC" type="xs:string" minOccurs="1"/>
</xs:sequence>
</xs:complexType>
Below is the chunk of Java code am using for schema validation.
When I debug to peek in the parse exception thrown, The message says
One of '{elementA, elementB}' is expected. (I suppose the problem is in SAXParseException.)
public void warning(SAXParseException e) throws SAXException {
//System.out.println("In Warning() of SchemaErrorHandler");
// Store warnings in the packet so that they can be retrieved from the endpoint
packet.invocationProperties.put("WARNING", e);
}
public void error(SAXParseException e) throws SAXException
{
count++;
String error = e.getMessage();
if(error.contains("cvc-enumeration-valid")){
StringTokenizer st = new StringTokenizer(error,"[");
st.nextElement();
StringTokenizer st2 = new StringTokenizer(st.nextElement().toString(),"]");
exceptionTypeDetails = (String) st2.nextElement();
Now when I trigger a SOAP request without ElementB(Mandatory) the SOAP throws an error saying "ElementA was not found, Excepted ElementA"(Even though ElementA is non Mandatory).
Please halp how can I debug the issue. Let me know incase of futher clarrifications needed on this question.
Related
QUESTION:
Is it possible to extract the original message body string (i.e., XML string or JSON string) - within the "post()" method - of a REST service?
Environment
Java 8
WebLogic 12.1.3 (w/ jax-rs(2.0,2.5.1) deployable library)
(The "request.getInputStream()" yields nothing... Seems that "read()" has already been applied "internally". Also, "mark()" or "reset()" is not supported)
"post()" method...
package aaa.bbb.ccc;
import javax.ejb.Stateless;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import aaa.bbb.ccc.fieldslist.FieldsList;
import java.io.*;
import java.net.URI;
import javax.ws.rs.core.*;
import javax.xml.bind.JAXBException;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
#Stateless
#Path("/fieldsList")
public class Testws {
private static final Logger LOG = LogManager.getLogger(Testws.class);
public Testws() {
}
#POST
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response post(#Context UriInfo uriInfo, #Context javax.servlet.http.HttpServletRequest request, FieldsList fieldsList) throws IOException, JAXBException, Exception {
try {
//...returns empty string...
String s = IOUtils.toString(request.getInputStream(), "UTF-8");
LOG.info("message string from request.getInputStream()=" + s); <== empty string...
} catch (Exception e) {
e.printStackTrace();
}
URI uri = UriBuilder.fromUri(uriInfo.getRequestUri()).build();
Response response = Response.created(uri).build();
return response;
}
}
I've tried using an interceptor (see "aroundReadFrom()" method) to manipulate the InputStream before it is used by the post() method, but, to no effect...
-That is, in the REST service's post() method, the request.getInputStream() continues to yield nothing...
"aroundReadFrom()" method...
package aaa.bbb.ccc;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
#Provider
public class MyReaderInterceptor implements ReaderInterceptor {
static final Logger LOG = LogManager.getLogger(MyReaderInterceptor.class);
#Override
public Object aroundReadFrom(ReaderInterceptorContext ctx) throws IOException {
try {
InputStream is = ctx.getInputStream();
byte[] content = IOUtils.toByteArray(is);
is.close();
ctx.setInputStream(new ByteArrayInputStream(content));
return ctx.proceed();
} catch (IOException | WebApplicationException e) {
e.printStackTrace();
}
return null;
}
}
Here is the test xml message...:
<?xml version="1.0" encoding="UTF-8"?>
<FieldsList xmlns="http://aaa.bbb.ccc.ws/testws">
<Fields>
<FieldA>fieldA_value</FieldA>
<FieldB>fieldB_value</FieldB>
<FieldC>fieldC_value</FieldC>
</Fields>
</FieldsList>
Here is the schema:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
targetNamespace="http://aaa.bbb.ccc.ws/testws"
attributeFormDefault="unqualified"
elementFormDefault="qualified"
xmlns:tw="http://aaa.bbb.ccc.ws/testws"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="FieldsType">
<xs:all>
<xs:element name="FieldA" type="xs:string" minOccurs="0" />
<xs:element name="FieldB" type="xs:string" minOccurs="0" />
<xs:element name="FieldC" type="xs:string" minOccurs="0" />
</xs:all>
</xs:complexType>
<xs:element name="FieldsList">
<xs:complexType>
<xs:sequence>
<xs:element name="Fields" type="tw:FieldsType" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
UPDATE:
Within the post() method I've only been able to reconstruct message string using this technique...
StringWriter sw = new StringWriter();
JAXBContext.newInstance(FieldsList.class).createMarshaller().marshal(fieldsList, sw);
System.out.println("posted xml string=" + sw.toString());
...However, this would not help if the same data is posted in JSON format. To clarify, it will reconstruct the JSON post message as an XML string rather than the original JSON string
Again, I what I'm trying to do is access the original posted XML/JSON message string within the post() method
Solution using Request Attribute - Tested work 100%
#Provider
public class ContainerRequestFilterImpl implements ContainerRequestFilter {
#Context
private HttpServletRequest request;
#Override
public void filter(ContainerRequestContext ctx) throws IOException {
InputStream is = ctx.getEntityStream();
byte[] content = IOUtils.toByteArray(is);
// Store content as Request Attribute
request.setAttribute("content", new String(content, "UTF-8"));
ctx.setEntityStream(new ByteArrayInputStream(content));
}
}
AND
#POST
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response post(#Context UriInfo uriInfo, #Context HttpServletRequest request, FieldsList fieldsList) throws IOException, JAXBException, Exception {
try {
String s = request.getAttribute("postContent");
LOG.info("Post content: " + s);
} catch (Exception e) {
e.printStackTrace();
}
}
I think you can use ReaderInterceptor instead of ContainerRequestFilter. It should work too.
You can just use a ReaderInterceptor. This is where you can get the raw data. You basically need to get the InputStream from the ReaderInterceptorContext, read it, then you need to set the new InputStream, since the original InputStream can only be read once. So you need to use some buffering strategy when reading the original stream
#Override
public Object aroundReadFrom(ReaderInterceptorContext context) {
InputStream ogStream = context.getInputStream();
// readStream with some buffer
// set new stream
context.setInputStream(bufferedStream);
return context.proceed();
}
See Also:
Filters and Interceptors
JAX-RS 2 print JSON request. This example is used for client side, but it can easily be refactored to use the server side filters (i.e. Container(Request|Response)Filter.
We use Jaxb (jaxb-api 2.2.5) to generate a Java class from an XSD. The 'someField' element has a nillable='true' attribute and an (implicit) minoccurs='1'. There is also an optional 'order' attribute.
When we set the order attribute on someField, but no value, JAXB will generate the XML element in the request without nill='true' and this is not accepted by the XSD and results in a SOAP fault.
The XSD for the field:
<xs:element name="someField" nillable="true">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="iata:AlphaNumericStringLength1to19">
<xs:attribute name="order" type="xs:integer" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
Jaxb translates this to the following field on our Java class:
#XmlElement(required = true, nillable = true)
protected SomeParentType.SomeField someField;
The SomeField class looks like this:
public static class SomeField{
#XmlValue
protected String value;
#XmlAttribute
protected BigInteger order;
// getters + setters
}
When we set the order ATTRIBUTE to 2 (for example), and set nothing for the value, JAXB will generate this:
<pay1:someField order="2"/>
This is not valid according to the XSD and it results in a SOAP fault when we send it.
This does work:
<pay1:someField xsi:nil="true" order="2"/>
Do you know how we can get JAXB be to generate the latter? And is JAXB actually wrong in generating the nil-less version?
And is JAXB actually wrong in generating the nil-less version?
Let me get back to you on this.
Do you know how we can get JAXB be to generate the latter?
Below is what you can do
Java Model
SomeParentType
To get the behaviour you are looking for with existing JAXB libraries the domain model needs to be of the following form:
import java.math.BigInteger;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
#XmlRootElement
public class SomeParentType {
#XmlElementRef(name="someField")
protected JAXBElement<SomeParentType.SomeField> someField;
public static class SomeField{
#XmlValue
protected String value;
#XmlAttribute
protected BigInteger order;
// getters + setters
}
}
Registry
To go along with the #XmlElementRef we need to have an #XmlElementDecl on a class annotated with #XmlRegistry.
import javax.xml.namespace.QName;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
#XmlRegistry
public class Registry {
#XmlElementDecl(name="someField")
public JAXBElement<SomeParentType.SomeField> createSomeField(SomeParentType.SomeField someField) {
return new JAXBElement(new QName("someField"), SomeParentType.SomeField.class, someField);
}
}
Demo Code
Below is some demo code to exercise your use case:
import javax.xml.bind.*;
import java.math.BigInteger;
public class Demo {
public static void main(String[] args) throws Exception {
// Create the JAXBContext to bring in the Registry
JAXBContext jc = JAXBContext.newInstance(SomeParentType.class, Registry.class);
// Create the instance of SomeField
SomeParentType.SomeField sf = new SomeParentType.SomeField();
sf.order = new BigInteger("1");
// Wrap the SomeField in a JAXBElement & specify the nil aspect
Registry registry = new Registry();
JAXBElement<SomeParentType.SomeField> jaxbElement = registry.createSomeField(sf);
jaxbElement.setNil(true);
SomeParentType spt = new SomeParentType();
spt.someField = jaxbElement;
// Marshal the domain model to XML
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(spt, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<someParentType>
<someField xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" order="1" xsi:nil="true"/>
</someParentType>
I have simple web service server which receive SOAP with MIME attachment, store it to the HashMap and send back message with this mime attachment:
Java 1.7.0_13-b20
Spring WS 2.1.3
MOXy implementation of JAXB (EclipseLink 2.4.1)
Glassfish 3.1.1
MIME attachment & SOAP
enabled SAAJ MimePull to save to the hard drive
based on Enabling large file transfers through SOAP with Spring WS, MTOM/XOP and JAXB2 and Spring WS "mtom" sample
tested by SoapUI
If I send Request with small attachment (<80MB) everything is OK but when I send Request with >80 MB attachment the DataHandler object is null. So it is not parsed from Request and not stored to the HashMap. Does not matter if I have enabled mimepull, if yes the large attachment is stored to the temporary folder on hard drive.
Enabled MimePull:
-Dsaaj.use.mimepull=true
-Djava.io.tmpdir=/path/to/tmpdir
Enlarged GlassFish Thread Pools:
"Max Thread Pool Size" from 5 to 32
"Min Thread Pool Size" from 2 to 16
ImageRequest.java
package sample.mtom.schema;
#XmlRootElement
#XmlType(propOrder = {"fileName", "data"})
public class ImageRequest {
protected String fileName;
protected DataHandler data;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
#XmlMimeType("*/*")
public DataHandler getData() {
return data;
}
public void setData(DataHandler data) {
this.data = data;
}
}
ImageResponse.java
package sample.mtom.schema;
#XmlRootElement
#XmlType(propOrder = {"fileName", "data"})
public class ImageResponse {
// the same as ImageRequest
}
package-info.java
#XmlSchema(namespace = "http://www.example.org/Image",
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package sample.mtom.schema;
import javax.xml.bind.annotation.XmlSchema;
StubRepository.java
package sample.mtom.service;
public class StubRepository implements Repository {
private Map<String, DataHandler> files = new HashMap();
public DataHandler readFile(String name) throws IOException {
return files.get(name);
}
public void storeFile(String name, DataHandler file) throws IOException {
files.put(name, file);
}
}
ImageRepositoryEndpoint.java
package sample.mtom.ws;
#Endpoint
public class ImageRepositoryEndpoint {
private static Repository repository;
static {
repository = new StubRepository();
}
#PayloadRoot(localPart = "imageRequest", namespace = "http://www.example.org/Image")
#ResponsePayload
public ImageResponse handleRequest(#RequestPayload ImageRequest request) throws IOException {
repository.storeFile(request.getFileName(), request.getData());
ImageResponse response = new ImageResponse();
response.setFileName(request.getFileName());
response.setData(repository.readFile(request.getFileName()));
return response;
}
}
schema.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xmime="http://www.w3.org/2005/05/xmlmime"
targetNamespace="http://www.example.org/Image"
elementFormDefault="qualified">
<xsd:element name="imageRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="fileName" type="xsd:string"/>
<xsd:element name="data" type="xsd:base64Binary" xmime:expectedContentTypes="*/*"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="imageResponse">
<!-- the same as imageRequest -->
</xsd:element>
</xsd:schema>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>MyCompany HR Holiday Service</display-name>
<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>spring-ws</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
spring-ws-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:sws="http://www.springframework.org/schema/web-services"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="sample.mtom.ws"/>
<bean id="defaultMethodEndpointAdapter"
class="org.springframework.ws.server.endpoint.adapter.DefaultMethodEndpointAdapter">
<property name="methodArgumentResolvers" ref="marshallingPayloadMethodProcessor"/>
<property name="methodReturnValueHandlers" ref="marshallingPayloadMethodProcessor"/>
</bean>
<bean id="marshallingPayloadMethodProcessor"
class="org.springframework.ws.server.endpoint.adapter.method.MarshallingPayloadMethodProcessor">
<constructor-arg ref="jaxb2Marshaller"/>
</bean>
<bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="packagesToScan" value="sample.mtom.schema"/>
<property name="mtomEnabled" value="true"/>
</bean>
<sws:dynamic-wsdl id="image"
portTypeName="ImageResource"
locationUri="/">
<sws:xsd location="/WEB-INF/schema.xsd"/>
</sws:dynamic-wsdl>
</beans>
I've got a wsdl with the following code (just a part):
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:sch="http://www.test.com/App" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.test.com/App" targetNamespace="http://www.test.com/App">
<wsdl:types>
<xs:schema xmlns:process="http://www.test.com/App" xmlns:xdb="http://xmlns.oracle.com/xdb" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.test.com/App" version="1.0" xdb:mapStringToNCHAR="true" xdb:mapUnboundedStringToLob="true" xdb:schemaURL="ddd" xdb:storeVarrayAsTable="true">
<xs:simpleType name="ClientCountry">
<xs:restriction base="xs:string">
<xs:enumeration value="CLCO1">
<xs:annotation>
<xs:documentation>Spain</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="CLCO2">
<xs:annotation>
<xs:documentation>Portugal</xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
</xs:schema>
</wsdl:types>
</wsdl:definitions>
I've used this wsdl to generate the Java source files using Eclipse (File->New->Other->Web Services->Web Service Client).
It has generated all classes from the wsdl. However, when generating the enumeration types has made just this:
public class ClientCountry implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = -2280793720095616022L;
private java.lang.String _value_;
private static java.util.HashMap _table_ = new java.util.HashMap();
// Constructor
protected ClientCountry(java.lang.String value) {
_value_ = value;
_table_.put(_value_,this);
}
public static final java.lang.String _CLCO1 = "CLCO1";
public static final java.lang.String _CLCO2 = "CLCO2";
public static final ClientCountry CLCO1 = new ClientCountry(_CLCO1);
public static final ClientCountry CLCO2 = new ClientCountry(_CLCO2);
public java.lang.String getValue() { return _value_;}
public static ClientCountry fromValue(java.lang.String value)
throws java.lang.IllegalArgumentException {
ClientCountry enumeration = (ClientCountry)
_table_.get(value);
if (enumeration==null) throw new java.lang.IllegalArgumentException();
return enumeration;
}
public static ClientCountry fromString(java.lang.String value)
throws java.lang.IllegalArgumentException {
return fromValue(value);
}
public boolean equals(java.lang.Object obj) {return (obj == this);}
public int hashCode() { return toString().hashCode();}
public java.lang.String toString() { return _value_;}
public java.lang.Object readResolve() throws java.io.ObjectStreamException { return fromValue(_value_);}
}
I was wondering if there could be any option to make it generates a simple enumeration class using the xs:documentation description as keys for the enum.
Ok. Using jaxws with maven, the generated class now is:
#XmlType(name = "ClientCountry")
#XmlEnum
public enum ClientCountry {
/**
* Spain
*
*/
#XmlEnumValue("CLCO1")
CLCO_1("CLCO1"),
/**
* Portugal
*
*/
#XmlEnumValue("CLCO2")
CLCO_2("CLCO2");
private final String value;
ClientCountry(String v) {
value = v;
}
public String value() {
return value;
}
public static ClientCountry fromValue(String v) {
for (ClientCountry c: ClientCountry.values()) {
if (c.value.equals(v)) {
return c;
}
}
throw new IllegalArgumentException(v);
}
}
Much better, but still not perfect, is it?
I'm working on a Webservice to share data between 2 ERP-systems. First ERP calls the webservice, which serializes the data-object and sends it to the second ERP.
A data object looks like this:
<xs:complexType name="Parent">
<xs:sequence>
<xs:element ref="ta:ReceiptLine" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Child">
<xs:sequence>
...
<xs:element name="SerialNo" type="xs:string" nillable="true" minOccurs="0"/>
<xs:element name="Quantity" type="xs:int" nillable="false"/>
...
</xs:sequence>
</xs:complexType>
...
<xs:element name="Child" type="ta:Child" nillable="true"/>
The classes generated by XSD:
[System.Serializable]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://FSM4TA/DataObjects/")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://FSM4TA/DataObjects/", IsNullable=false)]
public partial class Parent {
private Child[] child;
[System.Xml.Serialization.XmlElementAttribute("Child", IsNullable=true)]
public Child[] Child {
get {return this.child;}
set {this.child = value;}
}
[System.Serializable]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://FSM4TA/DataObjects/")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://FSM4TA/DataObjects/", IsNullable=true)]
public partial class Child{
private string serialNo;
private int quantity;
[System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
public string SerialNo {
get {return this.serialNo;}
set {this.serialNo = value;}
}
public int Quantity {
get { return this.quantity;}
set {this.quantity = value;}
}
}
I'm serializing my data objects with XmlSerializer
The Problem Is: (On serialization) Every time in case of the Child object is empty (xsi:nil="true") XSD generates the whole Child structure anyway. And because Quantity is not nillable/nullable XSD writes 0 as value... Like this:
<Parent>
<Child xsi:nil="true">
<SerialNo xsi:nil="true" />
<Quantity>0</Quantity>
</Child>
</Parent>
I expected to get something like this:
<Parent>
</Child xsi:nil="true">
</Parent>
The Question Is: Is there a way to prevent XSD from parsing an xsi:nil="true"-Object ??
Any suggestions?
TIA
ok,
I got it now!
You have to mark the Quantity property with the XmlElementAttribute explicitly!
[XmlElement(IsNullable=false)]
public int Quantity {
get { return this.quantity;}
set {this.quantity = value;}
}
No idea why this hasn't been generated automatically...