XML Schema describes the types, elements, and structures of XML. However, generic tools such as Simple API for XML (SAX), Document Object Model (DOM), and XML Object Model (XOM) make it difficult to use the information quickly. XMLBeans is a data-binding framework that creates POJOs from an XML Schema, allowing you to read, manipulate, and write XML quickly.
When working with POJOs, especially thick clients, you need to know when the object changes—something commonly referred to as eventing or notification. Eventing is a core component of the Model View Controller (MVC) and Model 2 paradigm, which stresses the decoupling of graphical user interface (GUI) code from the model code. By using a Sudoku game as an example, we'll show you how to easily add eventing to XML-based Java applications with XMLBeans extensions.
Sudoku is a nine-by-nine grid divided into nine three-by-three subgrids. The game starts with a few of the cells already filled in with numbers ranging from 1 to 9. The player's job is to fill in the rest of the cells based on the following constraints:
- All cells must contain one number between 1 and 9. This is handled by the XMLBeans generated code.
- All nine cells within each row must be unique.
- All nine cells within each column must be unique.
- All nine cells within each subgrid must be unique.
Figure 1 shows a view of the board. We've developed an application that uses XMLBeans eventing to validate if constraints 2-4 are being met. If a constraint is not met, the view is updated to show the conflicting cells in red.
Figure 1. Sample Sudoku game board
For more information on the history and strategies of Sudoku, see Resources.
The Observer design pattern allows interested parties to be notified of changes. The XMLBeans eventing code described in this article is an instance of this design pattern. The idea is to allow objects to register for model changes in an XmlObject at run time. This way, you can attach listeners to Sudoku cells to enable validation on every change.
XMLBeans lets you use a configuration file, also known as the XSDCONFIG file, to customize the code generation. The XSDCONFIG file contains XML configuration for:
- Namespace mapping to a Java package
- Type and element QName mapping to a Java class
- Adding methods to generated code
- Adding implementation before and after a model changes
Item 4 allows for eventing to occur for interested parties if the interested parties are known at compile time. Unfortunately, this is not always the case. For more information on the contents of the XSDCONFIG file, see Resources.
To enable eventing, you need to implement items 3 and 4. You do this by following these four steps:
- Write an interface extension to allow for listeners to be registered to
XmlObjects. - Write a PrePostSet extension that notifies the listeners of a model change.
- Create an XSDCONFIG file with the necessary configuration information.
- Tell the XMLBeans utility
scompabout the XSDCONFIG file.
The interface extension feature of XMLBeans allows you to add methods with custom implementation for the generated POJOs. XMLBeans requires you to create an interface and a static implementation for each method in that interface. The interface that we created is located in com.ibm.wsrc.xmlbeans.IModelChangeEmitter and contains four methods, as shown in Listing 1.
Listing 1. com.ibm.wsrc.xmlbeans.IModelChangeEmitter.java
public interface IModelChangeEmitter {
public void fireModelChangeEvent(ModelChangeEvent event);
public void addModelChangeListener(IModelChangeListener modelChangeListener);
public void removeModelChangeListener(IModelChangeListener modelChangeListener);
public boolean hasModelChangeListeners();
}
|
The interface has two life cycle management listener methods, addModelChangeListener() and removeModelChangeListener(); one inspection of listeners, hasModelChangeListeners(); and one utility method, fireModelChangeEvent(), which is used to notify all registered listeners of a model change.
When any process calls one of these methods, XMLBeans passes the XmlObject that the method was called on and the parameters to a static implementation. In this case, the static implementation is in com.ibm.xmlbeans.eventing.ModelChangeEmitterHandler.java. Since this is a static implementation and you cannot store instance variables in XmlObjects, you need to store the list of interested parties elsewhere. In this implementation, the list is stored in a static HashMap. Listing 2 shows which Java code contents are stored in the class.
Listing 2. com.ibm.wsrc.xmlbeans.ModelChangeEmitterHandler.java
public class ModelChangeEmitterHandler {
private static HashMap<XmlObject, LinkedList<IModelChangeListener>> listeners =
new HashMap<XmlObject, LinkedList<IModelChangeListener>>();
public static void fireModelChangeEvent(XmlObject xo, ModelChangeEvent event) {
LinkedList<IModelChangeListener> list = listeners.get(xo);
if (list == null)
return;
for (IModelChangeListener listener : list)
listener.modelChange(event);
}
public static void addModelChangeListener(XmlObject xo,
IModelChangeListener modelChangeListener) {
LinkedList<IModelChangeListener> list = listeners.get(xo);
if (list == null) {
list = new LinkedList<IModelChangeListener>();
listeners.put(xo, list);
}
list.add(modelChangeListener);
}
public static void removeModelChangeListener(XmlObject xo,
IModelChangeListener modelChangeListener) {
LinkedList<IModelChangeListener> list = listeners.get(xo);
if (list == null)
return;
list.remove(modelChangeListener);
// remove references of list and XmlObject
if (list.isEmpty())
listeners.remove(xo);
}
public static boolean hasModelChangeListeners(XmlObject xo) {
return listeners.containsKey(xo);
}
}
|
The addModelChangeListener() code is a good example of how the listener list is utilized. The addModelChangeListener() method first checks to see if a listener list is registered for the XmlObject, then creates it if it doesn't exist already. Finally, it adds the listener to the list.
The PrePostSet extension lets you add custom processing to XMLBeans. You use a static class that has methods that run before and after the XmlObject changes, as shown in Listing 3.
Listing 3. PrePostSet extension pseudocode
if (PrePostSetExtension.preSet())
updateXmlObject();
PrePostSetExtension.postSet();
|
The static methods, preSet() and postSet(), both have the same signature: *Set(int opType, XmlObject xo, QName propertyName, boolean isAttr, int index). At first glance, it appears that the postSet() method is where all of the eventing code would live. However, the postSet() method does not have all of the information needed to fire an event. The postSet() needs to fire an event to all of the registered listeners with the object being updated, the action of the update, the value before, and the value now. The previous value is not known in the postSet() function. Therefore, you need to store the contents of the previous object statically.
The previous values are stored in a static HashMap called oldValues. On the preSet() method, the old value is copied into this HashMap, and on the postSet() method, the actual firing of the event is performed. Listing 4 shows what the preSet() method looks like.
Listing 4. com.ibm.xmlbeans.eventing.ModelChangePrePostHandler.preSet()
public static boolean preSet(int opType, XmlObject xo, QName propertyName,
boolean isAttr, int index) {
IModelChangeEmitter emitter = (IModelChangeEmitter)xo;
if (!emitter.hasModelChangeListeners())
return true;
// get the child that will be changed
XmlObject oldXO = null;
if (isAttr)
oldXO = xo.selectAttribute(propertyName);
else {
switch (opType) {
case PrePostExtension.OPERATION_SET:
case PrePostExtension.OPERATION_REMOVE:
oldXO = getChildXO(xo, propertyName, index);
break;
case PrePostExtension.OPERATION_INSERT:
break;
}
}
// store the old value
int hash = hashCode(xo, propertyName, isAttr, index);
Object oldValue = null;
if (oldXO == null)
oldValue = null;
else if (oldXO instanceof SimpleValue) {
oldValue = ((SimpleValue)oldXO).getObjectValue();
if (oldValue instanceof XmlObject)
oldValue = ((XmlObject)oldValue).copy();
} else
oldValue = oldXO.copy();
oldValues.put(hash, oldValue);
return true;
}
|
In this method, the code inspects the model object to see if it has any listeners attached. If listeners are attached, the current property value is cached. The utility method getChildXO() is called if it is not an attribute. Finally, this value is copied if it is a non-constant XmlObject. Either this copy or the original is then stored in the oldValues HashMap. Listing 5 shows the postSet() method.
Listing 5. com.ibm.xmlbeans.eventing.ModelChangePrePostHandler.postSet()
public static boolean postSet(int opType, XmlObject xo, QName propertyName,
boolean isAttr, int index) {
IModelChangeEmitter emitter = (IModelChangeEmitter)xo;
if (!emitter.hasModelChangeListeners())
return true;
// get the old value;
int hash = hashCode(xo, propertyName, isAttr, index);
Object oldValue = oldValues.get(hash);
oldValues.remove(hash);
ModelChangeEvent.Action action = ModelChangeEvent.Action.UPDATE;
// get the new xml object
XmlObject newXO = null;
if (isAttr)
newXO = xo.selectAttribute(propertyName);
else {
switch (opType) {
case PrePostExtension.OPERATION_SET:
newXO = getChildXO(xo, propertyName, index);
break;
case PrePostExtension.OPERATION_INSERT:
XmlObject[] children = xo.selectChildren(propertyName);
// on an insert, it is always the last element
newXO = children[children.length - 1];
action = ModelChangeEvent.Action.CREATE;
break;
case PrePostExtension.OPERATION_REMOVE:
action = ModelChangeEvent.Action.DELETE;
break;
}
}
// get the new value
Object newValue = null;
if (newXO == null)
newValue = null;
else if (newXO instanceof SimpleValue)
newValue = ((SimpleValue)newXO).getObjectValue();
else
newValue = newXO;
// if newValue != oldValue
if (((newValue == null) && (oldValue != null)) || ((newValue != null)
&& (!newValue.equals(oldValue))))
emitter.fireModelChangeEvent(new ModelChangeEvent(propertyName,
emitter, oldValue, newValue, action, index));
return true;
}
|
The postSet() method first checks to see if any listeners are attached to this XmlObject. If there are interested parties, the code retrieves the old value from the oldValues HashMap. It retrieves the current value from the current XmlObject and assigns it to the newValue variable. Finally, postSet() checks the oldValue to ensure that it is not equal to the newValue. The XmlObject creates and fires a model change event. Listing 6 shows the utility methods.
Listing 6. com.ibm.xmlbeans.eventing.ModelChangePrePostHandler utility methods
private static XmlObject getChildXO(XmlObject xo, QName propertyName, int index) {
XmlObject childXO = null;
XmlObject[] values = xo.selectChildren(propertyName);
if (values.length != 0) {
if (index == -1)
childXO = values[0];
else if (index < values.length)
childXO = values[index];
}
return childXO;
}
private static int hashCode(XmlObject xo, QName propertyName, boolean isAttr, int index) {
final int prime = 31;
int result = 1;
result = prime * result + index;
result = prime * result + (isAttr ? 1231 : 1237);
result = prime * result + ((propertyName == null) ? 0 : propertyName.hashCode());
result = prime * result + ((xo == null) ? 0 : xo.hashCode());
return result;
}
|
The getChildXO() method retrieves a property value from
the XmlObject. It uses the
selectChildren() method and then grabs the right child if more than one is returned. The hashCode() method calculates a unique hash for an XmlObject and the property of the XmlObject; Eclipse creates the method automatically.
To implement eventing on top of XMLBeans, you need to use and configure the interface extension feature and the PrePostSet extension feature. Listing 7 shows what the XSDCONFIG file looks like.
Listing 7. XSDCONFIG for XMLBeans eventing
<xb:config xmlns:xb="http://xml.apache.org/xmlbeans/2004/02/xbean/config">
<xb:extension for="*">
<xb:interface name="com.ibm.xmlbeans.eventing.IModelChangeEmitter">
<xb:staticHandler>
com.ibm.xmlbeans.eventing.ModelChangeEmitterHandler
</xb:staticHandler>
</xb:interface>
</xb:extension>
<xb:extension for="*">
<xb:prePostSet>
<xb:staticHandler>
com.ibm.xmlbeans.eventing.ModelChangePrePostHandler
</xb:staticHandler>
</xb:prePostSet>
</xb:extension>
</xb:config>
|
The first xb:extension element of xb:config contains the configuration of the interface extension. Here it defines the interface for all generated XmlObject instances to extend IModeChangeEmitter. XMLBeans will run the implementation in ModelChangeEmitterHandler.
The second xb:extension element of xb:config contains the configuration for the PrePostSet extension. It configures XMLBeans to call preSet() and postSet() in the ModelChangePrePostHandler class before and after a process modifies a property.
Finally, to input the configuration file to the generator, you specify the XSDCONFIG file as the last parameter to scomp. For the Sudoku game, you specify this in a Java class, as shown in Listing 8.
Listing 8. com.ibm.xmlbeans.generator.SudokuSchemaGenerator.java
public class SudokuSchemaGenerator {
public static void main(String[] args) {
String[] xmlBeanArgs = new String[] {
"-d", "xb_bin",
"-src", "xb_src",
"-out", "lib/sudokuXB.jar",
"-javasource", "1.5",
"schema/sudokuXBE.xsd",
"schema/eventing.xsdconfig"
};
org.apache.xmlbeans.impl.tool.SchemaCompiler.main(xmlBeanArgs);
}
}
|
For more information on scomp and the command-line
arguments, see Resources.
After running this command, you now have POJOs from sudokuXBE.xsd that allow for interested parties to be notified when the POJOs change.
Create the Sudoku game RCP application
The Sudoku game is an RCP application that notifies users when a cell has a bad value. The values are validated against the other cells in the row, column, and subgrid. If any of these values conflict, the offending cells will turn red, as shown in Figure 2.
Figure 2. The Sudoku game in action
Take the following steps to implement the game's user interface (UI):
- Create a Sudoku schema that represents the game board.
- From the schema, generate a set of XMLBeans with eventing.
- Using the generated XMLBeans with eventing, create an MVC-style application.
The general design of the application is to remove the coupling of the view from the model. The controller, which is the Standard Widget Toolkit (SWT) key listener, first modifies the model. The model then notifies the validators of the change. The affected validators check to see if the cell they control is still valid; if it is, they flip the valid flag if needed. This flip causes the framework to fire another event, which the view (cell) catches. This updates the background color of the cell. Figure 3 shows these four steps in a flow chart.
Figure 3. Event flow through the Sudoku RCP application
Model the Sudoku game in XML Schema
The overall schema definition is simple and starts with the concept of the game board. The game board contains nine rows and nine columns of cells that can only contain a value of 1 through 9. Use this simple definition to create the schema. The board type shown in Figure 4 consists of exactly nine row type elements. This is sufficient enough to define the overall nine-by-nine Sudoku grid.
Figure 4. BoardType XML schema type
You can further describe the board by defining a row type, as shown in Figure 5.
Figure 5. RowType XML schema type
The row type contains exactly nine cell type elements. By dividing the row type down to nine cell types, you can get the granularity needed to define the values that are in each cell of the row, as shown in Figure 6.
Figure 6. CellType XML schema type
The cell type is a simple element that can contain a range of values from 1 to 9 and a period (.) that represents no value. The cell type also has an attribute that represents whether the cell is valid based on the row, column, and grid requirements, as shown in Figure 7.
Figure 7. CellStringType XML schema type
The cell string type further defines the cell by enforcing the constraint that a cell can only contain a value of 1 through 9 and a period.
Using this schema definition and the XMLBeans generation code as defined in Configure XMLBeans generation, you've now produced a set of POJOs that represent the schema and also contain the eventing model so that the UI can be notified of any changes to the model.
You write the UI in a classic MVC style that's made possible by the eventing that is a part of the generated XMLBeans. The view of the model was developed using the Eclipse platform to create an RCP application. The RCP application is essentially an Eclipse plug-in, and as a part of the startup of the Eclipse plug-in, a view is initialized. As you create each SWT text widget and add it to the view, you add a key listener through the text.addKeyListener() method call. This allows you to intercept the key presses as the user enters values into blank cells. See Listing 9.
Listing 9. com.ibm.xmlbeans.tutorial.View.addKeyListeners().new KeyAdapter() {...}
public void keyPressed(KeyEvent e) {
switch (e.keyCode) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (text.getEditable()) {
final CellStringType.Enum newValue =
CellStringType.Enum.forString(String.valueOf(e.character));
Job job = new Job("Run Validation") {
protected IStatus run(IProgressMonitor arg0) {
XbeUtilities.getCell(Activator.getDefault().getBoard(), myRow, myCol)
.setCellValue(newValue);
return Status.OK_STATUS;
}
};
text.setText(String.valueOf(e.character));
job.schedule();
}
break;
case SWT.BS:
case SWT.DEL:
case '.':
case ' ':
if (text.getEditable()) {
Job job = new Job("Run Validation") {
protected IStatus run(IProgressMonitor arg0) {
XbeUtilities.getCell(Activator.getDefault().getBoard(), myRow, myCol)
.setCellValue(CellStringType.X);
return Status.OK_STATUS;
}
};
job.schedule();
text.setText(" ");
}
break;
// ... arrow navigation code ...
}
e.doit = false;
}
|
You then set the value in to the model through the setCellValue(newValue) call. By making this call, you update the model that is defined in XML by the schema. XMLBeans eventing code fires an event that notifies all listeners that the model has changed.
A key part of the eventing model is the notification of a cell's validity based on its position within a row, column, or three-by-three subgrid. From within the modelChange(ModelChangeEvent event) method, you call the runValidation() method to validate the cell, as shown in Listing 10.
Listing 10. com.ibm.xmlbeans.listeners.CellValidatorListener.modelChange(ModelChangeEvent)
public void modelChange(ModelChangeEvent event) {
if (XbeUtilities.CELLVALUE_QNAME.equals(event.getPropertyName())) {
// if the current cell value is set
if (!CellStringType.X.equals(cell.getCellValue())) {
if (cell.equals(event.getSource())) {
// run manual validation
int count = runValidation();
numInvalid = count;
} else {
// another cell
if (cell.getCellValue().equals(CellStringType.Enum.forString(
(String)event.getNewValue())))
numInvalid++;
else {
if (cell.getCellValue().equals(CellStringType.Enum.forString(
(String)event.getOldValue())))
numInvalid--;
}
}
} else if (cell.equals(event.getSource()))
numInvalid = 0;
if (numInvalid == 0)
cell.setValid(true);
else if (numInvalid < 0) {
Activator.getDefault().getLog().log(
new Status(IStatus.WARNING, Activator.PLUGIN_ID,
"Cell validator has a negative number of invalid cells. This is impossible."));
} else
cell.setValid(false);
}
}
|
As you can see, you first check which property has changed. In Listing 10, it is the CELLVALUE_QNAME property. If the cell is the event source, then you call runValidation() to validate against all criteria. However, if it is another cell, then you just need to verify that the values are not equal to the new value. If they are, then you need to increment the numInvalid count by one. If it is equal to the old value, then you need to decrease the numInvalid by one. Finally, if numInvalid is zero, then you can set the cell's valid flag to true through cell.setValid(true). Otherwise, you need to set the cell's valid flag to false through cell.setValid(false). When you set the valid flag on the cell through these calls, XMLBeans eventing fires another event. This event is processed by a view listener to update the border color of the cell, as shown in Listing 11.
Listing 11. com.ibm.xmlbeans.tutorial.View.addTextNotifier(...).new IModelChangeListener() {...}.modelChange(ModelChangeEvent)
public void modelChange(ModelChangeEvent event) {
if (XbeUtilities.VALID_QNAME.equals(event.getPropertyName())) {
// might not be in the display thread, need to run in it to
// modify the background!
text.getDisplay().asyncExec(new Runnable() {
public void run() {
if (cell.getValid())
text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_WHITE));
else
text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
}
});
}
}
|
As you can see, when the modelChange event is fired, the cell gets notified because you added modelChangeListener() to each cell. When the event is processed, the cell checks to see if it's valid and sets the background color of its corresponding text widget appropriately.
Through the use of eventing, you can use a simplified coding model to remove the dependency of the model on the user interface. This allows you to create the Sudoku game application quickly and without coupling the view to the model.
Through the use of two extension points in XMLBeans—the interface and PrePostSet—you can add eventing to XMLBeans. You use the interface extension to add listeners to XMLBeans, and you use the PrePostSet extension to capture the changes and notify the interested parties of the change. Finally, you integrate these extensions into XMLBeans generation through a configuration file.
| Description | Name | Size | Download method |
|---|---|---|---|
| XMLBeans eventing with Sudoku game Eclipse project | x-xmlbeanse/SudokuProject.zip | 2694KB | HTTP |
| Sudoku boards described in XML | x-xmlbeanse/SudokuBoards.zip | 4KB | HTTP |
| Sudoku game compiled for Windows | x-xmlbeanse/SudokuApplication-Win32.zip | 146346KB | HTTP |
Information about download methods
Learn
- The Apache
XMLBeans site: Get documentation and download information on XMLBeans so you can access XML by binding it to Java types.
- The Extension Interfaces Feature page: Visit the XMLBeans wiki for information on extending XMLBeans generated code.
- The PrePostSetFeature page in the XMLBeans wiki: Explore how to invoke code on model changes for generated
XmlObjects. - The Use XForms to create your own Sudoku game series (Nicholas Chase, developerWorks, February 2007): Learn to make a Sudoku game in XForms.
-
Programming With
XMLBeans (Abhinav Chopra, developerWorks, September 2004): Dig into this introduction to XMLBeans and see how XMLBeans revolutionizes data binding.
- The XMLBeans install guide: Find the environment variables and other setup information you need to run XMLBeans.
-
Sudoku: Read about the history of Sudoku and strategies in this Wikipedia article.
-
Configuring
XMLBeans (Hetal Shah, Dev2Dev, November 2004): Learn to use XSDCONFIG to alter the code generation by XMLBeans.
-
Observer pattern: In this Wikipedia article, read about the design pattern implemented by the XMLBeans eventing code.
-
scomp: Read the documentation on the command-line arguments for the schema compiler and code generator.
-
IBM XML certification: Find out how you can become an IBM-Certified Developer in XML and related technologies.
-
XML technical library: See the developerWorks XML Zone for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks.
-
developerWorks technical events and webcasts: Stay current with technology in these sessions.
- The technology
bookstore: Browse for books on these and other technical topics.
Get products and technologies
-
IBM trial software: Build your next development project with trial software available for download directly from developerWorks.
Discuss
- Participate in the discussion forum.
-
XML zone discussion forums: Participate in any of several XML-related discussions.
-
developerWorks blogs: Check out these blogs and get involved in the developerWorks community.

Jacob Eisinger currently works on the next-generation service-oriented architecture (SOA) technologies in Software Group Strategy, Emerging Standards at IBM. He has expertise in Web services, XML, Java, and brewing beer. Oh, and he loves Hokie football.

Vince Brunssen is currently leading prototypes on the latest Web service specifications as they're released. He's in Software Group Strategy, Emerging Standards at IBM. Throughout his career, he's run the gambit on technologies, from the user experience on Eclipse to the back end with Universal Description, Discovery, and Integration (UDDI). Vince also golfs, and he is really, really good at that, too!



