Clients Web services REST avec Java 6 (JAXB)

Hyacinthe MENIET
www.dotmyself.net

Vous trouverez de nombreux Web services REST sur l'Internet. Bon nombre d'entre eux sont accessibles exclusivement en lecture seule. C'est à dire que vous ne pourrez dialoguez avec eux que via la méthode GET (et parfois POST), dans le but d'en consulter les données. En voici quelques exemples :

Des Web services REST publics
Web service Description
Yahoo! Maps Web Services Donne la latitude et la longitude d'une adresse donnée (fonctionne au mieux avec les adresses des USA).
Yahoo! Search Web Services Extrait les expressions ou les mots significatifs d'un texte.
Flickr Photos Web Services Permet de rechercher des photos sur Flickr à partir du photographe, du sujet ou d'autres critères.
eBay Shopping Web Services Permet de rechercher dans les articles, les produits et les commentaires d'eBay
Amazon E-Commerce Service Permet de rechercher dans le catalogue produit d'Amazon.

Dans ce document je présente les trois premiers.

1. Pré-requis

2. Vue d'ensemble

2.1 Vue d'ensemble de REST

Je présente brièvement les fondamentaux de REST dans Web services REST avec Java 6 (JAXB).

2.2 Vue d'ensemble de l'article

Dans la pratique la plupart des Web services n'utilisent que les méthodes GET et POST ceci parce qu'une part importante des navigateurs ne supportent que ces deux méthodes. Toujours dans la pratique, pour consommer un Web service vous aurez rarement des URI de la forme : http://localhost:8080/weatherws/cities/307 ce sera plutôt http://localhost:8080/weatherws/cities?id=307.

Ce document sera donc dédié à la consommation de Web service réels (e.g. Yahoo! Maps Web Services, Yahoo! Search Web Services et Flickr Photos Web Services). Pour la sérialisation et désérialisation du java en XML, j'utiliserai JAXB et DOM.

3. Le client pour Yahoo! Maps Web Services

Yahoo! propose un Web service de géolocalisation assez facile à consommer : le Yahoo! Maps Web Services . Visitez-le pour avoir la liste exhaustive des paramètres qu'il accepte et ainsi l'utiliser de manière optimale. Yahoo! Maps Web Services associe à une adresse donnée sa longitude et sa latitude. Pour suivre les gros consommateurs de son service, Yahoo! exige de chacun un identifiant. Vous pouvez vous en procurer un à cette adresse sinon utiliser celui du didacticiel (e.g YahooDemo). Notez que chaque adresse IP est soumise à un quota de 50 000 requêtes par jour.

Un exemple sera plus parlant. Interrogez l'application depuis votre navigateur avec l'adresse du Metropolitan Museum of Art à New-York.

Pour consommer ce Web service vous aurez besoin de trois choses :

Générez les classes utiles à désérialisation via la commande :

$ xjc -d src -p 'net.dotmyself.restclient.yahoomaps' http://api.local.yahoo.com/MapsService/V1/GeocodeResponse.xsd
parsing a schema...
compiling a schema...
net/dotmyself/restclient/yahoomaps/ObjectFactory.java
net/dotmyself/restclient/yahoomaps/ResultSet.java
net/dotmyself/restclient/yahoomaps/ResultType.java
net/dotmyself/restclient/yahoomaps/package-info.java

Quelques explications :

Créez le wrapper (GeocodeMapper.java) :

/**
 * @author Hyacinthe MENIET
 * Created on 25 août 07
 */
package net.dotmyself.restclient.yahoomaps;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

/**
 * A class designed to wrap the Yahoo! Geocode service.
 */
public class GeocodeMapper {
	
