UP | HOME

TP3 - Partie 2 - Une bibliothèque - Mobilité des canaux - Vers l'approche réactive

Table of Contents

1 Introduction - Un portail comme réservoir de liens

Dans cette seconde partie, on considère deux sortes de bibliothèques :

  • des archives, qui stockent des livres,
  • des portails, qui référencent des archives.

Ces bibliothèques ne présentent pas les mêmes services. Toutes permettent la recherche d'un livre et l'obtention d'un catalogue, mais seules les archives permettent d'ajouter un livre ou d'interroger un livre. Un portail manipule non seulement les livres mais aussi les archives comme des hyperliens.

Après l'implémentation des archives dans la première partie de ce TP, cette seconde partie s'intéresse à l'implémentation des portails. Le but est de proposer plusieurs implémentations des méthodes de recherche, en recourant au parallélisme et à la communication asynchrone. Par parallélisme, on entend que plusieurs fils d'exécution sont possibles simultanément ; par communication asynchrone, on entend que l'appel d'un service n'est pas bloquant, autrement dit, que la latence apparente (temps logique écoulé entre la requête et la réponse) n'est pas nulle, du point de vue du client. Ces notions s'opposent respectivement à la séquentialité, caractérisée par l'unicité du fil d'exécution, et à la communication synchrone, caractérisée par une latence apparente nulle (du point de vue du client). Lorsque l'exécution est purement parallèle, il ne peut pas y avoir de blocage, si bien que la communication peut être considérée comme asynchrone.

2 Portail : les règles chimiques

On s'intéresse au serveur et à la seule méthode de recherche d'un livre, pour aller à l'essentiel. On distingue les différents cas, suivant la nature de la communication et celle de l'exécution.

2.1 Serveur portail : canaux et état

Pour simplifier, on s'intéresse à la description d'une unique session, correspondant à une seule requête.

Canaux (décrits par une méthode HTTP et un chemin, accompagnés de leurs arguments) :

  • recherche(livre, canalDeRetour) : recherche du livre livre renvoyant un canal pointant vers le livre ou une erreur (notée KO)
  • ret[h] : canal de retour utilisé pour la recherche sur une archive, indexé par le canal h utilisé pour la requête

Etat du serveur :

  • Liens(archives) : liste des canaux pointant vers le service de recherche des archives
  • Diffusion(archives, livre, canalDeRetour) : diffusion de la recherche de l aux archives restantes, canalDeRetour étant le canal à utiliser pour la réponse finale
  • Attente(h, rep) : attente de la réponse de la requête sur le canal h, canalDeRetour étant le canal à utiliser pour la réponse finale

Type de données : listes fonctionnelles (pour représenter la liste de canaux pointant vers les archives)

  • vide : liste vide
  • h::ar : liste de tête h et de reste ar

2.2 Règles chimiques

// 1. Réception d'une requête et initialisation des diffusions
Liens(ar) & recherche(l, rep) -> Liens(ar) & Diffusion(ar, l, rep)
// 2. Portail non vide
// 2.1 Diffusion des requêtes
Diffusion(h::ar, l, rep) 
  -> h(l, ret[h]) & Attente(h, rep) & Diffusion(ar, l, rep)
// 2.2 Réponses
// 2.2.1 Réponse positive  
Attente(h, rep) & ret[h](url) -> rep(url)
// 2.2.2 Réponse négative 
Attente(h, rep) & ret[h](KO) -> 
// 3. Portail vide ou diffusion terminée sans succès
Diffusion(vide, l, rep) & (Attente(_, _) inactive) -> rep(KO)

Le modèle chimique correspond à une communication asynchrone ou synchrone point-à-point (mais pas agent-à-agent) et une exécution parallèle.

  • Exécution séquentielle

Lorsque l'exécution est séquentielle, les règles s'effectuent dans un ordre prédéterminé.

Communication synchrone : chaque requête 2.1 est suivie par la réponse associée 2.2., soit 2.2.1 ou 2.2.2.

  • 1 ; (2.1 ; 2.2)* ; 3.

Communication asynchrone : les requêtes 2.1 sont effectuées puis les réponses 2.2 sont traitées.

  • 1 ; 2.1* ; 2.2* ; 3

