Some reader favorites:
EJB fundamentals and session beans
Create a scrollable virtual desktop in Swing
Wizard API updated!
Tim Boudreau has released a new version of the Swing Wizard library (version 0.997) that fixes the WizardException bug reported in JavaWorld's recent Open Source Java Project profile. The article's examples have been reworked to test out the new, improved WizardException. Thanks, Tim, for this helpful fix!
Open Source Java Projects: The Wizard API
Read the whole "Validation with Java and XML Schema" series:
On the other hand, everyone (and their dog!) is looking to get into XML. Using the Extensible Markup Language seems to be even more popular than hacking the Linux kernel these days and will even make your boss happy. So how do those two fit together? Well, XML, and specifically XML Schema, provides a perfect means of detailing constraints for Java data. And with a simple Java-based framework, you can build those constraints into Java objects and compare application data against them. The end result is a flexible, robust, XML-based framework for all your Java validation needs.
In this article, I'll show you how to parse an XML Schema and build up a set of constraints. Once those constraints are ready for your Java program to use, I'll detail the process of comparing data to them. Finally, your application will be given a means to pass in data and determine which constraint to apply to that data, indicating if that data is valid for that constraint. But before diving in, let me fill you in on what's already happened in this series.
In Part 1, I spent a lot of time talking about validation in general terms. I looked at some common bad practices you tend to find
in validation code, particularly the case of simply hard coding in constraints. Of course, that is not at all portable, so
I also looked at some utility classes, such as Jason Hunter's ParameterParser class from Java Servlet Programming. That class allows the simple conversion from the String format in which servlets receive data to various other Java formats such as ints, floats, and booleans. However, that still did not address other common validation needs such as range checking and specifying only a few allowed
values. Finally, I introduced XML as a possible solution, showing how an XML document is superior to Java's standard property
files.
In Part 2, I introduced the validation framework. Starting with some basic design, I showed the four basic classes you need to code:
Constraint class, which represents constraints for a single type, like the shoeSize type.
Validator class, which provides an interface for allowing developers to pass in data, and find out if the data is valid.
schemaParser class, which parses an XML Schema and creates the Constraint objects for use by the Validator class.
DataConverter helper class, which will convert from XML Schema data types to Java data types, and perform other data type conversions for
you.
In that article, I showed you the Constraint class in its entirety, providing basic methods that allowed setting an allowed range, a data type, and allowed values for
the data. If you were to add additional constraint types, such as pattern matching, you would add them to that class. I also
outlined the Validator class, and left a blank where schema parsing would occur, which I will fill in this article.
So now you are ready to dive into the guts, right? In this article, I'll start with parsing the XML Schema, and building up
constraints. Next, you'll see how to take those constraints and apply them to data in the Validator class. So let's get to it.
The bulk of the work that you need to do is in the schemaParser class. That class has one single task: to parse an XML Schema. While it parses, it should take each XML Schema attribute
and create a Constraint instance out of it. To refresh your memory, here's a sample XML Schema. That is still based on the shoe store that was discussed
in the previous articles but has some additional constraints:
<?xml version="1.0"?>
<schema targetNamespace="http://www.buyShoes.com"
xmlns="http://www.w3.org/1999/XMLschema"
xmlns:buyShoes="http://www.buyShoes.com"
>
<attribute name="shoeSize">
<simpleType baseType="integer">
<minExclusive value="0" />
<maxInclusive value="20" />
</simpleType>
</attribute>
<attribute name="width">
<simpleType baseType="string">
<enumeration value="A" />
<enumeration value="B" />
<enumeration value="C" />
<enumeration value="D" />
<enumeration value="DD" />
</simpleType>
</attribute>
<attribute name="brand">
<simpleType baseType="string">
<enumeration value="Nike" />
<enumeration value="Adidas" />
<enumeration value="Dr. Marten" />
<enumeration value="V-Form" />
<enumeration value="Mission" />
</simpleType>
</attribute>>
<attribute name="numEyelets">
<simpleType baseType="integer">
<minInclusive value="0" />
</simpleType>
</attribute>
</schema>
As an example, the schemaParser would parse the attribute for the constraint named "shoeSize" and create a new instance of the Constraint class. That class would have a data type of "int." Notice that it doesn't have "integer" because that is an XML Schema data
type. Instead, the Constraint class converts that data type (using the DataConverter class) to the Java equivalent. It will then have a value of 0 for the minimum (exclusive) value, and a value of 20 for the
maximum (inclusive) value. In that constraint, the minimum (inclusive), maximum (exclusive), and allowed values will all not
be used, as they aren't specified; another constraint might specify allowed values but no range at all. So with that in mind,
I'll start with looking at the class skeleton.
The schemaParser class has few public methods. The class's constructor takes in a java.net.URL pointing to the XML Schema to parse. That method will then need to fire off a private method, which will handle the parsing
and build up constraints. Once the instance has been constructed and parsing has occurred, the client needs to access the
built-up constraints. To allow that, two methods are provided: getConstraints(), which returns a list of the Constraint objects resulting for a parse, and getConstraint(String constraintName), which returns the Constraint for the supplied name (if it exists).
Here, then, is the skeleton for this class. It takes care of importing all the various classes that will be needed, and defines
the storage that will be used by the parseschema() method. Once that skeleton is in place, I'll show you how to handle the actual parsing.
package org.enhydra.validation;
import java.io.IOException;
import java.net.URL;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
// JDOM classes used for document representation
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.enhydra.validation.Constraint;
/**
* <p>
* The <code>schemaParser</code> class parses an XML Schema and creates
* <code>{@link Constraint}</code> objects from it.
* </p>
*/
public class schemaParser {
/** The URL of the schema to parse */
private URL schemaURL;
/** The constraints from the schema */
private Map constraints;
/** XML Schema Namespace */
private Namespace schemaNamespace;
/** XML Schema Namespace URI */
private static final String SCHEMA_NAMESPACE_URI =
"http://www.w3.org/1999/XMLschema";
/**
* <p>
* This will create a new <code>schemaParser</code>, given
* the URL of the schema to parse.
* </p>
*
* @param schemaURL the <code>URL</code> of the schema to parse.
* @throws <code>IOException</code> - when parsing errors occur.
*/
public schemaParser(URL schemaURL) throws IOException {
this.schemaURL = schemaURL;
constraints = new HashMap();
schemaNamespace =
Namespace.getNamespace(SCHEMA_NAMESPACE_URI);
// Parse the schema and prepare constraints
parseschema();
}
/**
* <p>
* This will return constraints found within the document.
* </p>
*
* @return <code>Map</code> - the schema-defined constraints.
*/
public Map getConstraints() {
return constraints;
}
/**
* <p>
* This will get the <code>Constraint</code> object for
* a specific constraint name. If none is found, this
* will return <code>null</code>.
* </p>
*
* @param constraintName name of constraint to look up.
* @return <code>Constraint</code> - constraints for
* supplied name.
*/
public Constraint getConstraint(String constraintName) {
Object o = constraints.get(constraintName);
if (o != null) {
return (Constraint)o;
} else {
return null;
}
}
/**
* <p>
* This will do the work of parsing the schema.
* </p>
*
* @throws <code>IOException</code> - when parsing errors occur.
*/
private void parseschema() throws IOException {
// Parse the schema and build up constraints
}
}
}
Pretty straightforward so far, right? Good. You should notice that the constructor and parseschema() method can both throw an IOException if problems arise. That gives the code a means of reporting problems back up the chain to the client, using the validation
framework. It also is the type of Exception that any Java code using the URL class might generate, which means that the code doesn't have to trap for those sorts of errors; instead, they are just thrown
up the calling chain.
Another important item you should note is the schemaNamespace variable. That variable holds the JDOM Namespace object for the XML Schema namespace. If you look at the XML Schema document again (shown above), the XML Schema namespace
URI is assigned to the default namespace. That means that all nonprefixed elements in the schema (which happens to be all
elements) are assigned to that default namespace, the XML Schema namespace. Getting the associated JDOM Namespace object for
that default, XML Schema, namespace will help you look up items in the document; in the next section, I'll show you how that
all fits together.
Once you have the skeleton in place, you need to handle actually parsing the XML Schema. I'll discuss that now.
The key to the entire schemaParser class is being able to (no surprise here) actually parse an XML Schema. Many XML parsers, such as Apache Xerces, currently
offer options for schema validation; however, you do not want to use those facilities. In fact, you don't want the XML Schema
to be handled as a schema at all. That is because all parsers, at least in their current versions, use vendor-specific structures
for handling XML Schemas. The result is nonportable code, the enemy of any Java programmer.
Instead, the schemaParser class can rely on the fact that an XML Schema document is actually an XML document as well. It conforms to XML's well-formedness
rules and, therefore, can be treated as any other XML document. Therefore, the schema parser can read in the XML Schema as
an XML document and operate on it as it would any other document with which it works. That is exactly what the parseschema() method does.
Using JDOM, which you can obtain in Resources, the parseschema() method first uses SAX to read in the supplied schema URL and build a JDOM Document object. And now is when that schemaNamespace variable comes into play (remember I said it would?). The XML Schema attribute construct represents all the constraints, so once the document is read into memory, those constraints are located within
the document by simply looking up all elements named attribute (I know, it's sort of confusing, isn't it? All the attribute
elements...) in the XML Schema namespace. Then, each of the resulting objects (represented by a JDOM Element) are passed to a utility method, handleAttribute(). The code shown here puts that into action:
/**
* <p>
* This will do the work of parsing the schema.
* </p>
*
* @throws <code>IOException</code> - when parsing errors occur.
*/
private void parseschema() throws IOException { /**
* Create builder to generate JDOM representation of XML Schema,
* without validation and using Apache Xerces.
*/
SAXBuilder builder = new SAXBuilder();
try {
Document schemaDoc = builder.build(schemaURL);
// Handle attributes
List attributes = schemaDoc.getRootElement()
.getChildren("attribute",
schemaNamespace);
for (Iterator i = attributes.iterator(); i.hasNext(); ) {
// Iterate and handle
Element attribute = (Element)i.next();
handleAttribute(attribute);
}
// Handle attributes nested within complex types
} catch (JDOMException e) {
throw new IOException(e.getMessage());
}
}
That is fairly straightforward and matches the concepts that I just walked through. The getChildren() method returns a Java List of elements matching the criteria supplied; the code then iterates through that List, peeling off each element, representing a constraint, and invokes the handleAttribute() method. Now I'll show you that method, which does the real work.
I'm not going to spend a lot of time walking through the handleAttribute() method; by now, you're starting to understand how the schemaParser class works and probably starting to see how simple JDOM makes things as well. The handleAttribute() method receives a JDOM Element, which represents a data constraint. Its task is to create a new constraint and add it to the constraint list in the class
instance.
First, a new Constraint is created. handleAttribute() then begins to run through all of the various options that a constraint can have and retrieves the data for each option.
If data is present, it sets the data for the Constraint instance. If not, it simply moves on to the next constraint option. In the method shown here, the name, data type, allowed
values, and ranges on a piece of data are examined and set:
/**
* <p>
* This will convert an attribute into constraints.
* </p>
*
* @throws <code>IOException</code> - when parsing errors occur.
*/
private void handleAttribute(Element attribute)
throws IOException {
// Get the attribute name and create a Constraint
String name = attribute.getAttributeValue("name");
if (name == null) {
throw new IOException("All schema attributes must have names.");
}
Constraint constraint = new Constraint(name);
// See if there is a data type on this constraint
String schemaType = attribute.getAttributeValue("type");
if (schemaType != null) {
constraint.setDataType(
DataConverter.getInstance().getJavaType(schemaType));
}
// Get the simpleType - if none, we are done with this attribute
Element simpleType = attribute.getChild("simpleType", schemaNamespace);
if (simpleType == null) {
return;
}
// Handle the data type
schemaType = simpleType.getAttributeValue("baseType");
if (schemaType == null) {
throw new IOException("No data type specified for constraint " + name);
}
constraint.setDataType(DataConverter.getInstance().getJavaType(schemaType));
// Handle any allowed values
List allowedValues = simpleType.getChildren("enumeration", schemaNamespace);
if (allowedValues != null) {
for (Iterator i=allowedValues.iterator(); i.hasNext(); ) {
Element allowedValue = (Element)i.next();
constraint.addAllowedValue(allowedValue.getAttributeValue("value"));
}
}
// Handle ranges
Element boundary = simpleType.getChild("minExclusive", schemaNamespace);
if (boundary != null) {
Double value = new Double(boundary.getAttributeValue("value"));
constraint.setMinExclusive(value.doubleValue());
}
boundary = simpleType.getChild("minInclusive", schemaNamespace);
if (boundary != null) {
Double value = new Double(boundary.getAttributeValue("value"));
constraint.setMinInclusive(value.doubleValue());
}
boundary = simpleType.getChild("maxExclusive", schemaNamespace);
if (boundary != null) {
Double value = new Double(boundary.getAttributeValue("value"));
constraint.setMaxExclusive(value.doubleValue());
}
boundary = simpleType.getChild("maxInclusive", schemaNamespace);
if (boundary != null) {
Double value = new Double(boundary.getAttributeValue("value"));
constraint.setMaxInclusive(value.doubleValue());
}
// Store this constraint
constraints.put(name, constraint);
}
Nothing here is too magical. If you have a need for other constraints, such as pattern matching or, perhaps, more complex
data types, you can add enhancements to the handleAttribute() method. For items such as pattern matching, you would want to add additional code to obtain the pattern element within the constraint and work with that value:
Element pattern = simpleType.getChild("pattern", schemaNamespace);
if (pattern != null) {
String patternValue = pattern.getAttributeValue("value");
// Set this pattern on the Constraint object
}
You would also need to make a change to the Constraint class and add a couple more methods. Making a change to the data types, though, involves a change to the helper class you
see used here, DataConverter. That class handles conversion from an XML Schema type to a Java type such as from integer (schema type) to int (Java type). You could add new data types to DataConverter, perhaps as XML Schema matures or as your needs increase. DataConverter is included, of course, in the source code available for download with this article.
Once this method has completed, the schemaParser constructor will complete, and return control to the invoking program. At that point, the invoking program (the original
Validator class constructor) can use the getConstraints() or getConstraint() method to obtain the data constraints and work with them. In fact, that's exactly what the Validator method, isValid(), does! So I'll return to that now.
So now I'm going to move back to the front line, the Validator class. If you remember, that is the actual class with which your clients will work, and the schemaParser class stays behind the scenes. The isValid() method, specifically, is what a client would use to check a specific piece of data against its constraints. That method would
be used like this:
Free Download - 5 Minute Product Review. When slow equals Off: Manage the complexity of Web applications - Symphoniq
![]()
Free Download - 5 Minute Product Review. Realize the benefits of real user monitoring in less than an hour. - Symphoniq