Silverlight accessing RSS Services hosted on Apache Tomcat

The How-To series demo Silverlight accessing Web Services in various configurations. In this post, we will demonstrate an RSS Service hosted on Apache Tomcat server consumed by a Silverlight client application. This scenario can be categorized as a D2D scenario with a cross domain policy.

Both the Silverlight Client Application and RSS service are available as an attachment to this post. Click here to download a completed version of this sample application.

Scenario

We would first develop a simple RSS service feeder for publishing weather updates for select cities. Then we would develop a Rich Internet Application that consumes this RSS feed and displays weather information to the user in the browser.

Technical environment

Requirement

For this How-To, we use the Apache Axis 2 framework. Please install Web Tools Platform (WTP) in eclipse IDE. This is by default available if you use Eclipse IDE for Java EE Developers. Otherwise you need to install it from
http://download.eclipse.org/webtools/downloads/.

Implementing the RSS service for Weather Application

Now we would describe various steps needed to create and setup the RSS service on tomcat.

Configure Tomcat inside Eclipse

We need to tell Eclipse about which Tomcat we are using and set up the integration. Use following steps to configure this.

Following screenshots shows the tomcat server details after successful installation.

Install and Configure Apache Axis 2

Creating Dynamic Web Project

getAllCitiesAsRSS

For demonstration purpose, we create RSS xml file (AllCities.xml) listing all cities, add this file to the project inside the same package com.weather. This file looks like.

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>Weather details of all cities</title>
        <link>http://www.eclipse4sl.org/</link>
        <description>Weather details of all cities</description>
        <language>en-us</language>
        <pubDate>Tue, 01 Dec 2008 04:00:00 GMT</pubDate>
        <lastBuildDate>Tue, 10 Dec 2008 04:00:00 GMT</lastBuildDate>
        <docs>http://www.eclipse4sl.org/allcities.rss</docs>
        <generator>Weather Web Service</generator>
        <managingEditor>editor@eclipse4sl.org</managingEditor>
        <webMaster>webmaster@eclipse4sl.org</webMaster>
        <ttl>10</ttl>
 
        <item>
            <title>Seattle</title>
            <link>http://www.eclipse4sl.org/Seattle</link>
            <description></description>
            <pubDate>Tue, 01 Dec 2008 04:00:00 GMT</pubDate>
            <guid>http://www.eclipse4sl.org/Seattle</guid>
        </item>
    
    <!-- Add list of all cities here -->  
  
        <item>
            <title>Redmond</title>
            <link>http://www.eclipse4sl.org/Redmond</link>
            <description></description>
            <pubDate>Tue, 01 Dec 2008 04:00:00 GMT</pubDate>
            <guid>http://www.eclipse4sl.org/Redmond</guid>
        </item>

        <item>
            <title>Rome</title>
            <link>http://www.eclipse4sl.org/Rome</link>
            <description></description>
            <pubDate>Tue, 01 Dec 2008 04:00:00 GMT</pubDate>
            <guid>http://www.eclipse4sl.org/Rome</guid>
        </item>   
    </channel>
</rss>

	

getAllCitiesAsRSS method would simply return contents of this file as xml string. Copy following code into this method.

public String getAllCitiesAsRSS() 
{
    try 
    {
        // Read city list RSS xml from template
        InputStream stream = getClass().getClassLoader()
            .getResourceAsStream("/com/weather/AllCities.xml");
        DocumentBuilderFactory dbf =     
            DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(stream);
        stream.close();

        // No processing here for demo purpose. Return city list directly
        // Return xml string for RSS xml document
        return getXmlStringFromXMLDocument(doc);
    } 
    catch (Exception e) 
    {
        // Need proper error handling, OK demonstration
        return null;
    }
}

	

getWeatherDetailsAsRSS

