You've already seen the output for a call to the CopyMe service. Even so, when creating a custom Handler
for a specific response it's a good idea to have a copy of the raw XML
response handy to compare with and view. The first step in implementing a
parser is to create that custom Handler class.
Create a new class in the existing HttpBasics namespace and call it CopyMeHandler.
Insert the following code into the class stub that is created.
private String currentElement;
public String copymeResponse;
public void startElement(String uri, String localName, String qName, Attributes attributes)
{
currentElement = localName;
}
public void characters(char[] ch, int start, int length)
{
if ( currentElement.equals( "string" ) )
{
copymeResponse = new String( ch, start, length );
}
}
Now, you need to modify the updateDestination method of the HttpBasicsMainScreen. Previously, it displayed the raw output into the _Output field. Now, however, you need to parse the output and display only the results. Comment out the existing contents of the run method.
Then insert the following code into the run method.
SAXParserImpl parser = new SAXParserImpl();
CopyMeHandler handler = new CopyMeHandler();
ByteArrayInputStream inputStream = new ByteArrayInputStream( text.getBytes());
try
{
parser.parse( inputStream, handler );
}
catch ( Exception e )
{
_Output.setText( "Unable to parse response.");
return;
}
_Output.setText( handler.copymeResponse );
What just happened?
Now when you click on the Post Data menu and call the CopyMe
service, the data that is shown in the output window is the text that
has been returned by the web service response. As the text is the same
as what was in the Copy Source
field, it isn't too interesting, but it's a lot better than the raw XML
response. Now, let's take a closer look at what you've changed and how
it works.
The first step that you took was to create a new handler for the CopyMe
service that will be used with the SAX parser. This new handler is
about as simple as one can get because there is just one element being
returned, not including the XML header element.
This new class utilizes only two of the methods that are available-the startElement and characters methods.
As the name suggests, the startElement
method is called each time a new element is encountered in the XML.
This method provides only a little detail, including the name of the new
element. Also notice what it doesn't provide, and that is any
information about the parenting or position of this node in relation to
others in the file. All you need to do in this method is to save the
name of the node in a member variable before the processing continues.
public void startElement(String uri, String localName, String qName, Attributes attributes)
{
currentElement = localName;
}
The other method that you overrode in this class is called characters.
This method is called after parsing the inner text of an element, that
is the text between the start and end tags, which is not otherwise
within a tag. If the element name that we are currently processing is
called "string", then the inner text will contain the value of the
string that has been returned by the web service. We capture this value
in another member variable so that it can be retrieved later.
Again, notice that this
method does contain any other contextual information about the element
containing the text. This is why you need to override the startElement
method and keep track of which element you are currently processing.
Because you know that there is only one element named "string" in the
XML this simple logic works. However, for large and complex XML files, a
lot more complex logic will be needed.
public void characters(char[] ch, int start, int length)
{
if ( currentElement.equals( "string" ) )
{
copymeResponse = new String( ch, start, length );
}
}
The DefaultHandler
class provides a lot of possible override points that you can take
advantage of to perform more complex processing of XML. Remember though,
that this is a Simple API for XML so you really can't get too complex. Still, methods such as startDocument, endElement, and error can be used to cover most scenarios.
The startElement
method though is probably one of the most important methods in this
class. In addition to the element name you are also provided with an
array of attributes that are attached to the element. Most of the time,
the data you are looking for will be found in the inner text or one of
the attributes of an element.
The last step is to change the behavior of the updateDestination method so that response is parsed out by using the SAX Parser and only the real response data will be put into the _Output field.
Initial setup is simple by creating the SAXParser and the Handler objects. Notice that we're actually creating a SAXParserImpl object and not a SAXParser object. This is because the SAXParser
class is an abstract class, and by definition, cannot be
instantiated. This was done so that other implementations of the SAX
Parser could be created if there was a special condition that wasn't
handled otherwise. I don't know what that might be, but the capability
is there if needed.
As part of the setup, you also
need to create a stream that the parser can consume. It's kind of silly
because the data comes in from a stream in the thread and is converted
into a string as it is read in. Then, in this step, you convert the
string back into a stream so that it can be parsed.
SAXParserImpl parser = new SAXParserImpl();
CopyMeHandler handler = new CopyMeHandler();
ByteArrayInputStream inputStream = new ByteArrayInputStream(text.getBytes());
The actual parsing must be
in done in an exception block in case there are catastrophic problems.
In this case, we simply set the output to indicate there is an error in
the catch block. The call to the parse method is where the actual work of parsing the output is done. The parse method then makes calls to the methods you have implemented in the CopyMeHandler and which ultimately retrieves the value from the XML.
try
{
parser.parse( inputStream, handler );
}
catch ( Exception e )
{
_Output.setText( "Unable to parse response.");
}
Lastly, if there are no problems parsing the response we set the _Output field to the real response text and not just the raw XML. Remember, the response text was added to the copymeResponse data member as part of the characters method in the handler class. Now that the parsing is completed, that member should contain the value.
_Output.setText( handler.copymeResponse );
So far you've seen how to call a basic web service by using a basic HTTP POST
command and have introduced the SAX parser as a way to parse data from
the response. These examples have so far been very simple; trivial
really. Web services can be much more complicated so the question is,
how well does this technique scale?
You can use an HTTP POST
command to call web services as long as the data being passed to a web
service is a string. The return data can be as complex as you want it,
because it all gets serialized into XML anyway. It's the sending part
that will only work if the data is a string.
If you must work with more complex data types then you must use SOAP requests instead of an HTTP POST
request. One possibility is to use the kSOAP2 library-an open source project hosted at SourceForge (http://ksoap2.sourceforge.net/). Another is the Sun Java Wireless Toolkit for CLDC (http://java.sun.com/products/sjwtoolkit/).
Even though the toolkit has been rolled into the Java ME SDK 3.0 you
can still download the 2.0 libraries and use them with the BlackBerry
SDK, which supports only Java ME 2.0. Using this toolkit will actually
create service stubs that handle the SOAP envelope creating and parsing
for you so you wouldn't need the SAX parser to get data out. Instead,
data would be nicely wrapped up in a class much like you would
experience when using a web service in Visual Studio.
There is another approach
that is recommended if you have control over both the client and
server-side communication. If you have control over both sides you have a
lot more flexibility. One thing that you always want to think about
with network communications is how much data is being transmitted over
the network. Web services with SOAP and XML are great as standards go,
but they can also be very verbose and cause a lot of traffic to be
generated. If you control both sides of communication you can eliminate a
lot of this extra traffic by transferring XML snippets, or even JSON
formatted data instead of a full SOAP packet.
This would mean that your
services wouldn't be easily reusable by other platforms, but the
performance and simplicity of your mobile client-side are significantly
better. Consider the very simple example that we did with the CopyMe
service. If you exchanged a single XML snippet or JSON formatted string
then the code to call the web service is exactly the same! Your data
can be as complex as you need it to be but your network communication
code isn't.
Now that we've covered the
details in the code for sending data, let's take a look at the transport
possibilities that are available.