2.3 Comparaison - Méthodes d'implémentation

  • Communication asynchrone

La communication asynchrone (sans blocage côté client) se fait traditionnellement de deux manières :

  • soit la requête renvoie immédiatement une promesse (de type Future), qui sera réalisée lorsque la réponse arrivera du serveur,
  • soit la requête ne renvoie rien mais à la place enregistre une fonction de rappel de type InvocationCallBack, qui sera appelée lorsque la réponse arrivera du serveur, dans une tâche dédiée.
  • Complexité en temps

Le temps total d'exécution d'une recherche (soit la complexité en temps) dépend des latences des canaux (délais entre les requêtes et les réponses), et aussi bien sûr des durées propres à chaque calcul local. Supposons qu'on ait \(n\) canaux. On note \((L_i)_i\) les latences relatives aux canaux indexés par \(i\) (des entiers naturels pour simplifier). Dans les évaluations ci-dessous des temps d'exécution, on néglige la durée de tout calcul local. On suppose que la recherche se termine par la réponse positive du canal \(k\).

  • Exécution séquentielle

Dans le cas d'une exécution séquentielle, on procède par une itération sur la liste des canaux pointant vers les archives, simple dans le cas synchrone, double dans le cas asynchrone. Dans le cas synchrone, pour chaque recherche sur un canal, on doit attendre la réponse tout en étant bloqué. Dans le cas asynchrone, on évite ce blocage, si bien qu'on doit attendre une seule fois, lors de la transition entre la première itération dédiée aux requêtes et la seconde dédiée aux réponses.

  • Cas synchrone : \(\Sigma_{0 \leq i \leq k} L_i\)
  • Cas asynchrone : \(\mathrm{Max}_{0 \leq i \leq k} L_i\)
  • Exécution parallèle

Pour obtenir une exécution parallèle, on utilise un programme multi-tâche. La méthode traditionnelle est de lancer autant de tâches que de requêtes ; dans le cas asynchrone, on peut plus simplement itérer sur les requêtes, en enregistrant les fonctions de rappel qui seront exécutées dans des tâches dédiées. Une fois les réponses obtenues, il est nécessaire de les coordonner pour produire le résultat final. On peut par exemple utiliser une barrière de synchronisation de type CountDownLatch. Le temps d'exécution peut dépendre de l'ordonnancement des tâches, celles dédiées aux réponses et celle principale produisant le résultat. Ainsi nous proposons un intervalle pour le temps d'exécution.

  • Cas synchrone : de \(L_k\) à \(\mathrm{Max}_{0 \leq i \leq k} L_i\)
  • Cas asynchrone : de \(L_k\) à \(\mathrm{Max}_{0 \leq i \leq k} L_i\)
  • Conclusions

Dans le cas d'une exécution séquentielle, la communication asynchrone doit être utilisée puisqu'elle permet de modifier la complexité (temps constant contre temps linéaire). Le parallélisme est une optimisation qui ne modifie pas la classe de complexité mais accélère l'exécution (dans le meilleur des cas, avec un ordonnancement efficace). Elle ne dépend pas de la nature de la communication, synchrone ou asynchrone.

  • Une méthode moderne d'implémentation

Une méthode moderne est d'abstraire la solution chimique (formée de l'état et des messages reçus ou à envoyer) par un observable : un observable possède un état et peut engendrer des événements, correspondant à des messages envoyés ou reçus ou à des changements d'états. Un observable est habituellement implémenté par un flot, une séquence de taille indéfinie (finie ou infinie), représenté

On remarque que les règles chimiques correspondent fréquemment à des opérations classiques sur les flots : application d'une fonction (map), filtrage, réduction, etc. On peut aussi étendre un flot par des fonctionnalités de souscription, qui permet d'observer les événements engendrés par le flot. C'est le choix fait par la bibliothèque ReactiveX. Pratiquement, dans le domaine des services, c'est la bibliothèque ReaciveX qui est utilisée, puisqu'elle est parfaitement adaptée à ce contexte en permettant de programmer des applications fondées sur la communication asynchrone et la gestion d'événements.

Reprenons les règles chimiques en précisant les opérations à réaliser sur le flot.