For demonstration purpose, this method also uses a template RSS file (WeatherDetails.xml) for retuning weather information RSS feed for specified city. Add this file to the project inside the same package com.weather. This file looks like

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>Seattle</title>
        <link>http://www.eclipse4sl.org/</link>
        <description>Weather details of Seattle</description>
        <language>en-us</language>
        <pubDate>Tue, 01 Dec 2008 04:00:00 GMT</pubDate>
        <lastBuildDate>Tue, 10 Dec 2008 04:00:00 GMT</lastBuildDate>
        <docs>http://www.eclipse4sl.org/</docs>
        <generator>Weather Web Service</generator>
        <managingEditor>editor@eclipse4sl.org</managingEditor>
        <webMaster>webmaster@eclipse4sl.org</webMaster>
        <ttl>10</ttl>
    
        <item>
            <title>10° F</title>
            <link>http://www.eclipse4sl.org/</link>
            <description>Current temperature</description>
            <pubDate>Tue, 01 Dec 2008 04:00:00 GMT</pubDate>
            <guid>http://www.eclipse4sl.org/</guid>
        </item>
    
        <item>
            <title>10-20° F</title>
            <link>http://www.eclipse4sl.org/</link>
            <description>Temperature Range for Today</description>
            <pubDate>Tue, 01 Dec 2008 04:00:00 GMT</pubDate>
            <guid>http://www.eclipse4sl.org/</guid>
        </item>
    
        <item>
            <title>Sunny</title>
            <link>http://www.eclipse4sl.org/</link>
            <description>Climate for Today</description>
            <pubDate>Tue, 01 Dec 2008 04:00:00 GMT</pubDate>
            <guid>http://www.eclipse4sl.org/</guid>
        </item>
    
    </channel>
</rss>

	

For demonstration purpose, getWeatherDetailsAsRSS() method reads this template file, and modify following parts with some random data:

Copy following code into getWeatherDetailsAsRSS() method.

public String getWeatherDetailsAsRSS(String cityName) 
{
    try
    {
        // Read WeatherDetails RSS xml from template
        InputStream stream = getClass().getClassLoader()
            .getResourceAsStream("/com/weather/WeatherDetails.xml");
        DocumentBuilderFactory dbf =
            DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(stream);
        stream.close();

        NodeList itemsTitle = doc.getElementsByTagName("title");

        // Set the city name
        itemsTitle.item(0).setTextContent(cityName);

        // Set weather information, randomly for this demo application
        Random generator = new Random();
        int mode = generator.nextInt(4) + 1;
        switch (mode) {
        case 1:
            itemsTitle.item(1).setTextContent("32° F");
            itemsTitle.item(2).setTextContent("20°-40° F");
            itemsTitle.item(3).setTextContent("Thunder Storm");
            break;

        case 2:
            itemsTitle.item(1).setTextContent("45° F");
            itemsTitle.item(2).setTextContent("40°-50° F");
            itemsTitle.item(3).setTextContent("Rainy");
            break;

        case 3:
            itemsTitle.item(1).setTextContent("63° F");
            itemsTitle.item(2).setTextContent("50°-70° F");
            itemsTitle.item(3).setTextContent("Cloudy");
            break;

        case 4:
            itemsTitle.item(1).setTextContent("82° F");
            itemsTitle.item(2).setTextContent("70°-90° F");
            itemsTitle.item(3).setTextContent("Sunny");
            break;
        }

        // Return xml string for the modified RSS xml document
        return getXmlStringFromXMLDocument(doc);
    } 
    catch (Exception e) 
    {
        // Need proper error handling, OK demonstration
        return null;
    }
}

	

Also add following helper method to this class.

private String getXmlStringFromXMLDocument(Document doc)
        throws TransformerFactoryConfigurationError, TransformerException 
{
    StringWriter stw = new StringWriter();
    Transformer serializer = TransformerFactory.newInstance()
        .newTransformer();
    serializer.transform(new DOMSource(doc), new StreamResult(stw));

    return stw.toString();
}

	

After adding these template xml files, the project structure now looks as follows

Expose WeatherServices class as Web Service

Now let us expose the WeatherServices class as web service. This is very easy using eclipse and can be done with following steps.

Deploy Web Service on Tomcat

Save all files, then Run the project on the Tomcat Server. Right click on the project node : Run As -> Run on Server.

The service is now hosted on Tomcat at
http://localhost:8080/WeatherServicesApplication/services/WeatherServices?wsdl

