Validate request.XML against an xml schema

  • Tags : xml, schema, validate
  • Latest : 0.1.1
  • Last Updated: 17 May 2010
  • Grails version : 1.2.1 > *
  • Authors : Brent Halsey
0 vote
Dependency :
compile ":xml-validator:0.1.1"

Documentation

Summary

Installation

grails install-plugin xml-validator

Description

XmlValidator plugin

Summary

Provides a simple mechanism to validate xml on the request with a given schema. Schema can be passed in as a relative file path or as a string. Throws SAXException for any validation errors. Syntax is easy to remember: change the familiar request.XML access into a method with the schema passed as an argument: request.XML( schemaInput ) .

The XmlValidator plugin simplifies validating incoming xml requests to a RESTful web service. The default converters plugin in Grails provides a simple mechanism to retrieve parsed xml as a GPathResult via the request.XML property accessor. Depending on what your application does, invalid xml could cause an obscure exception to be thrown further down the line. Since we're dealing with xml, it's natural to validate it with xml schema. The XmlValidator allows you to pass in an xml schema string or file path and validate the xml while you're retrieving it with request.XML( schemaInput ) . If the xml doesn't validate, a SAXException is thrown with a helpful message pointing out the problem in the xml.

The following examples use the same car records xml and schema as the Groovy User Guide's section on Validating XML with a W3C XML Schema.

Example without validation

class CarController {

def index = { }

/** pretend to add cars to something. Doesn't perform schema validation. */ def add = { def xml = request.XML int count = 0 xml.car.each { car -> log.warn "Adding car of make ${car.@make}" log.warn "has a record of type: ${car.record.@type} in ${car.record.text()}" // add cars here count++ }

render(contentType: "text/xml") { message("Added ${count} cars") } }

}

The above example demonstrates a typical usage of the XML accessor Grails provides to the request object in a controller. If a consumer of your web service passes in invalid xml, the forgiving GPathResult often doesn't mind, but your code might. And without explicitly checking for valid input, the consumer might receive a confusing error message. Cryptic error messages can make it hard to debug the problem.

The next example shows how we can use the XmlValidator plugin to validate incoming xml against an xml schema.

Example with XmlValidator

import org.xml.sax.SAXException

class CarController {

/** pretend to add cars to something. Performs schema validation. */ def addValidate = { try { def xml = request.XML(carRecordsSchema)

int count = 0 xml.car.each { car -> log.warn "Adding car of make ${car.@make}" log.warn "has a record of type: ${car.record.@type} in ${car.record.text()}" // add cars here count++ }

render(contentType: "text/xml") { message("Added ${count} cars.") } } catch (SAXException e) { render(contentType: "text/xml") { message("Error in xml schema: " + e.message) } } }

String carRecordsSchema = ''' <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"> <xs:element name="records"> <xs:complexType> <xs:sequence> <xs:element maxOccurs="unbounded" ref="car"/> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="car"> <xs:complexType> <xs:sequence> <xs:element ref="country"/> <xs:element ref="record"/> </xs:sequence> <xs:attribute name="make" use="required" type="xs:NCName"/> <xs:attribute name="name" use="required"/> <xs:attribute name="year" use="required" type="xs:integer"/> </xs:complexType> </xs:element> <xs:element name="country" type="xs:string"/> <xs:element name="record"> <xs:complexType mixed="true"> <xs:attribute name="type" use="required" type="xs:NCName"/> </xs:complexType> </xs:element> </xs:schema> ''' }

Now when we pass in invalid xml, we'll get a message back like

<message>Error in xml schema: cvc-complex-type.3.2.2: Attribute 'mmake' is not allowed to appear in element 'car'.</message>

This error message tells the consumer of your web service exactly what's wrong with their input.

Alternatively, you can specify the path of your xml schema file. The file is loaded as a resource stream by the ServletContext , so path names beginning with a / can be found in the web-app portion of your Grails application. In the next example, we use a schema file located at web-app/xsd/car-records.xsd . Keeping your schema file here has the added benefit of making the current schema available to your consumers via a url like http://servername:8080/ExampleXmlValidator/xsd/car-records.xsd .

Example with validation and schema file

import org.xml.sax.SAXException

class CarController {

/** pretend to add cars to something. Performs schema validation. */ def addValidateSchemaFile = { try { def xml = request.XML("/xsd/car-records.xsd")

int count = 0 xml.car.each { car -> log.warn "Adding car of make ${car.@make}" log.warn "has a record of type: ${car.record.@type} in ${car.record.text()}" // add cars here count++ }

render(contentType: "text/xml") { message("Added ${count} cars.") } } catch (SAXException e) { render(contentType: "text/xml") { message("Error in xml schema: " + e.message) } } }

}

Notes

The XmlValidator plugin is designed to play well with the caching used by the default converters plugin. If you call request.XML( schemaInput ) and subsequently make a call to request.XML , you'll get the cached GPathResult. Likewise, you can call request.XML and then make a call to request.XML( schemaInput ) to actually validate the xml.

Source and Examples

The XmlValidator source code is available at: http://github.com/bhalsey/XmlValidator

The examples shown here are taken from an example application with sample curl client scripts available at: http://github.com/bhalsey/ExampleXmlValidator

Author

Brent Halsey - mrbrent at gmail dot com