C H A P T E R   9

Accessing Web Services

An expert is a man who has made all the mistakes which can be made, in a narrow field.

— Niels Bohr

The modern application paradigm is clear: nothing lives in an isolated environment. Client applications interact with data obtained from a wide array of resources, both physical and logical. Whether data are retrieved from a hard disk, a remote database, or an exposed network resource, we expect our applications to be flexible, provide a wide array of data retrieval options, and, in general, work well with others.

So far, we've explained how the JavaFX Platform can be used both for rendering information and for interactively manipulating data. In this chapter, we provide a brief overview of the options available for integrating JavaFX applications with enterprise systems, and then continue with some specific examples of that process.

Our examples are constructed to demonstrate how easily a JavaFX application can access a REST resource and then translate the response (from either JSON or XML format) into a format understandable by JavaFX Controls. As our example external data source, the Twitter REST APIs are ideal, as they are publicly available, easy to understand, and widely used on the Internet.

Front-end and Back-end Platforms

JavaFX is often considered a front-end platform. Although that statement does not do justice to the APIs in the JavaFX platform that are not related to a user interface, it is true that most JavaFX applications focus on the rich and interactive visualization of “content.”

One of the great things about Java is the fact that a single language can be used within a wide range of devices, desktops, and servers. The same Java language that creates the core of JavaFX is also the fundamental core of the Java Platform, Enterprise Edition (Java EE).

The Java Platform is the number one development platform for enterprise applications. The combination of the JavaFX platform providing a rich and interactive user interface with enterprise applications running on the Java Platform creates huge possibilities. In order to achieve this, it is required to have JavaFX applications and Java enterprise applications exchanging data.

Exchanging data can happen in a number of ways, and depending on the requirements (from the front end as well as from the back end), one way might be more suited than another way.

Basically, there are two different approaches:

  • The JavaFX application can leverage the fact that it runs on the same infrastructure as typical enterprise applications, and can deeply integrate with these enterprise components. This is illustrated in Figure 9-1.
  • JavaFX applications live in a relatively simple Java Platform, and exchange data with enterprise servers using standard protocols that are already supported by Java enterprise components. This is shown in Figure 9-2.
images

Figure 9-1. JavaFX and enterprise components on a single system

images

Figure 9-2. JavaFX application communicates with enterprise components on a remote server

The first approach is mentioned and briefly touched upon, but the focus of this chapter is on the second approach, where the JavaFX client communicates with Java enterprise components on a remote server.

It should also be stressed here that as long as a standard, well-defined protocol (e.g., SOAP/REST) is used, it is very possible to connect a JavaFX application to a non-Java back-end application. The decoupling between client and server indeed allows for different programming languages to be used on the client and on the server.

Merging JavaFX and Java Enterprise Modules in the Same Environment

JavaFX 2 is built on top of the Java Platform, Standard Edition. As a consequence, all functionality provided by this platform can be leveraged in JavaFX 2. The Java Platform, Enterprise Edition is also built on top of the Java Platform, Standard Edition. As a consequence, JavaFX applications can live in the same environment as applications using the Java Platform, Enterprise Edition.

By doing so, the JavaFX developer can use his or her favorite enterprise tools to create applications. There are a number of advantages in doing so. Enterprise components offer tools that allow developers to focus on a specific domain layer, while shielding them away from, for example, database resources and transactions.

Java is a popular platform in the enterprise environment, and a number of enterprise components and libraries have been developed by companies, organizations, and individuals.

The Java Platform, Enterprise Edition is defined by specifications that are standardized via the Java Community Process (JCP) program. For the different constituting parts, individual Java Specification Requests (JSRs) are filed. The current version of the Java Platform, Enterprise Edition, Java EE 6 contains the following JSRs.

Java API for RESTful Web Services (JAX-RS) 1.1 (JSR 311)

Implementing Enterprise Web Services 1.3 (JSR 109)

Java API for XML-Based Web Services (JAX-WS) 2.2 (JSR 224)

Java Architecture for XML Binding (JAXB) 2.2 (JSR 222)

Web Services Metadata for the Java Platform (JSR 181)

Java API for XML-Based RPC (JAX-RPC) 1.1 (JSR 101)

Java APIs for XML Messaging 1.3 (JSR 67)

Java API for XML Registries (JAXR) 1.0 (JSR 93)

Java Servlet 3.0 (JSR 315)

JavaServer Faces 2.0 (JSR 314)

JavaServer Pages 2.2/Expression Language 2.2 (JSR 245)

Standard Tag Library for JavaServer Pages (JSTL) 1.2 (JSR 52)

Debugging Support for Other Languages 1.0 (JSR 45)

Contexts and Dependency Injection for Java (Web Beans 1.0) (JSR 299)

Dependency Injection for Java 1.0 (JSR 330)

Bean Validation 1.0 (JSR 303)

Enterprise JavaBeans 3.1 (JSR 318)

Java EE Connector Architecture 1.6 (JSR 322)

Java Persistence 2.0 (JSR 317)

Common Annotations for the Java Platform 1.1 (JSR 250)

Java Message Service API 1.1 (JSR 914)

Java Transaction API (JTA) 1.1 (JSR 907)

JavaMail 1.4 (JSR 919)

Java Authentication Service Provider Interface for Containers (JSR 196)

Java Authorization Contract for Containers 1.3 (JSR 115)

Java EE Application Deployment 1.2 (JSR 88)

J2EE Management 1.1 (JSR 77)

Java API for XML Processing (JAXP) 1.3 (JSR 206)

Java Database Connectivity 4.0 (JSR 221)

Java Management Extensions (JMX) 2.0 (JSR 255)

JavaBeans Activation Framework (JAF) 1.1 (JSR 925)

Streaming API for XML (StAX) 1.0 (JSR 173)

Most of these individual JSRs are implemented by a number of companies, and implementations are often grouped into a product. Typical enterprise components implement one or more JSRs, and they might include additional product-specific functionality. Among the most popular enterprise components we count the Spring Framework, Guice, Tomcat, Hibernate, JBoss, RestEasy, and Glassfish. A number of products implement all JSRs, and those products are then called implementations of the Java Platform, Enterprise Edition, often referred to as Java EE Platforms.

Technically, there are no restrictions in the JavaFX platform that prevent Java enterprise components from being used. However, enterprise development differs from client development in a number of ways:

  • Enterprise infrastructure is shifting towards the cloud. Specific tasks (e.g., storage, mail, etc.) are outsourced to components in a "cloud” that offer specific functionality. Enterprise servers are often located in a cloud environment, allowing fast and seamless interaction with cloud components.
  • Resource requirements: enterprise systems focus on computing resources (CPU, cache, and memory) where desktop computers and laptops focus instead on visual resources (e.g., graphical hardware acceleration).
  • Startup time is hardly an issue in servers, but is critical in many desktop applications. Also, servers are supposed to be up and running 24/7, which is not the case with most clients.
  • Deployment and lifecycle management are often specific to a server-product or a client-product. Upgrading servers or server software is often a tedious process. Downtime has to be minimized because client applications might have open connections to the server. Deploying a client application can happen in a number of ways such as via JNLP.
  • Enterprise development uses a number of patterns (e.g., Inversion of Control, container-based initialization) that can be useful in client development, but that often require a different architecture than traditional clients.

Using JavaFX to Call Remote (Web) Services

Enterprise components are often accessed via web resources. Some specifications clearly describe how web-based frameworks should interact with enterprise components for rendering information. However, there are other specifications that allow enterprise components (written in Java or in another language) to be accessed from non-web resources as well. Because those specifications allow for a decoupling between enterprise development and any other development, they have been defined by a number of stakeholders.

In 1998, SOAP was invented by Microsoft and subsequently used as “the” exchange format between Java applications and .Net applications. The SOAP protocol is based on XML, and the current version 1.2 became a W3C recommendation in 2003. Java provides a number of tools that allow developers to exchange data with SOAP.

Although powerful and relatively readable, SOAP is often considered to be rather verbose. With the rise of mashups and simple services offering specific functionality, a new protocol emerged: the representational state transfer (REST). The REST protocol allows server and client developers to exchange data in a loosely coupled way, where the protocol can be XML, JSON, Atom, or any other format.

