Silverlight Accessing Java REST Web services

1. Overview

This guidance is a part of Java-Silverlight interoperability guidance series to create a Silverlight application in ESL, which enables the developers to utilize interoperability scenarios in following protocols:

This guidance walks through how to build in Eclipse environment a Silverlight application accessing REST Web Services via HTTP requests. It covers preparation, developing, deploying, and testing a Data access application via Web services:

This guidance is intended for software architects and software developers who are building interoperable Web services across platforms and applications.

For your convenience, the REST service Eclipse project and the Silverlight Eclipse projects are available as an attachment here.

2. Pre-requisites and Product installation

The implementation of this guidance extends the Silverlight DataGrid sample by accessing the application data directly from REST Web service developed in Java. The following knowledge is required:

If you are new in Silverlight development in eclipse with ESL and Restlet framework, we recommend you go through the following tutorials in order beforehand:

  1. Data Binding example in ESL
  2. First Steps and First Resource of Restlet framework

The following softwares should be installed on your machine to work with this guidance.

2.1 Eclipse Ganymede with eclipse4SL 1.0.0 M2

Please read this page to install ESL in "Pure Eclipse configuration" mode. If you have already installed the Eclipse IDE for Java EE Developers distribution, all necessary tools are already installed and you can skip to next paragraph.

Otherwise, it is really straigntforward to install Java EE development tools via Eclipse Update Manager. In Eclipse, call the menu Help → "Software updates ...", in the next dialog you need to perform the following steps:

  1. Select the Tab "Available Softwares"
  2. Select the option "Java EE Developer tools" under "Web and Java EE Development" in the "Ganymede Update Site"
  3. And then click on the button "Install"
  4. Accept the standard options in remaining pages of this wizard to complete the installation.

2.2 Tomcat 6 Installation

The installation of Tomcat consists of three steps:

  1. Download the installer of version 6.x for Windows from http://tomcat.apache.org/
  2. Run the installer to install Tomcat on your local hard disk: e.g. d:\products\tomcat6
  3. Create a server runtime configuration in eclipse via Windows → Preferences → Server

Click on the "Add" button to add a new runtime environment. In the next dialog, select the "Apache Tomcat v6.0" entry of the "Apache" category.

Click on the "Browse..." button to set up the Tomcat installation location. And then click on the "Finish" button to complete the setting.

2.3 Restlet framework

Restlet is a lightweight implementation of REST in Java. Its installation is easy, you simply need to download the archive zip file of version 1.0.11 from http://www.restlet.org/ and unzip it somewhere in your hard disk: e.g. d:\products\restlet.

3. REST service development in Java

REST Service development with Restlet can be summarized as follow:

Our application manages a list of Customers. A Customer is characterized by some simple properties: contactName, company and country. After a short analysis, we need one Resource:

Now, let's define the URIs that will identify the resources. Assume that our application is hosted on a local computer known as "localhost" via the Tomcat server and is listening to port 8080:

Suppose we have three customers:

Contact Name Company Country
Luc Forrest EDF France
Scott Bizien Sun United States
Jean Pierret SAGEM France

The http GET request "http://localhost:8080/DataGridService/restlet/customers" should retrieve all customers in XML:

<?xml version="1.0" encoding="UTF-8" ?>
<ArrayOfCustomer>
   <Customer>
      <ContactName>Luc Forrest</ContactName>
      <Country>France</Country>
      <Company>EDF</Company>
   </Customer>
   <Customer>
      <ContactName>Scott Bizien</ContactName>
      <Country>United States</Country>
      <Company>Sun</Company>
   </Customer>
   <Customer>
      <ContactName>Jean Pierret</ContactName>
      <Country>France</Country>
      <Company>SAGEM</Company>
   </Customer>
</ArrayOfCustomer>

And the http GET request "http://localhost:8080/DataGridService/restlet/customers/france" should get the only two customers in France:

<?xml version="1.0" encoding="UTF-8" ?>
<ArrayOfCustomer>
   <Customer>
      <ContactName>Luc Forrest</ContactName>
      <Country>France</Country>
      <Company>EDF</Company>
   </Customer>
   <Customer>
      <ContactName>Jean Pierret</ContactName>
      <Country>France</Country>
      <Company>SAGEM</Company>
   </Customer>
</ArrayOfCustomer>

The image below describes the UI and interaction scenario of our Web application to developers:

3.1 Project creation