	private static final String baseUrl = "http://api.local.yahoo.com/MapsService/V1/geocode";
	private static final String appId = "YahooDemo"; //replace with your own code
	private static final String urlEncoding = "ISO-8859-1";
	/**
	 * Retrieves the location of an address.
	 * @param street
	 * 	The street address you are searching for.
	 * @param city
	 * 	The city for the location you are searching for.
	 * @param state
	 * 	The US state (if applicable) you are searching for.
	 * @param zip
	 * 	The US ZIP code (if applicable) you are searching for.
	 * @return list of {@link ResultType}
	 * @throws UnsupportedEncodingException 
	 * @throws JAXBException 
	 * @throws MalformedURLException 
	 */
	public static List<ResultType> getResultSet(String street, String city, String state,String zip) 
		throws UnsupportedEncodingException, MalformedURLException, JAXBException {
		
		StringBuilder sb = new StringBuilder(baseUrl);
		sb.append("?appid=");
		sb.append(URLEncoder.encode(appId, urlEncoding));
		if(street != null && street.length() > 1) {
			sb.append("&street=");
			sb.append(URLEncoder.encode(street, urlEncoding));
		}
		if(city != null && city.length() > 1) {
			sb.append("&city=");
			sb.append(URLEncoder.encode(city, urlEncoding));
		}
		if(state != null && state.length() > 1) {
			sb.append("&state=");
			sb.append(URLEncoder.encode(state, urlEncoding));
		}
		if(zip != null && zip.length() > 1) {
			sb.append("&zip=");
			sb.append(URLEncoder.encode(zip, urlEncoding));
		}
		ResultSet rs = extractLocation(new URL(sb.toString()));
		return (rs != null) ? rs.getResult() : null;
	}
	/**
	 * Retrieves the location of an address.
	 * @param location
	 * 	A free form field of address information.
	 * @return list of {@link ResultType}
	 * @throws UnsupportedEncodingException 
	 * @throws JAXBException 
	 * @throws MalformedURLException 
	 */
	public static List<ResultType> getResultSet(String location) throws UnsupportedEncodingException, 
		MalformedURLException, JAXBException {
		
		StringBuilder sb = new StringBuilder(baseUrl);
		sb.append("?appid=");
		sb.append(URLEncoder.encode(appId, urlEncoding));
		if(location != null && location.length() > 1) {
			sb.append("&location=");
			sb.append(URLEncoder.encode(location, urlEncoding));
		}
		ResultSet rs = extractLocation(new URL(sb.toString()));
		return (rs != null) ? rs.getResult() : null;
	}
	/**
	 * Uses the JAXB classes to process the returned XML 
	 * and returns the corresponding {@link ResultSet}
	 * @param url
	 * 	The URL for the query. 
	 * @return {@link ResultSet}
	 * @throws JAXBException
	 * @throws MalformedURLException
	 */
	private static ResultSet extractLocation(URL url) throws JAXBException, MalformedURLException {
		JAXBContext context = JAXBContext.newInstance("net.dotmyself.restclient.yahoomaps");
		Unmarshaller unmarshaller = context.createUnmarshaller();
		return (ResultSet) unmarshaller.unmarshal(url);		
	}
}

Créez le client Web service (GeocodeWSClient.java) :

/**
 * @author Hyacinthe MENIET
 * Created on 25 août 07
 */
package net.dotmyself.restclient.yahoomaps;

import java.io.StringReader;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;

/**
 * Web services Client which calls the Yahoo's Geocoding service. 
 */
public class GeocodeWSClient {

