SpazioCodice

GraphQL + OWL: Namespaces

GraphQL + OWL : Espaces de noms

Introduction

Dans la première partie (GraphQL + OWL : Une interface GraphQL “ontologisée”), nous avons introduit et discuté un design pour un schéma GraphQL qui fournit une interface de requête (par exemple, types, champs) conforme à un ensemble arbitraire d’ontologies.

Cette deuxième partie commence par l’implémentation d’un composant logiciel qui crée le schéma et, par conséquent, l’interface. Nous décrirons les outils nécessaires pour implémenter ce composant, puis nous nous concentrerons sur un concept fondamental des ontologies : les espaces de noms. Comment les traduire dans le monde de GraphQL ?

À la fin de l’implémentation, il devra être possible de :

  • accéder à un endpoint GraphQL
  • utiliser l’API d’introspection
  • naviguer dans le schéma discuté ici.

Environnement de Développement

Voici notre ensemble d’outils :

Le code exemple que nous allons discuter dans cet article est hébergé dans un dépôt GitHub privé.

Si vous souhaitez y jeter un œil, envoyez-nous simplement un email, et nous serons heureux de vous donner accès.

Commençons !

L'Application

Comme mentionné, l’application fournissant le service GraphQL est une application Spring Boot. Rien de particulier à ajouter : les ontologies que nous souhaitons utiliser pour initialiser le module sont définies dans la configuration. Dans l’exemple suivant, nous utilisons les ontologiese Dublin Core et Friend Of A Friend (FOAF).

				
					spring:
  application:
    name: graphowl-engine-api
graphiql:
  enabled: true
graphowl:
  ontologies: https://protege.stanford.edu/plugins/owl/dc/dublincore.owl,https://web.archive.org/web/20220614105937if_/http://xmlns.com/foaf/spec/20140114.rdf
				
			

Notez que nous utilisons l’interface utilisateur GraphIQL pour expérimenter. Nous utilisons également les profils Spring pour définir plusieurs cas d’utilisation, chacun expérimentant avec une ou plusieurs ontologies. Voici une capture d’écran du dossier resources :

 

Le dossier contient plusieurs exemples :

  • application.yml par défaut utilise FOAF + DC
  • application-bibframe.yml utilise BIBFRAME
  • application-foaf.yml utilise FOAF
  • application-musicontology.yml utilise la Music Ontology

Espaces de Noms comme Types de Haut Niveau (Query)

L’entrée du système avec lequel nous travaillons est une ontologie arbitraire ou même un ensemble d’ontologies.

Les premiers types que nous souhaitons définir dans le schéma sont les espaces de noms : pour chacun, il devrait y avoir un champ correspondant sous le type de requête GraphQL de haut niveau (Query).

Les espaces de noms sont généralement déclarés au tout début des documents XML. Les ontologies ne font pas exception : si, par exemple, nous utilisons BIBFRAME comme ontologie d’entrée, voici la liste des espaces de noms utilisés :

				
					<rdf:RDF 
    xml:base="http://id.loc.gov/ontologies/bibframe/"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" 
    xmlns:bf="http://id.loc.gov/ontologies/bibframe/" 
    xmlns:bflc="http://id.loc.gov/ontologies/bflc/" 
    xmlns:owl="http://www.w3.org/2002/07/owl#" 
    xmlns:skos="http://www.w3.org/2004/02/skos/core#" 
    xmlns:dcterms="http://purl.org/dc/terms/" 
    xmlns:foaf="http://xmlns.com/foaf/0.1/" 
    xmlns:cc="http://creativecommons.org/ns#">
...

</rdf:RDF>
				
			

Pour chaque espace de noms, nous devons créer un type qui inclut seulement deux propriétés (pour le moment) :

  • “uri” : qui correspond à l’URI de l’espace de noms
  • “prefix” : le code mnémonique court associé à l’URI de l’espace de noms

Lorsque l’application se charge en utilisant le profil par défaut (qui utilise FOAF et DC), voici ce que nous obtenons de l’API d’introspection GraphQL (GraphiQL utilise ces API dans la colonne de droite) :

À première vue, cela pourrait sembler déroutant : les noms des champs et des types partagent le même nom. Oui, c’est exact, et je conviens que cela peut être un peu trompeur ; cependant, cela est principalement dû à l’absence du concept d’espace de noms dans le monde de GraphQL.

Mis à part le schéma, l’idée est simple si l’on considère la perspective du demandeur de requête. Lorsqu’une personne souhaite exécuter une requête, elle doit déclarer l’espace de noms auquel appartient l’entité ou les entités qui composent la réponse.

Rappelez-vous, ceci n’est que la première brique de notre système, donc pour l’instant, étant encore partielle, l’interaction et l’interface ne sont pas encore entièrement claires. Disons qu’à ce stade, nous pouvons interroger un espace de noms comme suit :

				
					query {
    foaf {
        uri
        prefix
    }
}
				
			