SOAP

A number of Enterprise applications use SOAP at the back end, and thus require SOAP to be supported on the client as well. Fortunately, SOAP is supported in Java. The examples in this chapter use the REST protocol inasmuch as this is more comprehensive, but using the javax.xml.soap package is perfectly possible in JavaFX applications, because this package is available in the Java 2 Standard Platform.

REST

The remainder of this chapter is about calling REST-based web services. Plenty of resources and documentation about REST and REST-based web services can be found on the Internet. Web-based REST services expose a number of URIs (uniform resource identifiers) that can be accessed using the HTTP protocol. Typically, different HTTP request methods (get, post, put, delete) are used to indicate different operations on resources.

REST-based web services can be accessed using standard HTTP technologies, and the Java Platform comes with a number of APIs (mainly in java.io and java.net) that facilitate the access to REST-based web services.

One of the major advantages of JavaFX being written on top of the Java 2 Platform, Standard Edition is the ability to use all of these APIs in JavaFX applications. This is what we do in the first examples in this chapter. We show how we can use Java APIs for consuming REST-based web services, and how we can integrate the result in a JavaFX application.

Next, we show how to leverage the JavaFX APIs to avoid common pitfalls (e.g., unresponsive applications, no dynamic update, etc.). Finally, we give a brief overview of third-party libraries that make it easy for JavaFX developers to access REST-based web services.

Setting Up the Application

First of all, we create the framework for our samples. We want to obtain tweets and render them in a JavaFX Control. For most of the samples, we use a ListView, but we also demonstrate how we can leverage the TableView APIs.

A single tweet contains information about the author (screenname, full name, picture), the timestamp, the content, and some meta-information. The goal of our samples is not to create a complete client for the Twitter API, so we only take into account a few important fields of a tweet: the name of the author, the timestamp, and the content.

Initially, we represent a tweet by a Java Object with getters and setters. This is shown in Listing 9-1.

Listing 9-1. Tweet Class

public class Tweet {

    private String author;
    private String title;
    private String timeStamp;

    public Tweet() {
    }

    public Tweet(String a, String t, String s) {
        this.author = a;
        this.title = t;
        this.timeStamp = s;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getTimeStamp() {
        return timeStamp;
    }

    public void setTimeStamp(String timeStamp) {
        this.timeStamp = timeStamp;
    }
}

Our Tweet class has two constructors. The zero-arg constructor is needed in one of the following examples and we come back to this later. The constructor that takes three arguments is used for convenience in other examples.

In Listing 9-2, we show how to display tweets. In this first example, the tweets are not obtained via the Twitter API, but they are hard-coded in the example.

Listing 9-2. Framework for Rendering Tweets in a ListView

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class TweetApp1 extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("TweetList");
        ListView<Tweet> listView = new ListView<Tweet>();
        listView.setItems(getObservableList());
        StackPane root = new StackPane();
        root.getChildren().add(listView);
        primaryStage.setScene(new Scene(root, 500, 300));
        primaryStage.show();
    }

    ObservableList<Tweet> getObservableList() {
        ObservableList<Tweet> answer = FXCollections.observableArrayList();
        Tweet t1 = new Tweet("JavaFXFan", "I love JavaFX!!", "today");
        Tweet t2 = new Tweet("JavaDeveloper", "Developing "Hello World" in JavaFX...",image
"yesterday");
        answer.addAll(t1, t2);
        return answer;
    }
}

If you have read the previous chapters, this code does not contain anything new. We create a ListView, add it to a StackPane, create a Scene, and render the Stage.

The ListView is populated with an ObservableList containing Tweets. This ObservableList is obtained by calling the getObservableList() method. In the following samples, we modify this method and show a number of ways for retrieving Tweets from the Twitter API.

images Note The getObservableList returns an ObservableList. The ListView automatically observes this ObservableList. As a consequence, changes in the ObservableList are immediately rendered in the ListView control. In a later sample, we leverage this functionality.

Running this example results in the window shown in Figure 9-3.

images

Figure 9-3. The result of the first example

The resulting window contains a ListView with two entries. Those entries correspond to the two tweets that are created in the getObservableList() method at the bottom of Listing 9-2.

The information about the tweets that is shown in the window is not very useful. Indeed, we told the ListView that it should display some instances of Tweet, but we did not tell how those should be displayed. The latter can be achieved by specifying a CellFactory. In this chapter, our goal is not to create a fancy user interface; rather, we want to show how to retrieve data and render these data in the user interface. Hence, we briefly show how the developer can alter the visualization of data by using the CellFactory concept. For an overview of the UI Controls that we use in our examples (ListView and TableView), we refer to Chapter 5, “Using the JavaFX UI Controls.”

In Listing 9-3, we create a TweetCell class that extends ListCell and that defines how to lay out a cell.

Listing 9-3. Define TweetCell

import javafx.scene.control.ListCell;

public class TweetCell extends ListCell<Tweet> {

    @Override
    protected void updateItem(Tweet tweet, boolean b) {
        if (tweet != null) {
            StringBuilder sb = new StringBuilder();
            sb.append("[").append(tweet.getTimeStamp()).append("]").
                    append(tweet.getAuthor()).append(": ").append(tweet.getTitle());
            setText(sb.toString());
        }
    }
}

When a cell item has to be updated, we tell it to show some text containing the timestamp between square brackets, followed by the author and the content or title of the Tweet. Next, the ListView needs to be told that it should render TweetCells. We do this by calling the ListView.setCellFactory() method. In Listing 9-4, we show the modified version of the start method of our TweetApplication.

Listing 9-4. Use CellFactory on the ListView

public void start(Stage primaryStage) {
        primaryStage.setTitle("TweetList");
        ListView<Tweet> listView = new ListView<Tweet>();
        listView.setItems(getObservableList());
        listView.setCellFactory(new Callback<ListView<Tweet>, ListCell<Tweet>>() {

            @Override
            public ListCell<Tweet> call(ListView<Tweet> listview) {
                return new TweetCell();
            }
        });
        StackPane root = new StackPane();
        root.getChildren().add(listView);
        primaryStage.setScene(new Scene(root, 500, 30));
        primaryStage.show();
    }

If we now run the application, the output appears as in Figure 9-4.

images

Figure 9-4. The result of adding a TweetCell

For every tweet that is in the items of the ListView, the output is now what we expected it to be. We can do a lot more with CellFactories (e.g., we can use graphics instead of just text), but that is beyond the scope of this chapter.

We now replace the hard-coded tweets with real information obtained via the Twitter API.

Using the Twitter API