	/**
	 * The main method.
	 * @param args
	 * 	The XML representation of the address which you want 
	 * 	to retrieve longitude and latitude.
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		if(args == null || args.length < 1) {
			throw new IllegalArgumentException("You must indicate the address you want to geocode.");
		}
		JAXBContext context = JAXBContext.newInstance("net.dotmyself.restclient.yahoomaps");
		Unmarshaller unmarshaller = context.createUnmarshaller();
		// processes the given XML into a ResultType
		ResultType geocode = ((ResultSet) unmarshaller.unmarshal(new StringReader(args[0]))).getResult().get(0);
		// calls the web service
		List<ResultType> listOfResultType = GeocodeMapper.getResultSet(geocode.getAddress(),
				geocode.getCity(),geocode.getState(),geocode.getZip());
	    // Displays results
	    for(ResultType resultType : listOfResultType){
	        System.out.println("precision="+resultType.getPrecision()+","
	        		+"Latitude="+resultType.getLatitude()+","
	        		+"Longitude="+resultType.getLongitude()+","
	        		+"Country="+resultType.getCountry());
	   }
	}

}

Après compilation, l'exécution :

../file/source/7/yahoomaps/wscall.txt

Le comment je récupère les paramètres pour les transmettre au Web service, n'est pas décisif. J'ai choisi de re-utiliser le schéma XML de Yahoo! parce les schémas d'entrée et de sortie sont très proches. De plus en procédant ainsi, je maximise l'utilisation du code généré. Mais rien ne vous empêche de diverger.

4. Le client pour Yahoo! Search Web Services

Yahoo! propose un deuxième Web service : Yahoo! Search Web Services. Visitez-le pour avoir la liste exhaustive des paramètres qu'il accepte et ainsi l'utiliser de manière optimale. Ce service permet d'extraire les phrases et les mots importants d'un texte. Il peut par exemple servir à catégoriser les articles d'un blog. Pour suivre les gros consomateurs de son service, Yahoo! exige de chacun un identifiant. Vous pouvez vous en procurer un à cette adresse sinon utiliser celui du didacticiel (e.g. YahooDemo). Notez que chaque adresse IP est soumise à un quota de 50 000 requêtes par jour.

Pour s'affranchir de la limite des 2K des requêtes GET inhérente au navigateur MS Internet Explorer, ce Web service peut être appelé via la méthode POST. C'est d'ailleurs la voie que j'emprunterai dans le client Java. Pour varier les plaisirs.

Un exemple sera plus parlant. Interrogez l'application depuis votre navigateur avec cet extrait d'un billet de dotmyself.

Pour consommer ce Web service vous aurez besoin de trois choses :

Générez les classes utiles à désérialisation via la commande :

$ xjc -d src -p 'net.dotmyself.restclient.yahoosearch' http://api.search.yahoo.com/ContentAnalysisService/V1/TermExtractionResponse.xsd
parsing a schema...
compiling a schema...
net/dotmyself/restclient/yahoosearch/ObjectFactory.java
net/dotmyself/restclient/yahoosearch/ResultSet.java
net/dotmyself/restclient/yahoosearch/package-info.java

Quelques explications :

Créez le wrapper (TermExtractionMapper.java) :

/**
 * @author Hyacinthe MENIET
 * Created on 25 août 07
 */
package net.dotmyself.restclient.yahoosearch;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

/**
 * A class designed to wrap the Yahoo! Term Extraction service.
 */
public class TermExtractionMapper {
	
	private static final String baseUrl = "http://api.search.yahoo.com/ContentAnalysisService/V1/termExtraction";
	private static final String appId = "YahooDemo"; //replace with your own code
	private static final String urlEncoding = "ISO-8859-1";
	