First of all, we need to create a Web project: New → Project, select "Dynamic Web project" under the Web category in the new wizard:

Put the "DataGridRESTService" in the "Project name:" field and click on the "Finish" button.

And then, copy the following .jar files from Restlet's lib folder into the WebContent/WEB-INF/lib:

A "Web App Libraries" container is created automatically in this project. Here is the project content in the Java "Package explorer":

3.2 REST resource Implementation

In the Restlet Framework, instances of the Resource class are the final handlers of calls received by server connectors. A resource is responsible for declaring the list of supported representation types (instances of the Variant class) and for implementing the REST methods. For our customer list request, we need to create a Resource named CustomersResource.

package dataservice;

import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import org.restlet.Context;
import org.restlet.data.MediaType;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.resource.DomRepresentation;
import org.restlet.resource.Representation;
import org.restlet.resource.Resource;
import org.restlet.resource.ResourceException;
import org.restlet.resource.Variant;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
 * Resource that manages a list of items.
 */
public class CustomersResource extends Resource {
    private Map<String, Customer> customers;

    public CustomersResource(Context context, Request request, Response response) {
        super(context, request, response);

        // Declare the kind of representations supported by this resource.
        getVariants().add(new Variant(MediaType.TEXT_XML));

        // data initialization.
        customers = new ConcurrentHashMap<String, Customer>();
        customers.put("lforrest", new Customer("Luc Forrest", "EDF", "France"));
        customers.put("sbizien", new Customer("Scott Bizien", "Sun", "United States"));
        customers.put("jpierret", new Customer("Jean Pierret", "SAGEM", "France"));
    }
    /**
     * Returns a listing of customers.
     */
    public Representation represent(Variant variant) throws ResourceException {
        // Generate the right representation according to its media type.
       if (MediaType.TEXT_XML.equals(variant.getMediaType())) {
           // Filter if a country is appended to the request
           String countryFilter = (String) (getRequest().getAttributes().get("country"));
           try {
               DomRepresentation representation = new DomRepresentation(MediaType.TEXT_XML);
               // Generate a DOM document representing the list of items.
               Document d = representation.getDocument();
               Element r = d.createElement("ArrayOfCustomer");
                d.appendChild(r);
               for (Customer item : customers.values()) {
                   // Filter customers
                   if ((countryFilter == null) || countryFilter.equals(item.getCountry())) {
                      Element eltItem = d.createElement("Customer");
                      Element eltName = d.createElement("ContactName");
                      eltName.appendChild(d.createTextNode(item.getName()));
                      eltItem.appendChild(eltName);

                      Element eltCountry = d.createElement("Country");
                      eltCountry.appendChild(d.createTextNode(item.getCountry()));
                      eltItem.appendChild(eltCountry);
                      Element eltCompany = d.createElement("Company");
                      eltCompany.appendChild(d.createTextNode(item.getCompany()));
                      eltItem.appendChild(eltCompany);
                      r.appendChild(eltItem);
                   }
               }
               d.normalizeDocument();

               // Returns the XML representation of this document.
               return representation;
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
        return null;
    }
}

This class implements in fact only one method represent(Variant variant) to handle http GET request. The content type is indicated as XML in an instance Variant of MediaType.TEXT_XML. This method generates the entity according to a given Variant. If the parameter country is null, return all customers in XML. Otherwise, only the customers selected by the received country filter will be sent back.

The paragraph 5 Deployment explains the declaration between URL and this class.

4. REST Client development in Silverlight

The development of Silverlight REST client consists of two steps:

  1. UI development in XAML
  2. Data access via Web service

Before going forward, we need to create two projects in Eclipse: a Silverlight project and a Silverlight Web project named as DataGridRESTClient and DataGridRESTClient.Web respectively. If you are not yet familiar with the eclipse development environment for Silverlight, this Data Binding example will be useful to get started.

4.1. UI development in XAML

In DataGridRESTClient project, open the Page.xaml and modify it as follows:

<UserControl x:Class="DataGridRESTClient.Page"
   xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="300">
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
       <RowDefinition Height="Auto" />
       <RowDefinition />
    </Grid.RowDefinitions>
    <StackPanel Grid.Row="0" Grid.Column="0" Name="LayoutRoot" Orientation="Vertical">
      <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
        <TextBlock Margin="10" Text="Filter :" VerticalAlignment="Center"/>
        <TextBox Margin="10" Text="France" Name="country" Width="200"/>
        <Button Margin="10" Name="load" Content="Load" Click="Button_Click"
         Width="50" RenderTransformOrigin="0.5,0.5"/>
      </StackPanel>
    </StackPanel>
    <data:DataGrid Margin="5" Name="grid" Grid.Row="1" Grid.Column="0" 
        AutoGenerateColumns="True" RowBackground="Aquamarine"
        AlternatingRowBackground="White" RenderTransformOrigin="0.5,0.5">
    </data:DataGrid>
  </Grid>
</UserControl>

The first creation of a DataGrid element in XAML editor, by either DnD from palette tool or code completion, automatically updates the project structure by adding the "System.Windows.Control.Data" Reference if this Reference is not yet used. However, if you copy and paste this code in the XAML editor, you will need to add the reference manually in the following manner:

  1. Right click on the References Folder of your Silverlight project in "Project explorer"
  2. Select the "Add Reference" menu entry
  3. In the following dialog, select the System.Windows.Control.Data.dll and click on the "OK" button to confirm the operation.

4.2. Data access via Web service

In our application, when we click on the "Load" button the code-behind class will handle this event and invoke the Web service to access the application data and then display the data in the below DataGrid.

When we add or change a property event of XAML element in ESL editor, the event handler is generated automatically in code behind class. In our case, the generated event handler is Button_Click() in the Page.xaml.cs, which will access the Web services to get the application data.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Xml.Linq;
namespace DataGridRESTClient
{
    /**
     * Domain class
     */
    public class Customer
    {
        public string ContactName { get; set; }
        public string Country { get; set; }
        public string Company { get; set; }
    }
    /**
     * UI view
     */
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();
        }
        void client_DownloadStringCompleted(object sender,
		              DownloadStringCompletedEventArgs e)
        {
            string reponse = e.Result;
            IEnumerable<Customer> customers = null;
            if (e.Error == null)
            {
                XDocument doc = XDocument.Parse(reponse);
                customers = from customer in 
			   doc.Elements("ArrayOfCustomer").Elements("Customer")
                select new Customer
                {
                     ContactName = customer.Element("ContactName").Value.ToString(),
                     Country = customer.Element("Country").Value.ToString(),
                     Company = customer.Element("Company").Value.ToString()
                };
            }
            container.grid.ItemsSource = customers;

            // Enable Button
            container.load.IsEnabled = true;
        }

        /**
         * Load
         */
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // Disable Button
            container.load.IsEnabled = false;

            WebClient client = new WebClient();
            client.DownloadStringCompleted += new 
		DownloadStringCompletedEventHandler(client_DownloadStringCompleted);

            // Build URL with Filter
            string filter = container.country.Text;
            string requestURI = "
		http://localhost:8080/DataGridRESTService/restlet/customers/";

            if (!String.IsNullOrEmpty(filter))
            {
                requestURI += filter;
            }
            URI uri = new Uri(requestURI);
            client.DownloadStringAsync(uri);
        }
    }
}

