Enterprise Integration Zone is brought to you in partnership with:

I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 636 posts at DZone. You can read more from them at their website. View Full User Profile

Web services in Java

06.29.2010
| 22157 views |
  • submit to reddit

Web services are one of the most successful implementations of a Service Oriented Architecture, and they usually are presented as a public Api you can use to expand the capabilities of your applications. There's a web service for everything from obtaining songs lyrics to detect faces in an image, to the point that they can be considered the modern version of libraries.

Every application can count on a (moderately fast) internet connection, so the network bottleneck is not a problem like it was 10 years ago, especially when the interchanged data consist of text. But there are a few issues you should be aware of.

First, web services commonly do not present a standard Api: vendor lock-ins are the norm. What if a service is discontinued, or it changes its API? A Facade-like pattern can help here. Another issues is how to test an application which involves web services? Every test that involves a web service uses the network and when you have plenty of tests the suite becomes slower and slower.

Define what you want

When choosing to use a web service in your application, the first task to accomplish is writing an abstraction over it, in a way similar to wrapping an external library. Like libraries, web services APIs are catch-all so wrapping them is useful for hiding everything you won't need: often an interface composed of one or two methods captures any communication towards the service.

For example, if you want to use Google Translate, an interface with the single method:

String translate(String from, String to, String text)

will often suffice. You want to use it for detection instead?

String detect(String text)

You want to find out the lyrics of a song? I did it in my thesis project:

String getLyrics(String artist, String title)

To see if you're segregating interfaces right, try writing a stub which uses an internal prepopulated Map to satisfy requests, and see if it's too difficult because there are tons of methods.

When the abstraction over the web service is defined, you should build a Facade or a Bridge that implements this interface, so that you can swap in an implementation of a different service that does the same thing at will. Note that swapping in an implementation does not happen only if the web service dies: it happens every day in the testing environment (and ease of testing is the sign of good old loose coupling.)

You will usually write integration tests for the Facade, and isolate internal classes that uses it with unit tests that employ mocks and stubs. JUnit satisfies the requirements for both these types of tests, but you may want to define your base test case or helpers.

Throw away SOAP for REST-like (not RESTful)

SOAP and the plethora of WS-* specification are heavy, semantically poor and difficult to use without supporting libraries. Nearly every public web service offers a REST-like API, which is by far simpler to integrate. You can use the REST-like interface to do exploratory manual testing with Curl or even in your browser. Or you can access it from JavaScript other than from Java if you have a web-based interface.

Why the term REST-like? Because many services proclaim to be RESTful but they do not adhere to all six REST principles. But the simplicity is there at least, so we may call them REST-like like Solr does with itself.

REST-like interfaces return native representations instead of XML envelopes which replicate the HTTP semantics with their own headers (we have alreayd 4 TCP/IP levels; why do you think we need another one on top?)

Web services APIs are also resource-oriented instead of being based on Remote Procedure Call, and this paradigm is something the web has employed for twenty years (a proof that it can work.) You're retrieving information in the form of resources, like a list of tweets, a text, facebook status updates, links... Not calling a method.

Web services talk in XML or JSON; you usually can choose between them.

XML

XML as a format is natively supported in Java, by the SAX and DOM APIs (or even XSLT). If you have a JDK, you already have SAX and DOM available.

But please, do not try to define wrapper classes from the XML Schema (JAXB). The idea is generating a bunch of classes from the schema definition (.xsd files) that let you unmarshal XML documents in objects. Why shouldn't we do it?

  • most web services do not define a schema. Pretty good point.
  • Moreover, even if a schema is available you add 10, 20 or 30 classes to your codebase for nothing. Remember, if we have an abstraction the webservice-specific information cannot escape from the particular driver (Bridge implementation) or implementation (Facade). So either you use all the classes defined by JAXB in your implementation for this particular web service, or the majority of them will sit there rolling their thumbs in a nice gen/ folder.

To parse XML returned from a web service you'll probably just need DOM to extract what you want from the response document and throw away the rest:

public class XMLLyricWikiParser implements LyricWikiParser {

/* (non-Javadoc)
* @see it.polimi.chansonnier.driver.lyricwiki.LyricWikiParser#getLyrics(java.lang.String)
*/
public String getLyrics(String xmlContent) {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(xmlContent.trim()));
Document doc = db.parse(is);
doc.getDocumentElement().normalize();
NodeList lyricsNodes = doc.getElementsByTagName("lyrics");
Node lyricsNode = lyricsNodes.item(0);
Element lyricsElement = (Element) lyricsNode;
return _getTextValue(lyricsElement);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Malformed document.");
}
}

private static String _getTextValue(Element element)
{
return ((Node) element.getChildNodes().item(0)).getNodeValue();
}

}

For the response string, you can use URL and URLConnection to send HTTP requests without further intermiediate layers:

public class HttpLyricWikiBackend implements LyricWikiBackend {
private static final String LYRICWIKI_REST_API = "http://lyrics.wikia.com/api.php";

@Override
public String getSong(String title, String artist) {
try {
URL apiEndPoint = new URL(LYRICWIKI_REST_API
+ "?func=getSong&artist="
+ _escape(artist)
+ "&song="
+ _escape(title)
+ "&fmt=xml");
URLConnection connection = apiEndPoint.openConnection();
BufferedReader in = new BufferedReader(
new InputStreamReader(
connection.getInputStream()));

StringBuilder builder = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null) {
builder.append(inputLine);
builder.append("\n");
}
in.close();

return builder.toString();
} catch (MalformedURLException e) {
throw new RuntimeException("Url of the API is not correct.");
} catch (IOException e) {
throw new RuntimeException("Cannot communicate with the server.");
}
}

private String _escape(String phrase) {
return phrase.replaceAll(" ", "%20");
}
}

Unfortunately, this is very verbose with regard to dynamic languages. Try XPath if you need more concise code.

JSON

json.org has all you need for JSON parsing in Java. Six classes (six!) which do everything. You download them, put the code in a org/json folder in your class path and you can start parsing responses. Native types are welcome in this small library (like String or Boolean), but JSONObject or JSONArray are also included.

I even made an OSGi bundle if you're lazy, which you can also use as a Jar of course.

Conclusion

So accessing web services from a Java app, being it web-based or not, it's very simple if you isolate the service, hide everything you don't need and avoid slowing down your test suite by testing in isolation (separating integration tests from unit ones).

Formats are standard and well-supported, with quite long code snippets attached (DOM examples are twenty-lines to extract a field value) but all the wiring code is in the abstraction you create. Web services are just adapters for the ports of your application, but there are so many of them that they are worth considering for inclusion. In this article I presented the simplest approach which you can start with, but that can already take you to great results.

Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Sunil Pethe replied on Tue, 2010/06/29 - 10:30am

Its okay

Fred Gracely replied on Tue, 2010/06/29 - 1:01pm

Great article. I love people who keep tech real. The combination of REST-like with simple DOM xml parsing (no JAXB) or JSON is pure magic for productive development. The only addition I like to make sometimes is the use of XStream to go from value objects to xml easily. It's a simple non-schema oriented tech that you can be productive using within 5 minutes. And, it is just a convenience that can be ignored. People can consume your xml via DOM if they want to instead.

Giorgio Sironi replied on Tue, 2010/06/29 - 3:32pm in response to: Fred Gracely

You captured the sense of the article. Getting started is the hardest part, and there are many practices that get in the way. :)

William Siqueira replied on Tue, 2010/06/29 - 3:50pm

Giorgi, I did something like in a project. I agree with you about WEB Services. I love to spend my time building service wrappers or/and implementing little Mashups. I'm currently working in an API to bring the fun part of REST web services to JavaFX world(http://docs.google.com/View?id=dg22x7vj_159gbqgt5dq). I believe in REST on enterprise world too (I see some unbelievers about REST security :( )!

Gervais Blaise replied on Wed, 2010/06/30 - 3:39am

Nice article. I fully agree with you. There is just one things with which I don't agree : "most web services do not define a schema. Pretty good point." Maybe I don't understand this sentence because I'm a french guy but I think that web services without schemas are a bad thing. They will have a schema because a schema tell us how they work and what we can expect to receive. But you can ignore this schema later. In my opinion, a service without schema is a service writed (too) quickly and thus who can evolve many times.

Giorgio Sironi replied on Wed, 2010/06/30 - 3:47am in response to: William Siqueira

Part of the human-oriented it's built with REST: GET and POST requests, resource-oriented (the page is the single resource), uniform interface, and so on. REST builds on the success of the plain old web.

Giorgio Sironi replied on Wed, 2010/06/30 - 3:49am in response to: Gervais Blaise

I was talking about a XML Schema in this phrase. Web services define an API... that tell us how they work and what we can expect to receive. An XML Schema is not mandatory, in fact many web services return Json instead of XML.

John Dough replied on Thu, 2010/07/08 - 6:36am

Your services are, as you point out, not RESTful so why use the R-word at all?!? As far as I am concerned, it is XML (or JSON) web services over HTTP and nothing more. If I have a car without wheels and engine, then I wouldn't call it a "car-like" vehicle, I would call it a wreck.

Caner Tasdemira replied on Sat, 2011/05/28 - 7:02am

hey i need to examine your lyric program to understand fully how it works and so i can develop my own web service programs. Could you please send me your code?

Giorgio Sironi replied on Sun, 2011/05/29 - 8:00am in response to: Caner Tasdemira

@Caner, take a look here:

https://github.com/giorgiosironi/Chansonnier/tree/master/chansonnier/it.polimi.chansonnier.driver.lyricwiki/code/src/it/polimi/chansonnier/driver/lyricwiki

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.