	/**
	 * Retrieves the important words and phrases in a block of text.
	 * @param context
	 * 	The block of text that the terms will be extracted from.
	 * @param query
	 * 	A query to help identify the topic of the context.
	 * @return
	 * 	List of extracted String or <code>null</code> if
     * 	no String could be found.
	 * @throws JAXBException
	 * @throws IOException 
	 */
	public static List<String> getTerms (String context, String query) 
		throws JAXBException, IOException {
		
		// builds de query string
		StringBuilder sb = new StringBuilder("appid=");
		sb.append(URLEncoder.encode(appId, urlEncoding));
		if(context != null && context.length() > 1) {
			sb.append("&context=");
			sb.append(URLEncoder.encode(context, urlEncoding));
		}
		if(query != null && query.length() > 1) {
			sb.append("&query=");
			sb.append(URLEncoder.encode(query, urlEncoding));
		}
		// perform a POST connection
		URL url = new URL(baseUrl);
		URLConnection conn = url.openConnection();
		conn.setDoOutput(true);
		OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
		writer.write(sb.toString());
		writer.flush();
		// reads the response
		JAXBContext jbctx = JAXBContext.newInstance("net.dotmyself.restclient.yahoosearch");
		Unmarshaller unmarshaller = jbctx.createUnmarshaller();
		ResultSet rs = (ResultSet) unmarshaller.unmarshal(conn.getInputStream());
		return (rs != null) ? rs.getResult() : null; 
	}
}

Créez le client Web service (TermExtractionWSClient.java) :

/**
 * @author Hyacinthe MENIET
 * Created on 25 août 07
 */
package net.dotmyself.restclient.yahoosearch;

import java.io.ByteArrayInputStream;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Web services Client which calls the Yahoo! Term Extraction service.
 */
public class TermExtractionWSClient {

	/**
	 * The main method.
	 * @param args
	 * 	The block of text that the terms will be extracted from.
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		if(args == null || args.length < 1) {
			throw new IllegalArgumentException("You must indicate the text you want to extract important terms.");
		}
		// Reads and parse the given XML with DOM
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder constructor = factory.newDocumentBuilder();
		ByteArrayInputStream str = new ByteArrayInputStream(args[0].getBytes("ISO-8859-1"));
		Document document = constructor.parse(str);
		Element root = document.getDocumentElement();
		String context = getTagValue("context",root);
		String query = getTagValue("query",root);
		// Displays results
		List<String> terms = TermExtractionMapper.getTerms(context, query);
		for(String term : terms) {
			System.out.println(term);
		}
	}
	/**
	 * Returns the text content of the given tag.
	 * @param tag
	 * 	The tag.
	 * @param root
	 * 	The root Element.
	 * @return
	 * 	The String or <code>null</code> if
     * 	no String could be found.
	 */
	private static String getTagValue(String tag, Element root) {
        NodeList listOfTags = root.getElementsByTagName(tag);
        if(listOfTags != null && listOfTags.getLength() > 0) {
	        Node node = listOfTags.item(0);
	        return node.getTextContent();
        }
        return null;
	}

}

Après compilation, l'exécution :

java net.dotmyself.restclient.yahoosearch.TermExtractionWSClient "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><request><context>Je pense qu'Internet représente une formidable opportunité pour les propriétaires des droits sur la musique. En effet, il permet d'augmenter la visibilité d'une oeuvre et indirectement celle de son auteur. Mécaniquement cela devrait produire plus de coup de coeur, voire d'achat</context><query>music</query></request>"
coup de coeur
formidable

Cette fois j'utilise en entrée du XML que je lis à la volée grâce à DOM dans TermExtractionWSClient. Rien ne vous oblige à suivre cette voie.

5. Le client pour Flickr Photos Web Services

Terminons par Flickr qui fournit un service de recherche de photos, par auteur, par tag, par groupe et bien d'autres critères. Consultez le Flickr Photos Web Services pour avoir la liste des API. Comme Yahoo!, Flickr exige une clé pour utiliser son service. Je vous suggère d'en générer une depuis cette adresse.

Un exemple sera plus parlant. Interrogez l'application depuis votre navigateur avec une recherche sur les photos tagguées travel.

A partir des informations ci-dessus, vous pouvez inférer l'Url vers une photo. Les URL sont de la forme :

Pour consommer ce Web service vous aurez besoin de trois choses :

Créez la classe Photo qui est la structure de données associée à l'item <photo> (Photo.java) :

/**
 * @author Hyacinthe MENIET
 * Created on 26 août 07
 */
package net.dotmyself.restclient.flickrphotos;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "")
@XmlRootElement(name = "photo")
public class Photo {

