TP nº6: Application SCA

Comme vous avez pu le constater lors des précédents exercices, écrire des applications SOA, intégrant de nombreux services web, n’est pas toujours chose aisée. Notamment, la mise en oeuvre de services web (WS, REST, etc.) demande du temps et surtout du code technique en plus de vos classes métiers. Or, que diriez-vous de n’écrire que le code métier et simplement spécifier dans un fichier XML les services que vous souhaiter exposer sur le web ? SCA rend ceci possible ! Mais ce n’est pas le seul avantage, SCA vous permet aussi de bénéficier d’un cadre architectural pour vos applications orientées services. Enfin, il permet de mixer des applicatifs utilisant des technologies hétérogènes (bundle OSGi, Java, scripts, BPEL, etc.) et des protocoles de communication hétéroclites (SOAP, RMI, HTTP, JSON-RPC).

Installation

Il existe plusieurs implémentations des spécifications SCA (OW2 FraSCAti, Apache Tuscany, IBM WebSphere, etc.). Nous utiliserons FraSCAti, une plate-forme open-source du consortium OW2. Elle ne supporte pas tous les langages et protocoles spécifiés pour SCA (focus sur les technologies Java) mais, en contrepartie, fournit plusieurs fonctionnalités avancées, telles que le support de composants SCA réflexifs (permettant le changement de configuration à chaud des assemblages), des protocoles d'accès originaux comme UPnP, JNA ou encore des outils qui nous aiderons à observer l'état et administrer les assemblages de composants pendant leur exécution. FraSCAti est téléchargeable sous la forme d'une archive zip sur le site du projet (http://frascati.ow2.org). Pour installer la plateforme, il suffit d'extraire les fichiers de l'archive en suivant les instructions d'installation. Vous pouvez lire le manuel utilisateur pour mieux comprendre le modèle SCA et le fonctionnement de la plateforme FraSCAti. Téléchargez le squelette de projet avant de commencer.

Description de l'architecture

L'exemple fil rouge, MyWeather, consiste en une orchestration de plusieurs services. Dans un premier temps, nous allons interroger un compte twitter pour récupérer la localisation d’une personne, ensuite nous interrogerons un service météo afin d’obtenir le temps pour ce lieu. SCA propose un langage d'architecture décrit en XML qui permet de construire des applications par assemblage de composants, le composant représentant une fonctionnalité implémentée généralement par une classe. Un assemblage SCA est comparable à la notion de contexte d'application dans Spring, les beans étant assimilés à des composants SCA.
Il est possible de générer le descripteur d’architecture (fichier *.composite) en utilisant l’éditeur STP/SCA d’Eclipse, mais nous allons ici modéliser notre exemple directement en XML. Pour cet assemblage (ou composite) SCA nous devons réaliser deux composants :
  • le composant Decoder qui traduit les messages XML renvoyés par le service météo,
  • le composant Orchestration qui utilise les services twitter, météo et le composant Decoder. Il définit une propriété SCA utilisée pour configurer l'identifiant du compte twitter.

myWeather

SCA, en tant que vecteur d’interopérabilité et d’intégration technologique, n'impose pas de langage particulier pour ses composants. Il est possible d'implémenter des composants avec le langage Java ou Scala, des scripts Ruby ou JavaScript, ou encore d'utiliser des composants Spring. Cette flexibilité se retrouve pour la création ou l’utilisation de services pouvant être réalisés par le biais des technologies SOAP, HTTP, RMI, JMS, etc. Les nombreux langages et protocoles d'accès supportés par les implémentations de SCA présentent cette technologie comme solution efficace pour faciliter l'intégration de composants logiciels existants.

Mise en œuvre

Nous allons, dans un premier temps, nous intéresser à l'implémentation de notre composant de décodage qui sera utilisé pour traduire les réponses du service météo, encodées au format XML. Notons que ce composant ainsi que les objets JAXB sont nécessaires car le WSDL du service décrit le type de retour de ces méthodes comme une chaine de caractères et non le type réel (liste de Locations).
Le composant Decoder, développé en Java, fournit une méthode decode(...). Nous commençons par définir une interface Java, nommée Decoder, qui représentera le contrat du service. Cette interface a la signature suivante : Locations decode(String message). On nommera son implémentation DecoderImpl:

    1: public Locations decode(String message) {
    2:         InputStream is = new ByteArrayInputStream(message.getBytes());
    3:         try {
    4:                 JAXBContext context = JAXBContext.newInstance(Locations.class);
    5:                 Unmarshaller unmarshaller = context.createUnmarshaller();
    6:                 return (Locations) unmarshaller.unmarshal(is);
    7:         } catch (JAXBException e) {
    8:                 System.err.println("Unable to decode server message.");
    9:                 return null;
   10: }       }


Cette méthode permet de décoder (via JAXB) le message XML renvoyé par le service météo et donnant la liste des villes pour lesquelles le service météo est disponible. Cette liste est utilisée par le composant d'orchestration pour connaitre l'ensemble des villes où les informations météo sont accessibles. Notons qu'un composant SCA ne peut exposer un service que s'il fournit une interface décrivant ses méthodes (aussi appelée contrat). Ici, le contrat est une interface Java, mais SCA permet aussi de définir les contrats via des descripteurs WSDL.