// 1. Réception d'une requête et initialisation des diffusions
- Liens(ar) & recherche(l, rep) -> Liens(ar) & Diffusion(ar, l, rep)
- Flot initial : archives (liste de canaux)

// 2. Portail non vide
// 2.1 Diffusion des requêtes
- Diffusion(h::ar, l, rep) 
    -> h(l, ret[h]) & Attente(h, rep) & Diffusion(ar, l, rep)
- Opération : map(h -> h(l, ret[h]))
  // La communication transforme h(l, ret[h]) en ret[h](x).
- Opération : map(ret[_](url) -> url)

// 2.2 Réponses
  // 2.2.1 Réponse positive  
  - Attente(h, rep) & ret[h](url) -> rep(url)
  // 2.2.2 Réponse négative 
  - Attente(h, rep) & ret[h](KO) -> 
- Opération : filter(url -> (url != KO))

// 3. Portail vide ou diffusion terminée sans succès
- Diffusion(vide, l, rep) & (Attente(_, _) inactive) -> rep(KO)
- Opération : firstOrDefault(KO) 
  // Première url reçue (2.2.1) ou KO par défaut (3)

On peut ainsi décrire les règles chimiques par des transformations du flot représentant la solution initiale.

3 Implémentation en Java

3.1 Le modèle Objet

Le modèle Objet, fourni, contient les interfaces principales :

  • Bibliothèque, décrivant les services de recherche et de production de catalogue,
  • Archive, décrivant les sous-ressources livresques d'une bibliothèque,
  • BibliothequeArchive, héritière de Bibliotheque et Archive, pour décrire les bibliothèques contenant des livres,
  • AdminAlgo, interface d'administration permettant de choisir l'algorithme de recherche dans les bibliothèques archives répertoriées par un portail,
  • Portail, héritière de Bibliothèque et AdminAlgo, pour décrire les portails répertoriant des bibliothèques archives,
  • Livre, décrivant le type de données représentant les livres de la bibliothèque,
  • LivreRessource, extension de l'interface Livre, correspondant aux sous-ressources de la bibliothèque,
  • IdentifiantLivre, décrivant le type de données permettant d'identifier les livres,
  • AlgorithmeRecherche et AlgorithmeNomme pour représenter les algorithmes de recherche et leur nom, utilisé pour leur administration.

Les classes d'implémentation suivantes sont également fournies :

  • ImplemAlgorithmeNomme,
  • ImplemIdentifiantLivre,
  • ImplemLivre,
  • ImplemLivreRessource.

Une première classe d'implémentation, ImplemBibliotheque, de l'interface BibliothequeArchive, est aussi fournie : elle correspond à la première partie du TP. Le but de cette seconde partie est d'implémenter l'interface Portail, pour obtenir le schéma suivant. Cette classe d'implémentation, appelée ImplemPortail, utilise le patron de conception "Stratégie", qui permet de changer d'algorithme de recherche. Le but de cette seconde partie est aussi de développer huit implémentations de la recherche d'un livre dans des bibliothèques archives distantes :

  • communication synchrone ou asynchrone,
  • quatre méthodes d'implémentation :
    • séquentiel,
    • multi-tâche classique,
    • flots avec des streams de Java 8,
    • flots avec des observables de ReactiveX.

Voici un schéma récapitulatif.

Enfin, reprises de la première partie, deux classes génériques sont fournies pour représenter les hyperliens :

  • Hyperlien<T> : hyperlien vers une ressource de type T,
  • Hyperliens<T> : liste d'hyperliens vers des ressources de type T.

Le type T ne sert qu'au typage. Ces classes sont des variantes de la classe abstraite Link, qui sert à représenter des hyperliens dans JAX-RS 2.0. En les faisant dépendre d'un type, elles fournissent une information utile, le type de la ressource vers laquelle l'hyperlien pointe.

3.2 Serveur