Enabling Cross Domain Access

Since the SOAP service and the Silverlight application reside on different hosts, we need to overpass the Cross Domain Issue. Create at the root of the SOAP service host, a clientaccesspolicy.xml file with the following content. The root of service host for tomcat is typically the folder C:\Java\Apache\apache-tomcat-6.0.18\webapps\ROOT.

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

	

Silverlight Client Application

The development of Silverlight Web client consists of three steps:

First create a new Silverlight Web Project in eclipse and call it as EclipseSLWeatherApplication. If you are not familiar to creating Silverlight projects in eclipse, please refer to Hello World sample.

Designing UI for Weather Details

Let us design UI for displaying weather details for individual city. The UI should look like follows.

Various parts of this UI are as follows.

To design this UI, let us add a xaml page called "WeatherDetails.xaml" to the project. Also add various images to the project. Following diagram shows all images used for displaying weather details. Image names are self descriptive. Image names starting with "big" are used to indicate climate and image name containing sky are for setting panel background.

Now open WeatherDetails.xaml file and add following code.

<UserControl x:Class="EclipseSLWeatherApplication.WeatherDetails"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="320" Height="180">
    <UserControl.Resources>
        <ImageBrush ImageSource="blue-sky.png" x:Key="BlueSky" />
        <ImageBrush ImageSource="gray-sky.png" x:Key="GraySky" />

        <BitmapImage UriSource="big-sun.png" x:Key="BigSun" />
        <BitmapImage UriSource="small-sun.png" x:Key="SmallSun" />

        <BitmapImage UriSource="big-cloudy.png" x:Key="BigCloud" />
        <BitmapImage UriSource="small-cloudy.png" x:Key="SmallCloud" />

        <BitmapImage UriSource="big-rain.png" x:Key="BigRain" />
        <BitmapImage UriSource="small-rain.png" x:Key="SmallRain" />

        <BitmapImage UriSource="big-thunderstorm.png" x:Key="BigThunderStorm" />
        <BitmapImage UriSource="small-thunderstorm.png" x:Key="SmallThunderStorm" />
    </UserControl.Resources>

    <Canvas x:Name="LayoutRoot" Width="264" Height="180" 
            Background="{StaticResource BlueSky}">
        <Canvas Canvas.Top="13" Canvas.Left="13" Width="230" Height="110">
            <StackPanel x:Name="ConditionsScreen" >
                <Canvas Height="90">
                    <Image x:Name="ConditionsOverlay" Margin="-13,-13,0,0" 
                        Source="{StaticResource BigSun}"/>
                    <StackPanel>
                        <TextBlock x:Name="Temperature" Width="225" Height="37" 
                            TextAlignment="Right" FontSize="34"  Text="18° F" />
                        <TextBlock x:Name="Climate" Width="225" Height="14" 
                            TextAlignment="Right" Text="Sunny" />
                        <TextBlock x:Name="Range" Width="225" Height="14" 
                            TextAlignment="Right" Text="15°-29° F" />
                    </StackPanel>
                </Canvas>
                <Canvas Height="25">
                    <TextBlock x:Name="CityName" Text="Redmond" Width="230" 
                        Foreground="White" FontSize="30" TextAlignment="Center" 
                            Opacity="0.5" />
                </Canvas>
            </StackPanel>
        </Canvas>
    </Canvas>
</UserControl>

	

The instant preview for this xaml will be similar to the one displayed above.

Carousel of Weather Details

Let us try to show weather details of multiple cities in a nice fashion using Carousel. We want to show it as follows

All Weather details panels will be arranged circular fashion and these panels will keep rotating continuously, changing details for the panel at the front. This way user can take look weather information about all cities.

To implement Carousel, we will make use of a sample application available at
http://rhizohm.net/irhetoric/blog/74/default.aspx. This sample is known as Yet Another Carousel (YAC). We particularly chose this carousel for demonstration because it supports addition on any type of items like buttons, grids, images, videos etc. Apart from this, almost all other carousel samples can display only image. Of course there are few limitations of YAC. It can display only 5 items at a time instead of displaying all items in a circle. Remaining items are present virtually and will appear correctly when carousel moves. Any way, we will not go into details of Carousel code. One can always refer above link to learn more.

