Quick links: Tutorial - Examples - Files - Symbols.
Classes: Hierarchy - Index - List - Members.
Namespaces: Index - base - cs - display.
This page is available in English.

Architecture client-serveur

Cette section décrit l'architecture client-serveur de la bibliothèque.

L'architecture client-serveur de Cogitant permet de rendre disponibles les objets manipulés par la bibliothèque (support, graphes, règles) et opérations (entrées / sorties, recherche de projections, applications de règles, vérifications, etc.) à d'autres applications, éventuellement exécutées sur d'autres postes. Les utilisations de cette architecture client/serveur peuvent se diviser en deux catégories :

Dans les deux cas, le serveur peut être utilisé comme serveur de support(s) et de base(s) de graphes (stockage de graphes et exécution d'opérations). Dans cette partie, nous détaillons dans un premier temps la procédure de compilation et d'installation. Dans un deuxième temps nous décrivons plus précisément les classes de l'architecture client serveur. Enfin, dans la dernier point de cette partie, le protocole d'échange est décrit, sous la forme de la DTD du format.

Introduction

Un serveur Cogitant (appelé serveur TCP par la suite) peut être interrogé par des clients à travers une liaison TCP (mode connecté), ou un échange de messages suivant le protocole HTTP (chaque requête effectue une connexion qui est coupée après réception de la réponse), comme cela est illustré en figure 1. Dans le premier cas, les clients communiquent directement avec le serveur TCP. Dans le second cas, les clients communiquent avec un serveur HTTP (Apache http://www.apache.org par exemple), ce serveur HTTP transmet ses ordres au module d'interfaçage HTTP/TCP de Cogitant, qui, lui même est en communication avec un serveur TCP. Le module d'interfaçage communique avec le serveur TCP par le biais de TCP, et il est donc possible d'installer le serveur TCP sur une autre machine (A) que celle utilisée pour le serveur HTTP (B). De cette façon, il est possible d'installer le serveur TCP sur une machine visible uniquement sur un réseau local (et invisible depuis Internet), et de rendre ce serveur (indirectement) disponible (à C, D, E et F) par le biais d'un serveur HTTP, à condition que la machine serveur HTTP, sur laquelle est exécuté le module d'interfaçage HTTP/TCP, puisse avoir accès par TCP à la machine sur laquelle le serveur TCP est exécuté. L'interfaçage HTTP/TCP se fait par le biais d'un CGI, qui reçoit (par le protocole POST) les données en provenance du client. Si vous n'avez pas compris les phrases précédentes, prenez un aspirine, et relisez-les, sinon, passez à la suite.

archi_cs.png

Figure 1. Différentes méthodes d'accès au serveur.

Compilation

La bibliothèque doit être compilée avec des options particulières afin que les fonctionnalités client/serveur soit disponibles. Les échanges entre clients et serveur (échanges de messages XML, voir Protocole d'échange) peuvent se faire de différentes façons, mais pour l'instant, les seules possibilités d'échange sont l'utilisation du protocole TCP ou du protocole HTTP. Les échanges de message au niveau physique (sockets) peuvent être effectués de différentes façon, mais pour l'instant, les échanges TCP et HTTP ont été développées en utilisant la bibliothèque GNU Common C++ (http://www.gnu.org/software/commoncpp). Cette bibliothèque, libre et gratuite, offre un certain nombre de fonctionnalités qui ne sont pas présentes dans la bibliothèque standard C++. Elle n'est toutefois pas fournie en standard avec les compilateurs du marché. Il est donc nécessaire de télécharger les sources (http://www.gnutelephony.org/dist/tarballs/) et de la compiler avant de pouvoir compiler les fonctions client/serveur de Cogitant. A noter que dans la plupart des distributions GNU/Linux, cette bibliothèque est disponible sous forme de package. Si vous êtes dans ce cas, il n'est pas nécessaire de compiler Common C++ à partir des sources, il suffit d'installer le package correspondant ainsi que le package de développement (libcommonc++ et libcommonc++-dev sous Debian). Common C++ a été testé avec Cogitant sous Linux et sous Windows avec la version 1.7.3.

Sous Unix

Nous supposerons que la bibliothèque Common C++ est préalablement installée. Afin de le vérifier, tapez tout simplement ccgnu-config –version dans un shell, la commande doit être reconnue, et quelque chose comme 1.9.7 doit être affiché. Si vous utilisez la version 2 de Common C++, il vous faut utiliser ccgnu2-config –version. Pour compiler Cogitant avec les fonctions client/serveur, il est nécessaire de passer l'option –enable-cs au script configure. Les fonctions client/serveur pouvant être développées avec une bibliothèque autre que Common C++ (ce n'est pas le cas actuellement), il faut préciser que la compilation devra se faire en utilisant cette bibliothèque, pour cela, il faut rajouter l'option –with-ccxx. Au final, l'appel au script de configuration doit avoir cette forme :

./configure --enable-cs --with-ccxx2

Après le processus de configuration, lancer la compilation de la façon habituelle.

Sous Windows, avec Visual C++ 2008

Notez que l'utilisation de Borland C++ qui était la méthode conseillée dans les versions 5.1.x de Cogitant ne peut plus être utilisée, il est maintenant nécessaire d'utiliser Visual C++ afin de profiter de l'architecture client-serveur de Cogitant.

La première chose à faire est la compilation de Common C++ avec Visual C++. Pour cela, télécharger les sources de la biblithèque, et extraire le contenu de l'archive. Par la suite, on supposera que le contenu de l'archive de Common C++ a été copié dans D:\commoncpp2-1.x.x. Ouvrir le projet D:\commoncpp2-1.x.x\w32\vs2008\ccgnu2.vcproj. S'assurer que Visual C++ génèrera des fichiers en mode Release (et non Debug) et lancer la compilation. Une fois que la compilation est finie le répertoire D:\commoncpp2-1.x.x\w32\vs2008\Release contiendra les deux bibliothèques dynamiques composant Common C++ (CapeCommon17.dll et CapeExtras17.dll, mais cela peut varier selon la version de Common C++).

La configuration de Cogitant pour utiliser Common C++ se fait ensuite de la façon habituelle avec CMake : Lancer l'outil CMake sur le répertoire de Cogitant et, après avoir choisi la génération de fichiers pour Visual C++ 2008, cocher la case WithClientServer_CommonCPP. Cliquer sur Configure. Une erreur est détectée, ce qui est tout à fait normal : il faut choisir dans CcppBaseDir le répertoire qui contient Common C++, c'est à dire, ici, D:\commoncpp2-1.x.x. CcppLibraryDir (répertoire contenant les dll de Common C++) devrait alors être fixé automatiquement après un Configure ainsi que CcppLibraries (noms des dll, sans l'extension). Si ce n'est pas le cas, modifier les valeurs de ces variables. Cliquer sur Generate et ouvrir le projet dans Visual C++ afin de lancer la compilation.

Exemples

Afin de vérifier que la bibliothèque a été correctement compilée avec le support des fonctionnalités client/serveur, le plus simple est d'exécuter l'exemple de serveur et de client. Le serveur est samples/doc/server_1[.exe] et le client dans test/client. Ces deux programmes sont compilés par défaut (lors du make) sous Unix, et doivent être compilés (de la façon habituelle) sous Windows. Exécuter le serveur, puis lancer le client. Ce client teste le serveur, en lui envoyant un grand nombre de requêtes par TCP. Sous Windows, assurez vous que les bibliothèques dynamiques (dll) indispensables à l'exécution des programmes sont bien présentes dans les chemins de recherche : cogitant.dll (si Cogitant a été compilée en dll), les dll de Common C++, et les bibliothèques runtime du compilateur. Vous pouvez pour cela copier ces dll dans les répertoires contenant les exécutables, ou modifier la valeur du PATH afin qu'il contienne les répertoires contenant les dll en question.

Il est possible de se connecter au serveur en utilisant l'application telnet. Pour cela, exécuter la commande telnet 127.0.0.1 4246 (4246 est le port utilisé par le serveur par défaut). Vous pouvez alors saisir des requêtes en respectant le Protocole d'échange. La réponse du serveur est affichée en retour.

Installation du serveur TCP

Aucune installation particulière n'est nécessaire. Il suffit de compiler le serveur. Le code du serveur (server_1.cpp) est très simple, il ne fait qu'instancier des classes de la bibliothèque. Le seul point important réside en le choix des opérations qui sont offertes aux clients. Il est ainsi possible de construire un serveur offrant exactement les opérations que vous voulez offrir (et en interdire d'autres, par exemple, vous interdisez aux clients la possibilité de charger un nouveau support, ou charger des graphes, etc). En vous inspirant du source du serveur par défaut, vous pouvez aussi construire un serveur offrant de nouvelles opérations, il suffit pour cela de définir de nouvelles sous-classes de cogitantcs::OpeServer et de rendre ces opérations disponibles pour le serveur.

Installation du module serveur utilisable à travers HTTP

L'utilisation du protocole HTTP est préférable quand le serveur Cogitant doit être disponible à travers Internet. En effet, dans le cas de l'utilisation de HTTP, il suffit d'installer le module d'interface HTTP/TCP afin qu'il soit connu par le serveur HTTP (Apache, etc.), et de cette façon, le serveur Cogitant est accessible à travers le serveur HTTP, comme n'importe quelle page Web. Ainsi, les messages peuvent passer à travers un firewall (car le port 80 utilisé par le service HTTP n'est habituellement pas filtré), et il est inutile d'"ouvrir" un nouveau port pour laisser passer les messages comme le demanderait le serveur TCP.

Pour rendre le serveur Cogitant accessible par HTTP, il est donc nécessaire d'intaller le CGI d'interfaçage TCP/HTTP sur la machine exécutant le serveur HTTP. Les CGI sont souvent rédigés dans des langages de script (il serait possible d'utiliser un tel script ici), mais le module d'interfaçage est écrit en C++, même si son code est très simple: il ne fait que "rediriger" la requête (reçue par POST) au serveur TCP (par défaut, il redirige la requête sur le serveur TCP tournant sur la même machine, mais il suffit de modifier le source (cgtserver_cgi.cpp) pour rediriger la requête vers un serveur TCP d'une autre machine). Le source de ce CGI réside dans le répertoire samples/cgiserver.

Dans ce répertoire, il y a aussi un fichier query.html qui peut être utilisé pour transmettre une requête à un serveur Cogitant à partir d'un navigateur. Pour utiliser cette page html, le cgi doit être exécutable par le serveur HTTP dans le même répertoire (voir ci-dessous).

Configuration de Apache

Exécution et test

Lancez le serveur TCP (dans samples/doc), en tapant server_1. Il est alors possible de charger la page de test dans un navigateur (http://127.0.0.1/cogitant/query.html). Si vous ne voyez pas la page, vous avez mal configuré l'alias (avez-vous pensé à redémarrer apache ?) ou le lien.
L'exécution d'une requête doit provoquer une réponse (ou une erreur si la requête ne respecte pas la dtd cogitantcs.dtd, mais pas une erreur de type unknownserver, qui signale que le module d'interfaçage HTTP/TCP n'a pas trouvé le serveur TCP). Notez que le serveur est prévu pour communiquer avec des clients qui respectent la DTD, et que donc, si des requêtes incorrectes sont transmises au serveur, ceci peut provoquer des comportements étranges voire un blocage du serveur. Notez que la plupart des navigateurs (Mozilla, IE, etc.) n'affichent pas les documents XML dans la zone habituelle, et qu'il faut choisir l'affichage du source pour voir la réponse du serveur. Vous pouvez aussi utiliser l'application client de test, située dans test/client et appelée client, qui communique avec le serveur par TCP ou HTTP. Vous pouvez utiliser les paramètres passés à ce programme pour indiquer un nom de machine, un numéro de port, l'utilisation du protocole HTTP et l'URL (syntaxe : client [http] [http://<host>[:<port>]/<dir>/cgtserver.cgi] [tcp [<host_tcp>[<port_tcp>]]]).

Programmation

Les classes permettant l'échange entre serveur et client sont toutes dans l'espace de nom cogitantcs. Elles permettent d'utiliser des supports, graphes et règles distants (sur le serveur) comme s'ils étaient locaux (sur le client). Dès qu'un objet distant est nécessaire sur le client, une requête est envoyée par le client, et le serveur lui retourne les caractéristiques de cet objet. Se reporter aux exemples (samples/doc/server_*.cpp) et à la documentation des classes de l'espace de nom cogitantcs pour plus d'informations.

Programmation du serveur

La gestion du serveur est fournie par la classe cogitantcs::Server. Cette classe gère la réception des messages des clients, l'exécution des requêtes et l'envoi des réponses. À chaque serveur est associé un ensemble de cogitant::Environment, et c'est le contenu de ces environnements (support, graphes et règles ainsi que les opérations correspondantes) qui est offert aux clients. Afin de pouvoir adapter un serveur à différents usages, il est possible de personnaliser certaines aspects du serveur, comme les opérations disponibles et offertes au serveur et le mode de réception et d'émission.

De façon générale, l'exécution du serveur est lancée par l'instanciation de cogitantcs::Server (ou une sous-classe, voir plus loin), l'appel à la méthode cogitantcs::Server::addStdOperations() ou cogitantcs::Server::addMinOperations(), et enfin l'appel à cogitantcs::Server::mainLoop().

// server_1.cpp
// To be able to test this program, the current directory must be "samples/doc"
// as all the names of the BCGCT files are given relatively to this directory.
#include <iostream>
int main(int, char* [])
{
server.addStdOperations();
try
{
server.mainLoop();
}
catch (cogitant::Exception & e)
{
std::cerr << e << std::endl;
}
return 0;
}

La méthode addStdOperations() permet de lancer un serveur qui offre toutes les opérations standards fournies avec la bibliothèque, alors que la méthode addMinOperations() crée un serveur qui n'offre que les opérations minimales, c'est-à-dire celles d'accès (en lecture seule) aux supports et graphes du serveur. Dans ce dernier cas, les clients ne peuvent modifier les données offertes, il est donc nécessaire, avant d'appeler mainLoop(), de construire un (des) environnements. Pour cela, il suffit d'initialiser un environnement de la façon habituelle, puis d'ajouter cet environnement au serveur en appelant cogitantcs::Server::addEnvironment() (voir l'exemple ci-dessous).

// server_init.cpp
// To be able to test this program, the current directory must be "samples"
// as all the names of the BCGCT files are given relatively to this directory.
#include <iostream>
using namespace std;
int main(int, char* [])
{
try
{
env->readSupport("bcgct/bucolic/bucolic.bcs");
env->readGraphs("bcgct/bucolic/sleepandfish.bcg");
}
catch (cogitant::Exception const & e)
{
cerr << e;
return 1;
}
server.addStdOperations();
server.addEnvironment(env, "env_bucolic");
server.mainLoop();
return 0;
}

Très souvent, il est nécessaire d'ajouter de nouvelles opérations aux serveurs, par exemple, des opérations particulières d'interrogation d'une base de graphes, ou des opérations particulières d'entrées sorties. De telles opérations ne font pas partie du protocole "standard" de Cogitant mais peuvent être utilisées par un client "particulier" pour s'adresser à un serveur "particulier". Comme toutes les opérations de Cogitant, de telles opérations doivent être des sous-classes de cogitant::OperationBase. En fait, les opérations du serveur doivent être des sous-classes de cogitantcs::OpeServer. En utilisant ce mécanisme, il n'est pas nécessaire d'écrire une sous-classe de Server pour rajouter de nouvelles opérations, il suffit d'écrire une sous-classe de OpeServer correspondant à l'opération désirée, d'instancier cette classe, et de rajouter cette instance au serveur à l'aide de la méthode cogitantcs::Server::addOperation(). De cette façon, l'ajout de nouvelles opérations, ou la modification d'opérations existantes est simplifié, et peut même être fait dynamiquement, au cours de l'exécution du serveur. Pour définir une nouvelle opération il est donc nécessaire de définir une sous-classe de cogitantcs::OpeServer. Plus précisément, il est nécessaire de redéfinir deux méthodes dans la sous-classe à créer :

S'il est possible de personnaliser le fonctionnement d'un serveur en rajoutant de nouvelles opérations, il est aussi possible de le personnaliser en utilisant des fonctions particulières de transport des messages XML. En effet, selon l'application, la communication entre clients et serveur peut se faire de différentes façons. Cogitant est fourni avec un serveur pouvant être interrogé par le biais d'une liaison TCP (avec Common C++) et un client pouvant se connecter au serveur par le biais d'une liaison TCP ou HTTP (toujours avec Common C++). Il est possible de rajouter de nouvelles méthodes de transport, par exemple pour utiliser des bibliothèques autres que Common C++ ou en "attaquant" directement la couche sockets du système d'exploitation, ou pour utiliser d'autres méthodes de communications qui permettent de transporter des documents XML (par exemple des tubes, ou des tubes nommés). L'ajout de nouvelles méthodes de transport est très simple, car les opérations de transport ne font pas partie du serveur ou du client, mais elles sont déportées dans la classe abstraite cogitantcs::OperationCSIO (client server input output). À tout serveur et à tout client est associée une instance d'une sous-classe de cogitantcs::OperationCSIO. Et à chaque fois que le client ou le serveur doit envoyer des données, recevoir des données, ou se mettre en attente de connexions, il appelle des méthodes sur cette instance de OperationCSIO). Pour définir de nouvelles méthodes de transport d'information, il suffit donc de définir une sous-classe de cogitantcs::OperationCSIO et plus précisément de redéfinir les méthodes suivantes :

À titre d'exemple, cogitantcs::OperationCSIO_Simple fournit les opérations d'entrées sorties à partir de flux C++. Le constructeur de cette classe prend deux paramètres : un ostream et un istream. En utilisant cette opération d'entrées sorties, il est par exemple possible d'utiliser un serveur sur cout et cin, de lui passer des requêtes sur l'entrée standard, et de récupérer les résultats sur la sortie standard. Cette opération n'est toutefois pas utilisable pour la communication réelle avec un client. Pour de véritables connexions, on utilisera la classe cogitantcs::OperationCSIO_TCP qui se base sur Common C++ pour fournir des entrées sorties sur TCP. Cette classe est munie de deux constructeurs cogitantcs::OperationCSIO_TCP::OperationCSIO_TCP(), le premier ne prend qu'un paramètre entier, et doit être utilisé pour instancier l'opération devant être utilisée dans un serveur. L'entier est alors le numéro de port d'attente du serveur. Le second constructeur prend deux paramètres : une chaîne de caractères et un entier. Ce deuxième constructeur doit être utilisé dans un client. Le premier paramètre passé au constructeur sera alors le nom du serveur auquel devra se connecter le client, et le second paramètre le numéro de port. Pour utiliser une classe particulière de transport, il suffit d'instancier la classe choisie, et de passer un pointeur sur cette instance au constructeur de cogitantcs::Server ou cogitantcs::Client. Il est possible de faire la même chose plus simplement, en utilisant la classe cogitantcs::Server_TCP dont le constructeur ne prend qu'un paramètre, le numéro de port d'attente (voir l'exemple ci-dessus). Notez toutefois que l'utilisation de Server_TCP n'est qu'un raccourci et est exactement équivalente à l'instanciation de cogitantcs::OperationCSIO_TCP et le passage de l'instance créée au constructeur cogitantcs::Server::Server().

Programmation du client

Le client (cogitantcs::Client) peut être utilisé de différentes façons, plus ou moins simples et permettant d'accéder à plus ou moins de fonctions.

Évidemment, ces trois modes d'utilisation correspondent à une seule et même méthode de communication avec le serveur, l'échange de messages XML en utilisant la couche de transport.

Comme dans le cas d'un serveur, le transport de messages XML est effectué par une sous-classe de cogitantcs::OperationCSIO. Le constructeur de Client prend donc comme paramètre un pointeur sur une instance d'une sous-classe de OperationCSIO. Toutefois, afin d'utiliser plus simplement un client TCP, la classe cogitantcs::Client_TCP est définie, et son constructeur prend deux paramètres : le nom de l'hôte sur lequel est exécuté le serveur, et le numéro de port d'attente du serveur. De même, la classe cogitantcs::Client_HTTP permet de se connecter simplement à un serveur par HTTP. Le constructeur de cette classe ne prend qu'un seul paramètre, une chaîne de caractères qui doit contenir l'URL du CGI d'interfaçage.

Pour utiliser un client, il ne faut pas instancier la classe cogitant::Environment, car le client requiert l'utilisation de structures de données spécifiques. Ces classes particulières (cogitantcs::SetClient et ses sous-classes, cogitantcs::EnvironmentAllocatorClient) fournissent des ensembles (de types, de graphes) qui interrogent le serveur dès qu'un élément requis n'est pas connu du client. Ainsi, quand un client se connecte à un serveur, il ne transfère pas la totalité du support et des graphes, mais les objets sont transférés dès que nécessaire. Il ne faut donc pas instancier Environment, mais utiliser la méthode cogitantcs::Client::environment() qui retourne (un pointeur sur) l'environnement géré par le client. Mais cet environnement ne peut être utilisé dès la connexion au serveur. En effet, un serveur peut gérer plusieurs environnements, il est donc nécessaire de sélectionner lequel de ses environnements est choisi par le client. Ceci se fait par la méthode cogitantcs::Client::setServerEnvironment(). Cette méthode est surchargée : elle peut prendre comme paramètre un entier (numéro de l'environnement côté serveur, c'est-à-dire le résultat de cogitantcs::Server::addEnvironment()) ou une chaîne de caractères (nom de l'environnement, c'est-à-dire la chaîne passée en second paramètre à cogitantcs::Server::addEnvironment()). Ainsi l'exemple ci-dessous se connecte à un serveur et sélectionne l'environnement de nom env_bucolic.

// To use with server_init
#include <iostream>
int main(int , char* [])
{
cogitantcs::Client_TCP client("127.0.0.1", 4246);
client.setServerEnvironment("env_bucolic");
cogitant::Environment * env = client.environment();
std::cout << *(env->support()) << std::endl;
env->writeGraph(std::cout, 0);
return 0;
}

Comme on le voit sur cet exemple, une fois que le client est initialisé, il est possible d'accéder aux objets de l'environnement de la façon habituelle. Ainsi, l'exécution de ce programme (dont le résultat est donné ci-dessous) affiche le support et le premier graphe (au format linéaire), de la même façon que si ce support et ce graphe étaient chargés "localement".

cogitant::Support
 ConceptTypes : 0:Universal, 1:Action, 2:Attribute, 3:Boat, 4:Bucolic, 5:Couple,
 6:Entity, 7:Fish, 8:Lake, 9:Living being, 10:Painting, 11:Person, 12:Place, 13:
Scene, 14:Sleep, 15:Think
 PartialOrder : (0 (1, 2, 6)) (1 (15, 14, 7)) (2 (4)) (3 ()) (4 ()) (5 ()) (6 (1
3, 5, 3, 12, 10, 9)) (7 ()) (8 ()) (9 (11, 7)) (10 ()) (11 ()) (12 (8)) (13 ())
(14 ()) (15 ())
 RelationTypes : 0:agent, 1:attr, 2:in, 3:object, 4:on
 PartialOrder : (0 ()) (1 ()) (2 ()) (3 ()) (4 ())
 NestingTypes : 0:Description, 1:Component, 2:Representation
 PartialOrder : (0 (1, 2)) (1 ()) (2 ())
 Individuals : 0:Peter(11), 1:A(10)

sleepandfish:
        [Person:Peter]<-(agent)<-[Sleep]
        [Person]<-(agent)<-[Fish]->(in)->[Lake].

Dès qu'un accès sera effectué sur un objet de l'environnement qui n'est pas présent sur le client, une requête sera automatiquement générée en direction du serveur, qui retournera au client la description de cet objet. L'environnement client est donc une image de l'environnement serveur, cette image étant éventuellement incomplète car certains objets n'ont pas (encore) été transférés. Ceci a toutefois une conséquence : il est interdit de rajouter ou supprimer des objets dans l'environnement client, car dans ce cas, l'environnement client ne serait plus une image de l'environnement serveur. De plus, dans le cas où plusieurs clients sont connectés à un même serveur, il serait très difficile et coûteux de maintenir une cohérence entre tous les environnements. C'est pourquoi les seuls transferts automatiques se font dans le sens serveur vers client, et c'est l'environnement serveur qui fait foi.

Cependant, dans certains cas, il peut être nécessaire, à partir d'un client, de modifier les objets du serveur (modification d'un graphe existant, ajout ou suppression d'un graphe, chargement d'un support, etc.). Pour de tels usages, la classe cogitantcs::Client contient des méthodes telles cogitantcs::Client::newEnvironmentObject() qui permettent de modifier l'environnement serveur (et donc, indirectement, l'environnement client). Il est alors possible d'accéder à des fonctions qui ne sont pas disponibles à partir de l'environnement. Par exemple, si côté client un graphe est modifié, ces modifications ne sont pas répercutées sur le serveur car effectuées sur la copie locale. En utilisant la méthode cogitantcs::Client::commitEnvironmentObject(), il est possible de transférer au serveur les modifications. Le graphe correspondant côté serveur est alors modifié, à condition que le serveur accepte les modifications venant des clients. Une telle modification de l'environnement serveur ne se fait pas de façon automatique : il est nécessaire d'appeler explicitement cette méthode. Ainsi, les problèmes de concurrence (mise à jour d'un même objet par deux clients connectés à un même serveur par exemple) sont à la charge du programmeur. Se reporter à la documentation de cogitantcs::Client pour connaître toutes les méthodes permettant d'interagir avec le serveur.

Dans certains cas, il est préférable de ne disposer d'aucun mécanisme de mise à jour automatique, et d'effectuer explicitement toutes les mises à jour, que ce soit dans un sens ou dans l'autre. Entre autres inconvénients de la mise à jour automatique, les graphes portent le même identifiant (iSet) dans le serveur et dans le client, ce qui empêche d'en créer de nouveaux ou d'en détruire certains dans le client (sauf en passant par des méthodes d'accès au serveur, ce qui peut dans certains cas devenir lourd). Dans le cas où le client n'a besoin que de quelques graphes, il peut être plus simple de désactiver la mise à jour automatique, de récupérer certains graphes du serveur (qui ont dans le client un identifiant qui est éventuellement différent de leur identifiant dans le serveur), d'en créer localement ou d'en charger à partir d'un fichier local, puis envoyer certains de ces graphes au serveur afin de lui faire effectuer certains traitements. Il est possible de désactiver les mises à jour automatiques séparément pour le support et pour les objets de l'environnement. Il faut pour cela utiliser les deux derniers paramètres du constructeur de cogitantcs::Client (et ses sous-classes, comme cogitantcs::Client_TCP::Client_TCP()), comme cela est décrit dans les deux exemples ci-dessous.

Exemple. L'exemple ci-dessous crée un client dans lequel support et graphs sont différents de ceux manipulés par le serveur. De cette façon, le client peut modifier "son" support (en chargeant un fichier à l'aide de cogitant::Environment::readSupport()) et envoyer le support obtenu au serveur (cogitantcs::Client::commitSupport()). Côté serveur, l'environnement qui a été créé pour l'occasion (par cogitantcs::Client::newEnvironment()) reçoit le serveur envoyé par le client. Pour le vérifier, un deuxième client est créé et sélectionne l'environnement créé précédemment sur le serveur. En affichant alors le support associé à ce client, on obtient le support qui a été envoyé par le premier client.

// A utiliser avec server_1
#include <iostream>
int main(int , char* [])
{
try
{
cogitantcs::Client_TCP client("127.0.0.1", 4246, true, true);
cogitant::iSet ne = client.newEnvironment();
client.setServerEnvironment(ne);
client.environment()->readSupport("bcgct/sisyphus/sisyphus.bcs");
client.commitSupport();
client.close();
cogitantcs::Client_TCP client2("127.0.0.1", 4246);
client2.setServerEnvironment(ne);
cogitant::Environment * env = client2.environment();
std::cout << *(env->support()) << std::endl;
}
catch (cogitant::Exception const & e)
{
std::cout << e << std::endl;
}
return 0;
}

Exemple. Ce deuxième exemple illustre l'utilisation d'un support obtenu à partir du serveur, et de graphes (et règles) locaux au client. Ici, le client charge (en local) un graphe et une règle, et envoie ces deux objets au serveur (cogitantcs::Client::commitEnvironmentObject()), après avoir obtenu du serveur deux identifiants pour stocker ces deux objets (cogitantcs::Client::newEnvironmentObject()). Le graphe chargé à partir du client et envoyé au serveur est ensuite sauvegardé côté serveur (cogitantcs::Client::saveEnvironmentObject()). Le deuxième client permet de vérifier le résultat des opérations du premier : le graphe et la règle sont disponibles sur le serveur.

// To use with server_1
#include <iostream>
int main(int , char* [])
{
// Distant support; local graphs and rules
cogitantcs::Client_TCP client("127.0.0.1", 4246, false, true);
cogitant::iSet ne = client.newEnvironment();
client.loadSupport("bcgct/sisyphus/sisyphus.bcs", ne);
client.setServerEnvironment(ne);
cogitant::Environment * env = client.environment();
cogitant::iSet igraph = env->readGraphs("bcgct/sisyphus/locals.bcg");
cogitant::iSet irule = env->readGraphs("bcgct/sisyphus/near_1.bcr");
cogitant::iSet igraph_server = client.newEnvironmentObject();
client.commitEnvironmentObject(igraph, igraph_server);
client.saveEnvironmentObject("tmp.bcg", igraph_server);
cogitant::iSet irule_server = client.newEnvironmentObject();
client.commitEnvironmentObject(irule, irule_server);
client.close();
// A new client tries to access objects of the same environment
cogitantcs::Client_TCP client2("127.0.0.1", 4246);
client2.setServerEnvironment(ne);
env = client2.environment();
std::cout << *(env->graphs(igraph_server)) << std::endl;
std::cout << *(env->rules(irule_server)) << std::endl;
return 0;
}

Enfin, le troisième mode d'échange avec le serveur consiste à générer explicitement une requête sous la forme d'un document XML. En effet, les méthodes de cogitantcs::Client ne concernent que les messages "standards" de Cogitant. Si de nouvelles opérations sont définies dans le serveur, il est nécessaire d'envoyer des messages particuliers. Pour cela, on peut utiliser cogitantcs::Client::addPendingQuery() qui rajoute au document XML requête "en attente" une balise d'exécution d'opération et cogitantcs::Client::answer() qui retourne le document XML réponse reçu du serveur.

Il est possible aussi d'envoyer des messages XML à partir d'une application qui n'est pas basée sur Cogitant, pour utiliser par exemple les fonctions offertes par un serveur à partir d'une application Java. L'exemple ci-dessous, qui doit être utilisé conjointement avec le serveur server_1, permet de créer un environnement, charger un graphe et l'envoyer vers le client à partir d'un client java qui se contente d'envoyer et recevoir des messages XML.

// To use with server_init
import java.net.Socket;
import java.io.PrintStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Vector;
public class SimpleClient
{
private Socket m_socket;
private PrintStream m_os;
private BufferedReader m_is;
boolean m_trace;
public SimpleClient() throws IOException
{
m_socket = new Socket("127.0.0.1", 4246);
m_os = new PrintStream(m_socket.getOutputStream());
m_is = new BufferedReader(new java.io.InputStreamReader(m_socket.getInputStream()));
m_trace = true;
}
// Send the request and return the answer of the server
public Vector sendQuery(String q) throws IOException
{
m_os.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>");
m_os.println("<!DOCTYPE cogitantquery SYSTEM cogitantcs.dtd>");
m_os.println("<cogitantquery requiresheader=\"0\">");
m_os.println("\t<" + q + "/>");
m_os.println("</cogitantquery>");
if (m_trace)
System.out.println("--> " + q);
return getAnswer();
}
// Reading the server response
public Vector getAnswer() throws IOException
{
Vector v = new Vector();
String tmp;
do
{
tmp = m_is.readLine();
v.add(tmp);
} while (! tmp.equals("</cogitantanswer>"));
// The end of a cogitantanswer message is marked by a EOF character,
// which is passed to the next call to read()
m_is.read();
if (m_trace)
{
System.out.println("<-- " + v.get(0));
for (int i=1; i<v.size(); i++)
System.out.println(" " + v.get(i));
}
return v;
}
public static void main(String args[]) throws IOException
{
SimpleClient sc = new SimpleClient();
System.out.println("*** Querying the number of available environments on the server");
sc.sendQuery("qserver");
System.out.println("*** Creating a new environment");
sc.sendQuery("qnewenvironment");
// We should retrieve the identifier of the environment created
// from the server answer (in analysing the XML tags received).
// Here, we assume that it is 0.
int idenv = 0;
System.out.println("*** Loading a support");
sc.sendQuery("qloadsupport env=\"" + idenv + "\" file=\"bcgct/bucolic/bucolic.bcs\"");
System.out.println("*** Loading a graph");
sc.sendQuery("qloadgraphs env=\"" + idenv + "\" file=\"bcgct/bucolic/simplequery.bcg\"");
// We should retrieve the identifier of the read graph.
// Here, we assume that it is 0.
int idgra = 0;
System.out.println("*** Downloading this graph");
sc.sendQuery("qenvironmentobject env=\"" + idenv + "\" id=\"" + idgra + "\"");
System.out.println("*** Deleting the environment");
sc.sendQuery("qdeleteenvironment env=\"0\"");
}
};

L'exemple ci-dessus est très simplifié, car les réponses reçues du serveur ne sont pas analysées, et sont affichées telles quelles à l'écran. Il existe toutefois un grand nombre de bibliothèques Java qui permettent d'analyser du XML, et en les utilisant, on peut alors facilement interpréter les réponses du serveur, pour déterminer si le graphe a été correctement chargé, quels sont les sommets qui composent le graphe et construire une représentation "locale au client" du graphe, etc.

Protocole d'échange

Les échanges entre serveur et client se font à l'aide de messages XML. La DTD suivante documente les messages pouvant être envoyés par le client et par le serveur. Le client ne peut envoyer que des documents ayant pour racine une balise cogitantquery, et le serveur des documents ayant pour racine cogitantanswer.

<?xml version="1.0" encoding="UTF-8"?>

<!--

DTD CogitantCS 1.3

This DTD describes the exchange protocol between a Cogitant server and its
clients.

To apply this DTD, use the following syntax:
<!DOCTYPE cogitantquery PUBLIC "-//COGITANT//Cogitant Client-server Specification 1.3//EN" "http://cogitant.sourceforge.net/cogitantcs.dtd">
or
<!DOCTYPE cogitantanswer PUBLIC "-//COGITANT//Cogitant Client-server Specification 1.3//EN" "http://cogitant.sourceforge.net/cogitantcs.dtd">

This file is part of Cogitant which is a library facilitating construction of
applications using conceptual graphs.  It is under GPL licence.
http://cogitant.sourceforge.net
Cogitant version 5.2.0 - Last change of the DTD : 21/05/2009

-->


<!-- Extensions des attributs des balises standard. -->

<!ENTITY % cogitantQueryExtensions "">
<!ENTITY % cogitantAnswerExtensions "">


<!-- Document. -->

<!ELEMENT cogitantquery (qserver | qenvironment | qsetcontent | qconcepttype |
	qrelationtype | qnestingtype | qindividual | qenvironmentobject | qgraph |
	qrule | qcommitenvironmentobject | qcommitimmediateless | qcomparison |
	qimmediateless | qimmediategreater | qbannedobjects | qgraphobject
	qnewenvironment | qnewenvironmentobject | qloadsupport | qsavesupport |
	qloadgraphs | qsaveenvironmentobjects | qdeleteenvironment |
	qdeleteenvironmentobject | qnewsupportobject | qcommitsupportobject |
	qprojections | qruleapplications | qruleapply | qrulesclosure |
	qconstraintsatisfaction |
	qcopyenvironmentobject |
	qoperationconfig |
	qaddconcept | qaddrelation | qaddnesting | qdelgraphobject |
	qaddedge | qdeledge |
	%cogitantQueryExtensions; )*>
	<!-- reguiresheader : si égal à 0, le serveur n'envoie pas une entête
		destinée au serveur http (Content-type: ... Cet attribut doit donc être
		utilisé (avec la valeur 0) quand le serveur est utilisé en accès direct
		(TCP) pour ne pas recevoir ce header. -->
<!ATTLIST cogitantquery
	requiresheader CDATA '1'
>

<!ELEMENT cogitantanswer (server | environment | setcontent | concepttype |
	relationtype | nestingtype | individual | environmentobject | graph | rule |
	deletedobject | commitenvironmentobject | commitimmediateless | comparison |
	immediateless | immediategreater | bannedobjects | graphobject
	newenvironment | newenvironmentobject | loadsupport |
	loadgraphs | saveenvironmentobjects | deleteenvironment |
	deleteenvironmentobject | newsupportobject | commitsupportobject |
	projections | ruleapplications | ruleapply | rulesclosure |
	constraintsatisfaction |
	copyenvironmentobject |
	operationconfig |
	addconcept | addrelation | addnesting | delgraphobject |
	addedge | deledge |
	error %cogitantAnswerExtensions;)*>
<!ATTLIST cogitantanswer
>

<!ELEMENT error EMPTY>
<!ATTLIST error
	<!-- syntax : syntaxe xml non respectée dans le message requête.
		 environmentid : identificateur d'environnement inconnu (attribut env).
		 setid : identificateur d'ensemble inconnu (attribut set).
		 objectid : identificateur d'objet inconnu (par ex, dans un graphe, une
		  arête a pour extrémité un objet qui n'existe pas).
		 objecttype : l'identificateur d'objet se réfère à un objet du mauvais
		  type (par ex, vérification d'une contrainte avec l'id d'un objet de l'
		  environnement qui n'est pas une contrainte.
		 unknownserver : retourné par cgiserver quand il n'arrive pas à établir
		  une connexion avec le serveur TCP.
		 arg : contient un descriptif plus détailé de l'erreur (parfois). -->
	type (syntax | environmentid | setid | objectid | objecttype | unknownserver) #REQUIRED
	arg CDATA #IMPLIED
>

<!-- Propriétés d'un objet -->
<!ELEMENT properties (property*)>
	<!-- subid : Identificateur de sous-ensemble de propriétés. Si absent,
		l'élément prop désigne l'ensemble principal. -->
<!ATTLIST properties
	subid CDATA #IMPLIED
>

<!ELEMENT property EMPTY>
<!ATTLIST property
	type CDATA #IMPLIED
	value CDATA #IMPLIED
>

<!-- A partir de la DTD 1.3, certains attributs d'objets ne sont plus transférés comme
	des propriétés mais des attributs. Les attributs correspondent à des informations
	faisant partie du modèle (conformité d'un type de relation, par exemple), alors que
	les propriétés représentent des informations externes au modèle (position d'un sommet,
	par exemple). -->
<!ELEMENT attribute EMPTY>
<!ATTLIST attribute
	type CDATA #IMPLIED
	value CDATA #IMPLIED
>


<!-- Identificateur d'un objet de l'environnement. -->
<!ELEMENT eoid EMPTY>
<!ATTLIST eoid
	id CDATA #REQUIRED
>


<!-- Serveur. -->

<!ELEMENT qserver EMPTY>
<!ATTLIST qserver>

<!ELEMENT server (properties*)>
	<!-- environments : Nombre d'environnements disponibles sur le serveur -->
<!ATTLIST server
	environments CDATA #REQUIRED
>

<!-- Environnement. -->

<!ELEMENT qenvironment EMPTY>
	<!-- env (numéro de l'environnement) ou name doivent être donnés. -->
<!ATTLIST qenvironment
	env CDATA #IMPLIED
	name CDATA #IMPLIED
>

<!ELEMENT environment (properties*, support)>
	<!-- objects : Taille de l'ensemble d'EnvironmentObjects -->
<!ATTLIST environment
	env CDATA #REQUIRED
	objects CDATA #REQUIRED
	name CDATA #IMPLIED
>

<!ELEMENT support (properties*)>
	<!-- xxxtypes : Tailles des ensembles du support -->
<!ATTLIST support
	concepttypes CDATA #REQUIRED
	relationtypes CDATA #REQUIRED
	nestingtypes CDATA #REQUIRED
	individuals CDATA #REQUIRED
	bannedtypes CDATA #REQUIRED
>

<!-- Contenu d'un ensemble (de TC, de TR, de TN, de MI, de EnvironmentObject). -->

<!ELEMENT qsetcontent EMPTY>
	<!-- set : Numéro de l'ensemble = numéro de l'environnement * 10 +1 (TC)
		+2 (TR) +3 (TN) +4 (MI) +5 (EnvironmentObjects).
		 id : Identificateur dans l'ensemble de l'élément demandé ou "all" :
		tout le contenu de l'ensemble. -->
<!ATTLIST qsetcontent
	set CDATA #REQUIRED
	id CDATA #REQUIRED
>

<!-- Type de concept. -->

<!ELEMENT qconcepttype EMPTY>
	<!-- env : Numéro de l'environnement. (idem relationtype, nestingtype,
		individual, environmentobject)
		 id : Numéro dans l'ensemble de l'objet demandé ou "all" (tout le
		contenu de l'ensemble). (idem relationtype, nestingtype, individual,
		environmentobject) -->
<!ATTLIST qconcepttype
	env CDATA #REQUIRED
	id CDATA #REQUIRED
>

<!ELEMENT concepttype (attribute*, properties*)>
<!ATTLIST concepttype
	set CDATA #REQUIRED
	id CDATA #REQUIRED
>

<!-- Type de relation. -->

<!ELEMENT qrelationtype EMPTY>
<!ATTLIST qrelationtype
	env CDATA #REQUIRED
	id CDATA #REQUIRED
>

<!ELEMENT relationtype (attribute*, properties*)>
<!ATTLIST relationtype
	set CDATA #REQUIRED
	id CDATA #REQUIRED
>

<!-- Type d'emboîtement. -->

<!ELEMENT qnestingtype EMPTY>
<!ATTLIST qnestingtype
	env CDATA #REQUIRED
	id CDATA #REQUIRED
>

<!ELEMENT nestingtype (attribute*, properties*)>
<!ATTLIST nestingtype
	set CDATA #REQUIRED
	id CDATA #REQUIRED
>

<!-- Marqueur individuel. -->

<!ELEMENT qindividual EMPTY>
<!ATTLIST qindividual
	env CDATA #REQUIRED
	id CDATA #REQUIRED
>

<!ELEMENT individual (attribute*, properties*)>
<!ATTLIST individual
	set CDATA #REQUIRED
	id CDATA #REQUIRED
>

<!-- Élément de l'environnement. -->

<!ELEMENT qenvironmentobject EMPTY>
	<!-- env : Numéro de l'environnement côté serveur.
		 id : Numéro de l'objet dans l'environnement ou "all".
		 iddest : Numéro d'identifiant chez le client. N'est précisé que dans
		le cas où il est différent d'id. La valeur de cet attribut est
		simplement recopiée telle quelle par le serveur dans la réponse, et
		peut être utilisée par le client. Cet attribut ne doit pas être utilisé
		dans le cas ou id vaut "all". -->
<!ATTLIST qenvironmentobject
	env CDATA #REQUIRED
	id CDATA #REQUIRED
	iddest CDATA #IMPLIED
>

<!ELEMENT deletedobject EMPTY>
<!ATTLIST deletedobject
	set CDATA #REQUIRED
	id CDATA #REQUIRED
>

<!-- Graphe. -->

<!ELEMENT qgraph EMPTY>
	<!-- cf. environmentobject -->
<!ATTLIST qgraph
	env CDATA #REQUIRED
	id CDATA #REQUIRED
	iddest CDATA #IMPLIED
>

<!ELEMENT graph (properties*, (internalgraph | concept | relation | nesting | coreferenceclass)*)>
	<!-- size : Taille de l'ensemble de noeuds.
		 set, id : sont obligatoires quand le graphe est transféré en tant
		qu'objet de l'environment, sont interdits quand le graphe est
		transféré à l'intérieur d'une règle. -->
<!ATTLIST graph
	set CDATA #IMPLIED
	id CDATA #IMPLIED
	iddest CDATA #IMPLIED
	size CDATA #REQUIRED
>

<!ELEMENT internalgraph (properties*, edge*)>
<!ATTLIST internalgraph
	id CDATA #REQUIRED
>
<!ELEMENT concept (properties*, edge*)>
<!ATTLIST concept
	id CDATA #REQUIRED
>
<!ELEMENT relation (properties*, edge*)>
<!ATTLIST relation
	id CDATA #REQUIRED
>
<!ELEMENT nesting (properties*, edge*)>
<!ATTLIST nesting
	id CDATA #REQUIRED
>
<!ELEMENT coreferenceclass (properties*, edge*)>
<!ATTLIST coreferenceclass
	id CDATA #REQUIRED
>
	<!-- Attention, l'ordre des arêtes liées à un noeud d'un graphe est
		important, et doit être transféré "tel quel". -->
<!ELEMENT edge EMPTY>
	<!-- env : Extrémité de l'arête (identificateur de sommet, ou * pour
		ISET_NULL)
		 label : Étiquette de l'arête (entier (étiquette de l'arête) ou 'P'
		(lien parent) 'C' (lien enfant) ou '=' (lien de coreference). -->
<!ATTLIST edge
	end CDATA #REQUIRED
	label CDATA #REQUIRED
>

<!-- Règle. -->

<!ELEMENT qrule EMPTY>
	<!-- cf. environmentobject -->
<!ATTLIST qrule
	env CDATA #REQUIRED
	id CDATA #REQUIRED
	iddest CDATA #IMPLIED
>

<!ELEMENT rule (properties*, hypothesis, conclusion, conpt*)>
<!ATTLIST rule
	set CDATA #IMPLIED
	id CDATA #REQUIRED
	iddest CDATA #IMPLIED
>
<!ELEMENT hypothesis (graph)>
<!ATTLIST hypothesis>
<!ELEMENT conclusion (graph)>
<!ATTLIST conclusion>
<!ELEMENT conpt EMPTY>
<!ATTLIST conpt
	idc1 CDATA #REQUIRED
	idc2 CDATA #REQUIRED
>

<!-- Transfert d'un objet de l'environnement du client vers le serveur. -->

<!ELEMENT qcommitenvironmentobject (graph | rule)>
<!ATTLIST qcommitenvironmentobject
	env CDATA #REQUIRED
>

<!ELEMENT commitenvironmentobject EMPTY>
<!ATTLIST commitenvironmentobject 
	error CDATA #IMPLIED>


<!-- Accès aux plus petits immédiats d'un type. -->

<!ELEMENT qcommitimmediateless (qilid*)>
	<!-- tp= "c": type de concept, "r": type de relation, "n": type d'emboîtement -->
<!ATTLIST qcommitimmediateless
	env CDATA #REQUIRED
	tp CDATA #REQUIRED
>

<!ELEMENT qilid EMPTY>
	<!-- id1 > id2
		 setunset= "s" fixer id1 > id2, "u" supprimer id1 > id2 -->
<!ATTLIST qilid
	id1 CDATA #REQUIRED
	id2 CDATA #REQUIRED
	setunset CDATA 's'
>

<!ELEMENT qcommitimmediateless>
<!ATTLIST qcommitimmediateless
	error CDATA #IMPLIED>


<!-- Comparaison de deux types. -->

<!ELEMENT qcomparison EMPTY>
	<!-- set : Numéro de l'ensemble = numéro de l'environnement * 10 +1 (TC)
		+2 (TR) +3 (TN). -->
<!ATTLIST qcomparison
	set CDATA #REQUIRED
	id1 CDATA #REQUIRED
	id2 CDATA #REQUIRED
>

<!ELEMENT comparison EMPTY>
<!ATTLIST comparison
	set CDATA #REQUIRED
	id1 CDATA #REQUIRED
	id2 CDATA #REQUIRED
	result CDATA #REQUIRED
>

<!-- Accès aux plus petits immédiats d'un type. -->

<!ELEMENT qimmediateless EMPTY>
	<!-- set : Numéro de l'ensemble = numéro de l'environnement * 10 +1 (TC)
		+2 (TR) +3 (TN).
		 id : identificateur du type ou "all". -->
<!ATTLIST qimmediateless
	set CDATA #REQUIRED
	id CDATA #REQUIRED
>

<!ELEMENT immediateless (ilid*)>
<!ATTLIST immediateless
	set CDATA #REQUIRED
	id CDATA #REQUIRED
>
<!ELEMENT ilid EMPTY>
<!ATTLIST ilid
	id CDATA #REQUIRED
>

<!-- Accès aux plus grands immédiats d'un type. -->

<!ELEMENT qimmediategreater EMPTY>
	<!-- set : Numéro de l'ensemble = numéro de l'environnement * 10 +1 (TC)
		+2 (TR) +3 (TN).
		 id : identificateur du type ou "all". -->
<!ATTLIST qimmediategrater
	set CDATA #REQUIRED
	id CDATA #REQUIRED
>

<!ELEMENT immediategreater (igid*)>
<!ATTLIST immediategreater
	set CDATA #REQUIRED
	id CDATA #REQUIRED
>
<!ELEMENT igid EMPTY>
<!ATTLIST igid
	id CDATA #REQUIRED
>

<!-- Accès aux types interdits. -->

<!ELEMENT qbannedtypes EMPTY>
	<!-- env : identificateur de l'environnement à interroger. -->
<!ATTLIST qbannedtypes
	env CDATA #REQUIRED>

<!ELEMENT bannedtypes (bannedtype*)>
<!ATTLIST bannedtypes
	env CDATA #REQUIRED>
	<!-- L'élément bannedtypes contient autant d'éléments bannedtype qu'il y a
	de types interdits. -->
<!ELEMENT bannedtype (btid*)>
	<!-- Chaque élément bannedtype contient des éléments btid pour chaque
	identificateur de type primitif composant le type conjonctif interdit. -->
<!ELEMENT btid EMPTY>
<!ATTLIST btid
	id CDATA #REQUIRED
>

<!-- Accès à un objet (concept, relation, graphe interne, emboîtement, classe
de coref) composant un graphe. -->

<!ELEMENT qgraphobject EMPTY>
	<!-- env : Numéro de l'environnement côté serveur.
	     idgraph : Identificateur du graphe.
		 idobject : Identificateur de l'objet l'intérieur du graphe. -->
<!ATTLIST qgraphobject
	env CDATA #REQUIRED
	idgraph CDATA #REQUIRED
	idobject CDATA #IMPLIED
>
<!ELEMENT graphobject (internalgraph | concept | relation | nesting | coreferenceclass)>
<!ATTLIST graphobject
	env CDATA #REQUIRED
	idgraph CDATA #REQUIRED
>

<!-- Création d'un nouvel environnement. -->

<!ELEMENT qnewenvironment EMPTY>
<!ATTLIST qnewenvironment
	<!-- Optimisation automatique de l'ordre partiel des types de concepts. Par
	défaut, oui. Attention, s'il est optimisé, il ne doit plus être modifié.
	-->
	optorderc CDATA "true"
	<!-- Idem, sur les types de relations. -->
	optorderr CDATA "true"
	<!-- Idem, sur les types d'emboîtements. -->
	optordern CDATA "true"
	<!-- Optimisation automatique pour la recherche rapide de types de concepts
	par leur intitulé. -->
	optlabelc CDATA "true"
	<!-- Idem, sur les types de relations. -->
	optlabelr CDATA "true"
	<!-- Idem, sur les types d'emboîtements. -->
	optlabeln CDATA "true"
	<!-- Idem, sur les marqueurs individuels. -->
	optlabeli CDATA "true"
>

<!ELEMENT newenvironment EMPTY>
<!ATTLIST newenvironment
	env CDATA #REQUIRED
>

<!-- Création d'un nouvel objet dans l'environnement. -->

<!ELEMENT qnewenvironmentobject EMPTY>
	<!-- tp= "g": graphe (par défaut), "r": règle, "p": contrainte positive,
	"n": contrainte négative. -->
<!ATTLIST qnewenvironmentobject
	env CDATA #REQUIRED
	tp (g | r | p | n) "g"
>

<!ELEMENT newenvironmentobject EMPTY>
<!ATTLIST newenvironemntobject
	id CDATA #REQUIRED
>

<!-- Chargement d'un support. -->

	<!-- Le support peut être chargé à partir d'un fichier disponible sur
l'hote serveur, en utilisant l'attribut file. Le support peut aussi être
transféré à partir du client. Dans ce cas, l'attribut file ne doit pas être
donné, et l'attribut embedded doit valoir "bcgct" ou "cogxml" ou "cgif". La balise
qloadsupport doit alors avoir comme fils le texte bcgct ou cogxml du support.
-->
<!ELEMENT qloadsupport (#PCDATA)>
<!ATTLIST qloadsupport
	env CDATA #REQUIRED
	file CDATA #IMPLIED
	embedded CDATA #IMPLIED
>

<!ELEMENT loadsupport EMPTY>
<!ATTLIST loadsupport
	error CDATA #IMPLIED
>

<!-- Sauvegarde du support côté serveur.
 Si l'attribut embedded est passé, il doit valoir "bcgct", "cogxml" ou "cgif".
 Dans ce cas, l'attribut "file" ne doit pas être passé, et le resultat de
 l'écriture dans le format choisi n'est pas enregistré sur disque, mais
 transmis dans la réponse saveenvironmentobjects comme texte emboîté (dans un
 PCDATA). -->

<!ELEMENT qsavesupport EMPTY>
<!ATTLIST qsavesupport
	env CDATA #REQUIRED
	file CDATA #IMPLIED
	embedded CDATA #IMPLIED
>

<!ELEMENT savesupport (#PCDATA)>
<!ATTLIST savesupport
	error CDATA #IMPLIED
>

<!-- Chargement de graphes (ou règles).
 Transfert d'un graphe en PCDATA par utilisation de l'attribut embedded.
-->

<!ELEMENT qloadgraphs (#PCDATA)>
<!ATTLIST qloadgraphs
	env CDATA #REQUIRED
	file CDATA #IMPLIED
	embedded CDATA #IMPLIED
>

<!ELEMENT loadgraphs (loadedgraph*)>
<!ATTLIST loadgraphs
	error CDATA #IMPLIED
>

<!ELEMENT loadedgraph EMPTY>
<!ATTLIST loadedgraph
	id CDATA #REQUIRED
>

<!-- Sauvegarde d'un graphe (ou règle).
 Si l'attribut embedded est passé, il doit valoir "bcgct", "cogxml" ou "cgif".
 Dans ce cas, l'attribut "file" ne doit pas être passé, et le resultat de
 l'écriture dans le format choisi n'est pas enregistré sur disque, mais
 transmis dans la réponse saveenvironmentobjects comme texte emboîté (dans un
 PCDATA). -->

<!ELEMENT qsaveenvironmentobjects (eoid*)>
<!ATTLIST qsaveenvironmentobjects
	env CDATA #REQUIRED
	file CDATA #IMPLIED
	id CDATA #IMPLIED
>

<!ELEMENT saveenvironmentobjects (#PCDATA)>
<!ATTLIST saveenvironmentobjects
	error CDATA #IMPLIED
>

<!-- Destruction d'un environnement. -->

<!ELEMENT qdeleteenvironment EMPTY>
<!ATTLIST qdeleteenvironment
	env CDATA #REQUIRED
>

<!ELEMENT deleteenvironment EMPTY>
<!ATTLIST qdeleteenvironment
>

<!-- Destruction d'un objet de l'environnement -->

<!ELEMENT qdeleteenvironmentobject EMPTY>
<!ATTLIST qdeleteenvironmentobject
	env CDATA #REQUIRED
	id CDATA #REQUIRED
>

<!ELEMENT deleteenvironmentobject EMPTY>
<!ATTLIST deleteenvironmentobject
	error CDATA #IMPLIED
>

<!-- Création d'un nouveau type ou marqueur -->

<!ELEMENT qnewsupportobject EMPTY>
	<!-- tp= "c": type de concept, "r": type de relation, "n": type
	d'emboîtement, "i": marqueur individuel.
	Le label est l'intitulé (optionnel) de l'élément à créer. Si aucun
	intitulé n'est passé, utiliser qcommitsupportobject pour envoyer la
	propriété label. -->
<!ATTLIST qnewsupportobject
	env CDATA #REQUIRED
	tp CDATA #REQUIRED
	label CDATA #IMPLIED
>

<!ELEMENT newsupportobject EMPTY>
<!ATTLIST newsupportobject
	id CDATA #REQUIRED
>

<!-- Transfert d'un objet du support (TC, TR, TN, I) du client vers le serveur. -->

<!ELEMENT qcommitsupportobject (properties*)>
<!ATTLIST qcommitsupportobject 
	env CDATA #REQUIRED
	tp CDATA #REQUIRED
	id CDATA #REQUIRED
>

<!ELEMENT commitsupportobject EMPTY)
<!ATTLIST commitenvironmentobject 
	error CDATA #IMPLIED>
>


<!-- Projection. -->

<!ELEMENT projection (couple*)>
<!ELEMENT couple EMPTY>
<!ATTLIST couple
	id1 CDATA #REQUIRED
	id2 CDATA #REQUIRED
>

<!-- Demande du calcul des projections d'un graphe dans un autre. -->

<!ELEMENT qprojections (projectionconfig?)>
<!ATTLIST qprojections
	env CDATA #REQUIRED
	<!-- id1: graphe projeté. -->
	id1 CDATA #REQUIRED
	<!-- id2: graphe dans lequel on projette. -->
	id2 CDATA #REQUIRED
>

<!ELEMENT projectionconfig EMPTY>
<!ATTLIST projectionconfig
	<!-- Nombre max de projections trouvées. 0 pour ne pas limiter. -->
	maxsize CDATA #IMPLIED 0
	<!-- Si true les projections (ensemble de couples) sont retournées,
	     sinon, seul le nombre est retourné. -->
	memorize CDATA #IMPLIED "true"
>

<!ELEMENT projections (projection*)>
<!ATTLIST projections
	<!-- Le nombre de projections trouvées. -->
	size CDATA #REQUIRED
>

<!-- Demande du calcul des applications d'une règle dans un graphe. -->

<!ELEMENT qruleapplications (projectionconfig?)>
<!ATTLIST qruleapplications
	env CDATA #REQUIRED
	<!-- idr: identificateur de la règle. -->
	idr CDATA #REQUIRED
	<!-- idg: identificateur du graphe. -->
	idg CDATA #REQUIRED
>

<!ELEMENT ruleapplications (projection*)>
<!ATTLIST ruleapplications
	size CDATA #REQUIRED
>

<!-- Application d'une règle sur un graphe selon une projection. -->

<!ELEMENT qruleapply (projection)>
<!ATTLIST qruleapply
	env CDATA #REQUIRED
	idr CDATA #REQUIRED
	idg CDATA #REQUIRED
>

<!ELEMENT ruleapply EMPTY>
<!ATTLIST ruleapply 
	error CDATA #IMPLIED>
>

<!-- Fermeture d'un graphe par un ensemble de règles.-->

	<!-- Les éléments eoid fils de la balise doivent contenir les
	identificateurs des règles à appliquer. Si aucun eoid n'est précisé, toutes
	les règles de l'environnement sont utilisées. -->
<!ELEMENT qrulesclosure (eoid*)>
<!ATTLIST qrulesclosure
	env CDATA #REQUIRED
	<!-- Identificateur du graphe. L'application de l'opération modifie ce
	graphe. -->
	idg CDATA #REQUIRED
	<!-- Nombre maximum d'applications de règles. Par défaut 0, i.e.
	applications illimitées. -->
	maxa CDATA '0'
>

<!ELEMENT rulesclosure EMPTY>
<!ATTLIST rulesclosure 
	error CDATA #IMPLIED
>

<!-- Vérification de la validité d'un graphe par rapport à une contrainte. -->

<!ELEMENT qconstraintsatisfaction>
<!ATTLIST qconstraintsatisfaction
	env CDATA #REQUIRED
	<!-- idr: identificateur de la contrainte. -->
	idc CDATA #REQUIRED
	<!-- idg: identificateur du graphe. -->
	idg CDATA #REQUIRED
>

<!ELEMENT constraintsatisfaction>
<!ATTLIST constraintsatisfaction
	<!-- result: "true" si le graphe vérifie la contrainte, "false" sinon. -->
	result CDATA #REQUIRED
>


<!-- Copie d'un objet de l'environnement (graphe ou règle). -->

<!ELEMENT qcopyenvironmentobject EMPTY>
<!ATTLIST qcopyenvironmentobject
	env CDATA #REQUIRED
	idsrc CDATA #REQUIRED
	iddst CDATA #REQUIRED
>

<!ELEMENT copyenvironmentobject EMPTY>
<!ATTLIST copyenvironmentobject
	error CDATA #IMPLIED
>


<!-- Modification de la configuration d'une opération. -->

<!ELEMENT qoperationconfig EMPTY>
<!ATTLIST qoperationconfig
	env CDATA #REQUIRED
	<!-- ope: opération concernée, param: nom du paramètre à modifier, value: valeur. -->
	<!-- Les valeurs autorisées pour l'instant sont :
	     - ope : iohandler  param : graphobjectidentifiers  value : true | false
		   Si défini à true, l'opération de chargement de graphes conserve dans la propriété
		   Property::IDENTIFIER l'identificateur lu dans le fichier.
	-->
	ope (iohandler) #REQUIRED 
	param (graphobjectidentifiers) #REQUIRED
	value (true | false) #REQUIRED
>

<!ELEMENT operationconfig EMPTY>
<!ATTLIST operationconfig
	error CDATA #IMPLIED
>


<!-- Ajout d'éléments dans un graphe. -->

<!-- Ajout d'un sommet concept dans un graphe. -->

<!ELEMENT qaddconcept>
<!ATTLIST qaddconcept 
	env CDATA #REQUIRED
	<!-- idg : identificateur du graphe. -->
	idg CDATA #REQUIRED
	<!-- idp : identificateur de l'InternalGraph dans lequel le sommet doit
	être créé, 0 par défaut. -->
	idp CDATA "0"
	<!-- idt : type de concept. Il s'agit de l'iSet du type à créer. -->
	idt CDATA #REQUIRED
	<!-- idm : marqueur individuel. Ne pas utiliser cet attribut pour créer
	un sommet concept générique. Si cet attribut est utilisé, il doit contenir
	l'iSet du marqueur. -->
	idm CDATA #IMPLIED
>
<!ELEMENT addconcept EMPTY>
<!ATTLIST addconcept
	<!-- id : identificateur de l'objet créé dans le graphe. -->
	id CDATA #IMPLIED
	error CDATA #IMPLIED
>

<!-- Ajout d'un sommet relation, cf. qaddconcept. -->
<!ELEMENT qaddrelation>
<!ATTLIST qaddrelation 
	env CDATA #REQUIRED
	idg CDATA #REQUIRED
	idp CDATA "0"
	idt CDATA #REQUIRED
>
<!ELEMENT addrelation EMPTY>
<!ATTLIST addrelation
	id CDATA #IMPLIED
	error CDATA #IMPLIED
>

<!-- Ajout d'un emboîtement, cf. qaddconcept. -->
<!ELEMENT qaddnesting>
<!ATTLIST qaddnesting 
	env CDATA #REQUIRED
	idg CDATA #REQUIRED
	idp CDATA #REQUIRED
	idt CDATA #REQUIRED
>
<!ELEMENT addnesting EMPTY>
<!ATTLIST addnesting
	id CDATA #IMPLIED
	error CDATA #IMPLIED
>

<!-- Suppression d'un élément d'un graphe. -->

<!ELEMENT qdelgraphobject>
<!ATTLIST qdelgraphobject
	env CDATA #REQUIRED
	<!-- idg : identificateur du graphe. -->
	idg CDATA #REQUIRED
	<!-- id : identificateur de l'objet à détruire (iSet). -->
	id CDATA #REQUIRED
>
<!ATTLIST delgraphobject EMPTY>
<!ATTLIST delgraphobject
	error CDATA #IMPLIED
>


<!-- Ajout d'une arête au graphe. -->

<!ELEMENT qaddedge EMPTY>
<!ATTLIST qaddedge
	env CDATA #REQUIRED
	<!-- idg : identificateur du graphe. -->
	idg CDATA #REQUIRED
	<!-- idr : identificateur du sommet relation dans le graphe (iSet). -->
	idr CDATA #REQUIRED
	<!-- idr : identificateur du sommet concept dans le graphe (iSet). -->
	idc CDATA #REQUIRED
	<!-- lab : étiquette de l'arête à créer. L'étiquette doit avoir une valeur
	inférieure ou égale à l'arité du type de relation du sommet relation. -->
	lab CDATA #REQUIRED
>
<!ELEMENT addedge EMPTY>
<!ATTLIST addedge
	error CDATA #IMPLIED
>

<!-- Suppression d'une arête d'un graphe. -->

<!ELEMENT qdeledge>
<!ATTLIST qdeledge
	env CDATA #REQUIRED
	<!-- idg : identificateur du graphe. -->
	idg CDATA #REQUIRED
	<!-- idr : identificateur du sommet relation (iSet). -->
	idr CDATA #REQUIRED
	<!-- lab : étiquette de l'arête à supprimer. -->
	lab CDATA #REQUIRED
>
<!ATTLIST deledge EMPTY>
<!ATTLIST deledge
	error CDATA #IMPLIED
>