    @XmlAttribute
    protected Integer farm;
    @XmlAttribute
    protected Integer id;
    @XmlAttribute
    protected Integer isfamily;
    @XmlAttribute
    protected Integer isfriend;
    @XmlAttribute
    protected Integer ispublic;
    @XmlAttribute
    protected String owner;
    @XmlAttribute
    protected String secret;
    @XmlAttribute
    protected Integer server;
    @XmlAttribute
    protected String title;

    /**
     * Gets the value of the farm property.
     * 
     * @return
     *     possible object is
     *     {@link Integer }
     *     
     */
    public Integer getFarm() {
        return farm;
    }

    /**
     * Sets the value of the farm property.
     * 
     * @param value
     *     allowed object is
     *     {@link Integer }
     *     
     */
    public void setFarm(Integer value) {
        this.farm = value;
    }

    /**
     * Gets the value of the id property.
     * 
     * @return
     *     possible object is
     *     {@link Integer }
     *     
     */
    public Integer getId() {
        return id;
    }

    /**
     * Sets the value of the id property.
     * 
     * @param value
     *     allowed object is
     *     {@link Integer }
     *     
     */
    public void setId(Integer value) {
        this.id = value;
    }

    /**
     * Gets the value of the isfamily property.
     * 
     * @return
     *     possible object is
     *     {@link Integer }
     *     
     */
    public Integer getIsfamily() {
        return isfamily;
    }

    /**
     * Sets the value of the isfamily property.
     * 
     * @param value
     *     allowed object is
     *     {@link Integer }
     *     
     */
    public void setIsfamily(Integer value) {
        this.isfamily = value;
    }

    /**
     * Gets the value of the isfriend property.
     * 
     * @return
     *     possible object is
     *     {@link Integer }
     *     
     */
    public Integer getIsfriend() {
        return isfriend;
    }

    /**
     * Sets the value of the isfriend property.
     * 
     * @param value
     *     allowed object is
     *     {@link Integer }
     *     
     */
    public void setIsfriend(Integer value) {
        this.isfriend = value;
    }

    /**
     * Gets the value of the ispublic property.
     * 
     * @return
     *     possible object is
     *     {@link Integer }
     *     
     */
    public Integer getIspublic() {
        return ispublic;
    }

    /**
     * Sets the value of the ispublic property.
     * 
     * @param value
     *     allowed object is
     *     {@link Integer }
     *     
     */
    public void setIspublic(Integer value) {
        this.ispublic = value;
    }

    /**
     * Gets the value of the owner property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getOwner() {
        return owner;
    }

    /**
     * Sets the value of the owner property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setOwner(String value) {
        this.owner = value;
    }

    /**
     * Gets the value of the secret property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getSecret() {
        return secret;
    }

    /**
     * Sets the value of the secret property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setSecret(String value) {
        this.secret = value;
    }

    /**
     * Gets the value of the server property.
     * 
     * @return
     *     possible object is
     *     {@link Integer }
     *     
     */
    public Integer getServer() {
        return server;
    }

    /**
     * Sets the value of the server property.
     * 
     * @param value
     *     allowed object is
     *     {@link Integer }
     *     
     */
    public void setServer(Integer value) {
        this.server = value;
    }

    /**
     * Gets the value of the title property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getTitle() {
        return title;
    }

    /**
     * Sets the value of the title property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setTitle(String value) {
        this.title = value;
    }

}

Créez la classe Photos qui est la structure de données associée à l'item <photos> (Photos.java) :

/**
 * @author Hyacinthe MENIET
 * Created on 26 août 07
 */
package net.dotmyself.restclient.flickrphotos;

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "photo"
})
@XmlRootElement(name = "photos")
public class Photos {