Twitter (http://twitter.com) allows third-party developers to browse and access tweets using a REST-based interface. Twitter maintains a number of REST-based APIs, but for our examples we limit ourselves to the Search API. Detailed information on the Search API is obtained from https://dev.twitter.com/docs/api/1/get/search or https://dev.twitter.com/docs/using-search.

The resource URL—the endpoint for the REST service—is very simple:

http://search.twitter.com/search.format?q=searchterm

The search.format defines the format of the response. Currently, JSON, atom, and RSS are valid formats. Atom and RSS are both XML-based formats. The JSON (JavaScript Object Notation) format is also a human-readable text format that is very popular in REST services. In our examples, we use the JSON and the RSS format. The searchterm defines the search query. Apart from the format and the search query, there are a number of additional optional parameters that can be supplied with the request. For our examples, those two parameters are sufficient though.

If we want to retrieve tweets on “javafx” in JSON format, we call the following url.

http://search.twitter.com/search.json?q=javafx

The result is something like the JSON-text in Listing 9-5.

Listing 9-5. JSON Response Obtained from the Twitter Search API

{"completed_in":0.032,
"max_id":156099093272346624,
"max_id_str":"156099093272346624",
"next_page":"page=2&max_id=156099093272346624&q=javafx",
"page":1,
"query":"javafx",
"refresh_url":"?since_id=156099093272346624&q=javafx",
"results":[
{"created_at":"Sun, 08 Jan 2012 19:44:58+0000",
"from_user":"ITfacto",
"from_user_id":435927432,
"from_user_id_str":"435927432",
"from_user_name":"ITfacto",
"geo":null,
"id":156099093272346624,
"id_str":"156099093272346624",
"iso_language_code":"fr",
"metadata":{"result_type":"recent"},
"profile_image_url":"http://a1.twimg.com/profile_images/1704224541/ITfacto_mini_normal.JPG",
"profile_image_url_https":"https://si0.twimg.com/profile_images/1704224541/image
ITfacto_mini_normal.JPG",
"source":"&lt;a href=&quot;http://twitterfeed.com&quot;image
rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;",
"text":"#J2EE NetBeans IDE 7.1 : Oracle met en avant JavaFX 2.0 et PHP - Journal du Netimage
http://t.co/j9GK25ET",
"to_user":null,
"to_user_id":null,
"to_user_id_str":null,
"to_user_name":null
},
{ <other tweets omitted> }
],"results_per_page":15,"since_id":0,"since_id_str":"0"}

If we would rather have the result in the XML-based RSS format, we call the following url.

http://search.twitter.com/search.rss?q=javafx.

This query results in the XML response in Listing 9-6.

Listing 9-6. RSS Response Obtained from the Twitter Search API

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:google="http://base.google.com/ns/1.0"image
xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/"image
xmlns:media="http://search.yahoo.com/mrss/" xmlns:twitter="http://api.twitter.com/">
    <channel>
        <title>javafx - Twitter Search</title>
        <description>javafx - Twitter Search</description>
        <link>http://search.twitter.com/search?q=javafx</link>
        <twitter:refresh_url>http://search.twitter.com/search.rss?since_id=image
156099093272346624&amp;q=javafx</twitter:refresh_url>
        <pubDate>Sun, 08 Jan 2012 19:44:58 +0000
</pubDate>
        <openSearch:itemsPerPage>15</openSearch:itemsPerPage>
        <item>
                <title>#J2EE NetBeans IDE 7.1 : Oracle met en avant JavaFX 2.0 et PHP –image
Journal du Net http://t.co/j9GK25ET</title>
                <link>http://twitter.com/ITfacto/statuses/156099093272346624</link>
                <description>&lt;a href="http://search.twitter.com/search?q=%23J2EE"image
title="#J2EE" class=" "&gt;#J2EE&lt;/a&gt; NetBeans IDE 7.1 : Oracle met en avantimage
&lt;em&gt;JavaFX&lt;/em&gt; 2.0 et PHP - Journal du Net &lt;aimage
href="http://t.co/j9GK25ET"&gt;http://t.co/j9GK25ET&lt;/a&gt;</description>
                <pubDate>Sun, 08 Jan 2012 19:44:58 +0000</pubDate>
                <guid>http://twitter.com/ITfacto/statuses/156099093272346624</guid>
                <author>[email protected] (Itfacto)</author>
                <media:content type="image/jpg" height="48" width="48"image
url="http://a1.twimg.com/profile_images/1704224541/ITfacto_mini_normal.JPG"/>
                <google:image_link>http://a1.twimg.com/profile_images/image
1704224541/ITfacto_mini_normal.JPG</google:image_link>
                <twitter:metadata>
                        <twitter:result_type>recent</twitter:result_type>
                </twitter:metadata>
        </item>
       <Other items omitted>
    </channel>
</rss>

Although the data in the JSON response contain the same information as the data in the RSS/XML response, the format is of course very different. JSON and XML are both widely used on the Internet, and a large number of web services offer responses in both formats.

Depending on the use-case and the developer, one format may be preferred over the other. In general, JavaFX applications should be able to work with both formats, because they have to connect with third-party data, and the JavaFX developer cannot always influence the data format used by the back end.

images Note Many applications allow a number of formats, and by specifying the HTTP “Accept” Header, the client can choose between the different formats.

In the next example, we show how to retrieve and parse the JSON response used in the Twitter Search API.

JSON Response Format

JSON is a very popular format on the Internet, especially in web applications where incoming data are parsed with JavaScript. JSON data are rather compact and more or less human readable.

A number of tools exist in Java for reading and writing JSON data. Unfortunately, at the time of writing there is no standard specification in Java that describes how to read and write JSON data. However, a Java Specification Request regarding JSON has been filed recently at www.jcp.org/en/jsr/detail?id=353 with the title “JSR 353: Java API for JSON Processing.“

The description of this JSR reads as follows.

The Java API for JSON Processing (JSON-P) JSR will develop a Java API to process (for e.g. parse, generate, transform and query) JSON.

Once this JSR is accepted, there will be a uniform way in Java for dealing with JSON data. Until then, developers have to choose an existing JSON library or parse the data in their own code.

Each JSON library comes with its own set of tools, patterns, and advantages. Among the popular Java JSON libraries are

json.org (www.json.org/java/index.html)

jackson (http://jackson.codehaus.org/)

jsonmarshaller (http://code.google.com/p/jsonmarshaller/)

json.simple (http://code.google.com/p/json-simple/)

In our examples, we use the jackson library. The other tools are able to achieve the same result, though. The Jackson JSON Processor is also used in a number of frameworks (e.g., Jersey, RESTeasy, Apache Camel, etc.).

We now replace the hard-coded list containing two fake tweets with real Tweets obtained via the REST-Twitter API with JSON response format. We keep the existing code, but we modify the getObservableList() method as shown in Listing 9-7.

Listing 9-7. Obtain Tweets Via the Twitter REST API, JSON Format and Parse the JSON

    ObservableList<Tweet> getObservableList() throws IOException {
        ObservableList<Tweet> answer = FXCollections.observableArrayList();

        String url = "http://search.twitter.com/search.json?q=javafx";
        JsonFactory f = new JsonFactory();
        JsonParser jp = f.createJsonParser(new URL(url));

        JsonToken token = jp.nextToken();
        while (token != JsonToken.START_ARRAY) {
            token = jp.nextToken();
        }
        while (token != JsonToken.END_ARRAY) {
            token = jp.nextToken();
            if (token == JsonToken.START_OBJECT) {
                Tweet tweet = parseTweet(jp);
                answer.add(tweet);
            }
        }
        return answer;
    }

    private Tweet parseTweet(JsonParser jp) throws IOException {
        Tweet tweet = new Tweet();
        JsonToken token = jp.nextToken();

        while (token != JsonToken.END_OBJECT) {
            if (token == JsonToken.START_OBJECT) {
                while (token != JsonToken.END_OBJECT) {
                    token = jp.nextToken();
                }
            }
            if (token == JsonToken.FIELD_NAME) {
                String fieldname = jp.getCurrentName();

                if ("created_at".equals(fieldname)) {
                    jp.nextToken();
                    tweet.setTimeStamp(jp.getText());
                }
                if ("from_user".equals(fieldname)) {
                    jp.nextToken();
                    tweet.setAuthor(jp.getText());
                }
                if ("text".equals(fieldname)) {
                    jp.nextToken();
                    tweet.setTitle(jp.getText());
                }
            }
            token = jp.nextToken();
        }
        return tweet;
    }

The code is a bit longer than in the case of the hard-coded tweets, and that is mainly because we do the JSON parsing ourselves. Later, we show how we can reduce the amount of code by using existing tools and libraries.

Before we dive into the code, we show the result of the modified application in Figure 9-5.

images

Figure 9-5. The result of the TweetApplication retrieving JSON data

The code in Listing 9-7 can be divided into four parts:

  1. Call the REST endpoint.
  2. Obtain the raw JSON data.
  3. Convert each item into a tweet.
  4. Add the Tweets to the result.

Calling the REST endpoint is very straightforward:

        String url = "http://search.twitter.com/search.json?q=javafx";
        JsonFactory f = new JsonFactory();
        JsonParser jp = f.createJsonParser(new URL(url));

We create a URL Object that refers to the desired location, and pass that URL to the JsonFactory.createJsonParser method.

images Note JsonFactory and JsonParser are specific to the Jackson JSON Parser. Other JSON libraries have other (factory) methods for calling endpoints.

We now have a JSON parser that contains a link to the data we want to have. Parsing these JSON data manually requires specific code for a specific case.

images Note It is not our intention to deliver an exhaustive JSON parsing guide. We only try to show how JSON data can be parsed for our specific use case. You can easily find a number of tutorials on JSON on the Internet. For example, the Jackson JSON parser we are using here already comes with a number of tutorials and examples.

In Listing 9-5, we observe that the tweets are in an array named “results,” starting with the left square bracket “[“. By calling the JsonParser.next() method, we jump from one token to the next one. The following loop causes the JsonParser to jump until the first occurrence of an Array element.

        while (token!= JsonToken.START_ARRAY) {
               token = jp.nextToken();
        }

As long as we don't encounter the STOP_ARRAY token (the right square bracket “]”) at this level, we know that we are processing the tweet results. Because parsing the individual tweets is a separate process, we isolated that in a specific parseTweet method.

In the parseTweet method, we create a Tweet instance and fill its fields based on what the JsonParser reads in the JSON data. We keep reading tokens until we encounter an END_OBJECT token at the same level at which we started. Our response contains nested elements inside a single tweet result, and thus we have to be careful not to stop parsing on the first END_OBJECT token. Listing 9-5 indeed shows a metadata field that contains an object as well. In order to make sure we keep parsing until we reach the end of the tweet information, we include the following loop.

        if (token == JsonToken.START_OBJECT) {
                while (token != JsonToken.END_OBJECT) {
                       token = jp.nextToken();
                }
        }

As you can see, there is a lot of information about individual tweets in the JSON response. For our example, we only need the author, the timestamp, and the content of the tweet. This information is present in the JSON response in the following format.

...
"created_at":"Sun, 18 Dec 2011 02:16:28 +0000",
"from_user":"bieroduwysm9",

"text":"@KGWiley83 http://t.co/pbAAmhjg",
...

In the parseTweet() method, once we know we are not processing nested elements (e.g., the metadata element), we check the type of the current token. If that type is JsonToken.FIELD_NAME, we obtain the name of the field by calling

String fieldname = jp.getCurrentName();

The token following the FIELD_NAME contains the information about the token. Hence, if the fieldName is “created_at”, the next token will contain the value. Because we have all information in text format, we can call jp.getText() to obtain the information as a String.

The result of the parseTweet() method is a single Tweet. This tweet is added to the ObservableList by the following snippet.

                Tweet tweet = parseTweet(jp);
                answer.add(tweet);

This example shows that it is relatively easy to retrieve and parse JSON data obtained from a REST endpoint. However, the parsing is a bit cumbersome and error prone. Good-enough tools exist that help us out in this area. The Jackson JSON parser contains a JSON – Object mapper that directly binds text in JSON format to a Java Object.

Jackson supports Full Data Binding and Simple Data Binding. Full Data Binding allows the developer to read the incoming JSON data and automatically create a POJO. Simple Data Binding also reads and parses the incoming data, but the result is put in a Map rather than in a specific dedicated POJO.

images Note The word “binding” in the jackson documentation should not be confused with the Binding concept in JavaFX.

Our next example uses the Simple Data Binding. Again, the only part of the code we have to change is the getObservableList() method. Listing 9-8 introduces the Simple Data Binding.

Listing 9-8. Obtain Tweets from JSON Response Using Simple Data Binding

    ObservableList<Tweet> getObservableList() throws IOException {
        ObservableList<Tweet> answer = FXCollections.observableArrayList();
        String url = "http://search.twitter.com/search.json?q=javafx";
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> rawMap = mapper.readValue(new URL(url), Map.class);
        List<Map<String, Object>> results = (List) rawMap.get("results");
        for (Map<String, Object> entry : results) {
            Tweet tweet = new Tweet();
            tweet.setTimeStamp((String) entry.get("created_at"));
            tweet.setAuthor((String) entry.get("from_user"));
            tweet.setTitle((String) entry.get("text"));
            answer.add(tweet);
        }
        return answer;
    }

As you can observe, the code has become much simpler, mainly because we don't have to do the manual token processing anymore. The class that is helping us here is the org.codehaus.jackson.map.ObjectMapper.

The following code snippet creates a key-value Map based on the JSON response.

        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> rawMap = mapper.readValue(new URL(url), Map.class);

Each key in the Map points to an element in the response. The value corresponding to a key can be the simple value of the element, a list of elements, or a nested element.

We know that the element with key “results” is a list of tweets.

List<Map<String, Object>> results = (List) rawMap.get("results");

Therefore, the preceding snippet creates a list of tweets, with the information about a specific tweet in a specific Map<String, Object>.

We iterate over this list in order to obtain the specific information on the specific tweets. Each entry in the list contains a Map<String, Object> with the tweet-specific information (e.g., “created_at”, “from_user”, “text”). After parsing each individual tweet, we add it to the result.

The examples that we analyzed in this section show that we can easily call REST-based endpoints, and convert the resulting JSON response into a ListView. In the next section, we demonstrate a similar process for XML responses.

XML Response Format

The XML format is widely used in the Java platform. As a consequence, standardization of XML-based operations in Java already happened years ago. There are a number of XML tools built into the Java Platform, Standard Edition, and we can use these APIs and tools in JavaFX without any external dependency. In this section, we first use a DOM processor for parsing the XML response obtained from the Twitter API. Next, we use the JAXB standard to automatically obtain Java Objects.

Changing our application from JSON-input to XML-input requires only the getObservableList method to be changed. The new implementation is shown in Listing 9-9.

Listing 9-9. Obtaining Tweets from the XML-Based Response

    ObservableList<Tweet> getObservableList() throws IOException,image
 ParserConfigurationException, SAXException {
        ObservableList<Tweet> answer = FXCollections.observableArrayList();
        String url = "http://search.twitter.com/search.rss?q=javafx";

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(url);
        NodeList tweetNodes = doc.getElementsByTagName("item");
        int count = tweetNodes.getLength();
        for (int i = 0; i < count; i++) {
            Tweet tweet = new Tweet();
            Node tweetNode = tweetNodes.item(i);
            NodeList childNodes = tweetNode.getChildNodes();
            int cnt2 = childNodes.getLength();
            for (int j = 0; j < cnt2; j++) {
                Node me = childNodes.item(j);
                String nodeName = me.getNodeName();
                if ("pubDate".equals(nodeName)) {
                    tweet.setTimeStamp(me.getTextContent());
                }
                if ("author".equals(nodeName)) {
                    tweet.setAuthor(me.getTextContent());
                }

                if ("title".equals(nodeName)) {
                    tweet.setTitle(me.getTextContent());
                }
            }
            answer.add(tweet);
        }
        return answer;
    }

Again, the goal of this section is not to give a comprehensive overview of the DOM APIs. There are a large number of resources available on the Internet that provide information about XML in general, or DOM in particular.

In order to be able to compile the code in Listing 9-9, the following import statements had to be added.

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

Before we go into detail about the code, we show the output of this example in Figure 9-6.

images

Figure 9-6. The result of the tweet application using XML response

The code in Listing 9-9 shows some similarities to the code in Listing 9-7. In both cases, we process data available in a text format (JSON or XML/RSS) and convert the data into Tweet instances. In Listing 9-8, the DOM approach is used to inspect the received response.

An org.w3c.dom.Document instance is obtained using the following code snippet.

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(url);

The resulting Document can now be queried. From the XML response shown in Listing 9-6, we learn that the individual tweets are enclosed in XML Elements named “item”. We use the following to obtain a list of those XML Elements.

    NodeList tweetNodes = doc.getElementsByTagName("item");

We then iterate over this list, and obtain the tweet-specific fields by inspecting the childNodes in the respective XML Elements. Finally, we add the resulting tweet to the answer.

This approach is rather simple, but we still have to do some manual XML parsing. Although this allows for flexibility, parsing becomes harder and more error prone with increasing complexity of the datastructure.

Fortunately, the Java 2 Standard Edition APIs contain tools for converting XML directly into Java Objects. The specification for these APIs is defined by the JAXB standard, and is available in the javax.xml.bind package. The process of converting XML data into Java Objects is called unmarshalling.

We now modify our example and make it use a mix of DOM parsing and JAXB unmarshalling. Again, we only change the getObservableList() method. The modified implementation is shown in Listing 9-10.

Listing 9-10. Combining XML Parsing and JAXB

   ObservableList<Tweet> getObservableList() throws IOException,image
ParserConfigurationException, SAXException {
        ObservableList<Tweet> answer = FXCollections.observableArrayList();
        String url = "http://search.twitter.com/search.rss?q=javafx";

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(url);
        NodeList tweetNodes = doc.getElementsByTagName("item");
        int count = tweetNodes.getLength();
        for (int i = 0; i < count; i++) {
            Node tweetNode = tweetNodes.item(i);
            DOMSource source = new DOMSource(tweetNode);
            final Tweet tweet = (Tweet) JAXB.unmarshal(source, Tweet.class);
            answer.add(tweet);
        }
        return answer;
    }

The only difference between this approach and the approach used in Listing 9-9 is the parsing of the individual tweets. Instead of using DOM parsing for obtaining the specific fields of the individual tweets, we use the unmarshal method in JAXB. The JAXB specifications allow for lots of flexibility and configuration, and the JAXB.unmarshal method is only a convenience method. However, in many cases, this method is sufficient. The JAXB.unmarshal method takes two parameters: the input source and the class that is the result of the conversion.

We want to convert the XML source into instances of our Tweet class, but how does the JAXB framework know how to map the fields? In many cases, the mapping is straightforward and does not require changes to existing code, but in other cases, the mapping is a bit more complex. Good enough, a whole package with annotations exists that we can use to help JAXB determine the conversion between XML and the Java Object.

In order to make the code in Listing 9-10 work, we made some minor modifications to the Tweet class. The new code for the Tweet class is shown in Listing 9-11.

Listing 9-11. Tweet Class with JAXB Annotations

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;

@XmlAccessorType(XmlAccessType.FIELD)
public class Tweet {

    private String author;
    private String title;
    @XmlElement(name = "pubDate")
    private String timeStamp;

    public Tweet() {
    }

    public Tweet(String a, String t, String s) {
        this.author = a;
        this.title = t;
        this.timeStamp = s;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getTimeStamp() {
        return timeStamp;
    }

    public void setTimeStamp(String timeStamp) {
        this.timeStamp = timeStamp;
    }

}

We added two annotations to the original Tweet class. First, we annotated the class itself with

@XmlAccessorType(XmlAccessType.FIELD)

This annotation tells the JAXB framework to map XML data on the fields of this class, as opposed to on the JavaBean properties (getter/setter methods) of this class. The second annotation is added to the timeStamp field:

    @XmlElement(name = "pubDate")
    private String timeStamp;

This indicates that the timeStamp field corresponds to an XML element named “pubDate”. Indeed, if we look at Listing 9-6, it shows that the creationDate is in an element with the name “pubDate”. We have to instruct the JAXB runtime to map this element with our timeStamp field, and this is what we do with the @XmlElement annotation.

Using the JAXB annotations made it easy to convert the XML tweet elements into individual Tweet instances, but we still had some manual XML processing in our main class. However, we can completely remove the manual XMLParsing and convert the whole XML response into a Java Object. Doing so, the getObservableList() method becomes very simple, as shown in Listing 9-12.

Listing 9-12. Parsing Incoming XML Data Using JAXB

    ObservableList<Tweet> getObservableList() throws MalformedURLException {
        String loc = "http://search.twitter.com/search.rss?q=javafx";
        URL url = new URL(loc);
        TwitterResponse response = JAXB.unmarshal(url, TwitterResponse.class);
        List<Tweet> raw = response.getTweets();
        ObservableList<Tweet> answer = FXCollections.observableArrayList(raw);
        return answer;
    }

In this example, we use JAXB to convert the XML response into an instance of TwitterResponse, and the tweets are then obtained via this TwitterResponse instance. Note that we convert the tweets from a regular List Object into an ObservableList object, as required by the method signature. We later show an example where we don't have to do that additional conversion.

The TwitterResponse class has two goals: map the XML response on a Java Object and make the tweet items available as a List of Tweet instances. This is achieved by the code in Listing 9-13.

Listing 9-13. TwitterResponse Class, Enabling Conversion Between XML Response and Java Objects

import java.util.LinkedList;
import java.util.List;
import javax.xml.bind.annotation.*;

@XmlRootElement(name="rss")
@XmlAccessorType(XmlAccessType.FIELD)
public class TwitterResponse {

  public List<Tweet> getTweets() {
    return channel.getItem();
  }

  private TwitterResponse.Channel channel = new Channel();

  @XmlAccessorType(XmlAccessType.FIELD)
  private static class Channel {

    private String title;
    @XmlElement(name="item")
    private List<Tweet> item = new LinkedList<Tweet>();

    public Channel() {
    }

    public List<Tweet> getItem() {
      return item;
    }

    public void setItem(List<Tweet> item) {
      this.item = item;
    }
  }

}

The TwitterResponse class itself has two annotations:

@XmlAccessorType(XmlAccessType.FIELD)

was already discussed before and

@XmlRootElement(name="rss")

indicates that this class corresponds to a root object in the XML structure, with the name “rss”. This indeed corresponds to the syntax of the XML response of the Twitter API shown in Listing 9-6.

The TwitterResponse has a field named channel (defined by an inner class named Channel). This field corresponds to the “channel” XML element in the response in Listing 9-6. The Channel class contains a List of Tweet instances, annotated with the @XmlElement(name=”item”) in order to map those on the respective XML elements.

The previous examples show how existing technologies available in the Java 2 Platform, Standard Edition, can be used to obtain data from web services and inject these data in JavaFX controls. We now modify the example code in order to take advantage of some specific features of the JavaFX Platform.

Asynchronous Processing

A major problem with the examples so far is that they block the user interface during the process of data retrieval and parsing. In many real-world situations, this is unacceptable. Calls to external web services might take longer than expected due to network or server issues. Even when the external calls are fast, a temporarily unresponsive user interface decreases the overall quality of the application.

Fortunately, the JavaFX Platform allows for concurrency and asynchronous tasks. The concepts of Task, Worker, and Service have already been discussed in Chapter 6. In this section, we show how to leverage the javafx.concurrent package when accessing web services. We also leverage the fact that the ListView watches the ObservableList that contains its items.

The basic idea is that, when creating the ListView, we immediately return an empty ObservableList, while retrieving the data in a background Thread. Once we retrieve and parse the data, we add it to the ObservableList and the result will immediately be visible in the ListView.

The main class for this example is shown in Listing 9-14. We started with the code of the previous example, using an XML response and a TwitterResult class containing the response. With some minor modifications, we could use the JSON response as well, though.

Listing 9-14. Use a Background Thread for Retrieving Tweets ListView

import java.net.MalformedURLException;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Worker.State;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;

public class TweetApp8 extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws MalformedURLException {
        primaryStage.setTitle("TweetList");
        ListView<Tweet> listView = new ListView<Tweet>();
        listView.setItems(getObservableList());
        listView.setCellFactory(new Callback<ListView<Tweet>, ListCell<Tweet>>() {

            @Override
            public ListCell<Tweet> call(ListView<Tweet> listview) {
                return new TweetCell();
            }
        });

        StackPane root = new StackPane();
        root.getChildren().add(listView);
        primaryStage.setScene(new Scene(root, 500, 300));
        primaryStage.show();
        System.out.println("Setup complete");
    }

    ObservableList<Tweet> getObservableList() throws MalformedURLException {
        final ObservableList<Tweet> tweets = FXCollections.observableArrayList();

        String loc = "http://search.twitter.com/search.rss?q=javafx";
        final TwitterRetrievalService twitterRetrievalService = newimage
TwitterRetrievalService(loc);
        twitterRetrievalService.start();
        twitterRetrievalService.stateProperty().addListener(new InvalidationListener() {

            @Override
            public void invalidated(Observable arg0) {
                State now = twitterRetrievalService.getState();
                System.out.println("State of service = " + now);
                if (now == State.SUCCEEDED) {
                    tweets.setAll(twitterRetrievalService.getValue());
                }
            }
        });

        return tweets;
    }
}

The main method is not different from the previous example, apart from the addition of a System.out log message that will print a message when we are done with the setup.

The getObservableList method will first create an instance of ObservableList, and this instance is returned upon method completion. Initially, this instance will be an empty list. In this method, an instance of TwitterRetrievalService is created and the location of the REST endpoint is passed in the constructor. The TwitterRetrievalService, which extends javafx.concurrent.Service, is started, and we listen for changes in the State of the Service. When the state of the Service changes to State.SUCCEEDED, we add the retrieved tweets to the ObservableList. Note that on every state change in the instance of the TwitterRetrievalService, we log a message to System.out.

We now take a closer look at the TwitterRetrievalService in order to understand how it starts a new Thread, and how it makes sure that the retrieved tweets are added to the ListView control using the JavaFX Thread. The code of the TwitterRetrievalService is shown in Listing 9-15.

Listing 9-15. TwitterRetrievalService

import java.net.URL;
import java.util.List;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javax.xml.bind.JAXB;
public class TwitterRetrievalService extends Service<ObservableList<Tweet>> {

    private String loc;

    public TwitterRetrievalService(String loc) {
        this.loc = loc;
    }

    @Override
    protected Task createTask() {
        Task task = new SingleRetrieverTask(this.loc);
        return task;
    }

    private static class SingleRetrieverTask extends Task<ObservableList<Tweet>> {

        private String location;

        public SingleRetrieverTask(final String loc) {
            location = loc;
        }

        @Override
        protected ObservableList<Tweet> call() throws Exception {
            URL url = new URL(location);
            TwitterResponse response = JAXB.unmarshal(url, TwitterResponse.class);
            List<Tweet> raw = response.getTweets();
            ObservableList<Tweet> answer = FXCollections.observableArrayList(raw);
            return answer;
        }
    }
}

The TwitterRetrievalService extends Service and thus has to implement a createTask method. When the Service is started, this task is executed in a separate Thread. For clarity, we created an implementation of Task as an innerclass named SingleRetrieverTask. The createTask method on the TwitterRetrievalService creates a new SingleRetrieverTask and returns it. The location of the URL endpoint is passed in the constructor.

The signature of the SingleRetrieverTask,

private static class SingleRetrieverTask extends Task<ObservableList<Tweet>>

ensures that the return value of the SingleRetrieverTask has the type

ObservableList<Tweet>

which is needed in the getObservableList method in the main class.

Indeed, the following code snippet states that the twitterRetrievalService.getValue() should return an ObservableList<Tweet>

    if (now == State.SUCCEEDED) {
        tweets.setAll(twitterRetrievalService.getValue());
    }

Because SingleRetrieverTask implements Task, we have to implement the call method. This method is actually doing what the getObservableList method in the previous examples was doing: retrieving the data and parsing them.

Although the real work in a Service (the Task created by createTask) is done in a background Thread, all methods on the Service, including the getValue() call, should be accessed from the JavaFX Thread. The internal implementation makes sure that all changes to the available properties in the Service are executed on the JavaFX application Thread.

Running the example gives the exact same visual output as running the previous example. However, we added some System.out messages for clarity. If we run the example, the following messages can be seen on the console.

Setup complete
State of service = RUNNING
State of service = SUCCEEDED

This shows that the getObservable method returns before the tweets are obtained and added to the list.

images Note In theory, you could notice a different behavior inasmuch as the background thread might be completed before the other initialization has been done. In practice, however, this behavior is unlikely when network calls are involved.

Converting Web Services Data to TableView

So far, all our examples showed tweets in a ListView. The ListView is an easy and powerful JavaFX Control, however, there are other controls that are in some cases more suitable to render information.

We can show the Tweet data in a TableView as well, and that is what we do in this section. The retrieval and parsing of the data stay the same as in the previous example. However, we now use a TableView to render the data, and we have to define which columns we want to see. For each column, we have to specify the origination of the data. The code in Listing 9-16 shows the start method used in the example.

Listing 9-16. The Start Method in the Application Rendering Tweets in a TableView

  @Override
    public void start(Stage primaryStage) throws MalformedURLException {
        primaryStage.setTitle("TweetTable");
        TableView<Tweet> tableView = new TableView<Tweet>();
        TableColumn<Tweet, String> dateColumn = new TableColumn<Tweet, String>("Date");
        TableColumn<Tweet, String> authorColumn = new TableColumn<Tweet, String>("Author");
        TableColumn<Tweet, String> textColumn = new TableColumn<Tweet, String>("Text");
        textColumn.setPrefWidth(400);
        dateColumn.setCellValueFactory(new Callback<CellDataFeatures<Tweet, String>,image
ObservableValue<String>>() {

            @Override
            public ObservableValue<String> call(CellDataFeatures<Tweet, String> cdf) {
                Tweet tweet = cdf.getValue();
                return new SimpleStringProperty(tweet.getTimeStamp());
            }
        });
        authorColumn.setCellValueFactory(new Callback<CellDataFeatures<Tweet, String>,image
ObservableValue<String>>() {

            @Override
            public ObservableValue<String> call(CellDataFeatures<Tweet, String> cdf) {
                Tweet tweet = cdf.getValue();
                return new SimpleStringProperty(tweet.getAuthor());
            }
        });
        textColumn.setCellValueFactory(new Callback<CellDataFeatures<Tweet, String>,image
ObservableValue<String>>() {

            @Override
            public ObservableValue<String> call(CellDataFeatures<Tweet, String> cdf) {
                Tweet tweet = cdf.getValue();
                return new SimpleStringProperty(tweet.getTitle());
            }
        });
        tableView.getColumns().addAll(dateColumn, authorColumn, textColumn);
        tableView.setItems(getObservableList());

        StackPane root = new StackPane();
        root.getChildren().add(tableView);
        primaryStage.setScene(new Scene(root, 500, 300));
        primaryStage.show();
    }

Clearly, this example requires more code than the example showing a ListView. Setting up a table is slightly more complex, due to the different columns that are involved. There is not much difference between setting the contents of the ListView and setting contents of the TableView. This is achieved doing

        tableView.setItems(getObservableList());

where the getObservableList() method is the same implementation as in the previous example.

When using a TableView, we have to define a number of TableColumns. This is done in the following code snippet.

        TableColumn<Tweet, String> dateColumn = new TableColumn<Tweet, String>("Date");
        TableColumn<Tweet, String> authorColumn = new TableColumn<Tweet, String>("Author");
        TableColumn<Tweet, String> textColumn = new TableColumn<Tweet, String>("Text");

Using the TableColumn constructor, we create one TableColumn with title “Date”, one with title “Author”, and a third one titled “Text”. The Generics <Tweet, String> indicate that each entry in a row represents a Tweet, and the individual cells in the specified column are of type String.

Next, the instances of TableColumn that we created need to know what data they should render. This is done using CellFactories, as shown in the following snippet.

        dateColumn.setCellValueFactory(new Callback<CellDataFeatures<Tweet, String>,image
ObservableValue<String>>() {

            @Override
            public ObservableValue<String> call(CellDataFeatures<Tweet, String> cdf) {
                Tweet tweet = cdf.getValue();
                return new SimpleStringProperty(tweet.getTimeStamp());
            }
        });

A detailed description of the setCellValueFactory method is beyond the scope of this chapter. As can be observed from the example, we have to specify a Callback class with a call method that returns an ObservableValue containing the content of the specific cell. The tweet we are displaying in this row can be obtained via the CellDataFeatures instance that is passed in the call method. Because we want to show the timestamp, we return a SimpleStringProperty whose content is set to the timeStamp of the specified Tweet.

The same technique has to be used for the other TableColumns (containing the author and the title of the Tweet).

Finally, we have to add the columns to the TableView:

        tableView.getColumns().addAll(dateColumn, authorColumn, textColumn);

Running this example results in the visual output shown in Figure 9-7.

images

Figure 9-7. Using a TableView for rendering tweets

This sample requires lots of boilerplate code for a simple table, but fortunately the JavaFX Platform contains a way to reduce the amount of code. Manually setting the CellValueFactory instances for each column is cumbersome, but we can use another method for doing this, by using JavaFX Properties. Listing 9-17 contains a modified version of the start method of the main class, where we leverage the JavaFX Properties concept.

Listing 9-17. Rendering Data in Columns Based on JavaFX Properties

    @Override
    public void start(Stage primaryStage) throws MalformedURLException {
        primaryStage.setTitle("TweetList");
        TableView<Tweet> tableView = new TableView<Tweet>();
        TableColumn<Tweet, String> dateColumn = new TableColumn<Tweet, String>("Date");
        dateColumn.setCellValueFactory(new PropertyValueFactory<Tweet, String>("timeStamp"));
        TableColumn<Tweet, String> authorColumn = new TableColumn<Tweet, String>("Author");
        authorColumn.setCellValueFactory(new PropertyValueFactory<Tweet, String>("author"));
        TableColumn<Tweet, String> textColumn = new TableColumn<Tweet, String>("Text");
        textColumn.setCellValueFactory(new PropertyValueFactory<Tweet, String>("title"));

        textColumn.setPrefWidth(400);

        tableView.getColumns().addAll(dateColumn, authorColumn, textColumn);
        tableView.setItems(getObservableList());

        StackPane root = new StackPane();
        root.getChildren().add(tableView);
        primaryStage.setScene(new Scene(root, 500, 300));
        primaryStage.show();
    }

This code is clearly shorter than the code in the previous sample. We actually replaced

        dateColumn.setCellValueFactory(new Callback<CellDataFeatures<Tweet, String>,image
ObservableValue<String>>() {

            @Override
            public ObservableValue<String> call(CellDataFeatures<Tweet, String> cdf) {
                Tweet tweet = cdf.getValue();
                return new SimpleStringProperty(tweet.getTimeStamp());
            }
        });

by

        dateColumn.setCellValueFactory(new PropertyValueFactory<Tweet, String>("timeStamp"));

The same holds for the authorColumn and the textColumn.

We are using instances of javafx.scene.control.cell.PropertyValueFactory<S,T>(String name) for defining what specific data should be rendered in which cell.

The PropertyValueFactory searches for a JavaFX property with the specified name and returns the ObservableValue of this property when called. In case no property with such a name can be found, the JavaDoc says the following.

In this example, the "firstName" string is used as a reference to an assumed firstNameProperty() method in the Person class type (which is the class type of the TableView items list). Additionally, this method must return a Property instance. If a method meeting these requirements is found, then the TableCell is populated with this ObservableValue. In addition, the TableView will automatically add an observer to the returned value, such that any changes fired will be observed by the TableView, resulting in the cell immediately updating.

If no method matching this pattern exists, there is fall-through support for attempting to call get<property>() or is<property>() (that is, getFirstName() or isFirstName() in the example above). If a method matching this pattern exists, the value returned from this method is wrapped in a ReadOnlyObjectWrapper and returned to the TableCell. However, in this situation, this means that the TableCell will not be able to observe the ObservableValue for changes (as is the case in the first approach above).

From this, it is clear that JavaFX Properties are the preferred way for rendering information in a TableView. So far, we used the POJO Tweet class with JavaBean getter and setter methods as the value object for being displayed in both a ListView and a TableView.

Although the example above also works without using JavaFX Properties, as stated by the JavaDoc, we now modify the Tweet class to use Properties for the author information. The timeStamp and the text fields could have been modified to use JavaFX Properties as well, but the mixed example shows that the fall-through scenario described in the JavaDoc really works. The modified Tweet class is shown in Listing 9-18.

Listing 9-18. Implementation of Tweet Class Using JavaFX Properties for the Author Field

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;

@XmlAccessorType(XmlAccessType.PROPERTY)
public class Tweet {

    private final StringProperty authorProperty = new SimpleStringProperty();

    public final StringProperty authorProperty() {
        return authorProperty;
    }
    private String title;
    private String timeStamp;

    public Tweet() {
    }

    public Tweet(String a, String t, String s) {
        this.authorProperty.set(a);
        this.title = t;
        this.timeStamp = s;
    }

    public String getAuthor() {
        return authorProperty.get();
    }

    public void setAuthor(String author) {
        authorProperty.set(author);

    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getTimeStamp() {
        return timeStamp;
    }

    @XmlElement(name = "pubDate")
    public void setTimeStamp(String timeStamp) {
        this.timeStamp = timeStamp;
    }
}

The authorProperty follows the standard JavaFX Convention, as explained in Chapter 3, “Understanding the JavaFX Bean Convention.”

Apart from the introduction of JavaFX Properties, there is another major change in the implementation of the Tweet class. The class is now annotated with

@XmlAccessorType(XmlAccessType.PROPERTY)

The reason for this is that when doing so, the setter methods will be called by the JAXB.unmarshal method when it creates an instance of the Tweet with some specific information. Now that we are using JavaFX Properties instead of primitive types, this is required. The JAXB framework could easily assign the value of the XML Element “author” to the author String field, but it cannot assign a value to a JavaFX Property object by default.

By using XmlAccessType.PROPERTY, the setAuthor(String v) method will be called by the JAXB framework, supplying the value of the XML Element to the setAuthor method. The implementation of this method

        authorProperty.set(author);

will then update the JavaFX Property that is subsequently being used by the TableColumn and the TableView.

The examples we have shown so far in this chapter demonstrate that the Java Platform, Standard Edition, already contains a number of APIs that are very useful when accessing Web services. We also showed how to use the JavaFX Concurrent Framework, the ObservableList pattern, JavaFX Properties, and the PropertyValueFactory class in order to enhance the flow between calling the web service and rendering the data in the JavaFX Controls.

Although there is no rocket science involved in the examples, additional requirements will make things more complex, and more boilerplate code will be required. Fortunately, a number of initiatives already popped up in the JavaFX community, with the goal of making our lives easier.

Using External Libraries

With the exception of the JSON parser used in the first examples, all our examples so far did not require any additional external library. The Java 2 Platform, Standard Edition and the JavaFX Platform offer a great environment that can be used for accessing web services. In this section, we use three external libraries and show how they make accessing web services easier.

RestFX

The RestFX project is hosted at http://code.google.com/p/restfx/ and that web site mentions the following mission statement.

REST/FX provides a set of classes for producing and consuming REST services in a JavaFX application.

RestFX contains implementations of Services that allow asynchronous access to web services, with Listeners that are notified when the State of the Service is changed. As such, it resembles Listing 9-15 where we created a primitive implementation of such a Service.

Using RestFX in our examples is very easy. Again, we only have to change the getObservableList() method, but for clarity we show the complete code of the main class here. Listing 9-19 shows how to retrieve tweets from the Twitter API with JSON output, and render them in a ListView.

Listing 9-19. Obtaining Tweets Using RestFX

import java.net.MalformedURLException;
import java.util.List;
import java.util.Map;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
import restfx.web.GetQuery;
import restfx.web.Query;
import restfx.web.QueryListener;

public class TweetAppRestFX extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws MalformedURLException {
        primaryStage.setTitle("TweetAppRestFX");
        ListView<Tweet> listView = new ListView<Tweet>();
        listView.setItems(getObservableList());
        listView.setCellFactory(new Callback<ListView<Tweet>, ListCell<Tweet>>() {

            @Override
            public ListCell<Tweet> call(ListView<Tweet> listview) {
                return new TweetCell();
            }
        });

        StackPane root = new StackPane();
        root.getChildren().add(listView);
        primaryStage.setScene(new Scene(root, 500, 300));
        primaryStage.show();
        System.out.println("Setup complete");
    }

    ObservableList<Tweet> getObservableList() throws MalformedURLException {
        final ObservableList<Tweet> tweets = FXCollections.observableArrayList();
        GetQuery getQuery = new GetQuery("search.twitter.com", "/search.json");
        getQuery.getParameters().put("q", "javafx");
        getQuery.execute(new QueryListener<Object>() {

            @Override
            @SuppressWarnings("unchecked")
            public void queryExecuted(Query<Object> task) {
                Map<String, Object> value = (Map<String, Object>) task.getValue();
                List<Object> results = (List<Object>) value.get("results");
                for (Object target : results) {
                    Map<String, String> tweetMap = (Map<String, String>) target;
                    String timeStamp = tweetMap.get("created_at");
                    String author = tweetMap.get("from_user");
                    String title = tweetMap.get("text");
                    Tweet tweet = new Tweet(author, title, timeStamp);
                    tweets.add(tweet);
                }
            }
        });
        return tweets;
    }
}

The ObservableList creates a GetQuery and supplies the required information about the web services Endpoint. RestFX supports GetQuery, PostQuery, PutQuery, and DeleteQuery and as such it aligns very well with the REST specifications. When we want to execute the query, we have to specify an implementation of the QueryListener interface with a callback method that is called once execution is completed.

In our example, the result of the rest call is a JSON response, and similar to the jackson API we used in a previous example, the result is contained in a Map<String, Object>.

The “results” key in this Map corresponds with a List of Map<String, String> that contains information about a specific tweet. Querying this map allows us to construct a Tweet instance, and add it to the ObservableMap.

Being very much aligned with the REST/HTTP protocol, RESTFX provides APIs for easy manipulation of HTTP headers for Authentication, and for supplying query and form parameters. RESTFX uses the concept of “serializers” for translating content of a specific format into Java objects (e.g., Map). We used the JSON Serializer in our example, but RESTFX contains a number of other serializers (e.g., a CSVSerializer).

DataFX

The DataFX library is described at http://javafxdata.org and it provides an end-to-end toolkit for retrieving, parsing, massaging, populating, viewing, and rendering data. These data might be obtained using REST-based web services, but can also be obtained from a local filesystem, a database, or a number of other datasources.

DataFX consists of two integrated parts:

  • Cell Factories, providing a number of useful CellFactories and hence reducing the boilerplate code that is often required in projects
  • DataSources, providing a level of abstraction about the origin of the data, both regarding the physical location (file, network resources) and the format (JSON, XML, JDBC, etc.)

In the next example, we integrate DataFX with our Twitter example. Once again, the only change is in the getObservableList method, but for clarity, we show the whole main class in Listing 9-20.

Listing 9-20. Obtaining Tweets Using DataFX

import java.io.IOException;
import java.net.MalformedURLException;
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.concurrent.Service;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
import org.javafxdata.datasources.io.NetworkSource;
import org.javafxdata.datasources.protocol.ObjectDataSource;

public class TweetAppDataFX extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws MalformedURLException, IOException {
        primaryStage.setTitle("TweetList");
        ListView<Tweet> listView = new ListView<Tweet>();
        listView.setItems(getObservableList());
        listView.setCellFactory(new Callback<ListView<Tweet>, ListCell<Tweet>>() {

            @Override
            public ListCell<Tweet> call(ListView<Tweet> listview) {
                return new TweetCell();
            }
        });

        StackPane root = new StackPane();
        root.getChildren().add(listView);
        primaryStage.setScene(new Scene(root, 500, 300));
        primaryStage.show();
        System.out.println("Setup complete");
    }

    ObservableList<Tweet> getObservableList() throws MalformedURLException, IOException {
        String url = "http://search.twitter.com/search.rss?q=javafx";
        NetworkSource reader = new NetworkSource(url);
        ObjectDataSource objectDataSource = newimage
ObjectDataSource().reader(reader).tag("item").clazz(Tweet.class);
        Service service = objectDataSource.retrieve();
        return objectDataSource.getData();
    }
}

The relevant part, the implementation of the getObservableList method, is very simple. We first construct a NetworkSource that points to the required REST endpoint. Next, we create an ObjectDataSource and use the builder pattern to do the following:

  1. Assign the NetworkSource as the source of the data.
  2. Indicate that we are interested in items that are defined as “item” elements.
  3. Convert these items into instances of the Tweet class.

By calling the ObjectDataSource.retrieve method, an asynchronous Service is started. Instead of waiting for the result, we can immediately return the result object to our visual controls. The DataFX framework will update the result object while it reads and parses incoming data. For large chunks of data, this is very useful, because this approach allows developers to already render parts of the data while other parts are still coming in, or are still being processed.

Apart from the ObjectDataSource, DataFX has a number of other DataSources, including an XMLDataSource. Developers familiar with the Java XML APIs can use this DataSource and use xpath expressions in order to define what columns in a TableColumn should render what parts of the incoming XML.

Jersey-Client

Jersey is the reference implementation of JAX-RS. Although Jersey itself has no direct link to JavaFX, we mention it here for two reasons:

  • Jersey-client provides some useful tools for calling REST-based web services.
  • JAX-RS 2.0 will contain a client specification.

The JAX-RS 2.0 specification work is done in JSR 339 and the final release is expected in the second quarter of 2012. Jersey will provide the reference implementation for this JSR. At this moment, Jersey already provides the Reference Implementation for JAX-RS 1.x, which mainly defines the Server components of a REST implementation, but the client components will only be standardized in JAX-RS 2.0. Jersey uses the same principles we have shown in the examples in this chapter (support for both JSON and XML, unmarshalling data to Objects, etc.).

Once the JAX-RS 2.0 specification is approved and the Jersey-Client reference implementation is available, it will provide useful tools for reducing the amount of boilerplate code. At this moment, one of the flavors of DataFX already uses Jersey-Client, and it is expected that more JavaFX Frameworks will leverage the standard Java 2 REST retrieving and parsing APIs that are defined in JAX-RS 2.0.

In the next example, we modify the TwitterRetrievalService of Listing 9-15 to use the Jersey-Client API. This is shown in Listing 9-21.

Listing 9-21. Using Jersey-Client for Retrieving Tweets

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import java.util.List;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Service;
import javafx.concurrent.Task;

public class TwitterRetrievalService extends Service<ObservableList<Tweet>> {

        private String host;
        private String path;
        private String search;

        public TwitterRetrievalService(String h, String p, String s) {
                this.host = h;
                this.path = p;
                this.search = s;
        }

        @Override
        protected Task createTask() {
                Task task = new SingleRetrieverTask(host, path, search);
                return task;
        }

        private static class SingleRetrieverTask extends Task<ObservableList<Tweet>> {

                private String host;
                private String path;
                private String search;

                public SingleRetrieverTask(final String host, final String path, finalimage
String search) {
                        this.host = host;
                        this.path = path;
                        this.search = search;
                }

                @Override
                protected ObservableList<Tweet> call() throws Exception {
                        try {
                                Client client = new Client();
                                WebResource wr =image
client.resource(host).path(path).queryParam("q", search);
                                TwitterResponse response = wr.get(TwitterResponse.class);
                                List<Tweet> raw = response.getTweets();
                                ObservableList<Tweet> answer =image
FXCollections.observableArrayList(raw);
                                return answer;
                        } catch (Exception e) {
                                e.printStackTrace();
                        }
                        return null;
                }
        }
}

In order to show one of the nice tools of Jersey, we slightly modified the constructor of the TwitterRetrievalService to take three parameters:

        public TwitterRetrievalService(String host, String path, String search);

This is because Jersey allows us to use the Builder pattern to construct REST resources, allowing a distinction among hostname, path, query parameters, and others.

As a consequence, we have to make a slight modification in Listing 9-14:

String loc = "http://search.twitter.com/search.rss?q=javafx";
final TwitterRetrievalService twitterRetrievalService = new TwitterRetrievalService(loc);

is replaced by

final TwitterRetrievalService twitterRetrievalService = newimage
 TwitterRetrievalService("http://search.twitter.com", "search.rss", "javafx");

The hostname, path, and search parameter are used to create a Jersey WebResource:

Client client = new Client();
WebResource wr = client.resource(host).path(path).queryParam("q", search);

On this WebResource, we can call the get(Class clazz) method, and supply a class parameter. The result of the REST call will then be parsed into an instance of the supplied class, which is also what we did using JAXB in our example in Listing 9-15.

TwitterResponse response = wr.get(TwitterResponse.class);

The response now contains a list of Tweets, and we can use the exact same code as in Listing 9-15 to render the tweets.

Summary

In this chapter, we explained briefly two options for integrating JavaFX applications and enterprise applications. We demonstrated a number of techniques for retrieving data available via web services, and also showed how to render the data in typical JavaFX controls as ListView and TableView.

We used a number of third-party tools that facilitate the process of retrieving, parsing, and rendering data. We demonstrated some JavaFX-specific issues related to remote web services (i.e., updating the user interface should happen on the JavaFX application thread).

It is important to realize that the decoupling between JavaFX client applications and web services allows for a large degree of freedom. There are different tools and techniques for dealing with web services, and developers are encouraged to use their favorite tools in their JavaFX application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.192.7