Grâce à SCA, nous allons très facilement utiliser ou réaliser des services distants sans nous soucier des détails techniques propres aux protocoles d'accès. Nous illustrerons l'intégration et l'exposition de services avec SCA via la création d'un composant d'orchestration qui proposera la mise à jour des informations météorologiques pour des utilisateurs du service twitter. Pour obtenir les informations relatives à un compte utilisateur, twitter propose une API conforme au principe d'architecture REST (REpresentational State Transfer). Le service de météo est lui, accessible via le protocole SOAP. Nous allons accéder à ces services de nature différente via les connecteurs REST et Web Service fournis par la plateforme FraSCAti. L'utilisation d'un connecteur avec SCA est non intrusive, elle n'impacte pas l'implémentation même du composant. C'est FraSCAti qui se chargera de la création des souches/squelettes en charge des connexions distantes.

Accès à un service REST : twitter

Nous passons à l'intégration des services twitter et météo afin de les rendre utilisables par notre orchestration. Pour rappel, la technologie SCA propose l'intégration des services via de nombreux protocoles d'accès dont l'utilisation est spécifiée via le descripteur d'architecture. Par exemple, pour accéder aux méthodes exposées par twitter nous sélectionnerons le binding REST en ajoutant, dans la description de notre composite SCA, la balise XML suivante:

    1: <frascati:binding.rest uri="http://twitter.com"/>


L’attribut URI permet d'indiquer l'adresse de la ressource web à accéder via REST, dans notre cas "http://twitter.com". Pour permettre à notre composant, implémenté en Java, d'accéder aux informations du profil utilisateur, Nous devons écrire l'interface qui reflètera le service utilisé.

    1: import javax.ws.rs.*;
    2: public interface Twitter {
    3:         @GET
    4:         @Path("/users/show/{id}.xml")
    5:         User getUser(@PathParam("id") String id);
    6: }


Nous utiliserons ainsi la méthode getUser() qui permet de récupérer les informations contenues dans le profil utilisateur. Cette méthode prend en paramètre l'identifiant de l'utilisateur. Dans le cas d’un service REST, nous devons utiliser les annotations de l'API JAX-RS afin de préciser le chemin d’accès, la commande HTTP, et les paramètres utilisés pour accéder à la ressource. L'appel à la méthode getUser() renvoie un objet de type User reflétant l’architecture de la ressource REST reçue. La classe User comporte les annotations de l'API JAXB afin de réaliser la correspondance entre les tags XML et les champs de l'objet. Le code java pour la classe User est donné ci-dessous :

    1: import javax.xml.bind.annotation.*;
    2: @XmlRootElement
    3: public class User {
    4:         public String id;
    5:         public String name;
    6:         public String screen_name;
    7:         public String location;
    8:         //...
    9: }


La valeur de l'attribut location de l'objet User contient les informations de localisation renseignées dans le compte twitter. Nous pouvons passer à l’intégration du service météo.

Accès à un Web Service (SOAP) : météo

Le service météo que nous utilisons est accessible via le protocole SOAP, il est publié à l'adresse "http://www.webservicex.net/globalweather.asmx". Ce service web présente deux opérations : GetCitiesByCountry() qui permet de lister les villes où les informations sur la météo sont disponibles et GetWeather() pour connaitre la météo d'une ville. Pour utiliser ce service nous allons faire appel au connecteur SOAP de la plateforme FraSCAti. Dans notre descripteur d'assemblage, nous décrivons l'accès à ce service via une référence comportant un binding web service. Le service météo est alors rendu accessible pour les composants SCA de notre assemblage, et donc accessible pour le composant d'orchestration. Dans le descripteur d’assemblage SCA, cette liaison se traduit par la balise suivante :

    1: <binding.ws wsdli:wsdlLocation= "http://www.webservicex.net/globalweather.asmx?wsdl" wsdlElement= "http://www.webservicex.net#wsdl.port(GlobalWeather/GlobalWeatherSoap)" />


La présence des attributs wsdlLocation et wsdlElement permettent à la plateforme de retrouver le descripteur de l'interface WSDL et le port d'accès au service web. Pour rendre le service météo utilisable par le composant d'orchestration, nous devons générer l'interface Java reflétant ce service à partir de son descripteur WSDL. Pour réaliser cette opération FraSCAti propose la commande wsdl2java :

    1: % frascati wsdl2java -u http://www.webservicex.net/globalweather.asmx?wsdl -o src/generated


Si vous avez des problèmes de proxy (il faut modifier le script frascati pour ajouter les propriétés proxyHost et proxyPort), téléchargez le fichier WSDL depuis votre navigateur (ou utilisez celui fourni dans le squelette de projet) et utilisez la commande suivante:

    1: % frascati wsdl2java -f globalweather.wsdl -o src/generated


Elle permet d'obtenir l'interface GlobalWeatherSoap dans le dossier des sources Java.

L'interface obtenue est décorée avec les annotations de l'API JAX-WS, indiquant le nom des opérations, paramètres et messages proposés par le service web. C’est via cette interface que l’on pourra interroger le service météo.

Réalisation de l'orchestration

