Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

javax.xml.bind.MarshalException - with linked exception: [javax.xml.bind.JAXBException: class ** nor any of its super class is known to this context

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.

like image 353
Apps Avatar asked Dec 19 '13 16:12

Apps


1 Answers

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);
        ...
    }
    ...
}
like image 123
My-Name-Is Avatar answered Oct 17 '22 01:10

My-Name-Is