We have isolated two classes from this sample, with minor modifications. Add following two classes to the project.

Now open Page.xaml and paste following code in xaml source window.

<UserControl x:Class="EclipseSLWeatherApplication.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="800" Height="600" >
    <Canvas x:Name="LayoutRoot" Background="White" Width="600" Height="400" 
        MouseLeftButtonUp="OnMouseLeftButtonUp">
    </Canvas>
</UserControl>

	

Here one event handler, OnMouseLeftButtonUp, is added to changes direction of carousel rotation. Every mouse click on this page will reverse the direction.

Next step is to get list of all cities from the RSS service, create WeatherDetail control for each of them and add it to the Carousel. To call the web service, we need web service reference in the project. Currently it is not possible using eclipse4sl plugin. Therefore this has to be done using Visual Studio 2008.

Add Service Reference to the Project

Consuming a RSS service with Silverlight

Now the RSS Service is exposed and we have a client proxy to consume the Web Service with Silverlight.

In Page.xaml.cs, add an import to web service reference and related library.

using EclipseSLWeatherApplication.WeatherApplication;
using System.Xml;
using System.ServiceModel;
using System.ServiceModel.Syndication;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

	

Call getAllCitiesAsRSS() method

Let us populate Carousel with all cities. Here we need to call getAllCitiesAsRSS() method of the web service. This is done by calling getAllCitiesAsRSS() method asynchroneously in the Page_Loaded() handler. Add the Page_Loaded() handler in the Page constructor. This code is as follows.

public Page()
{
    InitializeComponent();
    this.Loaded += new RoutedEventHandler(Page_Loaded);
}

private void Page_Loaded(object sender, RoutedEventArgs e)
{
    // Get list of all cities from RSS service
    WeatherServicesPortTypeClient client = new WeatherServicesPortTypeClient();
    client.getAllCitiesAsRSSCompleted +=
        new EventHandler<getAllCitiesAsRSSCompletedEventArgs>
            (client_getAllCitiesAsRSSCompleted);
    client.getAllCitiesAsRSSAsync();

    // Set timer for Carousel rotatio. It is set to 2 seconds
    DispatcherTimer dt = new DispatcherTimer();
    dt.Interval = new TimeSpan(0, 0, 0, 0, 2000);
    dt.Tick += new EventHandler(dt_Tick);
    dt.Start();
}

	

Here we have registered an handler for completion of getAllCitiesAsRSS() service call. This handler code is as follows

void client_getAllCitiesAsRSSCompleted(object sender, getAllCitiesAsRSSCompletedEventArgs e) 
{
    // Load feed into SyndicationFeed
    XmlReader reader = XmlReader.Create(new System.IO.StringReader(e.Result.ToString()));
    SyndicationFeed feed = SyndicationFeed.Load(reader);

    // Create a new Carousel
    carousel = new Carousel();

    // Add each city weather detail in the Carousel
    foreach (SyndicationItem item in feed.Items)
    {
        // Create weather detail widget
        WeatherDetails weatherDetail = new WeatherDetails();
        weatherDetail.Width = 320;
        weatherDetail.Height = 180;
        weatherDetail.CityName.Text = item.Title.Text;
        carousel.CreateItem(weatherDetail, item.Title.Text);

        // Pouplate weather detail by calling RSS service
        WeatherServicesPortTypeClient client = new WeatherServicesPortTypeClient();
        client.getWeatherDetailsAsRSSCompleted +=
            new EventHandler<getWeatherDetailsAsRSSCompletedEventArgs>
                (client_getWeatherDetailsAsRSSCompleted);
        client.getWeatherDetailsAsRSSAsync(item.Title.Text, weatherDetail);  
    }

    // Add Carousel to the main panel
    this.LayoutRoot.Children.Add(carousel);
}

	

In this handler, we will get list of all cities as RSS feed in the e.Result variable. We use the SyndicationFeed class to load this RSS feed. We can iterate through each item inside the feed using foreach loop on feed.Items collection. Each feed item will contain the city name. We then create WeatherDetail control for each city and add this control to the Carousel. And finally we add Carousel created dynamically to the main panel.

