I have a generic web service which is expecting a WebServiceRequest
object. This object has a payload which is of type Object. Below is the type of my payload.
<xs:complexType name="payload">
<xs:sequence>
<xs:any processContents="lax"></xs:any>
</xs:sequence>
</xs:complexType>
I created JAXB
classes for the web service input and output types. So for payload, this the field that was generated.
@XmlAnyElement(lax = true)
private Object any;
Below is the structure of my JAXB
generated WebServiceRequest
VO.
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "serviceRequest", namespace = "http://ws.test.svc.com/", propOrder = {
"payload"
})
public class WebServiceRequest{
@XmlElement
private Payload payload;
}
public class Payload{
@XmlAnyElement(lax = true)
private Object any;
}
I have some custom POJOs which I need to populate and set as the payload. I annotated these POJOs using the following annotation
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class AddressVO {
@XmlElement
private String pinCode;
@XmlElement
private String city;
}
I populated the data for this POJO and tried to set as the payload of WebServiceRequest
. But when I do that, I'm getting the below exception.
javax.xml.bind.MarshalException
- with linked exception:
[javax.xml.bind.JAXBException: class com.vo.test.AddressVO nor any of its super class is known to this context.
Could you please suggest some ways to overcome this? In one link it was mentioned to include @XmlSeeAlso
, but i can't do that since my payload is very generic. Kindly help me in this regard.
In case that you can't apply the @XMLSeeAlso
annotation, you will need to create a custom MessageBodyReader
and MessageBodyWriter
which are responsible to marshall and unmarshall between java and XML. Below there is shown an abstract implementation of a generic MessageBodyReader
which was actually intended to perfom a type specific XML validation. The writer is quite similar, hence it isn't added.
public abstract class AbstractXmlValidationReader<T> implements
MessageBodyReader<T> {
private final Providers providers;
private final Schema schema;
public AbstractXmlValidationReader(final Providers providers,
final ServletContext servletContext, final String xsdFileName) {
this.providers = providers;
try {
SchemaFactory sf = SchemaFactory
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
File xsd = new File(servletContext.getRealPath(xsdFileName));
schema = sf.newSchema(xsd);
} catch (Exception e) {
throw new RuntimeException(
"Unable to create XSD validation schema", e);
}
}
@Override
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
@SuppressWarnings("unchecked")
Class<T> readableClass = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
if (type == readableClass
&& type.isAnnotationPresent(XmlRootElement.class)) {
return true;
}
return false;
}
@Override
public T readFrom(Class<T> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
try {
JAXBContext jaxbContext = null;
ContextResolver<JAXBContext> resolver = providers
.getContextResolver(JAXBContext.class, mediaType);
if (null != resolver) {
jaxbContext = resolver.getContext(type);
}
if (null == jaxbContext) {
jaxbContext = JAXBContext.newInstance(type);
}
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setSchema(schema);
@SuppressWarnings("unchecked")
T entity = (T) unmarshaller.unmarshal(entityStream);
return entity;
} catch (JAXBException e) {
throw new MessageBodyReaderValidationException(
"Failure while performing xml validation or xml marhalling!",
e);
}
}
}
And a concreate implementation for type Address
@Provider
@Consumes(MediaType.APPLICATION_XML)
public class AddressXmlValidationReader extends
AbstractXmlValidationReader<Address> {
private final static String xsdFileName = "/xsd/Address.xsd";
public AddressXmlValidationReader(@Context Providers providers,
@Context ServletContext servletContext) {
super(providers, servletContext, xsdFileName);
}
}
What you need now is a slighlty modified readFrom
method of the MessageBodyReader
which may look as followed. For the MessageBodyWriter
the method is called writeTo
.
@Override
public T readFrom(Class<T> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
try {
JAXBContext jaxbContext = null;
ContextResolver<JAXBContext> resolver = providers
.getContextResolver(JAXBContext.class, mediaType);
if(entityStream != null){
// TODO read the entityStream and determine the concrete type of the XML content
type = ... ;
}
if (null != resolver) {
jaxbContext = resolver.getContext(type);
}
if (null == jaxbContext) {
jaxbContext = JAXBContext.newInstance(type);
}
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setSchema(schema);
@SuppressWarnings("unchecked")
T entity = (T) unmarshaller.unmarshal(entityStream);
return entity;
} catch (JAXBException e) {
throw new MessageBodyReaderValidationException(
"Failure while performing xml validation or xml marhalling!",
e);
}
}
With this approach you define a general type via the concrete subclass of the Reader and Writer instance. And a possible more specific type can either be determine within the abstract base class (like in this example) or it's getted injected from somewhere else. Of course you can modify this MessageBodyReader
so that the concrete type fo the XML input is determined somewhere or somehow else. But in general this is the way how you will solve your issue.
Notice:
Don't forget to register the concrete Reader and writer implementation in the web services Application
class.
@ApplicationPath("/services")
public class WSApplication extends Application {
private Set<Object> singletons = new HashSet<Object>();
private Set<Class<?>> classes = new HashSet<Class<?>>();
public WSApplication() {
...
classes.add(AddressXmlValidationReader.class);
...
}
...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With