In Silverlight, WCF provides WebClient API to simplify development. More information about "Accessing Web Services in Silverlight" can be found on this Web site here.

Here is the scenario of the method Button_Click() accessing the Web service:

When this operation is completed, the event listener client_FindCustomersFromCompleted() will be executed to process the results of the Web service request.

The response to the GET request is the Data in XML. We need to process this XML stream to rebuild the Customer instances. There are several possibilities available to process it:

Here, we have used the "LINQ to XML". More information can be found on Microsoft Web site here.

string reponse = e.Result;
IEnumerable<Customer> customers = null;
if (e.Error == null)
{
    XDocument doc = XDocument.Parse(reponse);
    customers = from customer in doc.Elements("ArrayOfCustomer").Elements("Customer")
    select new Customer
    {
         ContactName = customer.Element("ContactName").Value.ToString(),
         Country = customer.Element("Country").Value.ToString(),
         Company = customer.Element("Company").Value.ToString()
    };
}
    

And then, the list of customers will be displayed in the DataGrid named as "grid":

// Put the list of customers as DataGrid's Data
grid.ItemsSource = e.Result;
// Enable the button
load.IsEnabled = true;

5. Deployment

If a Silverlight application and the Web services are hosted in the same Web server, this deployment mode is called "single domain deployment".

Single domain

In single domain mode, Java Web services and Silverlight applications share the same domain name, port number and protocol transport.