Nous avons réalisé le composant de décodage des messages, puis écrit/généré les interfaces qui permettent d'interagir avec les services externes : twitter et météo. Nous pouvons, à présent, nous occuper de l'implantation du composant d'orchestration qui se chargera d'agréger les informations récupérées des services externes et de décoder les informations de localisation. Nous commençons par créer la classe Orchestration suivante :

    1: public class Orchestration {
    2:         @Reference protected Twitter twitter;
    3:         @Reference protected Decoder decoder;
    4:         @Reference protected GlobalWeatherSoap weather;
    5:         @Property protected String userId;
    6:         //...
    7: }


Dans notre implémentation, nous utilisons deux annotations spécifiques à SCA. L'annotation @Reference précise les attributs de la classe permettant d’utiliser d’autres services, internes (ici le decoder) ou distants (twitter et météo dans notre cas). L'injection d'une référence vers un autre service ou un autre composant est réalisée par la plateforme SCA. La seconde annotation @Property définit une propriété
configurable du composant, la valeur de cette propriété sera définie dans le descripteur d'assemblage SCA. Il ne nous reste qu'a implémenter la méthode d'orchestration que nous nommerons getWeatherForUser(). Nous allons d'abord appeler le service twitter et vérifier que notre utilisateur a renseigné la localisation dans son profil:

    1: public String getWeatherForUser() {
    2:         User user = twitter.getUser(userId);
    3:         if (user.location.equals("")) {
    4:                 System.err.println("The user " + userId + " did not publish his location");
    5:                 return "N/A";
    6:         } //...


Nous supposons que l'attribut de localisation comporte le nom de ville et du pays séparés par une virgule, que nous récupérons dans les variables cityName et countryName

    1:         //...
    2:         String[] locations = user.location.split("[\\s]*,[\\s]*", 2);
    3:         String cityName = locations[0];
    4:         String countryName = locations[1];
    5:         System.out.println("User '" + userId + "' is living in " + cityName + " (" + countryName + ")"); ...


On récupère la liste des villes proposées par le service météo en utilisant la référence correspondante : weather, puis on appelle le composant de décodage. On obtient alors une instance d'objet Locations.

    1:         String cities = weather.getCitiesByCountry(countryName);
    2:         Locations l = decoder.decode(cities);
    3:         //...


Enfin, on compare la liste des villes avec la localisation de l'utilisateur. S’il y a correspondance, on appelle de nouveau le service météo pour obtenir les conditions météo détaillées:

    1:         String result = "";
    2:         if (l != null) {
    3:                 boolean done = false;
    4:                 for (Location loc : l.locations) {
    5:                         if (loc.city.value.toLowerCase().contains(cityName.toLowerCase())) {
    6:                                 result += "Current weather in " + loc.city.value + ":\n";
    7:                                 result += weather.getWeather(loc.city.value, countryName) + "\n";
    8:                                 done = true;
    9:                 }       }
   10:                 if (!done) {
   11:                         System.err.println("Unable to find '" + cityName + "' in available cities of the weather service");
   12:         }       }
   13:         return result;
   14: }


Descripteur d'assemblage

Nous avons implémenté les différents composants et interfaces nécessaires pour réaliser notre orchestration. Nous pouvons maintenant décrire l'architecture de notre application SCA. Le descripteur d'architecture appelé composite permet de construire notre application en définissant les composants, leurs liaisons, les propriétés de configuration ou encore les connecteurs utilisés. L'écriture d'un composite commence avec l'élément XML composite qui mentionne obligatoirement un attribut name. Notez également la définition des espaces de nommages via l'attribut xmlns, qui seront ensuite utilisés dans la description:

    1: <composite name="myWeather" xmlns="http://www.osoa.org/xmlns/sca/1.0" xmlns:wsdli="http://www.w3.org/2004/08/wsdl-instance" xmlns:frascati="http://frascati.ow2.org/xmlns/sca/1.1">
    2: <!--....-->


On définit ensuite les composants SCA. Dans un premier temps, nous ajoutons le composant responsable du décodage des messages XML. Les lignes suivantes permettent de nommer le composant et de lui associer la classe d'implémentation Java écrite précédemment:

    1:         <component name="decoder">
    2:                 <implementation.java class="twitterweather.lib.DecoderImpl" />
    3:         </component>


Nous ajoutons le second composant, responsable de l'orchestration. Outre la classe d'implémentation, nous décrivons les références vers les services nommés twitter et weather, ainsi que vers le composant de décodage. Nous définissons également la propriété userId qui utilisera la valeur définie dans la propriété nommée userId du composite (réutilisation de propriétés):

    1:         <component name="orchestration">
    2:                 <implementation.java class="twitterweather.lib.Orchestration"/>
    3:                 <reference name="twitter"/>
    4:                 <reference name="weather"/>
    5:                 <reference name="decoder" target="decoder"/>
    6:                 <property name="userId" source="$userId"/>
    7:         </component>


Finalement, on décrit les références vers les services en spécifiant les connecteurs utilisés et on définit une valeur pour la propriété userId au niveau du composite:

    1:         <component name="orchestration">
    2:                 <reference name="twitter" promote="orchestration/twitter">
    3:                         <frascati:binding.rest uri="http://twitter.com"/>
    4:                 </reference>
    5:                 <reference name="weather" promote="orchestration/weather">
    6:                         <binding.ws wsdli:wsdlLocation="http://www.webservicex.net/globalweather.asmx?wsdl" wsdlElement="http://www.webservicex.net#wsdl.port(GlobalWeather/GlobalWeatherSoap)" />
    7:                 </reference>
    8:                 <property name="userId">YOUR_USER_ID_HERE</property>
    9:         </component>
   10: </composite>


