Etude de cas PLM Patrice TORGUET IRIT Université Paul Sabatier Plan Exemple PLM Répartition avec Sockets Répartition avec RMI Répartition avec CORBA Répartition avec JMS Répartition avec Java EE Améliorations Exemple orienté PLM Base de donnée très simple permettant de gérer un ensemble de produits avec plusieurs versions et des fichiers CAO (CatProduct) pour chaque version But de l’étude de cas : rendre accessible cette base de donnée via plusieurs technologies réseaux 2 projets Netbeans téléchargeables ici : http://torguet.net/cours/PLM/ Exemple orienté PLM Exemple orienté PLM Utilisation de l’API JDBC : Java DataBase Connectivity Package java.sql et utilisation du SGBD H2 PLMJDBC.java Méthodes boolean creerVersion(String nom, String nomFichier) boolean changerFichier(String nom, int version, String nomFichier) String dernierFichier(String nom) ArrayList<Historique> historique(String nom) Sockets Application Client/Serveur multi-protocole transport TCP/UDP Quelque soit le protocole le traitement est le même Les résultats sont partagés (si on fait une modif en TCP, elle est visible en UDP et vice versa) Fonctionnement Client TCP 1 Sock Client Sock Ecoute Sock Service Client TCP 2 Client UDP 1 Th Ecoute Th1 Sock DG Th2 Serveur multi-protocole Th DG Client UDP 2 ProtoPLM But : traiter les requêtes indépendamment du protocole transport (TCP ou UDP) Utilise PLMJDBC En fonction des types de requêtes appelle la bonne méthode de PLMJDBC Méthode synchronized pour les threads ProtoPLM Le serveur implante les requêtes suivantes : CREER nom nomFichier crée une nouvelle version du produit en précisant le nom du fichier CHANGER nom version nomFichier remplace le nom du fichier pour une version actuelle du produit RECUP_DERNIER nom demande le nom du fichier de la dernière version du produit RECUP_HISTO nom demande un historique de toutes les versions du produit Gestion Proto Le client recevra les réponses du serveur sous la forme suivante : OK commande informe le client que la commande s'est correctement déroulée. ERREUR raison la raison de l'échec de la commande sous forme de chaîne de caractères. DERNIER <nomFichier> envoie au client le nom du fichier de la dernière version HISTO historique envoie au client une chaîne de caractère résumant l’historique du produit Serveur Version gérant TCP et UDP Partage d’un objet ProtoPLM Modif TCP visible dans UDP et vice-versa Tous les threads TCP utilisent le même objet ProtoPLM Partie UDP gérée par un thread Serveur Version TCP en multi-thread Socket d’écoute sur le port 1234 (même port que UDP) Boucle infinie faisant des accept + création de thread Threads gérant chacun un socket de service Tous les threads partagent le même ProtoPLM Serveur Thread service TCP Attention : méthode run() sans paramètre. On doit travailler avec le socket de service et le ProtoPLM Récupération du socket de service (un par thread) Et du ProtoPLM (partagé par tous les threads – d’où le synchronized) dans le constructeur On recopie les références dans des attributs pour pouvoir les utiliser dans la méthode run() Serveur Thread service TCP Méthode run() Construit des flux de haut niveau permettant la lecture et l’écriture de String (requête et réponse) Lecture : BufferedReader Reader alors que les sockets renvoient un InputStream (uniquement lecture d’octets) Utilisation de InputStreamReader qui transforme un InputStream en Reader BufferedReader permet de faire des readLine() Ecriture : PrintStream C’est le type de System.out PrintStream permet de faire des println() Appel de la méthode traiter pour “transformer” une requête en réponse Serveur Thread gérant UDP Constructeur qui récupère le ProtoPLM (partagé avec la partie TCP) On recopie la référence dans un attribut pour l’utiliser dans la méthode run() Serveur Thread gérant UDP DatagramSocket utilisant le même numéro de port que la partie TCP (il y a autant de ports TCP que de ports UDP) DatagramPacket utilisant un tampon de 1024 octets (suffisamment long pour contenir requêtes et réponses a priori mais attention à l’historique) On reçoit un datagramme UDP Les données vont dans le tampon, l’adresse IP et le port distants dans des attributs (getAddress() et getPort()) On construit une String à partir de la partie du tampon correspondant à la réception (de 0 jusqu’à getLength() ) On appelle la méthode traiter (comme pour TCP) On transforme la réponse en tableau d’octets On place ce tableau d’octet dans le DatagramPacket reçu On renvoie à l’expéditeur (son addresse et son port sont dans les attributs) Client TCP Créer un socket client en précisant le nom de la machine serveur (ou son adresse IP) et le numéro de port Construire des flux de haut niveau (BufferedReader et PrintStream) comme côté serveur Envoyer une requête (ici en dur mais on pourrait demander à l’utilisateur de la taper, ou faire un menu texte, une interface graphique...) Attendre la réponse puis l’afficher Puis fermer la connexion (on aurait pû envoyer plusieurs requêtes avant) Client UDP On crée un socket datagramme en laissant le système choisir le port local On récupère l’adresse IP du serveur On construit un DatagramPacket qui va porter la requête (ici en dur mais...) en précisant l’adresse IP et le port du serveur On envoit le DatagramPacket au serveur On construit un DatagramPacket pour recevoir la réponse (avec un tampon suffisamment grand) On attend la réponse On reconstruit une String avec la partie du tampon qui correspond à ce qu’on a reçu On affiche la chaîne de caractères RMI RMI RMI Interface PLM dérivant de java.rmi.Remote Reprend les méthodes de PLMJDBC et les rend accessibles à distance Ne pas oublier les “throws RemoteException” qui sont générés côté client en cas de problème dû à RMI RMI Classe d’implantation Dérive de la classe UnicastRemoteObject (gestion des références distantes : souche et squellette) Implante l’interface précédente Méthodes qui délèguent au méthodes identiques de PLMJDBC RMI Programme principal de mise en place Création du RMI Registry sur le port standard : 1099 Création d’un objet serveur et nommage RMI Programme client Recherche de la souche dans le RMI Registry distant Transtypage vers le type de l’interface (on ne connait pas le type réel de l’objet souche) Appels de méthodes distantes comme si c’était des méthodes locales RMI Attention à la sérialisation ! Tous les objets doivent pouvoir être sérialisés String, Date et ArrayList le sont en standard Historique doit être déclaré comme “implements Serializable” CORBA Contrat IDL Reprise du type Historique comme une struct IDL Type sequence correspondant à notre ArrayList Ajout d’une exception (problème des null qui ne sont pas portables) Interface correspondant à l’interface RMI Génération des classes Java CORBA Création du servant Ici approche par héritage de PLMPOA Utilisation d’un PLMJDBC Méthodes déléguant au PLMJDBC Attention aux null, on doit à la place lancerdes exceptions Attention à Historique : on doit utiliser la classe générée. Donc on recrée l’historique complet en tant que tableau. CORBA Serveur Utilisation de l’ORB de Java ou de Visibroker Pour l’ORB Java on peut vouloir démarrer le NameService (ORBD) automatiquement CORBA Etapes de mise en place côté Serveur Initialisation de l’ORB Récupération du POA Racine Création servant Activation de l’objet dans le POA Activation du POA Gestion du nom et de l’IOR Attente de la fin de l’exécution de l’ORB CORBA Etapes de mise en place côté Client Initialisation de l’ORB Gestion du nom et de l’IOR Transtypage vers le type de l’interface générée Appel des méthodes distantes au travers de l’ORB JMS Utilisation d’OpenJMS Implémentation libre et assez facile à mettre en place Outil d’administration permettant de créer des outils de communication (Files ou Sujets de discussion) Ici on va créer une file PLM qui servira pour faire passer les messages requêtes du client au serveur JMS Programme Serveur Création d’un InitialContext JNDI à partir de propriétés permettant de préciser la classe de la factory et la machine et le port du JMSProvider Recherche de la file et de la Connection Factory Création de la connexion, de la session, d’un consommateur de message utilisant la file Création d’un producteur de message ne précisant pas la file utilisée (passage par des files temporaires) JMS Programme Serveur (suite) Utilisation d’un PLMJDBC Boucle d’attente/réception de message Transtypage vers MapMessage (utilisé pour les requêtes par le client) Récupération du JMSType des messages et des paramètres Appel de la bonne méthode du PLMJDBC Création de la réponse et envoie via la file temporaire précisée en JMSReplyTo Note : on peut envoyer un ArrayList<Historique> dans un ObjectMessage grâce à la sérialisation JMS Programme Client Création d’un InitialContext JNDI à partir de propriétés permettant de préciser la classe de la factory et la machine et le port du JMSProvider Recherche de la file nommée et de la Connection Factory Création de la connexion, de la session Création d’une file temporaire pour recevoir les réponses Création d’un consommateur de message utilisant la file temporaire Création d’un producteur de message utilisant la file nommée JMS Programme Client (suite) Création des requêtes de type MapMessage précisant le bon JMSType et les paramètres Ajout de la file temporaire en JMSReplyTo Envoi du message et attente de la réponse Extraction des informations dans la réponse après transtypages On peut récupérer n’importe quel objet sérialisable via un ObjectMessage Java EE Création d’un projet de type Application d’Entreprise avec Netbeans Création d’un sous-projet EJB (on peut aussi avec des projets Web liés pour le client par exemple) 2 types d’EJB utilisés ici Entité : ORM Session : Métier Un Web Service Java EE EJB Entité créé automatiquement depuis une base de donnée Utilisation de JavaDB (derby) déjà inclu avec Netbeans Entreprise Création base de donnée Exécution script SQL Configuration dans le serveur d’entreprise Glassfish Java EE Code généré qui gère l’association entre les objets et le modèle relationnel Attributs correspondant aux colonnes de la table Génération de requêtes nommées Ajout d’une requête nommée (Plm.findByNomVersion) pour sélectionner les lignes en précisant le nom du produit et la version d’un produit Java EE EJB Session de type Stateless avec une interface Locale (pourrait être Remote) Code métier de l’application utilisant l’EJB entity et un EntityManager pour la persistence Injection de ressources : ici l’EntityManager créé et géré par le conteneur d’EJBs Java EE Le code métier utilise les requêtes nommées et des requêtes supplémentaires en précisant les paramètres Pour simplifier on renvoit une chaine de caractères pour l’historique (on aurait pû renvoyer du texte formatté en JSON pour une utilisation facilité dans un client JavaScript) Java EE NetBeans permet de générer automatiquement un Web Service depuis un EJB Session en rendant ses méthodes accessibles à distance On pourra noter ici aussi de l’injection de ressource pour lier le WS avec l’EJB Session Java EE Configuration de Glassfish Démarrage de JavaDB (onglet Services) Création base de donnée “PLM” sur JavaDB Exécution du script ...\DUPLM2\PLMdb.sql Démarrage de Glassfish Lancement Console d’administration de Glassfish Java EE Configuration de Glassfish Création d’un Connexion Pool JDBC Nommé par exemple PLMPool Resource Type : javax.sql.DataSource Database Driver Vendor : Derby Next Additional Properties DatabaseName : PLM Password : APP Java EE Configuration de Glassfish Création d’une Ressource JDBC Nommée jdbc/PLM Pool Name : PLMPool Déployer l’application en cliquant sur “Run” après un clic droit sur le projet DUPLM2-ejb Tester le WS en cliquant sur “Test Web Service” après un clic droit sur le Web Service (dans le dossier Web Services du projet) Améliorations de l’application On se propose d’ajouter les améliorations suivantes : A chaque version d’un produit est associé un responsable ; Un produit est constitué d’éléments qui sont eux-mêmes des produits ; A un produit correspond un nombre quelconque d’instances dont certaines sont vendues et pour lesquelles on doit associer un client et des informations d’usure pour la maintenance Améliorations de l’application Choisissez une version de l’application et implantez ces diverses améliorations en suivant la méthodologie suivante : Rétro-conception de l’application avec UML Conception de l’amélioration avec UML Codage, déploiement et tests de l’amélioration