    @XmlElement(required = true)
    protected List<Photo> photo;
    @XmlAttribute
    protected Integer page;
    @XmlAttribute
    protected Integer pages;
    @XmlAttribute
    protected Integer perpage;
    @XmlAttribute
    protected Integer total;

    /**
     * Gets the value of the photo property.
     * 
     * This accessor method returns a reference to the live list,
     * not a snapshot. Therefore any modification you make to the
     * returned list will be present inside the JAXB object.
     * This is why there is not a <CODE>set</CODE> method for the photo property.
     * 
     * For example, to add a new item, do as follows:
     *    getPhoto().add(newItem);
     * 
     * 
     * Objects of the following type(s) are allowed in the list
     * {@link Photo }
     * 
     * 
     */
    public List<Photo> getPhoto() {
        if (photo == null) {
            photo = new ArrayList<Photo>();
        }
        return this.photo;
    }

    /**
     * Gets the value of the page property.
     * 
     * @return
     *     possible object is
     *     {@link Integer }
     *     
     */
    public Integer getPage() {
        return page;
    }

    /**
     * Sets the value of the page property.
     * 
     * @param value
     *     allowed object is
     *     {@link Integer }
     *     
     */
    public void setPage(Integer value) {
        this.page = value;
    }

    /**
     * Gets the value of the pages property.
     * 
     * @return
     *     possible object is
     *     {@link Integer }
     *     
     */
    public Integer getPages() {
        return pages;
    }

    /**
     * Sets the value of the pages property.
     * 
     * @param value
     *     allowed object is
     *     {@link Integer }
     *     
     */
    public void setPages(Integer value) {
        this.pages = value;
    }

    /**
     * Gets the value of the perpage property.
     * 
     * @return
     *     possible object is
     *     {@link Integer }
     *     
     */
    public Integer getPerpage() {
        return perpage;
    }

    /**
     * Sets the value of the perpage property.
     * 
     * @param value
     *     allowed object is
     *     {@link Integer }
     *     
     */
    public void setPerpage(Integer value) {
        this.perpage = value;
    }

    /**
     * Gets the value of the total property.
     * 
     * @return
     *     possible object is
     *     {@link Integer }
     *     
     */
    public Integer getTotal() {
        return total;
    }

    /**
     * Sets the value of the total property.
     * 
     * @param value
     *     allowed object is
     *     {@link Integer }
     *     
     */
    public void setTotal(Integer value) {
        this.total = value;
    }

}

Créez la classe Rsp qui est la structure de données associée à l'item <rsp> (Rsp.java) :

/**
 * @author Hyacinthe MENIET
 * Created on 26 août 07
 */
package net.dotmyself.restclient.flickrphotos;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "photos"
})
@XmlRootElement(name = "rsp")
public class Rsp {

    @XmlElement
    protected Photos photos;
    @XmlAttribute
    protected String stat;

    /**
     * Gets the value of the photos property.
     * 
     * @return
     *     possible object is
     *     {@link Photos }
     *     
     */
    public Photos getPhotos() {
        return photos;
    }

    /**
     * Sets the value of the photos property.
     * 
     * @param value
     *     allowed object is
     *     {@link Photos }
     *     
     */
    public void setPhotos(Photos value) {
        this.photos = value;
    }

    /**
     * Gets the value of the stat property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getStat() {
        return stat;
    }

    /**
     * Sets the value of the stat property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setStat(String value) {
        this.stat = value;
    }

}

Créez le wrapper (PhotoMapper.java) :

/**
 * @author Hyacinthe MENIET
 * Created on 26 août 07
 */
package net.dotmyself.restclient.flickrphotos;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

/**
 * A class designed to wrap the Flickr Photos Web Services.
 */
public class PhotoMapper {
	