Créer un service web

Pour exposer notre service myWeather et le rendre accessible via SOAP, nous allons ajouter une interface le décrivant. Nous nommerons cette interface MyWeather. Notez l'utilisation de l'annotation @Service pour indiquer que l'interface Java sera utilisée comme contrat pour le service SCA. Voici l'interface qu'implémentera alors la classe Orchestration.

    1: @Service
    2: public interface MyWeather {
    3:         String getWeatherForUser();
    4: }


On ajoute ensuite la définition du service au niveau du composite:

    1: <service name="tw" promote="orchestration/TwitterWeather">
    2:         <binding.ws uri= "http://localhost:8080/myWeather"/>
    3: </service>


Le service tw sera ainsi accessible soit via un appel à partir d’un client SOAP, soit localement. Il ne nous reste plus qu’à compiler et exécuter notre intégration de service implémentée avec la technologie SCA. Pour créer le jar de l'application, nous appelons FraSCAti avec la commande de compilation:

    1: % frascati compile src MyWeather


L'exécution est ensuite réalisée grâce à la commande run. On précisera les noms du service (option –s) et de la méthode (option –m) à appeler ainsi que le chemin vers le jar de l'application:

    1: % frascati run myWeather -libpath MyWeather.jar -s tw -m getWeatherForUser



PS. Attention à la configuration du proxy (modifier le fichier frascati.bat pour ajouter les propriétés proxyHost et proxyPort)

Examiner les applications en cours d’exécution

FraSCAti Explorer est un outil capable d’observer, dans le détail, les applications en cours d'exécution. Il permet de naviguer dans l’architecture d’un assemblage SCA (les composants, leurs services, références, bindings, propriétés métiers et aspects techniques). Ceci prend tout son intérêt lorsque l’on sait que FraSCAti permet de faire évoluer cette architecture dynamiquement. FraSCAti Explorer est donc un véritable microscope vous permettant de visiter et faire évoluer votre application. Pour lancer FraSCAti Explorer, tapez frascati explorer. Chargez vos composites à l’aide d’un clic droit sur le domaine SCA et du menu contextuel Load. Naviguez dans votre système de fichiers jusqu’à l’archive TwitterWeather.jar. Double-cliquez dessus pour naviguer à l’intérieur du jar et chargez le composite twitter-weather.composite.
explorer-load
L’explorer permet aussi de définir des plugins pour nos applications métiers. Dans cet exemple, nous fournissons un panel pour le service tw. Il sera affiché dans la partie droite lorsque le service sera sélectionné. Il nous permet d’invoquer le service par un simple clic sur le bouton Call myWeather service et affiche le résultat de la requête. Testons maintenant notre service en utilisant le panel. Vous devriez obtenir une réponse ressemblant à ceci:

    1: Current weather in Lille:
    2: <?xml version="1.0" encoding="utf-16"?>
    3: <CurrentWeather>
    4:         <Location>Lille, France (LFQQ) 50-34N 003-06E 52M</Location>
    5:         <Time>Sep 28, 2010 - 07:00 AM EDT / 2010.09.28 1100 UTC</Time>
    6:         <Wind> Variable at 2 MPH (2 KT):0</Wind>
    7:         <Visibility> 2 mile(s):0</Visibility>
    8:         <SkyConditions> overcast</SkyConditions>
    9:         <Temperature> 57 F (14 C)</Temperature>
   10:         <DewPoint> 57 F (14 C)</DewPoint>
   11:         <RelativeHumidity> 100%</RelativeHumidity>
   12:         <Pressure> 30.00 in. Hg (1016 hPa)</Pressure>
   13:         <Status>Success</Status>
   14: </CurrentWeather>


A noter que ce service n’est pas toujours disponible (saturation du serveur). Dans ce cas, l’application ne sera pas chargée et vous aurez le message d’erreur suivant:

    1: WSDLException (at /html): faultCode=INVALID_WSDL.


Reconfiguration dynamique

La visualisation est une chose mais l'interaction en est une autre. FraSCAti Explorer permet donc aussi d’interagir avec les applications. Il est possible, par exemple, de modifier la valeur d’une propriété sur un composant en cours d’exécution. Avec notre exemple, nous pouvons changer l’identifiant Twitter de l’utilisateur pour lequel nous souhaitons récupérer les informations. Pour vérifier que le changement a bien été pris en compte, il nous suffit d’invoquer à nouveau notre service grâce au panel disponible sur le service tw.
explorer-property
Avec FraSCAti Explorer, nous pouvons également ajouter (ou supprimer) des composants, mettre à jour une liaison (
wire) ou un binding. Nous allons mettre en oeuvre ces techniques sur notre exemple.

