Open Source Software Technical Articles

Want the Best of the Wazi Blogs Delivered Directly to your Inbox?

Subscribe to Wazi by Email

Your email:

Connect with Us!

Current Articles | RSS Feed RSS Feed

Google Web Toolkit and Web Services: The XML Way

  
  
  

Modern web applications that make heavy use of Ajax benefit from mashing up data from different services. Applications built with the Google Web Toolkit (GWT) can work with both XML and JSON data. In this two-part series, we'll build a project to show how to get and process XML and JSON data, and deal with sundry matters such as security restrictions and server-side proxies. What you'll learn here should help you deal with all kinds of services and enhance your GWT applications.

In order to profit from this article, you need to have a working knowledge of GWT, the basics of XML, and some JavaScript practice.

To show how to process XML and JSON services, we first need a simple API that can deliver data in both these formats. The Google Geocoding API fits the bill. Geocoding is a process that can derive geographic coordinates (usually latitude and longitude) from an address such as "10901 W 120th Ave, Broomfield, CO." (There's also a reverse geocoding service to turn coordinates into an address, but we won't be using it.) You can then use the returned data to show a marker on a map or for navigation purposes. We'll use the current version 3 of this API here, but we'll also use V2 for our JSONP examples in part 2 of this series.

To show what you can do with GWT, we'll write a simple application, based on a form where a user can enter an address to look up, a couple of output fields for the geographic coordinates of the place, and a text area for some extra information about the found site. (See figure below.) Check the source code for details, but building the form is straightforward. It's based on a HTML simple design to which widgets are added through GWT.

[caption id="attachment_92363" align="alignnone" width="300" caption="Our basic form, based on straightforward GWT"][/caption]

We'll use three ways of getting geocoding data from the remote server; each of the three buttons will invoke a different one. If the API call happens to fail, the application will give a warning. On success, it will show the latitude and longitude of the address and some extra information.

19a98812-f823-48dc-841e-bf029c63c6d7

Getting Data from Any Server


Getting data from the server where your GWT application is loaded is easy to do and well-documented. You can use both Remote Procedure Calls (RPC) for connecting with your own Java-based GWT servlets, and Ajax for other services.

However, you cannot use these methods to connect to other remote servers, because of the Same Origin Policy (SOP), a security restriction built into browsers that prevents any page from loading data from any URL other than its origin. The origin of a page is considered to be the protocol plus host plus port, so there's no way to access data from a URL that differs in any of those three parts. If your page was loaded from http://your.site/some/place/within/it, SOP won't let you get data from addresses such as https://your.site (different protocol), http://another.site (different host), or http://your.site:8080 (different port; the standard is 80).

Why is SOP a good idea? Because it ensures that whenever you view a site, you actually execute code downloaded from that site, and only that site. If phishers could disable the SOP, they could set up pages that use actual code from legitimate servers, plus some extra rogue code to monitor your interactions or lift your passwords, for example.

If you want to access web services from your client code, SOP becomes an obstacle. Connecting to remote servers is out of the question, and you might even have problems connecting to services of your own, if they require a different port or protocol.

There are some ways out. One method is Cross-Origin Resource Sharing (CORS), the W3C Access Control Specification, but that isn't a complete solution, at least for the time being, because not all browsers implement it. A better solution is to implement a server-side proxy: the client code requests a service from the proxy, the proxy server connects to the requested remote server and get whatever data it provides, then the proxy sends the data back to the application for processing. For our example, a simple proxy capable of dealing with GET and POST requests is enough (in fact we can do with just GET requests) but you could extend it for full REST compliance, or for working with SOAP.