Et obtenir la réponse suivante :

				
					{
  "data": {
    "foaf": {
      "uri": "http://xmlns.com/foaf/0.1/",
      "prefix": "foaf"
    }
  }
}
				
			

L'Implémentation

Comment extraire les espaces de noms à partir de l’ensemble d’ontologies fourni en entrée ? Comme mentionné, les espaces de noms sont généralement déclarés au tout début d’un document XML. Cependant, ils peuvent également être définis dans le document lui-même, pour qualifier des éléments ou des attributs spécifiques. Par conséquent, nous devons parcourir le ou les documents, collecter chaque espace de noms distinct et créer un champ de requête (Query) correspondant dans le schéma GraphQL.

Lors de l’analyse du document, nous collectons, pour chaque espace de noms, le préfixe et l’URI. De cette manière, en plus de créer les structures du schéma, nous pouvons également implémenter les fetchers de données correspondants. Voici le code :

				
					private void registerOntologyNamespaces(
        RuntimeWiring.Builder registryBuilder, 
        TypeDefinitionRegistry registry, 
        String ontologyUrl) {
    
    // Read the ontology from its URL
    var url = new URL(ontologyUrl);
    var connection = url.openConnection();
    try (var stream = connection.getInputStream()) {
    
        // Create the DOM representation of the ontology XML document
        var factory = DocumentBuilderFactory.newInstance();
        var builder = factory.newDocumentBuilder();
        var ontologyDocument = builder.parse(stream);

        // Retrieve the top-level GraphQL Query type
        var queryTypeDefinition = registry.getType(C_Query).orElseThrow();
        var queryNamedChildren = queryTypeDefinition.getNamedChildren();
        var topLevelQueries = queryNamedChildren.getChildren(C_fieldDefinitions);
        
        // Retrieve the GraphQL String type (for literals) 
        var stringDataType = TypeName.newTypeName("String").build();

        // Iterate over all elements
        var xPath = XPathFactory.newInstance().newXPath();
        var allNodes = xPath.compile("//*");
        var nodes = (NodeList) allNodes.evaluate(ontologyDocument, XPathConstants.NODESET);

        for (int i = 0; i < nodes.getLength(); i++) {
            var node = nodes.item(i);
            if (node.hasAttributes()) {
            
                // for each element, get its attributes
                var attributes = node.getAttributes();
                for (int y = 0; y < attributes.getLength(); y++) {
                    var attribute = attributes.item(y);
                    var attributeName = attribute.getNodeName();
                    
                    // is it a namespace declaration?
                    if (attributeName.startsWith(XMLNS_PREFIX)) {
                        var prefix = attributeName.substring(XMLNS_PREFIX.length());
                        var namespaceURI = attribute.getNodeValue();
                        
                        // Did we already register the namespace?
                        if (registry.hasType(TypeName.newTypeName(prefix).build())) {
                            continue;
                        }

                        var typeDef = new ObjectTypeDefinition(prefix);
                        var namespaceNamedChildren = typeDef.getNamedChildren();
                        var namespaceFields = namespaceNamedChildren.getChildren(C_fieldDefinitions);
                        
                        // "uri" field for the namespace
                        namespaceFields.add(newFieldDefinition().name("uri")
                                .comments(List.of(new Comment("The Uniform Resource Identifier (URI) associated with this namespace.", null)))
                                .type(stringDataType)
                                .build());

                        // "prefix" field for the namespace
                        namespaceFields.add(newFieldDefinition().name("prefix")
                                .comments(List.of(new Comment("A short mnemonic code associated with this namespace URI.", null)))
                                .type(stringDataType)
                                .build());

                        topLevelQueries.add(newFieldDefinition()
                                .name(prefix)
                                .type(TypeName.newTypeName(prefix).build())
                                .build());

                        registry.add(typeDef.withNewChildren(namespaceNamedChildren));


                        // Data fetcher for "uri" and "prefix" fields
                        registryBuilder.type(newTypeWiring(C_Query)
                                       .dataFetcher(prefix, context -> 
                                                        Map.of("prefix", prefix,
                                                               "uri", namespaceURI)));
                    }
                }
            }
        }

        registry.remove(queryTypeDefinition);
        registry.add((SDLDefinition) queryTypeDefinition.withNewChildren(queryNamedChildren));
    } catch (Exception exception) {
        ... log the error
    }
}
				
			
 

Et voilà. Maintenant :

  1. Lancez l’application
  2. Ouvrez votre navigateur
  3. Rendez-vous su http://127.0.0.1:8080/graphiql 
  4. Exécutez la requête suivante :
				
					query {
    foaf {
        uri
        prefix
    }
}
				
			

et attendez-vous à la réponse suivante. Vous pouvez également essayer avec les autres espaces de noms.

Nous serions ravis de recevoir vos questions, doutes et retours concernant cet article de blog !
N’hésitez pas à nous contacter ou à laisser un message dans la section des commentaires ci-dessous.

Share this post

Laisser un commentaire

En savoir plus sur SpazioCodice

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Poursuivre la lecture