Friday, May 9, 2008

Custom Error message in XML - XSD validations for Java.

When you validate your XML with XSD you will get error message with some code and
Text. Based on the error code we can customize the error message.
Always In error message values(node name and ndoe value) will come within
single qoutes. By using string tokenier we can decode the node name and value ,
but order of apperance in error message should be known for writing custom message.

In properties file value is assinged against each errorcode.

Poperties file:

cvc-complex-type.2.4.a=Value of ''{1}'' is empty (or) expected before ''{0}''.

Java
Validator
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

/**
* Validator.java
* Class extends org.xml.sax.helpers.DefaultHandler
* Author : Prakash Krishnan
* Date : 09-May-08
**/
public class Validator extends DefaultHandler {
public boolean validationError = false;
public SAXParseException saxParseException = null;
/**
Receive notification of a recoverable parser error.
**/
public void error(SAXParseException exception) throws SAXException {
validationError = true;
saxParseException = exception;
}
/**
* Report a fatal XML parsing error.
**/
public void fatalError(SAXParseException exception) throws SAXException {
validationError = true;
saxParseException = exception;
}
/**
* Receive notification of a parser warning.
**/
public void warning(SAXParseException exception) throws SAXException {
}
}

XMLXSDValidator
import java.io.StringReader;
import java.io.FileInputStream;
import java.text.MessageFormat;
import java.util.StringTokenizer;
import java.util.PropertyResourceBundle;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXParseException;

/**
* XMLXSDValidator.java
* Utility to validate an xml string input against an xsd file.
* Returns a String with the validation results
**/

/* Author : Prakash Krishnan
Date : 09-May-08
*/

public class XMLXSDValidator {

/**
* Retuns a report of the results of validating an xml String against an xsd with
a property file
* The property file is used to customize the error message.
* Note:The XSD and properties file name paramerter require full path
**/
synchronized static String ValidateXML(String XSDUrl, String xmlContents,
String propertyFileName) {
{
//convert String to an InputSource; required for the validator.
source = new InputSource(new StringReader(xmlContents));

System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
"org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(true);
factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
"http://www.w3.org/2001/XMLSchema");
factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource",
XSDUrl);
DocumentBuilder builder = factory.newDocumentBuilder();
//instantiate com.exel.www.hubware.Validator. This class extends Default
// handler
Validator handler = new Validator();
builder.setErrorHandler(handler);
builder.parse(source);
if (handler.validationError == true)
return handleError(handler.saxParseException,propertyFileName);
else
return null; // return nothing if there is no exception;
} catch (Exception e) {
return e.toString(); //if any exception is caught, then return null.
//The application will take care of reporting an error for this case.
}
}

/**
* @param handler
* @throws Exception
*/
private static String handleError(SAXParseException saxParseException,
String propertFileName) throws Exception {
//get the error message
String errorMessage = saxParseException.getMessage();
//split error message using :, error messages will be of the format
//errorcode:errormessage
int spiltpoint = errorMessage.indexOf(':');
//get the error code
String errorcode = errorMessage.substring(0, spiltpoint);
//get the error message
errorMessage = errorMessage.substring(spiltpoint + 1, errorMessage.length());

//open the property file errormsg.properties
try
{
PropertyResourceBundle properties = new PropertyResourceBundle
(new FileInputStream(propertFileName ));
// check if the errocode has a match in the property file
String propStr = (String) properties.handleGetObject(errorcode);

if (propStr == null || propStr.length() == 0) {
return errorMessage; //if no match return the error message
}

StringTokenizer stringTokenizer = new StringTokenizer(errorMessage, "\'");
//two quotes make a string. So,
// the number of elements will be the number of tokens.
//+1 is done in case the number of quotes reported are in odd number.
//This will avoid exceptions.
String[] values = new String[stringTokenizer.countTokens() / 2 + 1];
int i = 0;
//Extract data within the quotes into the array
while (stringTokenizer.hasMoreElements()) {
stringTokenizer.nextElement();
if (stringTokenizer.hasMoreElements()) {
values[i++] = (String) stringTokenizer.nextElement();
}
}
return MessageFormat.format(propStr, values); //replace the patterns {0}, {1} ,
// etc. with the values array.
} catch (Exception e) {
// if the property file cannot be loaded or in case of any exception in the
//property extraction just return the errorMessage
return errorMessage;
}
}


}


Output:

Praser error message:
cvc-complex-type.2.4.a: Invalid content was found starting with element 'XXXX'. One of '{YYYY}'
is expected.
After running below program you will get:
Value of '{YYYY}' is empty (or) expected before 'XXXX'.

1 comment:

Yuriy said...

Very nice post. But I assume it will fail (or at least will compute wrong results) when one of arguments in error message contains "\'". There is no a complete solution for this situation, but it's not the worst approach. I used the one with regex expressions.
The question is - can we set a custom message formatter to Xerces implementation? In this way we could get all the arguments and error codes in their pure form.