Monday, February 14, 2011

When @SchemaValidation in JAX-WS web services fails

I'm currently developing web services in an Oracle WebLogic environment. To enable schema validation on the web service, one only has to use the @SchemaValidation annotation in the web service class. Like this:


import com.sun.xml.ws.developer.SchemaValidation;

@WebService(...)
@SchemaValidation
public class WebService implements WebServiceInterface {
...
}


Unfortunately, that doesn't work for me. I get this error when I try to deploy my application:


Caused by: javax.xml.ws.WebServiceException:
Annotation@com.sun.xml.internal.ws.developer.SchemaValidation
(handler=class com.sun.xml.internal.ws.server.DraconianValidationErrorHandler)
is not recognizable,
atleast one constructor of class com.sun.xml.internal.ws.developer.SchemaValidationFeature
should be marked with @FeatureConstructor


I also tried creating my own validator to use in the annotation (@SchemaValidation(handler = ExampleValidationHandler.class)):


import org.xml.sax.SAXParseException;

import com.sun.xml.internal.ws.developer.ValidationErrorHandler;

public class ExampleValidationHandler extends ValidationErrorHandler {

public void warning(SAXParseException exception) {
;
}

public void error(SAXParseException exception) {
;
}

public void fatalError(SAXParseException exception) {
;
}
}


Then I enabled schema validation explicitly in the Oracle WebLogic web services deployment descriptor: weblogic-webservices.xml.


<?xml version="1.0" encoding="UTF-8"?>
<weblogic-webservices
xmlns="http://xmlns.oracle.com/weblogic/weblogic-webservices"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-webservices http://xmlns.oracle.com/weblogic/weblogic-webservices/1.1/weblogic-webservices.xsd">

<webservice-description>
<webservice-description-name>ExampleWSService</webservice-description-name>
<webservice-type>JAXWS</webservice-type>
<port-component>
<port-component-name>ExampleWSService</port-component-name>
<validate-request>true</validate-request>
<reliability-config>
<inactivity-timeout>P0DT600S</inactivity-timeout>
<base-retransmission-interval>P0DT3S</base-retransmission-interval>
<retransmission-exponential-backoff>true</retransmission-exponential-backoff>
<acknowledgement-interval>P0DT3S</acknowledgement-interval>
<sequence-expiration>P1D</sequence-expiration>
<buffer-retry-count>3</buffer-retry-count>
<buffer-retry-delay>P0DT5S</buffer-retry-delay>
</reliability-config>
</port-component>
</webservice-description>
</weblogic-webservices>


Unfortunately, that didn't work either. Google shows me three other developers with the same problem, but there is no working solution for me, and for them. So, another approach is needed to enable schema validation on incoming messages.

The solution presented in this blog post makes use of web service handlers. A web service handler is like an intercepting filter, which enables you to intercept incoming and outgoing messages, before they are passed on to the (external) system. The steps needed for this solution:

1. Create web service handler class
2. Define handler chain in XML-descriptor
3. Use annotation in web service class

1. Create web service handler class

There are two types of web service handlers:
- LogicalHandler
- SOAPHandler

The LogicalHandler provides access to the logical message, without protocol details like: SOAP envelopes. The SOAPHandler provides access to SOAP protocol details. For the purpose of message validation, the LogicalHandler is a better choice.


package com.javaeenotes;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.ws.LogicalMessage;
import javax.xml.ws.handler.LogicalHandler;
import javax.xml.ws.handler.LogicalMessageContext;
import javax.xml.ws.handler.MessageContext;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

public class ValidationHandler implements LogicalHandler<LogicalMessageContext> {
private String schemaUrl = "http://127.0.0.1:8080/webservice/schema.xsd";

@Override
public boolean handleMessage(LogicalMessageContext context) {
Boolean isOutBound = (Boolean) context
.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

if (isOutBound) {
return true;
}

LogicalMessage lm = context.getMessage();
Source payload = lm.getPayload();
StreamResult res = new StreamResult(new StringWriter());
String message = "";

try {
Transformer trans;
trans = TransformerFactory.newInstance().newTransformer();
trans.transform(payload, res);
message = res.getWriter().toString();
// Validate
validate(message, schemaUrl);
} catch (TransformerConfigurationException e) {
// When Source payload Transformer object could not be obtained.
throw new WebServiceException(e);
} catch (TransformerFactoryConfigurationError e) {
// When Source payload Transformer object could not be obtained.
throw new WebServiceException(e);
} catch (TransformerException e) {
// When Source payload could not be transformed to String.
throw new WebServiceException(e);
} catch (MalformedURLException e) {
// When URL to schema is invalid.
throw new WebServiceException(e);
} catch (ParserConfigurationException e) {
// When parser needed for validation could not be obtained.
throw new WebServiceException(e);
} catch (IOException e) {
// When something is wrong with IO.
throw new WebServiceException(e);
} catch (SAXException e) {
// When XSD-schema validation fails.
throw new WebServiceException(e);
}

return true;
}

private void validate(String xml, String schemaUrl)
throws ParserConfigurationException, IOException,
MalformedURLException, SAXException {

DocumentBuilder parser = null;
Document document = null;
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
parser = dbf.newDocumentBuilder();

byte bytes[] = xml.getBytes();
document = parser.parse(new ByteArrayInputStream(bytes));
SchemaFactory factory = SchemaFactory
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

Schema schema = null;
schema = factory.newSchema(new URL(schemaUrl));
javax.xml.validation.Validator validator = schema.newValidator();
validator.validate(new DOMSource(document));
}

@Override
public boolean handleFault(LogicalMessageContext context) {
return true;
}

@Override
public void close(MessageContext context) {
;
}

}


