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.
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:
The following softwares should be installed on your machine to work with this guidance.
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:
The installation of Tomcat consists of three steps:
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.
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.
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:
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":
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.
The development of Silverlight REST client consists of two steps:
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.
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:
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:
WebClient client = new WebClient();
client.findCustomersFromCompleted += new EventHandler<DataService.findCustomersFromCompletedEventArgs> (client_FindCustomersFromCompleted);
// Build URL with Filter string requestURI = "http://localhost:8080/DataGridRESTService/restlet/customers/"; string filter = container.country.Text; if (!String.IsNullOrEmpty(filter)) { requestURI += filter; } URI uri = new Uri(requestURI);
client.DownloadStringAsync(uri);
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;
If a Silverlight application and the Web services are hosted in the same Web server, this deployment mode is called "single domain deployment".
In single domain mode, Java Web services and Silverlight applications share the same domain name, port number and protocol transport.
Otherwise, it is a so called "cross 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.
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:
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.