Créer un nouveau projet, de type Dynamic Web Projet.

  • Ajouter les dépendances suivantes dans le fichier pom.xml. On importe les bibliothèques de Jersey à la fois côté serveur et client (du fait que le portail est aussi client des archives) et la bibliothèque ReactiveX.

    <dependency>
       <groupId>org.glassfish.jersey.containers</groupId>
       <artifactId>jersey-container-servlet</artifactId>
       <version>2.23.2</version>
       <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>org.glassfish.jersey.media</groupId>
       <artifactId>jersey-media-json-jackson</artifactId>
       <version>2.23.2</version>
       <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>org.glassfish.jersey.containers</groupId>
       <artifactId>jersey-container-grizzly2-http</artifactId>
       <version>2.23.2</version>
       <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>org.glassfish.jersey.ext</groupId>
       <artifactId>jersey-proxy-client</artifactId>
       <version>2.23.2</version>
       <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>org.glassfish.jersey.core</groupId>
       <artifactId>jersey-client</artifactId>
       <version>2.23.2</version>
       <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>io.reactivex</groupId>
       <artifactId>rxjava</artifactId>
       <version>1.2.9</version>
       <scope>compile</scope>
     </dependency>
    
  • Dans le fichier WebContent/WEB-INF/web.xml, ajouter après l'élément welcome-file-list la référence de la servlet à utiliser.

    <servlet>
      <servlet-name>Jersey</servlet-name>
      <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    </servlet>
    
    <servlet-mapping>
       <servlet-name>Jersey</servlet-name>
       <url-pattern>/*</url-pattern>
    </servlet-mapping>
    

Importer l'archive à partir de la racine.

Cinq paquets sont importés :

  • modele : le modèle objet,
  • infrastructure : du code lié à JAXB ou JAX-RS,
  • configuration : l'ensemble des configurations, en particulier toutes les constantes utilisées pour configurer le service web (classe JAXRS),
  • serveur : programme permettant de déployer dix bibliothèques archives, en utilisant le serveur Grizzly.

3.2.1 Annotations JAX-RS des interfaces AdminAlgo et Bibliotheque

Annoter la méthode algorithmeRecherche de l'interface AdminAlgo de manière à vérifier les propriétés suivantes. On utilisera les constantes de la classe configuration.JAXRS.

  • La méthode est qualifiée par le type idoine de méthode HTTP.
  • Le chemins relatif d'accès est admin/recherche.
  • Les données sont produites ou consommées au format application/xml.

Annoter la méthode chercherAsynchrone de l'interface Bibliotheque de manière à vérifier les propriétés suivantes.

  • La méthode est qualifiée par le type idoine de méthode HTTP.
  • Le chemins relatif d'accès est async?titre=….
  • Les données sont produites ou consommées au format application/xml.
  • La méthode utilise le filtre associé à @ReponsesNull404GET.
  • Le paramètre supplémentaire de type AsyncResponse, dont la valeur sera fournie par l'implémentation de JAX-RS lors de chaque invocation de la méthode, représente la réponse. Il doit être annoté par @Suspended, du paquet javax.ws.rs.container.

Cette dernière méthode correspond à une communication asynchrone. Pour définir une méthode utilisant une communication asynchrone, on procède donc ainsi :

  • ajout d'un paramètre supplémentaire de type AsyncResponse,
  • annotation par @Suspended,
  • pas de type de retour (void) ou type de retour fictif, ne servant que côté client.

Avec une telle méthode, côté client, l'appel distant n'est pas bloquant mais renvoie une promesse, soit une valeur de type de Future<X>, si la réponse est initialisée côté serveur avec une valeur de type X. Côté serveur, l'invocation renvoie immédiatement le canal utilisé pour la réponse, qui sera inclus dans la promesse, puis réalise le calcul associé à la méthode pour produire une réponse via le canal renvoyé. Nous avons fait le choix de typer la méthode de manière à ce qu'elle renvoie une valeur de type Future<HyperLien<LivreRessource>>. De fait, ce sera la valeur null, puisque la réponse est transmise par l'argument de type AsyncResponse inséré automatiquement. C'est simplement une indication de typage. La solution retenue par la norme JAX-RS, celle d'un paramètre supplémentaire typé simplement, n'est pas idéale.

3.2.2 Annotations JAXB

Annoter la classe d'implémentation ImplemAlgorithmeNomme et l'interface AlgorithmeNomme de manière à ce qu'un objet de type ImplemAlgorithmeNomme soit transformé en un document XML de la forme suivante.

<algo nom="..."/>

3.2.3 Filtrage des réponses

Lorsque le portail interroge une archive pour chercher un livre, il peut recevoir une réponse de statut 404, ce qui signifie que la recherche a été infructueuse. A réception, une telle réponse déclencherait une exception. Plutôt que traiter des exceptions, on préfère convertir les réponses de statut 404 en la valeur null, en utilisant un filtre et un intercepteur, côté client (autrement dit portail, dans ce contexte).

Définir dans le paquet infrastructure.jaxrs.annotations une annotation Reponses404Null. S'inspirer des annotations existantes.

Remarque : l'annotation ne sert pas car nous avons choisi de configurer dynamiquement le client utilisé (cf. configuration.Orchestrateur.clientJAXRS) plutôt que d'annoter des méthodes de l'interface côté client (autrement dit, côté portail).

Dans le paquet infrastructure.jaxrs, définir la classe AdapterReponses404Null implémentant ClientResponseFilter et ReaderInterceptor.

  • L'annoter par @Provider et @Reponses404Null.
  • Lui donner pour priorité Priorities.HEADER_DECORATOR.
  • Dans la méthode filter, s'il s'agit d'une réponse de type 404, ajouter un en-tête Pragma : ERREUR404 et mettre le statut à OK (constante de Response.Status).
  • Dans la méthode aroundReadFrom(reponse), si la réponse possède l'en-tête Pragma : ERREUR404, alors renvoyer null sinon renvoyer reponse.proceed(), ce qui a pour effet d'appeler la méthode de traduction (unmarshalling).

Pour pouvoir utiliser ce filtre lorsque le portail joue le rôle de client (ou d'orchestrateur), enregistrer une instance de la classe AdapterReponses404Null dans la configuration définie dans configuration.Orchestrateur.clientJAXRS.

3.2.4 Implémentation du portail

Définir la classe modele.ImplemPortail en suivant les instructions suivantes.

  • C'est un singleton.
  • Cette ressource est accessible par le chemin relatif portail (constante définie dans la classe JAXRS).
  • Elle implémente Portail mais aussi RechercheAsynchroneDeclenchantRechercheSynchrone, pour hériter de l'implémentation de chercherAsynchrone (qu'on étudiera).
  • Son état est formé
    • d'une table de type ConcurrentMap<AlgorithmeNomme, AlgorithmeRecherche>,
    • d'un client de type javax.ws.rs.client.Client,
    • d'un algorithme de recherche,
    • d'une liste d'hyperliens vers des bibliothèques archives.
  • Son constructeur initialise ses différents champs ainsi :
    • une table de hachage, initialement vide,
    • un client de valeur Orchestrateur.clientJAXRS(),
    • un algorithme de recherche valant null,
    • d'une liste d'hyperliens valant Initialisation.bibliotheques().
  • La méthode chercher redirige l'appel vers la méthode chercher de l'algorithme de recherche.
  • La méthode repertorier réalise la somme des catalogues des archives pointées par la liste. Pour récupérer un proxy, on utilisera la méthode LienVersRessource.proxy. Pour sommer des listes d'hyperliens (de la classe HyperLiens<LivreRessource>), on utilisera la fonction Outils.sommeHyperLiens.
  • La méthode algorithmeRecherche(nom) procède ainsi : si la table associe à nom un algorithme de recherche, alors celui-ci devient le nouvel algorithme de recherche, sinon l'algorithme courant est conservé.

Ajouter une fonction technique utilisée pour typer correctement les méthodes asynchrones des proxies (voir plus bas la méthode RechercheAsynchroneAbstraite.rechercheAsync).

public static GenericType<HyperLien<LivreRessource>> typeRetourChercherAsync(){
  return typeRetourChercherAsync;
}
private static GenericType<HyperLien<LivreRessource>>  typeRetourChercherAsync;
static {
  Method m = null;
  try {
    m = ImplemPortail.class.getDeclaredMethod("chercher", Livre.class);
  } catch (SecurityException e) {
    e.printStackTrace();
  } catch (NoSuchMethodException e) {
    e.printStackTrace();
  }
  typeRetourChercherAsync = new GenericType<HyperLien<LivreRessource>>(m.getGenericReturnType());
}

Enregistrer la classe Portail dans le constructeur de configuration.ServicePortail.

Enfin, à chaque fois qu'un nouvel algorithme de recherche est créé, il faudra l'ajouter à la table et au moins une fois initialiser l'algorithme de recherche dans le constructeur.

Exemple

// Initialisation requise au moins une fois
algorRecherche = new RechercheY();

// ajout dans la table, algo étant une variable locale
algo = new RechercheX();
table.put(algo.nom(), algo);

3.2.5 Implémentation des algorithmes de recherche

On suit le schéma récapitulatif, en commençant par les classes abstraites puis par les huit classes concrètes. Toutes ces classes implémentent l'interface AlgorithmeRecherche. Pour pouvoir déterminer la tâche ("thread") en cours d'exécution, on pourra utiliser la fonction Outils.afficherInfoTache.

3.2.5.1 Classe abstraite RechercheSynchroneAbstraite

Implémenter la méthode suivante, réalisant une recherche synchrone dans la bibliothèque (archive) passée en argument.

protected HyperLien<LivreRessource> rechercheSync(Bibliotheque bib, Livre l);
3.2.5.2 Classe abstraite RechercheAsynchroneAbstraite

Implémenter les méthodes suivantes, réalisant une recherche asynchrone dans la bibliothèque pointée par le lien. La première utilise une promesse, la seconde une fonction de rappel.

protected Future<HyperLien<LivreRessource>> rechercheAsync(
  HyperLien<BibliothequeArchive> h, Livre l, Client client);
protected Future<HyperLien<LivreRessource>> rechercheAsyncAvecRappel(
  HyperLien<BibliothequeArchive> h, Livre l, Client client,  
  InvocationCallback<HyperLien<LivreRessource>> retour);
3.2.5.3 Cas synchrone

Implémenter les quatre algorithmes en suivant les indications suivantes. Tous héritent de RechercheSynchroneAbstraite.

  • Cas séquentiel : classe RechercheSynchroneSequentielle
    • Nom : "recherche sync seq"
    • Une itération sur les hyperliens
  • Cas multi-tâche : classe RechercheSynchroneMultiTaches
    • Nom : "recherche sync multi"
    • Une itération sur les hyperliens
      • A chaque étape, créer une tâche dédiée à la recherche dans la bibliothèque pointée par l'hyperlien en utilisant un ExecutorService, initialisé par Executors.newCachedThreadPool() (du paquet java.util.concurrent).
      • Utiliser une barrière de synchronisation de type CountDownLatch : dans la tâche dédiée, utiliser la méthode countDown, dans la tâche principale (celle de la méthode), utiliser await. Dans le cas d'une réponse positive, on peut passer la barrière directement.
  • Cas stream Java 8 : RechercheSynchroneStreamParallele
    • Nom : "recherche sync stream 8"
    • Une définition d'un flot suivi d'une réduction à un hyperlien vers un livre ou à null
      • Partir de la liste d'hyperliens vers des bibliothèques archives.
      • Créer un flot parallèle.
      • Appliquer une fonction permettant de récupérer un proxy (en utilisant LienVersRessource.proxy).
      • Appliquer la fonction de recherche synchrone.
      • Retirer les valeurs null.
      • Réduire à n'importe quelle valeur ou à défaut à null.
  • Cas stream ReactiveX : RechercheSynchroneStreamRx
    • Nom : "recherche sync stream rx"
    • Une définition d'un flot suivi d'une réduction à un hyperlien vers un livre ou à null
      • Partir d'un observable créé à partir de la liste d'hyperliens.
      • Appliquer une fonction permettant de récupérer un proxy (en utilisant LienVersRessource.proxy).
      • Appliquer la fonction (p -> Observable.fromCallable(() -> rechercheSync(p, l))) puis linéariser (ordonner suivant le temps). Indication : voir flatMap.
      • Pour une exécution multi-tâche, ajouter un appel à subscribeOn(Schedulers.io()) après l'appel à fromCallable.
      • Retirer les valeurs null.
      • Prendre la première valeur ou à défaut null.
      • Pour produire la valeur finale, utiliser un appel à toBlocking() puis single(). Pour observer un observable, soit on utilise une souscription, soit on transforme l'observable en BlockingObservable.
3.2.5.4 Cas asynchrone

Implémenter les quatre algorithmes en suivant les indications suivantes. Tous héritent de RechercheAsynchroneAbstraite.

  • Cas séquentiel : classe RechercheAsynchroneSequentielle
    • Nom : "recherche async seq"
    • Une première itération sur les hyperliens permettant d'obtenir une liste de promesses
    • Une seconde itération sur les promesses permettant de produire le résultat
  • Cas multi-tâche : classe RechercheAsynchroneMultiTaches
    • Nom : "recherche async multi"
    • Une itération sur les hyperliens
      • A chaque étape, créer une fonction de retour de type InvocationCallback<HyperLien<LivreRessource>> puis appeler la méthode de recherche asynchrone.
      • Utiliser une barrière de synchronisation de type CountDownLatch : dans fonction de retour, utiliser la méthode countDown, dans la tâche principale (celle de la méthode), utiliser await. Dans le cas d'une réponse positive, on peut passer la barrière directement.
  • Cas stream Java 8 : RechercheAsynchroneStreamParallele
    • Nom : "recherche async stream 8"
    • Une définition d'un flot suivi d'une réduction à un hyperlien vers un livre ou à null
      • Partir de la liste d'hyperliens vers des bibliothèques archives.
      • Créer un flot parallèle.
      • Appliquer la fonction de recherche asynchrone.
      • Appliquer la fonction Outils::remplirPromesse.
      • Retirer les valeurs null.
      • Réduire à n'importe quelle valeur ou à défaut à null.
  • Cas stream ReactiveX : RechercheAsynchroneStreamRx
    • Nom : "recherche async stream rx"
    • Une définition d'un flot suivi d'une réduction à un hyperlien vers un livre ou à null
      • Partir d'un observable créé à partir de la liste d'hyperliens.
      • Appliquer la fonction (h -> Observable.from(rechercheAsync(h, l, client))) puis linéariser (ordonner suivant le temps). Indication : voir flatMap.
      • Pour une exécution multi-tâche, ajouter un appel à subscribeOn(Schedulers.io()) après l'appel à from.
      • Retirer les valeurs null.
      • Prendre la première valeur ou à défaut null.
      • Pour produire la valeur finale, utiliser un appel à toBlocking() puis single(). Pour observer un observable, soit on utilise une souscription, soit on transforme l'observable en BlockingObservable.

3.2.6 Déploiement

Dans le fichier web.xml, sous l'élément servlet, référencer la définition du service dans la classe configuration.ServicePortail.

<init-param>
  <param-name>javax.ws.rs.Application</param-name>
  <param-value>configuration.ServicePortail</param-value>
</init-param>
<load-on-startup>1 </load-on-startup>
<async-supported>true</async-supported>

Etudier le code de cette classe. Cette configuration est propre à Jersey.

Bien noter l'usage de la balise async-supported permettant la communication asynchrone.

Déployer sur le serveur Tomcat le portail.

Lancer l'application ServeurLancement. Elle déploie dix bibliothèques archives comptant chacune dix livres, de titre ServicesX.Y (où X et Y varient entre \(0\) et \(9\)).

Tester votre application avec Poster.

Administration

Recherche

3.3 Client

Créer un nouveau projet Java. Réaliser un test automatique du portail.

  • Pour chaque algorithme, faire une ou plusieurs recherches, en mode synchrone et asynchrone. Mesurer le temps en utilisant System.nanoTime().
  • Expliquer les résultats dans un fichier readme.org. Bien noter que le test se fait sur une seule machine, avec plusieurs coeurs. Noter aussi que les réponses des bibliothèques archives sont retardées en cas de recherche infructueuse : voir la fonction Outils.patienter.
  • Reprendre les tests en répartissant l'application :
    • le portail sur sa propre machine,
    • les dix bibliothèques archives sur dix autres machines.
  • Expliquer les résultats dans un fichier readme.org.

3.4 Bonus : le catalogue

Développer une architecture analogue pour la méthode Bibliotheque.repertorier.

Author: Hervé Grall
Version history: v1: 2017-05-02.
Comments or questions: Send a mail.
The webpage content is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.