It is important to note that the payload must be defined in an XSD-schema. A definition in a WSDL file cannot be used. Use the boolean property: MessageContext.MESSAGE_OUTBOUND_PROPERTY to differentiate between out-bound or in-bound messages. Throw a WebServiceException when something goes wrong.

2. Define handler chain in XML-descriptor

Now, create a handler chain XML-descriptor file, which refers to the handler created in the previous step. In this example, I named the file handlers.xml and placed it in the same directory as the handler. The location of the file is important in the last step.


<?xml version="1.0" encoding="UTF-8"?>
<handler-chains 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/javaee_web_services_metadata_handler_2_0.xsd">
<handler-chain>
<handler>
<handler-name>ValidationHandler</handler-name>
<handler-class>com.javaeenotes.ValidationHandler</handler-class>
</handler>
</handler-chain>
</handler-chains>


3. Use annotation in web service class

The final step is to use the @HandlerChain annotation in the web service class. The file parameter must be a relative path to the XML-descriptor file from the annotated class file or an absolute URL.


package com.javaeenotes;

import javax.jws.HandlerChain;
import javax.jws.WebService;

import com.javaeenotes.ws.ExampleWS;
//import com.sun.xml.ws.developer.SchemaValidation;

@WebService(name = "ERSService",
targetNamespace = "http://javaeenotes.com/",
serviceName = "ExampleWSService",
portName = "ExampleWSPort",
endpointInterface = "com.javaeenotes.ws.ExampleWS",
wsdlLocation = "WEB-INF/wsdl/ExampleWSService.wsdl")
//@SchemaValidation
@HandlerChain(file = "handlers.xml")
public class WsImplementation implements ExampleWS {

@Override
public int sum(int arg0, int arg1) {
return 0;
}

@Override
public String greet(String arg0) {
return null;
}

@Override
public int multiply(int arg0, int arg1) {
return 0;
}
}

11 comments:

  1. thank you!

    I just added the handler-chain element under the endpoint element in the sun-jaxws.xml file. I do not need the @HandlerChain Annotation.

    Manfred

    ReplyDelete
  2. Schema Validations can be enforced on Server side as well as Client side.

    ReplyDelete
  3. This works like a charm. Thanks!

    If you don't want to expose the schema you can also copy it onto the classpath in your build and then load it as follows:

    import java.net.URL;

    public class SchemaLocation {
    private static final URL LOCATION =
    SchemaLocation.class.getResource("/wsdl/MessageTypes.xsd");
    private SchemaLocation() {}
    public static final String get() {
    return LOCATION.toString();
    }
    }

    You then call SchemaLocation.get() instead of your URL to the schema.

    Of course, none of this would be necessary if JBoss schema validation weren't broken to begin with.

    ReplyDelete
  4. Wow, I just saw you were writing this for Web Logic, not JBoss. Well, it works for JBoss too. good thing I didn't notice :-)

    ReplyDelete
  5. Thanks for sharing your blogs.Great effort.
    Keep it up.
    Ecommerce developer

    ReplyDelete
  6. Thanks a lot ... it really helped me.

    ReplyDelete
  7. Well, what happens when we have 20 services with different XSDs and that must have the validation done?
    Do we create Handlers for each one of them?

    ReplyDelete
  8. SchemaValidation annotation is working but make sure you're importing correct class: com.sun.xml.ws.developer.SchemaValidation instead of com.sun.xml.internal.ws.developer.SchemaValidation.

    Find the difference.

    ReplyDelete
  9. I solved this problem using the correct dependency provided by WebLogic, without the need for custom implementation. Check my answer: http://stackoverflow.com/questions/28070129/jax-ws-validate-schema-in-weblogic-with-schemavalidation/28070130#28070130

    ReplyDelete
  10. Very nice article, I enjoyed reading your post, very share, I want to twit this to my followers. Thanks!.
    video and blog marketing

    ReplyDelete