Supposons que le service météo que nous utilisons soit en panne. Nous allons reconfigurer notre application afin qu’elle utilise un autre service météo. En général, les APIs des services ne sont pas les mêmes. Pour contourner ce problème, nous ajouterons un composant adaptateur qui respectera l’interface du service météo utilisé par défaut, et traduira les appels vers notre nouveau service météo. Voyons l’implantation de ce composant:

    1: public class WundergroundProxy implements GlobalWeatherSoap {
    2:         /** Reference to the Weather Underground service. */
    3:         @Reference private Wunderground weatherBackUp;
    4:         @Override
    5:         public String getCitiesByCountry(String country) {
    6:                 // Not implemented
    7:                 return null;
    8:         }
    9:         @Override
   10:         public String getWeather(String city, String country) {
   11:                 StringBuilder sb = new StringBuilder().append(city).append(',').append(country);
   12:                 Forecast forecast = weatherBackUp.getWeather( sb.toString() );
   13:                 return forecast.getTxt_forecast().getForecastdays().get(0).getFcttext();
   14: }       }


Nous allons charger ce nouveau composite SCA
myWeather-backup via le menu Load de FraSCAti Explorer. Une fois chargé, nous ajoutons le composant wunderground dans notre composite myWeather. Ceci se réalise en faisant un glisser/déposer du composant wunderground vers le composite TwitterWeather. Nous supprimons la liaison entre le composant TwitterWeather et le service météo défaillant, en supprimant le binding web service sur la référence weather via un clic droit sur le binding (voir figure ci-dessous).
ws-binding-removal

Ensuite, nous stoppons le composant
orchestration en le sélectionnant, puis avec un clic droit nous choisissons l’action stop. Nous pouvons alors réaliser la liaison (wire) entre sa référence weather et notre service météo de secours wunderground. Notez que lorsque le composant orchestration est stoppé, tous les appels émis vers ce composant sont mis en attente et seront traités une fois le composant redémarré. Ainsi, le service d’orchestration est toujours disponible vis-à-vis du client.

Toujours à l’aide du clic droit, mais cette fois sur la référence, nous sélectionnons le menu
Wire.... Une boîte de dialogue s’ouvre, nous sélectionnons le service weather du composant wunderground qui se trouve maintenant dans le même composite que le composant orchestration. Nous validons en cliquant sur OK et nous redémarrons le composant orchestration, notre reconfiguration est terminée !
explorer-wire

Appelons de nouveau notre service grâce au panel disponible sur le service
tw. Nous voyons maintenant que nous obtenons toujours les informations météo mais légèrement différentes aussi bien au niveau des données que du format. C’est logique car nous avons changé de fournisseur de service météo.

FraSCAti FScript, langage d’interrogation et de reconfiguration d’architecture