	private static final String baseUrl = "http://www.flickr.com/services/rest/";
	private static final String appId = "your_own_code"; //replace with your own code
	private static final String method = "flickr.photos.search";
	private static final String urlEncoding = "ISO-8859-1";
	/**
	 * Retrieves photographs by tags.
	 * @param tags
	 * 	Small blocks of text attached to a photograph to identify its content.
	 * @return list of {@link Photo} or <code>null</code> if
	 * 	no {@link Photo} could be found.
	 * @throws UnsupportedEncodingException 
	 * @throws MalformedURLException 
	 * @throws JAXBException 
	 */
	public static List<Photo> getPhotos(String tags) 
		throws UnsupportedEncodingException, MalformedURLException, JAXBException {
		
		StringBuilder sb = new StringBuilder(baseUrl);
		sb.append("?api_key=");
		sb.append(URLEncoder.encode(appId, urlEncoding));
		sb.append("&method=");
		sb.append(URLEncoder.encode(method, urlEncoding));
		if(tags != null && tags.length() > 1) {
			sb.append("&tags=");
			sb.append(URLEncoder.encode(tags, urlEncoding));
		}
		Photos ps = extractPhotos(new URL(sb.toString()));
		return (ps != null) ? ps.getPhoto() : null;
	}
	/**
	 * Uses the JAXB classes to process the returned XML
	 * and returns the corresponding {@link Photos}
	 * @param url
	 * 	The URL for the query.
	 * @return
	 * 	{@link Photos} or <code>null</code> if
	 * 	no {@link Photos} could be found.
	 * @throws JAXBException 
	 */
	private static Photos extractPhotos(URL url) throws JAXBException {
		JAXBContext context = JAXBContext.newInstance(Rsp.class);
		Unmarshaller unmarshaller = context.createUnmarshaller();
		Rsp rsp = (Rsp) unmarshaller.unmarshal(url);
		return (rsp != null) ? rsp.getPhotos() : null;
	}
	/**
	 * Creates the photographs thumbnail URL.
	 * @param photo
	 * 	The {@link Photo}
	 * @return
	 * 	The thumbnail URL.
	 * @throws UnsupportedEncodingException
	 */
	public static String buildThumbnailUrl(Photo photo) throws UnsupportedEncodingException {
		if(photo == null) {
			throw new NullPointerException("You should provide a valid Photo in order to ask to thumbnail URL");
		}
		String url = MessageFormat.format("http://farm{0}.static.flickr.com/{1}/{2}_{3}_t.jpg",
				new Object[] {
					URLEncoder.encode(photo.getFarm().toString(), urlEncoding),
					URLEncoder.encode(photo.getServer().toString(), urlEncoding),
					URLEncoder.encode(photo.getId().toString(), urlEncoding),
					URLEncoder.encode(photo.getSecret().toString(), urlEncoding)
				});
		return url;
	}

}

Créez le client Web service (PhotoWSClient.java) :

/**
 * @author Hyacinthe MENIET
 * Created on 26 août 07
 */
package net.dotmyself.restclient.flickrphotos;

import java.util.List;

/**
 * Web services Client which calls the Flickr Photos Web Services.
 */
public class PhotoWSClient {

	/**
	 * The main method.
	 * @param args
	 * 	The tags.
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		if(args == null || args.length < 1) {
			throw new IllegalArgumentException("You must indicate the text you want to extract important terms.");
		}
		List<Photo> photos = PhotoMapper.getPhotos(args[0]);
		for (Photo photo : photos) {
			System.out.println(PhotoMapper.buildThumbnailUrl(photo));
		}
	}

}

Après compilation, l'exécution :

java net.dotmyself.restclient.flickrphotos.PhotoWSClient travel
http://farm2.static.flickr.com/1081/1241102488_1cb8b1444b_t.jpg
http://farm2.static.flickr.com/1038/1241108964_262bc03ca2_t.jpg
[...]
http://farm2.static.flickr.com/1409/1240885274_648c2d3e43_t.jpg

Visualisez l'image de votre choix depuis votre navigateur pour valider que les Urls générées sont correctes.