ISNet35 Rapport phase II Cadre du projet Ce projet à été déposé dans le centre de compétence Informatique de gestion et systèmes (ISNet) dans le cadre du programme de la réserve stratégique de la HES-SO en septembre 2001. Il est sous la responsabilité du Laboratoire de Technologie Objet de la Haute Ecole de Gestion de Genève. 1.1. Rappel des objectifs du projet Les objectifs du projet sont initialement définis ainsi : A partir des classes Java obtenues à l'issue d'un processus d'Analyse et de Conception • Concevoir un processus générique automatique d'implantation de la persistance d'objets Java dans une base de données relationnelle-objet, offrant en particulier les possibilités ¾ de gérer cette persistance de façon transparente pour le programme d'application Java, ¾ de permettre l'accès aux données représentées par les objets Java par des applications quelconques en préservant l'intégrité des données. • Elaborer une démarche méthodologique permettant de comparer le résultat de ce processus aux technologies existantes. • Réaliser et valider un prototype implantant effectivement ce processus générique. • Rédiger une publication qui sera soumise à une revue spécialisée. 1.2. Objectifs de la phase II La phase II, intitulée Réalisation et validation du prototype est définie initialement par les objectifs suivant : • Implanter effectivement le prototype décrit par le cahier des charges obtenu à l’issue de la première phase; • Valider l’outil obtenu en développant au moins deux applications métier types selon les deux processus de développement cités ; les applications seront à priori différentes exploiteront un ensemble de données commun représentées par les trois technologies (relationnelle pure, relationnelle-objet, vue relationnelle-objet) • Appliquer la démarche méthodologique d’évaluation ; • Produire un document de synthèse comprenant : ¾ Les résultats et conclusion de l’étude comparative ¾ Une description du processus de réalisation et de validation mis en œuvre 22.06.2005 1/22 1.3. Document Le présent document est le rapport de synthèse du travail du groupe de compétence ISNet-Ne de la Haute école Neuchâteloise en fin de phase II du projet, son contenu devra s’intégrer dans le cadre global du projet. Suite aux résultats de la phase I, l’activité de l’équipe neuchâteloise s’est principalement orientée sur la réalisation d’un prototype de génération de script de création d’objets persistant dans la base de données à partir de leurs spécifications contenues dans un fichier XMI. Le modèle de persistance retenu est le modèle relationnel-objet d’Oracle. Prototype de générateur SQL-DDL 1.4. Introduction Le prototype que nous avons réalisé permet de générer le script SQL-DDL (Data Defintion Language) pour une base de données relationnelle (le SGBD cible retenu lors de l'étape I est Oracle) à partir d'un fichier XMI contenant un modèle de domaine UML (seul les diagrammes de classes sont pris en considération). Le prototype est loin d'être une solution de production. Notre objectif était de monter la faisabilité d'une transformation XMI-UML vers SQL-DDL. Un certain nombre de choix ont été implémentés sans forcément utiliser la solution la plus propre. Par exemple, nous utilisons un stéréotype pour gérer les contraintes NOT NULL au lieu d'utiliser la multiplicité à disposition pour les attributs UML. Nous avons également simplifié certaine partie de la mise en correspondance afin de respecter les délais qui nous étaient initialement fixés. Par exemple, pour le moment, les associations doivent être navigable que dans un sens. De ce fait, l'analyse du fichier XMI s'en trouve facilitée puisque nous avons contourné le problème posé par les associations bi-directionnelles (graphe cyclique). Les critères du génie logiciel et les principes de la conception orientée objet n'ont pas forcément été respectés dans le cadre de ce travail. En effet, nous avons réutilisé quelques classes issues d'un autre travail sur la génération de code SQL-DDL qui n'étaient initialement pas prévues pour être intégrées à ce projet. De plus, nous avons privilégié la fonctionnalité sur la bonne odeur du code source (au sens XP du terme). Dans le cas d'une implémentation de production de notre prototype, un important travail de refactoring sera nécessaire pour obtenir une solution de qualité. 1.5. Parsing XML Dans le cadre du développement de ce prototype, nous avons utilisé l’API de Sun JAXP (Java API for XML Processing) pour effectuer le parsing des fichiers XMI. Nous avons retenu la méthode de parsing de type DOM (Document Object Model). Par manque de temps, nous n’avons pas pu étudier l’autre alternative orientée événements qui s’appelle SAX (Simple API for XML). De toute façon, il nous semble absolument évident qu’une version de production du générateur devrait être construite sur la base de la norme Xpath du W3C. Etant donné, les évolutions futures d’XMI (la version 2.0 d’UML devrait bientôt être publiée), il faut 22.06.2005 2/22 absolument minimiser la maintenance nécessaire concernant les changements liés à l’organisation des fichiers XMI sur laquelle nous n’avons aucune influence. Nous avons déjà connu de telles difficultés car Rational Rose supporte XMI en version 1.1 et Poseidon for UML en version 1.2. De ce fait notre générateur ne fonctionne qu’avec des fichiers XMI générés à partir de Rational Rose. L’avantage de JAXP est de permettre au développeur de ne pas utiliser de classes spécifiques à un parseur X ou Y mais de travailler de façon abstraite avec n’importe quel parseur. Le choix du parseur se fait au niveau du système et l’instanciation de celui-ci est délégué à la classe DocumentBuilderFactory dans le cas du DOM. 1.5.1. Parsing DOM La manipulation d’un fichier XML avec le DOM se fait au moyen des interfaces Document et Node. Pour rappel, le DOM propose une méthode d’accès au contenu d’un fichier XML de type arborescente. A partir du nœud racine, il est possible d’accéder aux nœuds enfants et ainsi de suite. L’interface Node disposent des méthodes suivantes : Un nœud est une entité polymorphe qui peut-être un ELEMENT, un ATTRIBUTE, ou encore du texte. La méthode getNodeType() permet de connaître le type exacte du nœud. Comme nous l’avons dit plus haut, en utilisant JAXP, le code Java est indépendant du parseur utilisé. De ce fait, il faut recourir à une fabrique pour l’instanciation de celui-ci. DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance(); Une fois la fabrique instanciée, il est possible de paramétrer celle-ci. Le paramétrage étant effectué, il ne reste plus qu’à demander la création d’une instance de parseur. Dans le contexte du DOM, un parseur s’appelle un DocumentBuilder. DocumentBuilder b = bf.newDocumentBuilder(); Le parsing ne s’effectue pas automatiquement. Il faut pour ce faire appeler la méthode parse() et lui indiqué en paramètre le nom du fichier XML à traiter. Cette méthode retourne une instance conforme à l’interface Document qui sert de point de départ pour la manipulation de l’arbre qui vient d’être construit en mémoire sur la base du fichier XML. Document doc = b.parse("fichier.xml"); A partir du document, on peut obtenir le nœud racine au moyen de la méthode getDocumentElement(). 22.06.2005 3/22 Element root = doc.getDocumentElement() ; L’interface Element est une interface qui hérite de Node qui offre des méthodes spécifiques au traitement des balises XML (<!ELEMENT> dans une DTD). A partir de maintenant, puisque nous sommes en possession du nœud racine du document, JAXP ne nous est plus d’aucune utilité puisque c’est à nous d’écrire le code nécessaire pour traiter le document XML selon nos propres besoins. Il s’agit de s’appuyer sur la méthode getChildNodes() de l’interface Node pour réaliser ces traitements. 1.5.2. Parsing XPath Nous présentons dans la seconde partie de ce rapport l’utilisation d’une base de données Oracle pour l’interrogation d’un document XML au moyen de la norme XPath. Il nous semblait cependant intéressant de montrer qu’il est également possible d’utiliser ce mécanisme depuis un programme Java en utilisant un moteur de transformation XSL. Pour ce test, nous avons choisi d’utiliser l’outil Xalan de la fondation Apache. Nous n’avons malheureusement pas eu le temps de regarder si JAXP offrait une couche d’abstraction pour ce genre d’outil comme c’est le cas pour les parseurs DOM et SAX. En nous basant sur l’exemple de manipulation présenté ci-dessous dans la partie sur XPath et Oracle, voici la requête XPath qui devrait nous retourner le nom de la classe contenue dans le fichier XMI : /XMI/XMI.content/UML:Model/UML:Namespace.ownedElement/UML:Class/@name La première étape consiste à créer une instance compatible avec l’interface Document (voir la section ci-dessus Parsing DOM) qui sera ensuite transmise au moteur de recherche XPath. DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance(); DocumentBuilder b = bf.newDocumentBuilder(); Document doc = b.parse("demo.xmi"); Il ne nous reste plus qu’à demander au moteur de recherche XPath d’effectuer notre requête. Celui-ci retourne, en fonction de la méthode utilisée, une instance de Node ou une collection de Node. Node n = XPathAPI.selectSingleNode(doc, request) ; La variable request est une chaîne de caractères contenant la requête XPath à appliquer au document XML représenté par la variable doc. Le nœud reçu en réponse à notre requête peut maintenant être traité au moyen des méthodes de l’interface Node comme si nous avions effectué laborieusement le parsing de façon traditionnelle au moyen d’un arbre DOM. Grâce à l’utilisation de XPath pour le parsing de fichier XMI, il devient possible d’assurer à moindre frais l’évolutivité de l’application en regard des changement de la spécification XMI et/ou UML. Le chemin d’accès pourrait être prédéfinis dans un fichier de configuration afin de pouvoir être changés sans nécessiter la recompilation de l’application. L’utilisation de XPath peut se faire soit en utilisant un outil comme Xalan ou alors en utilisant les fonctionnalités d’une base de données XML comme Oracle (voir prochain chapitre). Dans ce deuxième cas, les tests réalisés ont montré un certain degré d’immaturité dans la solution proposée par Oracle c’est pourquoi la solution Xalan 22.06.2005 4/22 pourrait bien être une alternative intéressante. De plus l’utilisation d’un outil comme Xalan permet d’offrir une solution indépendante d’un quelconque SGBD du marché. Vous trouverez des plus amples informations sur XPath dans la deuxième partie de ce document ainsi que sur le site du W3C (http://www.w3.org/TR/xpath). Xalan est disponible sur le site XML de la fondation Apache (http://xml.apache.org/xalanj/index.html). 1.6. Implémentation du générateur SQL-DDL 1.6.1. Présentation générale De façon très simplifiée, le générateur fonctionne grâce à la collaboration des objets de trois classes. Une instance de la classe XmiAnalysator lit un fichier XMI et récupère les méta-données dans un conteneur de type XmiData. Ces méta-données sont ensuite transmise à une instance de ScriptGenerator. Celui est responsable de la transformation des méta-données au format relationnel (ajout de l’attribut clé étrangère par exemple). La classe SQL_DDLCreator est la façade du système. Elle coordonne l’analyse du fichier XMI et la production du fichier SQL-DDL correspondant. Il est possible de lancer l'application en mode ligne de commande ou en mode graphique (Swing). 22.06.2005 5/22 XmiAnalysator (from hegne) ScriptGenerator (from hegne) XmiAnalysator() XmiAnalysator() generat eXmiData() getClas s() getClas sAttributs() getClas sAttributType() getA ssociations() saveA ssociations() getB alise() seekClassName() getClas sAs sociations() getS tereoty pe() getConstraints() ScriptGenerator() ScriptGenerator() generateRelationalDataXmi() analyseXmiConstraint() analyseRelationalData() displayConstraint() schearchNumtable() writeScript() seekFK() correct() XmiData (from hegne) Modelname : Logical View::java::lang::String = "" 1.6.2. Présentation détaillée ch.hegne.SQL_DDLCreator Cette classe s’occupe de mettre en place la génération du script SQL-DDL à partir de l'analyse d'un fichier XMI. La méthode createTable() appelle dans un premier temps les méthodes de la classe XmiAnalysator pour l’analyse d’un fichier XMI. Ensuite createTable() lance l’analyse des meta-données obtenues en utilisant la classe ScriptGenerator. 22.06.2005 6/22 SQL_DDLCreator (f ro m hegn e) SQL_DDLCreator() SQL_DDLCreator() createTable() isAlreadyCreated() ch.hegne.XmiAnalysator Cette classe parse un fichier XMI en utilisant l’API JAXP (méthode DOM) et stocke les meta-données dans un container de type XmiData. La méthode getClass() fait appel à la méthode getAttributes() qui recherche les attributs d’une classe. Il faut préciser que la méthode getClass() doit impérativement être appelée avant getAssociations(). En effet, cette dernière recherche les classes précédemment trouvées pour ensuite y ajouter les associations détectées. La recherche d’informations dans un fichier XMI fait appel à des processus récursifs. Ces derniers doivent souvent recommencer l'analyse du fichier depuis la racine ce qui rend assez complexe la mémorisation des données trouvées. Nous avons donc choisi d'implémenter un parseur travaillant en plusieurs passes. Pour ce faire nous avons choisi d'utiliser des attributs de classes (static) afin d'assurer le partage temporaire des données entre les différents processus récursifs. ch.hegne.ScriptGenerator Cette classe génère le script SQL-DDL. Elle procède en deux phases. La consiste à convertir les données reçues par la classe XmiAnalysator en relationnelles. Cette conversion s’effectue grâce à la generateRelationalDataXmi(). Cette dernière réalise simplement le mapping types des attributs utilisés dans le modèle UML et le langage SQL. première données méthode entre les Pour le mapping UML – SQL des types de données, nous nous sommes basés sur le mapping proposé par Rational Rose. 22.06.2005 XMI: SQL: STRING VARCHAR2 (255) INTEGER NUMBER (10,0) DOUBLE FLOAT DATE DATE BOOLEAN NUMBER (1,0) BYTE NUMBER (3,0) SINGLE FLOAT LONG NUMBER (20,0) CURRENCY FLOAT 7/22 Il faut préciser que les méta-données de type RelationalData ne peuvent pas servir directement à la création du script SQL-DDL, elles doivent être encore analysées pour éviter par exemple le problème du double sens de navigabilité ou gérer les associations de type n-n. La deuxième phase qui consiste donc à analyser ces méta-données et est réalisée par la méthode analyseRelationalData(). Celle-ci génère directement le script SQL-DDL par le biais de la méthode writeScript(). ch.hegne.XmiData La classe XmiData sert à stocker les méta-données trouvées dans le fichier XMI analysé. Elle comporte un champ mémorisant le nom du modèle et un tableau contenant des objets de type XmiClass. La classe XmiClass contient un tableau d’objets de type XmiAttribut et un autre tableau stockant des objets de type XmiAssociation. 22.06.2005 8/22 XmiData (from hegne) Modelname : Logical View::java::lang::String = "" 0..* XmiConstraint (from hegne) name : Logical View::java::lang::String = "" expression : Logical View::java::lang::String = "" extendedElement : Logical View::java::lang::String = "" isCheckType : boolean = true 0.. * XmiClass (f rom heg ne) id : Logical View::java::lang::String = "" name : Logical View::java::lang::String = "" 0..* XmiAssociation (f ro m he gne) name : Logical View::java::lang::String = "" refclass : Logical View: :java::lang::St ring = "" rolename : Logical View: :java::lang::St ring = "" uppermultiplicity : Logical View::java: :lang::String = "" lowermult iplicity : Logical View: :java::lang::St ring = "" XmiAtt ribut (from hegne) id : Logical View::java::lang::String = "" name : Logical View::java::lang::String = "" type : Logical View::java::lang::String = "" lowerMultiplicity : Logical View::java::lang::String = "0" isAtrributAssociation : boolean = false 0..* ch.hegne.RelationalData Cette classe est un conteneur permettant de stocker les méta-données contenues dans le fichier XMI sous forme relationnelle. Cette classe comporte un tableau d’objets de type Table. La classe Table comporte un tableau d’objets de type Attribut. Les champ isComposed (si l’attribut est composé), isTable (si l’attribut est un tableau), isTraited (si l’attribut a déjà été analysé) et isReferenced (qui permet d’éviter le problème du double sens de navigation) facilitent l’analyse finale effectuée par la méthode analyseRelationalData() de la classe ScripGenerator. 22.06.2005 9/22 RelationalData (f rom heg ne) size : int = 0 0.. * Table (f rom heg ne) size : int = 0 name : Logical View::java::lang::String 0.. * At tribut (f rom heg ne) name : Logical View::java::lang::String = "" type : Logical View::java::lang::String = "" xmiid : Logical View::java::lang::String = "" isComposed : boolean = false isTable : boolean = false isTraited : boolean = false isReferenced : boolean = false isAtrributAssociation : boolean = false 0.. * Constraint (f rom heg ne) name : Logical View::java::lang::String = "" expression : Logical View::java::lang::String = "" 1.6.3. Fichier de configuration Un fichier de configuration s’appelant conf.txt doit être placé à la racine du disque C. Exemple de configuration : PATHLOGFILE=c:\\Daucourt\\log\\sql_ddlCreator.log SQL_USERNAME=Daucourt SQL_PASSWORD=Daucourt VARCHAR_SIZE=255 PATHLOGFILE SQL_USERNAME SQL_PASSWORD VARCHAR_SIZE 22.06.2005 Fichier de journalisation des événements de l’analyse XMI Nom de l’utilisateur pour se connecter à la BD (pas utilisé) Mot de passe de l’utilisateur pour se connecter à la BD (pas utilisé) Entier utilisé pour définir la taille des chaînes de caractères en SQL 10/22 1.6.4. Interface graphique Au chargement de l’application, le bouton permettant l’ouverture et par conséquent l’analyse d’un fichier XMI est désactivé. Il faut d’abord choisir le répertoire de destination (save directory) avant de pouvoir sélectionner un fichier XMI. L’onglet Events affiche le résultat de l’analyse du fichier XMI. Un fichier de log contenant ces informations est également (voir la propriété PATHLOGFILE du fichier de configuration). 22.06.2005 11/22 L’onglet Created tables présente quant à lui les différentes commandes SQL-DDL qui ont été générées suite à l’analyse du fichier XMI. On retrouve le code généré pour chaque table dans un fichier texte dans le répertoire de destination (save directory). Le fichier est préfixé avec sql_ et est suivi du nom de la classe et de l’extension .txt. 22.06.2005 12/22 1.7. Application de démonstration Voici le modèle UML que nous avons choisi pour illustrer l'utilisation de notre prototype de générateur SQL-DDL: 22.06.2005 13/22 Eleve <<PK>> Numero : Integer Nom : String Prenom : String age : Integer Eleve.age > 0 Inscription +eleve +eleve Dat eIns cription : Date 0..* 0..* estInscrit +filiere appartient 0..* 1 Filiere +classe Class e <<PK>> Numero : Integer Libelle : String <<PK>> Numero : Integer Libelle : String Ce modèle contient tout les éléments intéressants disponibles en UML: Association 1-N Association N-N Classe-association avec attribut Contrainte stéréotypée (PK, NOT NULL) Multiplicité minimale à un Contrainte OCL (CHECK) • • • • • • Remarque 1: Le nom des rôles est obligatoire car il est utilisé pour générer les attributs matérialisant une clé étrangère. Remarque 2: La contrainte OCL a été rajoutée à la main au fichier XMI généré par Rational Rose car cet outil ne supporte pas l'édition de contraintes OCL pour les attributs. Nous nous sommes inspirés pour cela du code XMI généré par Poseidon for UML. Nous avons bien essayé d'utiliser Poseidon for UML pour générer automatiquement l'ensemble du fichier XMI mais nous avons constaté que les deux outils n'utilisaient pas la même version de XMI (1.1 pour Rational Rose, 1.2 pour Poseidon for UML) et que cela posait des problèmes pour l'analyse du fichier. Voici le code SQL-DDL généré par notre prototype pour ce modèle: CREATE TABLE Classe( Numero Libelle ); NUMBER(10,0) VARCHAR2(255) PRIMARY KEY, CREATE TABLE Eleve( Numero NUMBER(10,0) PRIMARY KEY, Nom VARCHAR2(255) NOT NULL, Prenom VARCHAR2(255) NOT NULL, age NUMBER(10,0) check (age > 0), num_classe INTEGER REFERENCES Classe(Numero) ); CREATE TABLE Filiere( Numero Libelle 22.06.2005 NUMBER(10,0) VARCHAR2(255) 14/22 PRIMARY KEY, ); CREATE TABLE Inscription( num_filiere INTEGER REFERENCES num_eleve INTEGER REFERENCES DateInscription DATE ); Filiere(Numero), Eleve(Numero), Avant de lancer ce code SQL dans la base de données (copier/coller avec SQL Plus par exemple), il faut réfléchir à l’ordre dans lequel il faut exécuter les instructions afin de respecter les contraintes d’intégrités référentielles. Alternative au parseur, simplification du prototype 1.8. Introduction Lors de la réalisation du prototype, il nous est apparu qu’il serait intéressant d’utiliser les fonctionnalités de la base de donnée Oracle pour en simplifier le code. En effet, la base de données cible retenue étant forcément une base de donnée de ce constructeur, nous avons exploré les fonctionnalités de traitement de données XML offert. Le concept de base consiste à charger le fichier XMI dans la base de données et d’utiliser les outils basés sur Xpath pour simplifier la recherche de la valeur des nœuds. Nous n’avons pas modifié le prototype par manque de temps, mais réaliser quelques manipulations simples, soit : • • • • Installation de Oracle XMLDB Création d’une structure capable de stocker le document XMI Chargement du fichier dans la base de données Recherche et extraction de données sur des critères Xpath. 1.9. Exemple de manipulation Les tests ont été fait à partir d’une extraction du fichier XMI exemple, avec une seule classe, la classe Client Client numero : int nom : string Une fois la classe saisie avec Poseidon, nous avons généré le fichier XMI correspondant, dont voici un extrait : <?xml version = '1.0' encoding = 'UTF-8' ?> <XMI xmi.version = '1.2' xmlns:UML = 'org.omg.xmi.namespace.UML' timestamp = 'Mon Jun 16 15:46:04 CEST 2003'> <XMI.header> <XMI.documentation> <XMI.exporter>Netbeans XMI Writer</XMI.exporter> <XMI.exporterVersion>1.0</XMI.exporterVersion> </XMI.documentation> </XMI.header> 22.06.2005 15/22 t <XMI.content> <UML:TagDefinition xmi.id = 'a1' name = 'element.uuid' isSpecification = 'false' tagType = 'element.uuid'> <UML:TagDefinition.multiplicity> <UML:Multiplicity xmi.id = 'a2'> <UML:Multiplicity.range> <UML:MultiplicityRange xmi.id = 'a3' lower = '1' upper = '1'/> </UML:Multiplicity.range> </UML:Multiplicity> </UML:TagDefinition.multiplicity> </UML:TagDefinition> <UML:Model xmi.id = 'a4' name = 'Demo XMI' isSpecification = 'false' isRoot = 'false' isLeaf = 'false' isAbstract = 'false'> <UML:ModelElement.taggedValue> <UML:TaggedValue xmi.id = 'a5' isSpecification = 'false' dataValue = '-99-26--92-79-247d4a:f5d0b19cde:-8000'> <UML:TaggedValue.type> <UML:TagDefinition xmi.idref = 'a1'/> </UML:TaggedValue.type> </UML:TaggedValue> </UML:ModelElement.taggedValue> <UML:Namespace.ownedElement> <UML:Class xmi.id = 'a6' name = 'Client' visibility = 'public' isSpecification = 'false' isRoot = 'false' isLeaf = 'false' ... 1.9.1. Création de la table La première étape consisite à créer une structur capable de stocker le document XMI dans la base de données. Il existe plusieurs manière de réaliser ce stockage, nous avons opté pour le cas le plus simple, une table relationnelle objet basé sur la classe XMLType. create table xmi_table of xmltype; 1.9.2. Chargement du document XMI dans la base de données Le document doit d'abord être converti en une instance XMLType. Ceci est réalisé grâce aux constructeurs fournis par le type XMLType.. INSERT INTO xmi_table VALUES(XMLTYPE('<code_du_document_xmi>')); SQLPlus n'acceptant qu'un nombre de caractères limité, il n'est pas possible de le charger de cette manière. Deux alternatives existent : SQLLoader ou une fonction PL/SQL getDocument(). Cette dernière est décrite ci-après. 1.9.2.1. Fonction getDocumetn() 22.06.2005 16/22 Création d'une fonction getDocument() qui instancie un document XML en XMLType, à laquelle il suffit de spécifier le nom du fichier à charger, pour autant que le document soit dans un répertoire spécifique sur le serveur. L'idée est de créer un dossier dans la base Oracle pointant sur un répertoire du serveur contenant les documents XML à charger. La fonction retourne une variable CLOB contenant le document XML. Pour créer le répertoire il faut être connecté comme system create directory XMLDIR as 'c:\TEMP\xml'; grant read on directory xmldir to public with grant option; En fait de répertoire, Oracle fait pointer le dossier XMLDIR sur un emplacement existant du serveur, ici c:\TEMP\xml, qui est le dossier dans lequel sont contenus les fichiers XML à charger dans la base oracle. Créer une fonction qui retourne une valeur CLOB d'un fichier XML dont le nom est passé en paramètres. create function getDocument(filename varchar2) return clob authid current_user is xbfile bfile; xclob clob; begin xbfile := bfilename('XMLDIR', filename); dbms_lob.open(xbfile); dbms_lob.createtemporary(xclob, TRUE, dbms_lob.session); dbms_lob.loadfromfile(xclob, xbfile, dbms_lob.getlength(xbfile)); dbms_lob.close(xbfile); return xclob; end; / 1.9.2.2. Insertion d'un document dans la base A présent un document XML peut être chargé dans la table à l'aide de la fonction créée en spécifiant uniquement le nom du fichier en paramètres. Le fichier à donc été placé au préalable à l'endroit adéquat sur le serveur. Il suffit de remplacer le contenu du document XMI dans la syntaxe d'insertion, par l'appel de cette fonction : INSERT INTO xmi_table VALUES (XMLTYPE(getDocument('fichierXMI.xml'))); 1.9.3. Requêtes sur un document de la base Nous testons ici quelques manipulations de bases pour extraire finalement la valeur des nœuds en connaissant le chemin Xpath. 22.06.2005 17/22 1.9.3.1. Requête pour afficher le document en entier Deux syntaxes différentes sont supportées pour le même résultat : select x.getclobval() from xmi_table x; select value(x) from xmi_table x; 1.9.3.2. ExistsNode() Cette fonction renvoie la valeur booléenne VRAI (1) si le chemin Xpath saisi correspond à un nœud existant dans le document, ou FAUX (0) si elle ne le trouve pas. Par exemple pour savoir si le nœud '/XMI/XMI.header/ XMI.documentation ' existe comme dans l'extrait ci-dessous : <?xml version = '1.0' encoding = 'UTF-8' ?> <XMI xmi.version = '1.2' xmlns:UML = 'org.omg.xmi.namespace.UML' timestamp = 'Mon Jun 16 15:46:04 CEST 2003'> <XMI.header> <XMI.documentation> <XMI.exporter>Netbeans XMI Writer</XMI.exporter> <XMI.exporterVersion>1.0</XMI.exporterVersion> </XMI.documentation> </XMI.header> il suffit d'exécuter la commande suivante : SELECT existsNode(x.data, '/XMI/XMI.header/XMI.documentation') as doc FROM xmi_table x; DOC ---------1 Si le nœud n'existe pas : SELECT existsNode(x.data, '/n_importe_quoi/') as doc FROM xmi_table x; DOC ---------0 S'il y a deux documents dans la table contenant un tel nœud: SELECT existsNode(x.data, '/XMI/XMI.header/XMI.documentation') as doc FROM xmi_table x; DOC 22.06.2005 18/22 ---------1 1 Pour accéder à un nœud explicitement au cas ou deux nœud avec la même hiérarchie existeraient dans le même document, il suffit d'ajouter un chiffre entre crochets, "[]", indiquant la position du nœud par rapport aux autres ayant la même hiérarchie (en partant de 1). Don comme exemple pour le premier : SELECT existsNode(value(x), '/XMI/XMI.header/XMI.documentation[1]') as doc FROM xmi_table x; DOC ---------1 1.9.3.3. Afficher un noeud L'affichage d'un tag et de son contenu se fait de la même manière qu'auparavant : SELECT extract(value(x), '/XMI/XMI.header/XMI.documentation/XMI.exporter').getclobval() as exporter FROM xmi_table x; EXPORTER --------------------------------------------------------------<XMI.exporter>Netbeans XMI Writer</XMI.exporter> 1.9.3.4. Contenu d'un tag Pour extraire une information d'un tag (entre la balise d'ouverture et celle de fermeture), il faut utiliser extract() et getStringval(). SELECT extract(value(x), '/XMI/XMI.header/XMI.documentation/XMI.exporter/text()'). getstringval() as Exporter FROM xmi_table x; EXPORTER ----------------------Netbeans XMI Writer Il est possible de raccourcir la syntaxe en utilisant les capacités de Xpath ainsi: SELECT extract(value(x), '//XMI.exporter/text()').getstringval() as exporter FROM xmi_table x; 22.06.2005 19/22 ExtractValue() peut aussi être utilisé : SELECT extractValue(value(x), '//XMI.exporter') as exporter FROM xmi_table x; EXPORTER ----------------------Netbeans XMI Writer Pour afficher plusieurs nœuds il suffit de remonter dans la hiérarchie (le chemin indiqué sera plus court) : SELECT extract(value(x), '/XMI/XMI.header/XMI.documentation').getstringval() as Documentation FROM xmi_table x; DOCUMENTATION ------------------------------------------------------------<XMI.documentation> <XMI.exporter>Netbeans XMI Writer</XMI.exporter> <XMI.exporterVersion>1.0</XMI.exporterVersion> </XMI.documentation> 1.9.4. Afficher les enfants d'un nœud SELECT extract(value(x), '/XMI/XMI.header/XMI.documentation/*') as doc FROM xmi_table x; DOC ----------------------------------------------------<XMI.exporter>Netbeans XMI Writer</XMI.exporter> <XMI.exporterVersion>1.0</XMI.exporterVersion> Pour n'afficher que le contenu des enfants d'un nœud il n'est pas possible d'utiliser extractvalue() car cette fonction ne doit retourner qu'un seul nœud: Une possibilité existe avec extract(), mais les informations extraites sont concaténées en une seule chaîne, ce qui n'est pas vraiment idéal : SELECT extract(value(x), '/XMI/XMI.header/XMI.documentation/*/text()') as doc FROM xmi_table x; DOC ---------------------------------Netbeans XMI Writer1.0 Il est plus intéressant s’utiliser la séquence XML (XMLSequence) avec une table: SELECT value(t) FROM xmi_table x, 22.06.2005 20/22 TABLE (xmlsequence(extract(value(x), '/XMI/XMI.header/XMI.documentation/*'))) t; VALUE(T) -----------------------------------------------------<XMI.exporter>Netbeans XMI Writer</XMI.exporter> <XMI.exporterVersion>1.0</XMI.exporterVersion> et avec extractvalue() pour n'obtenir que le contenu des tags. SELECT extractvalue(value(t), '/') FROM xmi_table x, TABLE (xmlsequence(extract(value(x), '/XMI/XMI.header/XMI.documentation/*'))) t; EXTRACTVALUE(VALUE(T),'/') ------------------------------------------Netbeans XMI Writer 1.0 1.9.4.1. Attribut d'un tag Pour obtenir le nom de la Classe qui se trouve comme attribut d'un tag, il faut utiliser @nom_de_l'attribut à la place de text(), en plus des fonctions extract() et getStringval(). Avec le raccourci Xpath, on obtient : SELECT x.extract('//UML:Class/@name').getstringval() as classe FROM xmi_table x; CLASSE -----------Client Pour la reprise des attributs, les résultats de cette syntaxe n'est malheureusement pas satisfaisante, s'il existe plusieurs informations avec le même chemin. ces dernières sont alors concaténées dans une chaîne de caractères. Par exemple pour le nom des attributs: SELECT x.extract('//UML:Attribute/@name') AS Attribute FROM xmi_table x; ATTRIBUTE ------------numeronom Pour obtenir ces informations séparément il faudrait donc utiliser une table alimentée par une séquence XML comme vu précédemment. 1.10. Conclusion L’utilisation des capacités d’Oracle XMLDB d’Oracle serait assez intéressant pour simplifier le code du prototype pour éviter l’utilisation du parseur, en s’appuyant en 22.06.2005 21/22 particulier sur les requêtes Xpath. La maintenance du prototype serait nettement simplifiée, particulièrement en cas de modification/évolution de la norme XMI. Nous n’avons pas réalisé faute de temps, un prototype avec ces fonctionnalités, mais en première approche, cela ne semble guère compliqué, puisque la connexion peut se faire avec des drivers JDBC qui sont réputés très stables. Nous sommes néanmoins plus nuancé quand à la mise en pratique réelle de cette solution. Nous avons du hélas constaté que les fonctionnalités XMLDB d’Oracle 9.2 ne sont pas encore complètement mature, et nous avons du composer avec un produit des plus instable. Une problématique non traitée consiste à traiter le retour des requêtes qui sont parfois complexes et encore structurées. Un exemple simple peut être illustré par l’apparition d’un apostrophe dans une chaîne de caractère résultat. 22.06.2005 22/22