Nous avons, pendant l’exécution, modifié l’architecture de notre application avec l’outil graphique fourni par FraSCAti. Ceci est très pratique pour le test ou réaliser de petites modifications d’architecture, mais cela est beaucoup moins envisageable pour des applications en production.
FraSCAti propose d’autres moyens de reconfigurer une application autrement que par son outil de visualisation: soit via une API Java spécifique, soit via un langage dédié particulièrement adapté aux interrogations d’architecture :
FraSCAti FScript (extension SCA de FScript, http://fractal.ow2.org/fscript).

Ce langage possède une syntaxe similaire à XPath. Nous ne détaillerons pas toutes ses possibilités ici (plus d’informations sur
http://frascati.ow2.org/doc/current/ch08.html). Nous ne retiendrons que quelques éléments essentiels de FraSCAti FScript : l’utilisation d’axes de navigation pour parcourir une architecture (scachild, scaservice, scaproperty, scawire, scabinding, scaintent, etc.), la présence de noeuds sources et cibles de chaque côté d’un axe, et la possibilité de définir des variables et des procédures. Pour mieux comprendre, prenons un exemple:

    1: $domain/scachild::*


L’instruction ci-dessus demande au domaine SCA (variable prédéfinie) la liste de ses fils (sans restriction sur le nom des fils). Le résultat est le suivant:

    1: [#<scacomponent: myWeather>, #<scacomponent: myWeather-backup>, #<scacomponent: org.ow2.frascati.FraSCAti>]


Maintenant, si nous désirons obtenir un fils particulier, nous utilisons l’expression suivante:

    1: orch = $domain/scachild::myWeather/scachild::orchestration;


Nous voyons ci-dessus que nous pouvons enchaîner les axes de navigation et stocker le résultat (des noeuds) dans une variable. Il existe aussi quelques raccourcis. L’expression ci-dessus est équivalente à:

    1: orch = $domain/scadescendant::orchestration;


Pour obtenir toutes les références du composant orchestration, nous utilisons:

    1: $orch/scareference::*


Après un bref aperçu de ce langage, intéressons-nous à un cas d’utilisation. Si nous reprenons la reconfiguration dynamique effectuée avec
FraSCAti Explorer, nous pouvons la traduire avec la procédure FraSCAti Script suivante:

    1: action switchToWundergroundService() {
    2:         orchestration = $domain/scadescendant::orchestration;
    3:         weatherRef = $orchestration/scareference::weather;
    4:         wsBinding = $weatherRef/scabinding::*;
    5:         remove-scabinding($weatherRef, $wsBinding);
    6:         wunderground = $domain/scadescendant::wunderground;
    7:         twitterWeather = $domain/scachild::TwitterWeather;
    8:         add-scachild($twitterWeather, $wunderground);
    9:         set-state($orchestration, "STOPPED");
   10:         wundergroundService = $wunderground/scaservice::*;
   11:         add-scawire($weatherRef, $wundergroundService);
   12:         set-state($orchestration, "STARTED");
   13: }


Cette procédure peut être enregistrée et réutilisée par le moteur de
FraSCAti Script. Voyons comment. Tout d’abord, nous démarrons FraSCAti Explorer avec l’option -s afin d’activer le plugin FScript:

    1: % frascati explorer -s.


fscript-console

Ensuite, nous chargeons les composites
MyWeather et MyWeather-backup. A l’aide d’un clic droit sur le composite myWeather, choisissons FraSCAti Script console. Une console s’ouvre. Vous pourrez taper vos instructions FScript de manière interactive ou utiliser l’éditeur pour écrire des procédures, ouvrir des fichiers et charger des scripts dans le moteur. Nous allons cliquer sur Editor, taper la procédure décrite ci-dessus puis cliquer sur Register procedures. La procédure switchToWundergroundService() est maintenant disponible. Cliquons à nouveau sur editor pour masquer la fenêtre et exécutons l’instruction suivante :

    1: switchToWundergroundService()


La reconfiguration a été réalisée. Vous pouvez vous en assurer en rafraîchissant l’affichage de
FraSCAti Explorer. Le script de reconfiguration est bien plus rapide que les glisser/déposer réalisés précédemment avec FraSCAti Explorer. Mieux encore, nous aurions pu prévoir la panne du service météo et intégrer la gestion de cette panne dans notre application. Pour cela, deux possibilités s’offrent à nous : écrire un composant en Java et utiliser le moteur FraSCAti FScript depuis celui-ci (utile pour réaliser des traitements en plus de la reconfiguration) ou écrire un composant dont l’implantation sera un script FraSCAti FScript. Pour réaliser ce composant, il suffit de copier/coller l’action switchToWundergroundService() dans un fichier que nous nommons myreconfig.fscript. Ensuite, dans le descripteur d’assemblage de myWeather-backup, ajoutons les lignes suivantes:

    1: <component name="switch-to-wunderground-service">
    2:         <frascati:implementation.script script="myreconfig.fscript"/>
    3:         <service name="myReconfig">
    4:                 <interface.java interface="com.programmez.sca.myweather.MyReconfig"/>
    5:         </service>
    6: </component>


Vous noterez que nous devons écrire une interface Java reflétant les procédure disponibles dans le script. Voici son implantation:

    1: public interface MyReconfig {
    2:         void switchToWundergroundService();
    3: }


Comme pour tout composant SCA, il est possible d’exposer l’action
switchToWundergroundService() via un protocole d’accès comme SOAP, RMI, etc. Ainsi, l'architecture de notre application pourra être reconfigurée avec un simple appel distant. En ajoutant un mécanisme qui contrôle la disponibilité du service, on obtiendra alors une application autonome, capable de s’adapter en cas de panne du service météo dont elle dépend.

Ajouter des aspects non-fonctionnels

La spécification SCA propose aux développeurs de composants de définir des intentions (intents SCA) sur les services et références des composants. Les intentions traduisent en général des préoccupations non fonctionnelles telles que authentification, confidentialité, etc. que l’on décrit à haut niveau. Celles-ci sont généralement implantées par la plateforme SCA et appliquées au déploiement des composants. Ici, FraSCAti innove sur deux aspects: libre au développeur de fournir ces propres intents. Pour ne pas le perturber, il développera ces intents sous forme de composants SCA, même formalisme pour le métier et le non fonctionnel. Second point, vous l’aurez peut-être deviné, ces intents peuvent être appliqués au déploiement mais aussi dynamiquement, lorsque les applications s’exécutent, grâce à un mécanisme dérivé de la programmation orientée aspects (AOP). Lorsque l’on place un intent sur un service ou une référence, un intercepteur est généré et déroute l’appel vers le composant réalisant l’intent. Il est alors possible de réaliser des pré et post-traitements, de reprendre le cours d’exécution normal de l’application.
Écrire un
intent dans FraSCAti est très facile. Il suffit d’écrire un composant SCA proposant un service dont l’interface est org.ow2.frascati.tinfi.control.intent.IntentHandler pour laquelle il n’y a qu’une seule méthode à implanter: invoke(). Voyons ceci sur un exemple simple de journalisation des appels sur un composant. Nous allons écrire cet intent log. Voici sa description d’architecture:

    1: <component name="log">
    2:         <implementation.java class="com.programmez.sca.intent.Log" />
    3:         <service name="intent">
    4:                 <interface.java interface= "org.ow2.frascati.tinfi.control.intent.IntentHandler"/>
    5:         </service>
    6:         <property name="header">[FRASCATI-BASIC-LOG] </property>
    7: </component>


Et maintenant l’implantation de la classe
Log :

    1: public class Log implements IntentHandler {
    2:         /** A configurable header to add to log traces. */
    3:         @Property
    4:         protected String header;
    5:         @Override
    6:         public Object invoke(IntentJoinPoint ijp) throws Throwable {
    7:                 // Before the current invocation.
    8:                 System.err.println(header + " Before proceed");
    9:                 // Proceed the current invocation.
   10:                 Object ret = ijp.proceed();
   11:                 System.err.println(header + " result of proceed:" + (ret==null ? "null" : ret.toString()));
   12:                 // After the current invocation.
   13:                 System.err.println(header + " After proceed");
   14:                 return ret;
   15: }       }


Nous pouvons maintenant le charger dans
FraSCAti Explorer comme un composant classique. Pour utiliser cet intent, nous allons le tisser dynamiquement sur les services et références sur lesquels il doit s’appliquer. Par exemple, faisons un glisser/déposer du composant log vers le service tw pour tracer les appels à ce service. Nous voyons les traces suivantes s’afficher:

    1: [FRASCATI-BASIC-LOG] Before proceed
    2: [FRASCATI-BASIC-LOG] result of proceed: Current weather in Lille: Chance of Rain. High 18°C (64°F). Winds 10 kph NNE
    3: [FRASCATI-BASIC-LOG] After proceed


Comme notre intent de journalisation est un composant SCA, il peut lui aussi être reconfiguré.
explorer-intent

Si nous avions voulu positionner cet
intent au démarrage de l’application, nous aurions modifié la déclaration du service tw en ajoutant le mot-clé requires:

    1: <service name="tw" promote="orchestration/TwitterWeather" requires="log-intent">


De cette façon, nous indiquons la liste des
intents à appliquer à un service ou une référence.


À votre tour...

L'objectif principal du défi CoCoME est d'évaluer et de comparer l'application pratique de différents modèles de composants existants. Le but de ce projet est donc de développer l'application CoCoME en utilisant le modèle de composants SCA afin de la comparer aux autres implantations de cette même application. La version réalisée sera testée et distribuée dans le cadre de la plate-forme FraSCAti. En particulier, ce projet est l'occasion d'illustrer le développement d'une application SCA mettant en jeu différents langage de programmation (BPEL, Java, Scala, scripts, etc.) et différents protocoles de communication (Web services, REST, CORBA, etc.). Le code métier de l'application et des tests associés est disponible sur le site du défi, vous pouvez donc le télécharger et l'utiliser pour développer votre application SCA.

Tout projet fonctionnel qui sera déposé sur le site de rendu
PROF avant le 18 mars 2011 fera l'objet d'une bonification sur la moyenne de l'UE.

TP nº5: Application REST

Objectif

L'objectif de ce TP est de développer une application cliente riche pour Twitter. En particulier, cette application utilise Swing et l'API REST de Twitter pour poster et récupérer les messages de ses contacts. Si vous n'avez pas de compte Twitter, c'est l'occasion d'en créer un sur twitter.com. Vous pouvez utiliser Netbeans pour développer cette application mais vous pouvez également utiliser Eclipse selon vos affinités (le TP a été testé sous Netbeans 6.9.1).

Définir l'interface graphique

Cette partie se concentre sur la modélisation de l'interface graphique pour l'utilisateur en utilisant Swing. Nous utilisons pour ça une JFrame, vous être libres d'organiser et d'ajouter des éléments comme vous le souhaitez:
  1. Commencez par créer un nouveau projet ("New project... > Java > Java application" sous Netbeans) que vous nommerez TwitterClient. Désactivez la création de la classe principale.
  2. Placez vous dans le nouveau projet créé. Créez un nouveau formulaire JFrame ("Nouveau > Formulaire JFrame") que nous nommerez TwitterFrame (package twitter.client).
  3. Utilisez l'éditeur graphique en vue design pour insérer les composants Swing suivants:
  • Un bouton jButton1 que vous placerez en bas à droite de la fenêtre et dont le label sera "Update".
  • Un label texte (jLabel1) en bas à gauche qui sera utilisé pour afficher l'icone de l'utilisateur. Changez LES propriétés de taille du composant en 48x48 pixels.
  • Un champ texte (jTextField1) entre les deux composants précédents qui servira à saisir le statut de l'utilisateur.
  • Un scroll pane (jScrollPane2) qui occupera toute la partie supérieure de la fenêtre.
  • Une liste (jList1) que vous insérerez dans le scroll pane.

Afficher votre statut

Il s'agit désormais de créer une méthode permettant de récupérer les informations nécessaires en utilisant la méthode Twitter getUserTimeline(). Cette méthode vous retourne votre icone utilisateur et votre statut courant et vous permettra d'alimenter les différents composants de votre interface graphique. Pour ce faire:
  1. Générez un nouveau client RESTful ("New file... > Web Services > RESTful Java Client" sous Netbeans) que vous nommerez TimelineClient. Sélectionnez la ressource [Twitter OAuth] > [statuses] > [user_timeline.{format}] et demandez à générer les objets Java à partir du fichier WADL importé.
  2. Basculez dans la vue source de l'éditeur et ajoutez une nouvelle méthode initUserInfo(). Appelez cette méthode à la fin du constructeur de la classe.
  3. Complétez le code généré pour mettre à jour les composants Swing avec les données retournées par la méthode getUserTimeline() après avoir invoqué les méthodes login() et initOAuth().

Récupérer les clés d'authentification pour Twitter

  1. Pour pouvoir invoquer les services REST de Twitter, il est nécessaire de s'authentifier en utilisant la méthode OAuth et en récupérant les clés CUSTOMER et CUSTOMER_SECRET. Il donc nécessaire d'enregistrer une application dans Twitter pour pouvoir récupérer ces clés:
  2. Connectez vous avec votre compte Twitter et créez une nouvelle application (http://twitter.com/apps/new) en renseignant les différents champs et en prenant soin de spécifier qu'il s'agit d'une application cliente qui bénéficie d'un accès lecture/écriture.
  3. Copiez la valeur des clés récupérés dans les attributs de la classe TimelineClient prévus pour ça.

Exécuter l'application cliente

  1. Compilez et exécuter l'application (n'oubliez pas les propriétés -Dhttp.proxyHost=server et -Dhttp.proxyPort=port pour le proxy).
  2. Une fenêtre de navigateur va s'ouvrir pour vous demander l'autorisation d'accéder aux donner. Cliquez sur autoriser.
  3. La fenêtre va alors afficher un code PIN que vous allez copier et coller dans la console de Netbeans.
  4. Votre application cliente doit désormais s'afficher.

Appeler plusieurs services.

Nous allons maintenant développer une version plus aboutie du client Twitter qui va notamment utiliser plusieurs ressources, à savoir : user_timeline.{format}, update.{format} et friends_timeline.{format}. Les appels à ces services doivent donc partager le même client pour utiliser une authentification unique:
  1. Dans un premier temps, générez deux classes clientes pour accéder aux ressources update.{format} et friends_timeline.{format}.
  2. Copiez ensuite les méthodes getFriendsTimeline() et updateStatus() générées pour ces deux nouvelles classe dans la classe du client TimelineClient (que vous pouvez renommer au passage en RESTfulClient).
  3. Supprimez le paramètre du constructeur de la classe RESTfulClient et affectez la valeur "statuses" à la variable resourcePath du constructeur. Supprimez également le paramètre lors de l'appel au constructeur dans la méthode initUserInfo().
  4. Dans la méthode getUserTimeline(), insérez l'appel à la méthode path("user_timeline.xml") avant l'appel à webResource.queryParams(...).
  5. Faîtes de même avec les méthodes getFriendsTimeline() et updateStatus().
  6. Par précaution, commentez la méthode setResourcePath(...)
  7. Supprimer les deux classes que vous avez générées et qui sont désormais inutiles.

Ajouter une action de mise-à-jour

Dans la vue design, double-cliquez sur le bouton Update. L'éditeur bascule alors en mode source dans le corps de la méthode jButton1ActionPerformed(...) dont vous devez implanter le comportement en utilisant le code suivant:

clt.makeOAuthRequestUnique();
try {
    String status = URLEncoder.encode(jTextField1.getText().trim(), "UTF-8");
    clt.updateStatus(String.class, status, null);
} catch(UniformInterfaceException ex) {
    System.out.println("Exception when calling updateStatus = " + ex.getResponse().getEntity(String.class));
} catch (UnsupportedEncodingException ex) {
    Logger.getLogger(TwitterFrame.class.getName()).log(Level.SEVERE, null, ex);
}


Afficher le nom et le statut des amis

Pour mettre à jour le statut des amis, il est nécessaire d'utiliser une activité périodique qui rafraichira la fenêtre avec les nouvelles informations:
Dans un premier temps, ajoutez le code suivant avant l'appel à la méthode initComponents() de la fenêtre principale:

Timer t = new Timer("Twitter Updater`", false);
t.scheduleAtFixedRate(new TimerTask() {
        @Override public void run() {
    }    }, 1500, 75000);


Ensuite, pour afficher la liste des statuts dans la fenêtre graphique, il est nécessaire d'utiliser un objet de type
DefaultListModel que vous devez déclarer comme un attribut de la classe. Insérez le code suivant dans le corps de la méthode run():

try {
    clt.initOAuth();
    Statuses response = clt.getFriendsTimeline(Statuses.class, null, null, null, "10");
    statusesListModel.clear();
    for (final StatusType st : response.getStatus()) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                statusesListModel.addElement(st);
        }    });
    }
} catch (UniformInterfaceException ex) {
    System.out.println("Exception when calling getFriendsTimeline = " + ex.getResponse().getEntity(String.class));
}


Le modèle est désormais régulièrement mis-à-jour à partir des données retournées par la méthode
getFriendsTimeline(). La prochaine étape consiste donc à rafraichir la fenêtre avec ces nouvelles données:

Créez ensuite un nouveau formulaire JPanel que vous nommerez
Item. ajoutez-y un label et un text pane pour décrire les informations que nous afficherons à partir du modèle. Basculez dans le mode source pour que la classe Item implante l'interface ListCellRenderer. et ajouter le code suivant dans le corps de la méthode getListCellRendererComponent():

StatusType st = (StatusType) value;
jTextPane1.setText(st.getText());
jLabel1.setText("<html>" + st.getUser().getScreenName() + "</html>");
return this;


Revenez sur la fenêtre principale de votre client Twitter et spécifiez que le modèle de votre JList est le code statusesListModel, spécifiez également que le cellRenderer à utiliser est un "new Item()".

Vous pouvez de nouveau lancer votre application, les statuts doivent désormais se mettre à jour automatiquement.