HierarchicalMap Tutorial - Intermediate Level
HierarchicalMap is an interface. So anyone can implement it. This tutorial is based on BasicHierarchicalmap, a reference implementation of HierarchicalMap.
Following subjects are covered in this tutorial:
(Javascript must be enabled to allow syntax highlighting for source code snippets in this tutorial)
Advanced Level
13. Runtime Schema ValidationAny Map has poor type checking capability and usually does not have control over the objects beeing mapped. With JDK 1.5 this problem has minimized through the introduction of generics. On the other hand, with HierarchicalMap the problem is even worse since we can create a deeply nested map each of them having any sort of objects as its node.
HierarchicalMap can be checked against XML Schema Document (XSD). There are two classes within package org.dhmp.util.xml for this purpose: SchemaCollection and Schema. SchemaCollection is for parsing schema document and instantiate the corresponding Schema class, then cache it. Schema is used to validate the HierarchicalMap content.
Following are the basic components necessary for schema checking:
Prepare an XML schema, like one below, and save it as "/temp/test.xsd". Note that XML Schema is not simple to handle with plain text editor. It is recommended to use some schema authoring tool.
Basically, the above schema is defining an element "Param" with three attributes: "pure", "namespace" and "elementValueKey"
The attribute "pure" and "namespace" are both boolean and the attribute "elementValueKey" is a string with minimum length of 5 characters and maximum length of 10 characters.
Now, prepare a text file containing the following text and save it as "/temp/test_xsd.properties". Note that this is actually a ResourceBundle, therefore it must be written in a single line without line break.
Finally, compile and execute the code below:
The result of above code will be the following:
First result: true
Representing HierarchicalMap using XSD
Check the Working with XML tutorial and realize that there is no distinction between attribute and element for the HierarchicalMap. Therefore, the schema document below has exactly the same effect of schema document presented earlier in this tutorial section for HierarchicalMap validation.
The HierarchicalMap node is represented as choice or sequence group. The HierarchicalMap leaves can be represented by element or attribute. The data type "QName" is reserved to represent java objects using "default" field to specify the name of java class.
Providing Error Messages
The boolean result telling either the map is valid or not could be insufficient. Some times it is necessary to know the reason, why the map is not valid:
Try the following code:
Now, the result will be like:
Second result: false Messages: parameter pure must be true or false Error in field ["Param"]. It does not satisfy choice. Unexpected element found "Namespace". elementValueKey must contain at least 5 and at most 10 characters.
Look at schema document and note that element "pure" is boolean, therefore value must be "true" or "false". The element "namespace" is all lowercase. And the element elementValueKey has minLength and maxLength defined.
The validate method of Schema class can receive a StringBuffer where all the error messages are appended. Schema can provide three types of error messages:
Examine the schema document "/temp/test.xsd" and notice that thre are two "appinfo" inside "annotation".
Direct message is provided as follow:
Any validation error for particular element containing the above annotation, will append the content of "text" attribute.
Referenced message is provided as follow:
For each schema document, the Schema class trys to load the ResourceBundle from same location of schema document replacing all dots '.' by underscore '_'. Thus, for schema document "file:/temp/test.xsd" the corresponding ResourceBundle will be loaded from "file:/temp/test_xsd.properties".
The reference attribute defines the key used to retrieve the error message from ResourceBundle. The facet attribute defines when this error message applies. Also the current value of these facets are passed as argument for MessageFormat. The first argument "{0}" is the name of the element beeing validated, the second and on is the corresponding facets, separated by comma. There is no way to pass the element value as part of error message to avoid vulnerability.
When there is no error message defined inside schema document, Schema can provide its own error message if "setUseDefaultMessage" is set to "true, no error message will be provided otherwise.
Replacing Elements by Java Objects
The Schema class can also replace the objects in HierarchicalMap to corresponding Java classes according to the data type defined on schema document.
XML Schema data type | Java data type | Remarks |
---|---|---|
boolean | Boolean | must be "true" or "false" |
date | java.util.Date | time portion is set to 00:00:00 at GMT |
dateTime | java.util.Date | |
decimal | java.math.BigDecimal | |
double | Double | |
float | Float | |
int | Integer | |
integer | java.math.BigDecimal | decimal without fraction digits |
long | Long | |
string | String | |
time | java.util.Date | date portion is set to 1970-01-01 |
The code below will show the use of replace method:
The result of above code will be the following. Notice that the class of "Param/pure" has changed.:
Before replace: java.lang.String After replace: java.lang.Boolean
In this example, the "replace" method was called instead of "validate" method, both has the same signature.
Locale and TimeZone
The interpretation of number fields and date fields is locale sensitive. For instance, in the US, the date is represented as Month-Day-Year and in the BR the same date is represented as Day-Month-Year. The number representaiton is also different and US use dot for decimal point and comma as group separator, in the BR decimal point is comma and group separator is dot. The time zone affects the interpretation of time field. Therefore 02:00 pm can be another time depending on the place you are on the globe. The java also support DST, daylight saving time, that makes time manipulation a bit tricky.
SchemaCollection can be instantiated passig timezone and locale to it. It uses these information to decide how the dates, times and numbers should be validated.
SchemaCollection schemas = new SchemaCollection(TimeZone.getDefault(), Locale.US);
Be aware when checking boundaries or enumeration for time fields. For instance, if you are on Sao Paulo in Brazil (time zone is GMT-03:00), and create a schema document with time field containting a constraint of minInclude="14:00:00-03:00". When the clock on your computer shows "14:30", your validation of HierarchicalMap created with:
hmap.put("time", Calendar.getInstance().getTime());
may fail depending on DST. To make this example work correctly, the schema document should be changed to minInclude="14:00:00" (without time zone) and instantiate the SchemaCollection with default locale. Thus you are working everything relative to local time.
If you are dealing with global application, try to keep everything relative to GMT. Instead of changing server time keeping your timezone, sometimes the better approach can be changing your timezone to reflect your current time correctly (though, do not forget to change the timezone back after everything went back to normal)
Validating Single Object
It is also possible to validate a single object against a subset of Schema. Prepare the following Schema Document and save it as "/temp/test2.xsd".
Now, you can validate a single date like this:
Notice that locale is set to UK and time zone is set to Sao Paulo. This means that the date is parsed as mm/dd/yyyy though the time is relative to UTC-3:00. Now, take a look at Schema Document in "/temp/test2.xsd". It declared 05/28 as UTC-3:00 and 01/13 as UTC-2:00 even though the validate method returned true in both validation. This is due to the DST that applies in Sao Paulo during January. Remember that Schem class handles all date as datetime with time portion set to midnight therefore timezone has a great effect in date comparsion.
Setting Constraints Dynamically
Sometimes it is necessary to define a constraint during runtime. In this case, we can write a code like following:
Schema class represents all the schema document's constraints using HierarchicalMap. These constraints can be retrieved using "getConstraint()" method, then the values can be manipulated as ordinary HierarchicalMap. The new value's class must be the Java class corresponding to the schema data type. For instance, minIncluseive for "double" data type must be java.lang.Double.