We will define a ServerProxy service with two calls, getFromRemoteServer(…) and postToRemoteServer(…). (For extra security, we could have used GWT's RPC Cross-Site Request Forgery (XSRF) safe servlets, but given that our application cannot produce any server-side changes, going with the usual servlets is safe enough.) We'll define a ServerProxy interface (see listing below) for client-side use. Only one parameter is required: the URL of the remote service, complete even with all required parameters. Finally, in case an error happens, we'll throw an exception.
package com.google.gwt.kereki.xmlJsonTest.client;

import com.google.gwt.kereki.xmlJsonTest.shared.ServerProxyException;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("serverproxy")
public interface ServerProxy extends RemoteService {

public String getFromRemoteServer(final String serviceUrl)
throws ServerProxyException;

public String postToRemoteServer(final String serviceUrl)
throws ServerProxyException;
}

According to GWT RPC rules, we must also define the asynchronous equivalent, ServerProxyAsync (see listing below.) We must also add a callback that's invoked when results come back. Since both XML and JSON are plain text, the callback will be of AsyncCallback<String> class.
package com.google.gwt.kereki.xmlJsonTest.client;

import com.google.gwt.user.client.rpc.AsyncCallback;

public interface ServerProxyAsync {

void getFromRemoteServer(
final String serviceUrl,
AsyncCallback<String> callback);

void postToRemoteServer(
final String serviceUrl,
AsyncCallback<String> callback);
}

On the server side, we have to provide the actual implementation of the code that provides the service, named ServerProxyImpl; see listing below. Since we're using only GET calls, we can omit the POST part of the logic, but you can find it in the source code files provided with this article. As for error handling, note that we pared it to a minimum; should there be any kind of problem, a ServerProxyException, with no extra text or explanations, will be thrown; obviously you could (and should!) do a bit better for actual production code. The exception just extends Exception and has no code whatsoever, so we don't need to show its code here.
package com.google.gwt.kereki.xmlJsonTest.server;

...several import lines...

@SuppressWarnings("serial")
public class ServerProxyImpl extends RemoteServiceServlet implements
com.google.gwt.kereki.xmlJsonTest.client.ServerProxy {

@Override
public String getFromRemoteServer(final String serviceUrl)
throws ServerProxyException {

String result= "";

try {
final URL url= new URL(serviceUrl);

final BufferedReader in= new BufferedReader(new InputStreamReader(
url.openStream()));

String inputLine;
while ((inputLine= in.readLine()) != null) {
result+= inputLine;
}

in.close();
return result;

} catch (final Exception e) {
throw new ServerProxyException();
}
}

@Override
public String postToRemoteServer(final String serviceUrl)
throws ServerProxyException {
//
// we won't be using this code
// see its implementation in the provided source files
//
}
}

We'll see how this code is used in a moment. With this proxy code installed, we can connect our GWT client to any remote server; let's move on to our example.

Finding an Address Through the Google API


Let's turn back to the Google Geocoding API. A call to this API can have several parameters, but at its simplest it would look like http://maps.googleapis.com/maps/api/geocode/xml?address=address.to.be.geocoded&sensor=false. You could substitute json for xml in order to change the output format. You can add extra parameters to limit the search to a region, or to produce results in a given language, but these are not relevant to our example, so we won't be using them.

The answer to a query, in XML format, comprises one or more result elements, each with data that provides further descriptions of the found location. (See sample XML below.) We will (gratuitously!) assume that the first match is the best one, and we'll show that result in our application.
<?xml version="1.0" encoding="UTF-8"?>
<GeocodeResponse>
<status>OK</status>
<result>
<type>street_address</type>
<formatted_address>10901 W 120th Ave, Broomfield, CO 80021, USA</formatted_address>
<address_component>
<long_name>10901</long_name>
<short_name>10901</short_name>
<type>street_number</type>
</address_component>
<address_component>
<long_name>W 120th Ave</long_name>
<short_name>W 120th Ave</short_name>
<type>route</type>
</address_component>

...several lines snipped...

<address_component>
<long_name>80021</long_name>
<short_name>80021</short_name>
<type>postal_code</type>
</address_component>
<geometry>
<location>
<lat>39.9159880</lat>
<lng>-105.1178850</lng>
</location>
<location_type>ROOFTOP</location_type>
<viewport><em>...several lines snipped...</em></viewport>
</geometry>
</result>
</GeocodeResponse>

Notice there are no attributes in the XML code; it's just nodes and values. Since attributes are processed a bit differently, it would be better if the XML result included a few somewhere, so we can see the required code. We can cheat a little and modify the server proxy so it will add a couple of invented, random (useless and unneeded!) attributes to the <location> element by adding the code below to the while loop of the getFromRemoteServer(…) method. We will display these attributes in our XML processing code.
if (inputLine.trim().equals("<location>")) {
inputLine= "<location useless='"
+ (new BigInteger(48, new Random()).toString(32))
+ "' unneeded='" + (new BigInteger(48, new Random()))
+ "'>";
}

We now have a service (with some fakery thrown in) that we can use, and a proxy with which to access it. Let's turn to our simple GWT application and mesh everything together.

Getting and Processing XML


Let's go back to our GWT form and work on getting and processing XML code. The code for the first command button is as follows:
private final ServerProxyAsync serverProxy= GWT.create(ServerProxy.class);

getXmlFromServerButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
serverProxy.getFromRemoteServer(
"http://maps.googleapis.com/maps/api/geocode/xml?address="
+ URL.encode(addressField.getText()) + "&sensor=false",
new AsyncCallback<String>() {

@Override
public void onFailure(Throwable caught) {
Window.alert("Failure getting XML through proxy");
}

@Override
public void onSuccess(String result) {
processXml(result);
}
});
}
}
);