Cross domain

Otherwise, it is a so called "cross domain deployment".

5.1 Single domain Deployment

In this mode, we need to first deploy our Silverlight application on the web server where the Web services will be hosted. It can be done by copying the following files from DataGridRESTClient.Web to the WebContent folder of the DataGridRESTService.

To make the DataGridRESTClient.html the default page, you can rename it as index.html.

And then, we need to integrate our Web services into the Tomcat servlet services.

In Restlet, the Application Class is the manager for the Restlet Application. It takes care of the requests on a VirtualHost and finds the proper resource with respect to the URL that is presented to it.


package dataservice;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.restlet.Application;
import org.restlet.Context;
import org.restlet.Restlet;
import org.restlet.Router;
public class CustomerService extends Application {
    /**
     * Creates a root Restlet that will receive all incoming calls.
     */
    @Override
    public synchronized Restlet createRoot() {
        // Create a router Restlet that defines routes.
        Router router = new Router(getContext());

        // Defines a route for the resource List of Customers
        router.attach("/customers", CustomersResource.class);
        // Defines a route for the resource List of Customers for a Country
        router.attach("/customers/{country}", CustomersResource.class);
        return router;
    }
}

To deploy this class via servlet, we should modify the web.xml file in WebContent/WEB-INF folder by adding a Servlet element:


<servlet>
   <servlet-name>RestletServlet</servlet-name>
   <servlet-class>com.noelios.restlet.ext.servlet.ServerServlet</servlet-class>
</servlet>

This servlet takes the "org.restlet.application" parameter to start our Application class CustomerService, which binds the predefined URLs with our resources.

<context-param>
   <param-name>org.restlet.application</param-name>
   <param-value>dataservice.CustomerService</param-value>
</context-param>

This servlet will handle every request on /restlet/* URI, so we need to map to it with its name:


<servlet-mapping>
   <servlet-name>RestletServlet</servlet-name>
   <url-pattern>/restlet/*</url-pattern>
</servlet-mapping>

To integrate these Web services in Tomcat server, switch to the Java EE perspective and create a server in the "Server" view:

Right click on the background of the "Server" view, and select the context menu New → Server:

Select the "Tomcat v6.0 server" option under the Apache category, and then click the "Next >" button,

Select the project "DataGridRESTService", click on the "Add >" button to add it into the list of configured projects and click "Finish" button to confirm this operation.

Now, our server is configured and ready to start. You can start it by clicking on the Run button on the tool bar of the Server view. And can then open a web browser to test our Silverlight application via the URL
http://localhost:8080/DataGridRESTService/

Silverlight application, launched via Run configuration of Eclipse environment (see screenshot below), will fail to access the Web services in single domain deployment mode since ESL starts automatically a new Web server to host it. It falls automatically into the cross domain mode.

5.2 Cross domain deployment

In an enterprise with a heterogeneous environment, a Silverlight application may be hosted on a different Web server from the Web services. Using Silverlight for cross-domain communication requires guarding against several types of security vulnerabilities that can be used to exploit Web applications.

To enable a Silverlight control to access a service in another domain, the service must explicitly opt-in to allow cross-domain access. By opting-in, a service states that the operations it exposes can safely be invoked by a Silverlight control, without potentially damaging consequences to the data the service stores.

Silverlight 3 supports two different mechanisms for services to opt-in to cross-domain access:

More information about the cross-domain control can be found here.

Cross domain control in Web server depends on its configuration. Eclipse WTP provides three options:

  1. Use workspace metadata (by default)
  2. Use Tomcat installation
  3. Use customer location

There are two solutions to change the configuration:

1. Right click "Tomcat v6.0 server at localhost" in "Server" View and select "Open" menu

2. Right click "Tomcat v6.0 server at localhost" in "Server" View and select "Properties" menu:

The location of clientaccesspolicy.xml must be in Web server root folder, which depends on server configuration. You can find it by the concatenation of <Server path> and <Deploy path>/ROOT. Taking an example of option N° 1, the Web root should be
<workspace>/.metadata/.plugin/org.eclipse.wst.server.core/tmp0/wtpwebapps/ROOT.

To enable cross domain access in this configuration, we need to put a clientaccesspolicy.xml file in the Web root folder.

<?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*">
        <domain uri="*"/>
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

This configuration allows access from any other domain to all resources on the current domain. It is highly recommended to restrict access in a production environment.