To learn more about using SyndicationFeed class, please refer the
http://silverlight.net/quickstarts/syndicationfeedreader.aspx, a sample available on Silverlight site.

To populate each WeatherDeail control, we have to make call to RSS service method getWeatherDetailsAsRSS() method. Again this has to be done asynchronously using event handler
client_getWeatherDetailsAsRSSCompleted(). This event handler is shown bellow.

void client_getWeatherDetailsAsRSSCompleted(object sender, 
            getWeatherDetailsAsRSSCompletedEventArgs e)
{
    // Load feed into SyndicationFeed
    XmlReader reader = XmlReader.Create(new System.IO.StringReader(e.Result.ToString()));
    SyndicationFeed feed = SyndicationFeed.Load(reader);

    // Get associated carousel
    WeatherDetails weatherDetail = (WeatherDetails) e.UserState;
     
    // Get current temperature
    weatherDetail.Temperature.Text = feed.Items.ElementAt(0).Title.Text;
    
    // Get today's temperature range
    weatherDetail.Range.Text = feed.Items.ElementAt(1).Title.Text;
    
    // Get climate text
    weatherDetail.Climate.Text = feed.Items.ElementAt(2).Title.Text;

    // Asscociat various images for above climate text
    ImageBrush brush = null;
    switch (weatherDetail.Climate.Text)
    {
        case "Thunder Storm":
             brush = new ImageBrush();
            brush.ImageSource = new BitmapImage(new Uri("gray-sky.png", UriKind.Relative));
            weatherDetail.LayoutRoot.Background = brush;
            weatherDetail.ConditionsOverlay.Source = (BitmapImage)new BitmapImage(new 
                Uri("big-thunderstorm.png", UriKind.Relative));
            break;

        case "Rainy":
            brush = new ImageBrush();
            brush.ImageSource = new BitmapImage(new Uri("gray-sky.png", UriKind.Relative));
            weatherDetail.LayoutRoot.Background = brush;
            weatherDetail.ConditionsOverlay.Source = (BitmapImage)new BitmapImage(new 
                Uri("big-rain.png", UriKind.Relative));
            break;

        case "Cloudy":
            brush = new ImageBrush();
            brush.ImageSource = new BitmapImage(new Uri("gray-sky.png", UriKind.Relative));
            weatherDetail.LayoutRoot.Background = brush;
            weatherDetail.ConditionsOverlay.Source = (BitmapImage)new BitmapImage(new 
                Uri("big-cloud.png", UriKind.Relative));
        break;

        case "Sunny":
            brush = new ImageBrush();
            brush.ImageSource = new BitmapImage(new Uri("blue-sky.png", UriKind.Relative));
            weatherDetail.LayoutRoot.Background = brush;
            weatherDetail.ConditionsOverlay.Source = (BitmapImage)new BitmapImage(new
                 Uri("big-sun.png", UriKind.Relative));
            break;
    }
}

	

The above handler gets the RSS feed returned by getWeatherDetailsAsRSS() web service into variable e.Result. It again uses class SyndicationFeed to process the RSS feed. This method knows various items in the RSS feed and extract data like

Depending on climate text, this handler sets various images in associated WeatherDetail control. Look at switch statement to understand these settings.

Finally two handlers are written to control the Carousel rotation. A timer is set in Page_Loaded() method. Every time timer is hit, carousel is rotate by one position towards left of right, depending on value of rotateDirection flag.

// Timer event handler Used to rotate items in Carousel by one frame
void dt_Tick(object sender, EventArgs e)
{
    if (carousel != null)
    {
        if (rotateDirection == 1)
            carousel.MoveRight(1);
        else
            carousel.MoveLeft(1);
    }
}

	

Rotation direction can be modified by clicking on the main panel. Every time the Mouse left button is clicked and released, the rotation direction is reversed.

// Event handler used to toggle the direction of rotation
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    if (rotateDirection == 1)
        rotateDirection = 2;
    else
        rotateDirection = 1;
}