On detecting a click event, we build up the address of the geocoding service, and then call our server proxy through the serverProxy variable, using an anonymous inner class for the required AsyncCallback. If the call fails, we show an appropriate message (adequate error processing isn't our goal here); on success, we call the processXml(…) method, below. Note the use of URL.encode(…) when setting up the service call; all values must be "percent encoded" in order to have a valid URL. Also note that in order to process XML with GWT, you have to add <inherits name="com.google.gwt.xml.XML"/> to your application gwt.xml file.
public void processXml(final String xml) {
final Document xmlDoc= XMLParser.parse(xml);
final Element root= xmlDoc.getDocumentElement();
XMLParser.removeWhitespace(xmlDoc);

final NodeList results= root.getElementsByTagName("result");

final Element firstResult= (Element) results.item(0);

final NodeList addressParts= firstResult
.getElementsByTagName("address_component");

final Element geometry= (Element) firstResult.getElementsByTagName(
"geometry").item(0);

final Element location= (Element) geometry.getElementsByTagName(
"location").item(0);

final String latitude= location.getElementsByTagName("lat").item(0)
.getFirstChild().getNodeValue();

final String longitude= location.getElementsByTagName("lng").item(0)
.getFirstChild().getNodeValue();

latitudeField.setText(latitude);
longitudeField.setText(longitude);

String detailedAddress= "";

/*
* The following is just to show how to get the values of the invented
* "useless" and "unneeded" attributes of the <location> element
*/
detailedAddress= "USELESS=" + location.getAttribute("useless") + "
"
+ "UNNEEDED=" + location.getAttribute("unneeded") + "

";

/*
* Let's build up the address description by joining all the (possibly
* more than one) <type> and (only one) <long_name> values
*/
for (int i= 0; i < addressParts.getLength(); i++) {
Element components= (Element) addressParts.item(i);

/*
* Loop over all the <type> nodes
*/
String separator= "";

NodeList types= components.getElementsByTagName("type");
for (int j= 0; j < types.getLength(); j++) {
detailedAddress= detailedAddress + separator
+ types.item(j).getFirstChild().getNodeValue();
separator= ", ";
}

/*
* Add the <long_name> node value
*/
detailedAddress= detailedAddress
+ ": "
+ components.getElementsByTagName("long_name").item(0)
.getFirstChild().getNodeValue() + "
";
}
fullAddressField.setText(detailedAddress);
}

Processing XML starts by parsing the original text to build a Document. You can get the document root with the getDocumentElement(…) method; in this case, the <GeoResponse> element. You must always remember to removeWhiteSpace(…) from the document; GWT's XML parsing is based on the browser's own JavaScript DOM routines, and browsers usually create empty text nodes for all sorts of whitespace (tabs, line breaks, and so on) which, if not removed, will complicate and possibly even break your code. Also note that your code will usually require plenty of casts; it's up to you to decide what kind of nodes you will be processing, and let the compiler know.

Since there may be several <result> elements, we'll use the getElementsByTagName(…) method to get a list of all such nodes. We can then use item(0) to get at the first element in the list. To get a specific node within an element, we use the getElementsByTagName(…).item(0) pattern; see geometry and location, for example. Then, in order to access the value of a node, we need the getFirstChild().getNodeValue() pattern; see how we get the latitude and longitude values. If you need to process all results, use getLength(…) to get the node count; for an example of this, see how we process all the type values of the different addressParts. Finally, we get the values of the (invented) attributes of the <location> node by means of the getAttribute(…) method. You can see the results of this code in the following figure.

[caption id="attachment_92364" align="alignnone" width="300" caption="The result of using the XML service, including two invented attributes"][/caption]

There are a few more XML methods you might want to use, such as hasAttributes(…) and getAttributes(…) to determine if a node has attributes, and to get a map with all of them; getFirstChild(…), getLastChild(…), and getChildNodes(…) to access the first, the last, and the complete list of children of a node; and getNextSibling(…) and getPreviousSibling(…) to move between children, as documented), though you can usually make do with just the ones shown above.

In Conclusion


This is all the code you need to use GWT to contact a remote server through a proxy, get an XML response, and then extract data from it. In our second part, we'll change from XML to JSON, and also consider JSONP, which allows us to do without any proxy.




This work is licensed under a Creative Commons Attribution 3.0 Unported License
Creative Commons License.


This work is licensed under a Creative Commons Attribution 3.0 Unported License
Creative Commons License.

Comments

Many women flattering style, calm and comfortable property artist handbags, but many are in the atom has a few hundred dollars, to gucci replica prevent the model woman. Increasing women buy "designer inspired" replica purses built up in the latest trends. These replica wallets are absolutely arresting affinity thing, but is the cost of louis vuitton replica atoms. Login eBay and see what type of replica handbags affairs and those who receive a lot of bids. Note keywords and phrases domesticated bolt admit auction sellers. To see whether the action to change the style of the coat of chanel replica arms has a specific package you want to sell. Choose a well-known banker replica handbags. Purse should not be alone reflect the latest trends, but there are able-bodied made ??of high quality materials. Create your eBay lists. After matching photos from various angles in the wallet. State actual production and backpack bag size. To get a copy of honest wallet. When asked to describe your product, use adjectives such as "designer's inspiration," copy "or" celebrity inspiration. " Wallet ship to an acceptable applicant later closing of the http://www.pickrelldrlg.com/defaut.html transaction, you accept calm payment. Every woman loves bags. But to their favorite handbag, you will spend thousands of dollars? The rich do not care how we like, he did not so much money? There is a way to save money, get the perfect bag, which is to buy handbag. So how to buy high quality brand bags online? Cheap artist handbags online is not difficult to dior replica find, if you perceive the search area.
Posted @ Sunday, September 28, 2014 9:58 PM by Heaity
Post Comment
Name
 *
Email
 *
Website (optional)
Comment
 *

Allowed tags: <a> link, <b> bold, <i> italics