 | Level: Introductory C. Enrique Ortiz, Mobility Technologist and Writer, J2MEDeveloper.com
02 Nov 2004 Become familiar with the Web Services APIs for J2ME. This article, focusing on the JAXP, spotlights the general remote service invocation and XML parsing that saves you from embedding these functions into every application.
Introduction
The Web Services APIs (WSA) for Java 2 Platform, Micro Edition (J2ME), as defined by the Java Community Process Java Specification Request (JSR) 172 (see Resources), defines two independent optional packages for remote service invocation and XML parsing. These Java APIs are targeted at both the Connected Device Configuration (CDC) and the Connected Limited Device Configuration (CLDC 1.0 and CLDC 1.1)-based profiles. Because JSR 172 provides support for remote service invocation (see Web Services API for J2ME, Part 1) and XML parsing at the device level, you don't have to embed these functions into each application. This article introduces the Java API for XML Processing (JAXP) optional package.
The JAXP for J2ME
The JSR 172 XML parsing API for J2ME is based on a subset of Java 2 Platform, Standard Edition (J2SE) JAXP 1.2 API and Simple API for XML Parsing (SAX) 2.0 interfaces. JAXP for J2ME is based on a subset of JAXP 1.2 and SAX 2.0 because of the memory constraints found on typical J2ME platforms. JAXP for J2ME consists of the following Java packages:
javax.xml.parsers: contains the SAX parser, exception, and error classes
- Interfaces: None
- Classes:
SAXParserFactory (factory of SAX parsers), SAXParser (represents a SAX parser)
- Exceptions:
ParserConfigurationException (parser configuration)
- Errors:
FactoryConfigurationError (error related to parser factories such as parser not found)
org.xml.sax: the SAX 2.0 subset API
- Interfaces:
Attributes (an element's attributes), Locator (an event's document location)
- Classes:
InputSource (represents an XML input source)
- Exceptions:
SAXException (general/base SAX exception), SAXNotRecognizedException (feature not recognized), SAXNotSupportedException (feature recognized but not supported), SAXParseException (SAX parsing exception)
- Errors: None
org.xml.sax.helpers: defines the base class for event processing
- Interfaces: None
- Classes:
DefaultHandler (base event processing handler with default implementation)
- Exceptions: None
- Errors: None
JAXP for J2ME provides all the classes, interfaces, and exceptions needed to parse XML documents. The J2ME JAXP Javadoc or specification (see Resources) provides more information on these classes and interfaces.
XML parsers
JAXP for J2ME parsers must be XML 1.0 conformant, and can either be validating (based on a DTD) or not; the parsers follow the validation and non-validation rules found in the XML 1.0 specification (see Resources). Because validation is an expensive operation, the decision for including a validating parser implementation is decided by the device vendor based on the handset's resource and processing constraints. You can test if the parser is validating by calling the SAXParser.isValidating() method.
JAXP for J2ME XML parsers must support XML namespaces, as defined by the W3C XML Namespaces 1.0 Recommendation (see Resources), UTF-8 and UTF-16 character encoding, and DTDs. Not supported because of memory and run-time requirements are the Document Object Model (DOM) Level 1.x or 2.x and the Extensible Stylesheet Language Transformations (XSLT).
Typical elements of a JAXP for J2ME-based application
The JAXP for J2ME subset API provides all the functions needed to parse XML documents. In fact, you can parse XML documents by just using a subset of the functions provided by JAXP for J2ME. Figure 1 shows the typical elements of a JAXP for J2ME application.
Figure 1. Typical elements of a JAXP application
These elements are:
MyApplication: the J2ME application that you write.
MyEventHandler: a class that you write that performs the application-specific event handling. It extends org.xml.sax.DefaultHandler.
- Default event handler (
DefaultHandler): the base event processing class that your event handler extends. This base class provides default event processing.
- SAX parser factory (
SAXParserFactory): the SAX parser factory class that you use to get an instance of the SAX parser.
- SAX parser (
SAXParser): the SAX parser that you use to parse your input XML documents. SAX parser is created by calling SAXParserFactory.
- XML input stream: the input XML document to be parsed that is represented as a
java.io.InputStream or, preferably, as an org.xml.sax.InputSource. The input XML document could be read from a network connection or from local storage.
Of course, typical applications have more elements than the ones illustrated in Figure 1, such as screens, data models and controllers, command listeners to capture user interface input, and base classes such as MIDlet, if developing a MIDP application. Also note that it is a good practice to implement a class that encapsulates the application-specific XML logic. This logic basically consists of owning all the JAXP objects, initializing, invoking the parser, and processing the input XML document processing events.
Using JAXP for J2ME
Using the JAXP consists of three main steps:
- Define (write) your application-specific event handler, a subclass of
DefaultHandler.
- Use
SAXParserFactory to create an instance of the SAX parser (SAXParser).
- Parse the input XML document by invoking the
SAXParser.parse() method.
The SAX parser invokes your event handler's event-processing callbacks startDocument(), endDocument(), startElement(), endElement(), and characters() as different parts of the XML document are processed.
To understand the interaction between the different application objects over time, take a look at the high-level sequence diagram shown in Figure 2.
Figure 2. The JAXP API sequence diagram
These interactions are:
- The JAXP application instantiates the event handler (subclass of
DefaultHandler).
- The JAXP application instantiates the XML input source (
InputSource).
- The JAXP application gets an instance of the SAX parser factory (
SAXParserFactory).
- The JAXP application gets an instance of the SAX parser (
SAXParser) by calling the SAX parser factory.
- The SAX parser factory instantiates and returns a
SAXParser.
- The JAXP application calls the SAX parser to initiate the parsing of the XML document.
After parsing is initiated, the SAXParser invokes the event handler's callbacks as follows:
startDocument() is called at the start of the document parsing.
startElement() is called when a new element is encountered. This call also includes the element's attributes, if any.
characters() is called when an element's characters are encountered.
endElement() is called when a closing element is encountered.
endDocument() is called at the end of the document parsing.
Now, I'll discuss how to use the JAXP API by covering in more detail the three main steps previously mentioned:
- Define (write) your application-specific event handler.
- Create an instance of the SAX parser.
- Parse the input XML document.
These steps can be performed in the main application logic (for example, the MIDlet) or, as previously mentioned, I recommend that you encapsulate this XML-specific logic into its own set of classes, hiding these details from the application.
Write your application-specific event handler
The first step is to write an application-specific event handler. This event handler is a class that must extend org.xml.sax.helpers.DefaultHandler, typically overriding the methods startElement(), endElement(), and characters() to process your XML document elements and attributes. You can also override the startDocument() and endDocument() methods to perform logic at the beginning and end of the XML document parsing.
Listing 1 shows a typical XML document with nested elements and elements with attributes. The simple JAXP event handler MyEventHandler shown in Listing 2 parses the XML document shown in Listing 1.
Listing 1. Sample XML document
<?xml version="1.0"?>
<element1 attribute1="aaaa">
<element2>XXX</element2>
<element2>YYY</element2>
<element2>ZZZ</element2>
</element1>
|
MyEventHandler in Listing 2 shows how to parse elements element1, its attribute attribute1, and sub-element element2, while maintaining a list of element2 values.
Listing 2. A sample SAX event handler
/**
* Parser handler class to parse the XML input stream.
*/
class MyEventHandler extends DefaultHandler {
// Private members needed to parse the XML document
private boolean parsingInProgress; // keep track of parsing
private Stack qNameStack = new Stack(); // keep track of QName
private Vector myElement2s = new Vector(); // keep track of element2
// XML TAGS
private static final String ELEMENT1 = "element1";
private static final String ELEMENT2 = "element2";
private static final String ATTRIBUTE1 = "attribute1";
/**
* Start of document processing.
* @throws org.xml.sax.SAXException is any SAX exception,
* possibly wrapping another exception.
*/
public void startDocument()
throws SAXException {
parsingInProgress = true;
qNameStack.removeAllElements();
myElement2s.removeAllElements();
}
/**
* End of document processing.
* @throws org.xml.sax.SAXException is any SAX exception,
* possibly wrapping another exception.
*/
public void endDocument()
throws SAXException {
parsingInProgress = false;
// We have encountered the end of the document. Do any processing that is desired,
// for example dump all collected element2 values.
for (int i=0; i<myElement2s.size(); i++) {
MyElement2 o = (MyElement2) myElement2s.elementAt(i);
o.dump();
}
}
/**
* Process the new element.
* @param uri is the Namespace URI, or the empty string if the element
* has no Namespace URI or if Namespace processing is not being performed.
* @param localName is the The local name (without prefix), or the empty
* string if Namespace processing is not being performed.
* @param qName is the qualified name (with prefix), or the empty string
* if qualified names are not available.
* @param attributes is the attributes attached to the element. If there
* are no attributes, it shall be an empty Attributes object.
* @throws org.xml.sax.SAXException is any SAX exception,
* possibly wrapping another exception.
*/
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if (ELEMENT1.equals(qName)) {
// ELEMENT1 has an attribute, get it by name
String attribute1 = attributes.getValue(ATTRIBUTE1);
// Do something with the attribute
// ...
} else {
if (ELEMENT2.equals(qName)) {
// Keep track of the value of element2
MyElement2 o = new MyElement2();
myElement2s.addElement(o);
}
}
// Keep track of QNames
qNameStack.push(qName);
}
/**
* Process the character data for current tag.
* @param ch are the element's characters.
* @param start is the start position in the character array.
* @param length is the number of characters to use from the
* character array.
* @throws org.xml.sax.SAXException is any SAX exception,
* possibly wrapping another exception.
*/
public void characters(char[] ch, int start, int length)
throws SAXException {
String qName;
String chars = new String(ch, start, length);
// Get current QName
qName = (String) qNameStack.peek();
if (ELEMENT1.equals(qName)) {
// Nothing to process
} else if (ELEMENT2.equals(qName)) {
// Keep track of the value of element2
if (myElement2s.size() > 0) {
MyElement2 o = (MyElement2) myElement2s.lastElement();
o.setValue(chars);
}
}
}
/**
* Process the end element tag.
* @param uri is the Namespace URI, or the empty string if the element
* has no Namespace URI or if Namespace processing is not being performed.
* @param localName is the The local name (without prefix), or the empty
* string if Namespace processing is not being performed.
* @param qName is the qualified name (with prefix), or the empty
* string if qualified names are not available.
* @throws org.xml.sax.SAXException is any SAX exception,
* possibly wrapping another exception.
*/
public void endElement(String uri, String localName, String qName)
throws SAXException {
// Pop QName, since we are done with it
qNameStack.pop();
if (ELEMENT1.equals(qName)) {
// We have encountered the end of ELEMENT1
// ...
} else {
if (ELEMENT2.equals(qName)) {
// We have encountered the end of an ELEMENT2
// ...
}
}
}
}
|
Note how I use a Stack to keep track of the current qualified name (QName); the top of the Stack is the current XML element tag being processed. Using a Stack simplifies handling nested elements. I also use a Vector to keep track of element2 values. Element2 values are represented by the MyElement2 class as shown in Listing 3. Note how callback method startElement() processes element1's attribute1, how callback method characters() extracts and assigns the character data, and how endDocument() dumps any collected element2 values when the XML parsing has finished.
Listing 3. Class MyElement2
class MyElement2 {
private String value;
/** Constructor */
public MyElement2() {}
/**
* Sets Element2 value.
* @param v is the value to set.
*/
public void setValue(String v) {
this.value = v;
}
/**
* Dumps Element2 value.
*/
public void dump() {
System.out.println("Value is: " + value);
}
}
|
Create an instance of a SAX parser
Now, create an instance of the SAX parser by getting an instance of the SAXParserfactory to create a SAXParser.
Listing 4. Create a SAX parser
// Get an instance of the SAX parser factory
SAXParserFactory factory = SAXParserFactory.newInstance();
// Get an instance of the SAX parser
SAXParser saxParser = factory.newSAXParser();
|
The statements in Listing 4 might throw a SAXException, a FactoryConfigurationError, or a ParserConfigurationException, so you must enclose these within a try/catch block.
Parse the input XML document
Next, start parsing the XML document by calling the SaxParser.parse() method. The parse() method takes as arguments the XML document to parse, represented as an org.xml.sax.InputSource or as a java.io.InputStream, and the event handler to use (created in the first step). The preferred way to represent the XML document is to use an InputSource as it provides a number of helper methods such as setting and getting the input source's character encoding and the system identifier.
An InputSource can be created from a java.io.InputStream or a java.io.Reader. In the snippet shown in Listing 5 I use a java.io.InputStream that was created from a network connection such as an HttpConnection. After you have created the input source, you call the SAXParser.parse() method to initiate parsing.
Listing 5. Create the input source and calling the SAX Parser
// Initialize the URI and XML Document InputStream
String uri = "http://www.j2medeveloper.com/xmldocument.xml";
InputStream inputStream = getXMLDoc(uri);
// Create an InputSource from the InputStream
InputSource inputSource = new InputSource(inputStream);
// Parse the input XML document stream, using my event handler
MyEventHandler = new MyEventHandler();
saxParser.parse(inputSource, myEventHandler);
|
Because parsing is a long operation, you should call parse() in its own thread of execution; otherwise, if invoked in the main event thread, the user interface freezes until parsing completes. The parse method might throw an IOException or SAXException, so you must enclose these within a try/catch block.
Testing your application
You can write and test your JAXP application by using the J2ME Wireless Toolkit 2.1. The Wireless Toolkit provides support for the Web Services APIs as well as other optional packages. See Resources for a list of related resources.
Conclusion
This article introduced the JSR 172 Web Services API for the J2ME platform, with emphasis on the JAXP for J2ME XML parsing API. The article covered the typical elements of a JAXP application, the sequence of interactions between JAXP objects, and how to use the JAXP API to parse an input XML document. With JAXP for J2ME, a standard XML parsing API for J2ME is defined, and developers don't have to embed such functions into each of their applications, leaving them with more memory space and time to concentrate on their applications.
Resources
About the author  | |  | C. Enrique Ortiz is a software engineer with over 14 years of experience, most recently serving as Director of Mobile Applications at Aligo Inc., VP of Wireless at AGEA Corp., and as software engineer at Pervasive Software, Intelligent Reasoning Systems, and IBM. Enrique is a co-designer of the Mobile Java Developer Certification Exam of Sun Microsystems. He also co-authored of one of the first books on J2ME, the Mobile Information Device Profile for J2ME. Enrique is an active participant in the wireless Java community and in various J2ME expert groups. You can reach him at C. Enrique Ortiz. |
Rate this page
|  |