juillet
2009
Dans le billet précédant [step4] nous avons mis en place la page source de l’editeur et finalisé la synchronisation de l’instance EMF avec le contenu XML. Dans ce billet, nous allons :
- Modifier l’extension workflow en extension xml.
- Valider le contenu XML du workflow à l’aide du schéma XML workflow.xsd.
- Gérer plus finement l’état dirty de l’éditeur (problème réccurent dans un editeur multi-page).
Vous pouvez télécharger le projet org.example.workflow_step5.zip présenté dans ce billet.
Extension *.xml
Jusqu’à maintenant les extension des fichiers de workflow sont *.workflow. Si vous tentez de renommer le fichier My.workflow en My.xml et que vous essayez de l’ouvrir avec l’éditeur de workflow (Open With/Other…/Workflow Model Editor), vous aurrez l’erreur suivante :
Cette erreur s’explique par le fait que l’extension *.xml n’est pas reconnue par l’éditeur lors du chargement du modèle EMF WorkflowType. Le code qui permet de charger le modèle EMF dans une resource EMF, se trouve dans WorkflowEditor#createModel() :
URI resourceURI = EditUIUtil.getURI(getEditorInput());
Exception exception = null;
Resource resource = null;
try {
// Load the resource through the editing domain.
resource = editingDomain.getResourceSet().getResource(resourceURI, true);
}
catch (Exception e) {
exception = e;
resource = editingDomain.getResourceSet().getResource(resourceURI, false);
}
...
Il est très difficile d’expliquer ce code si vous n’avez pas les bases en EMF, mais grossièrement ce code utilise un ResourceSet EMF provenant d’un EditingDomain EMF pour charger l’instance EMF WorkflowType (à partir de l’uri du fichier ouvert par l’éditeur) et la stocke dans une Resource EMF (qui est, elle même stockée dans le ResourceSet). Dans notre cas l’instance EMF Resource attendue est une instance de notre classe WorkflowResourceImpl. Dans le cas d’une extension *workflow, la variable resource sera de type WorkflowResourceImpl, alors dans le cas d’une extension *.xml, la variable est de type org.eclipse.emf.ecore.xmi.XMIResource, alors que nous souhaitons travailler avec notre classe WorkflowResourceImpl (pour utiliser les Translator).
Nous avons vu dans le billet Conception d’un Editeur Eclipse de workflow XML [step 2] que notre Resource EMF WorkflowResourceImpl est instanciée via la factory WorkflowResourceFactoryImpl et que celle-ci est enregistrée via un point d’extension :
<parser
type="workflow"
class="org.example.workflow.model.util.WorkflowResourceFactoryImpl"/>
</extension>
Ce point d’extension permet d’enregistrer la factory WorkflowResourceFactoryImpl dans la Map Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap() avec l’extension workflow comme décrit ci dessus. Comme on peut le voir ceci ne traite que les extensions *.workflow et pas *.xml.
Ma première idée était de modifier l’attribut type du point d’extension en xml, mais ce point d’extension enregistre dans la Map globale Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap() les factory de resources EMF, ce qui est problématique car l’extension *.xml peut être utilisée dans d’autres type de modèles que le workflow. Il est possible d’effectuer cet enregistrement mais localement à l’éditeur via la ResourceSet EMF à l’aide de ce code :
editingDomain.getResourceSet().getResourceFactoryRegistry().getExtensionToFactoryMap().put("xml", new WorkflowResourceFactoryImpl());
Pour cela modifier WorkflowEditor#createModel() comme suit :
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated NOT
*/
public void createModel() {
URI resourceURI = EditUIUtil.getURI(getEditorInput());
Exception exception = null;
Resource resource = null;
// Register into local Map factory WorkflowResourceFactoryImpl with xml extension
editingDomain.getResourceSet().getResourceFactoryRegistry().getExtensionToFactoryMap().put("xml", new WorkflowResourceFactoryImpl());
...
}
En modifiant le code comme indiqué ci dessus, vous aurrez un problème de compilation :
The type org.eclipse.wst.common.internal.emf.resource.TranslatorResourceFactory cannot be resolved. It is indirectly referenced from required .class file
Pour corriger ce problème, il faut ajouter la dépendance org.eclipse.wst.common.emf. Vous pouvez ensuite relancer le plugin et constater que les fichier d’extension xml sont maintenant bien pris en compte.
Schéma XML
Il est très intéressant de pouvoir faire référence dans un fichier XML de workflow au schéma XML workflow.xsd pour les fonctionnalités suivantes :
- validation du contenu XML qui permet de valider la structure du document XML.
- auto-complétion qui permet de proposer les éléments XML possibles selon l’endroit ou l’on se situe dans l’éditeur XML.
Pour une information complète concernant les Schéma XML et WST je vous conseille de lire le billet Tutorial WST- DOM SSE [Step 3].
Catalogue XML – Extension
WST donne la possibilité à n’importe quel plugin, d’enregistrer un schéma XML dans le catalogue XML. En effet le point d’extension org.eclipse.wst.xml.core.catalogContributions permet à un plugin d’enregistrer un schéma XML dans le catalogue XML.
Nous allons modifier l’editeur de workflow pour enregistrer le schéma XML dans le catalogue XML de WST. Les étapes sont :
- Créer un répertore xsd dans le plugin org.example.workflow.editor.
- Créer workflow.xsd dans le répertore xsd.
- Ajouter le point d’extension org.eclipse.wst.xml.core.catalogContributions dans le fichier plugin.xml comme suit :
<catalogContribution
id="org.example.workflow.catalog">
<public
publicId="http://www.example.org/workflow"
uri="xsd/workflow.xsd">
</public>
</catalogContribution>
</extension>
Catalogue XML – Test
Si vous relancez le plugin, vous pouvez vérifier que le schéma XML workflow.xsd a bien été enregistré dans le catalogue XML de WST accéssible via Window/Preferences :
La référence à ce schéma peut maintenant s’effectuer en affectant le namespace http://www.example.org/workflow au root de l’éelement XML comme suit :
<workflow xmlns="http://www.example.org/workflow"> ...
Mofifier le fichier My.xml :
<workflow xmlns="http://www.example.org/workflow">
<state name="s1"/>
<state name="s2"/>
<action name="a1" fromState="s1" toState="s2"/>
</workflow>
Auto-complétion
L’éditeur XML est capable d’utiliser les informations du Schema XML pour proposer les noeuds DOM (element, attributs…) disponibles en fonction de la position du curseur dans l’éditeur. Si vous vous positionnez en dessous de l’élement workflow, puis que vous effectuez Ctrl+Space, la fenêtre d’auto-complétion s’affiche en proposant les élements XML state et action :
Validation
L’éditeur XML gère les erreurs de validation du contenu XML de l’editeur. Par exemple si vous tentez d’ajouter l’élement XML , un marqueur rouge s’affiche en indiquant que l’élement insérés ne suit pas le schéma XML :
Dirty & Multi page editor
La gestion de l’état dirty d’un éditeur multi-pages est complexe car il peut être constitué de plusieurs pages qui modifient le même modèle mais de manières différentes. Dans notre cas nous avons :
- des pages qui modifient le modèle EMF via l’EditingDomain EMF (Selection, Parent…)
- une page source qui modifie le modèle SSE qui modifie ensuite le modèle EMF via les Translator.
Dans cette section nous souleverons le problème actuel de l’état dirty, et nous expliquerons en fin de billet comment résoudre le problème.
Problème undo/redo actuel
Le problème que j’ai pu remarquer avec l’implémentation courante est la gestion du undo/redo dans l’éditeur multi-page. Si vous modifiez une page (ex : Selection) puis que vous annulez votre modification via Ctrl+Z tout en restant dans la même page, cette opération fonctionne très bien. Par contre dans le cas ou vous ne restez pas dans la même page (accédez à la page source), l’opération de Ctrl+Z ne fonctionne pas, il faut revenir à la page Selection (ou autres pages générées par EMF) pour que le Ctrl+Z fonctionne. Le problème invese est le même (modifiez la page source, accédez à la page Selection puis effectuez un Ctrl+Z).
Ce problème s’explique par le fait que les pages Selection,…gèrent leur propre stack de commandes via l’EditingDomain EMF et que la page source gère sa propre stack de commandes (géré par le modèle SSE). Nous nous retrouvons avec 2 stacks de commandes distinctes. L’idée est d’en avoir qu’une seule. je vais tenter dans la suite de mettre en évidence ces stacks de commandes (EditingDomain et Modele SSE) et de donner une solution pour règler ce problème (activer le Ctrl+Z dans n’importe quelle page, meme si ca n’est pas celle qui ait modifié le modèle EMF WorkflowType).
CommandStack & EditingDomain
Un des rôle de l’EditingDomain EMF est de pouvoir gérer le undo/redo via une stack de commandes EditingDomain#getCommandStack(). Lorsque l’utilisateur modifie par exemple le nom name d’un State via la vue Properties, cette opération ajoute à la commande stack la commande EMF SetCommand qui permet de modifier le nom name d’une instance EMF StateType. Pour vous en rendre compte, mettez un point d’arret dans la méthode StatetTypeImpl#setName(String name):
Modifier le nom d’un state par s3 dans la vue properties :
Le debug s’arrete sur le code de StateTypeImpl#setName(String name), ce qui permet de visualiser les classes qui ont été appelées :
On peut constater que la modification d’une propriété de la vue Properties, execute la commande EMF SetCommand via BasicCommandStack#execute(Command command) qui est en fait la stack de commandes de l’EditingDomain. Cette commande modifie ensuite le nom du StateType par s3. A cette étape, le nom du StatateType est s3.
Effectuez un Ctrl+Z dans la page Selection de l’editeur de workflow, le debug s’arrete à nouveau dans la méthode StateTypeImpl#setName(String name) :
On peut constater que BasicCommandStack#undo() est appelé, qui appele SetCommand#doUndo() qui appelle ensuite la méthode StateTypeImpl#setName(String name) avec s2.
CommandStack & Model SSE
Le modèle SSE est capable de gèrer aussi une stack de commandes accéssible via IStructuredModel#getUndoManager()#getCommandStack(). Cette commande de stack est aussi une stack de commandes EMF (mais ces commandes ne gèrent pas un modèle EMF). Pour mettre en évidence ceci, mettez un point d’arrêt dans la méthode BasicCommandStack#execute(), allez dans la page soure et modifiez le name d’un state avec s3 :
On peut constater que la modification du contenu XML engendre la création d’une commande EMF (StructuredTextCommandImpl) qui permet de mettre à jour le modèle SSE DOM. Si vous effectuez undo, vous pourrez constater que la méthode BasicCommandStack#undo() est appelée.
Solution
Comme nous avons pu voir il y a 2 gestion de stacks de commandes selon la page sélectionnée. Les pages générées (Selection…) mettent à jour la stack de commandes de l’EditingDomain et la page source constitué d’un StructuredTextEditor (qui fonctionne avec un modèle SSE) met à jour la stack de commandes du modèle SSE. La stack de commandes de l’EditingDomain est alimentée avec des commande (ex: SetCommand) qui mettent à jour l’instance EMF. La stack de commandes du modèle SSE est alimentée avec des commandes (ex : StructuredTextCommandImpl) qui mettent à jour le modèle SSE (et pas l’instance EMF!!!). La mise à jour du modèle SSE met à jour ensuite l’instance EMF via les Translator.
Je vais vous proposer une solution, mais je n’ai jamais vu aucun plugins gérer le problème de undo/redo comme ce que je vais expliquer dans la suite du billet. Dans les prochains billets je n’utiliserais pas cette gestion de undo/redo proposée car je ne sais pas si ca pourra poser des problèmes dans le futur. Mais d’après mes tests, le undo/redo fonctionne plutôt bien. n’hesitez pas à me faire des retours si vous trouvez des exemples qui pourraient poser problèmes.
L’idée générale est de partager la même stack de commandes et d’en avoir plus qu’une dans l’editeur multi page workflow. La méthode isDirty devient :
return ((BasicCommandStack) editingDomain.getCommandStack()).isSaveNeeded() /* || super.isDirty() */;
}
Pour partager la stack de commandes entre l’EditingDomain et le modèle SSE, mon idée est de passer la stack de commandes de l’EditingDomain à celle du modèle SSE. Pour cela ajouter la dépendance org.eclipse.wst.sse.core qui contient entre autres l’interface org.eclipse.wst.sse.core.internal.provisionalIStructuredModel interface du modèle SSE.
Créér la méthode getReadableSSEModel() dans la classe WorkflowEditor qui permet de récupérer le modèle SSE en mode lecture :
IFile file = ((IFileEditorInput) getEditorInput()).getFile();
IModelManager modelManager = StructuredModelManager.getModelManager();
IStructuredModel model = modelManager.getExistingModelForRead(file);
if (model != null)
return model;
try {
return modelManager.getModelForRead(file);
} catch (IOException e) {
e.printStackTrace();
} catch (CoreException e) {
e.printStackTrace();
}
return null;
}
Créer la méthode synchronizeCommandStackFormSSEModelWithCommandStackFormEditingDomain qui permet de synchroniser la stack de commandes du modèle SSE avec celle de l’EditingDomain :
// 1. Get SSE Model into Read Mode (NOT into Edit mode)
IStructuredModel model = getReadableSSEModel();
if (model != null)
{
// 2. Set the SSE Model CommandStack with CommandStack from EditingDomain
model .getUndoManager().setCommandStack(editingDomain.getCommandStack());
}
}
Appeler la méthode synchronizeCommandStackFormSSEModelWithCommandStackFormEditingDomain à la fin de createModel :
...
editingDomain.getResourceSet().eAdapters().add(problemIndicationAdapter);
synchronizeCommandStackFormSSEModelWithCommandStackFormEditingDomain();
}
Vous pouvez relancer le plugin et tester le undo/redo. N’importe quelle page est capable d’effectuer l’opération de undo/redo. Il est important de récupérer le modèle SSE en lecture et pas en edition (avec get*ModelForEdit) car d’apres mes tests si on modifie le modèle SSE et que l’on quitte l’éditeur sans sauvegarder, le modèle SSE ne change pas et garde le contenu qui n’a pas été sauvegardé???
Dans la suite des billets je ne vais pas continuer avec cette gestion de isDirty car je n’ai trouvé aucun plugin qui gère ce cas-ci comme ce que j’ai pu proposer. Par prudence, je n’utiliserais pas ce code et le probléme de undo/redo décrit ci dessus existera.
6 Commentaires + Ajouter un commentaire
Articles récents
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step5]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step4]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step3]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step2]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step1]
Commentaires récents
- Conception d’un Editeur Eclipse de workflow XML [step 0] dans
- Conception d’un Editeur Eclipse de workflow XML [step 19] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans
Pour le livre voici la référence : http://www.amazon.fr/Eclipse-Modeling-Framework-David-Steinberg/dp/0321331885 (Il s’agit de la seconde édition). Pour le namespace je m’avance peut être un peu vite mais ecore:package a peut être un lien avec cela. Je vais me renseigner au mieux en attendant!
Olivier
Bonjour knuck ,
>Merci beaucoup pour ta réponse je ne l’avais pas vu en écrivant mon deuxième message mais oui c’était bien le problème que tu exposes!
Cool:)
>Je me suis également procuré le seul livre sur le sujet
Tu parles de quel livre? Moi je n’ai jamais rien trouvé sur le sujet, je me suis débrouillé un peu tout seul.
>mais je ne trouve pas comment ajouter des namespace dans le xml généré par l’éditeur. Aurais-tu déjà rencontré ce problème ou une piste à suivre >pour résoudre ça?
Tu souhaiterais ajouter le namespace « » auotmatiquement lorsque l’on cree l’instance EMF? Si c’est ca je n’ai jamsi reussi a le faire car il me cree toujours un truc avec
Je sais qu’il y a un Translator EMF avec les namespaces mais je n’ai jamais utilisé.
Bon courgae
Angelo
Merci beaucoup pour ta réponse je ne l’avais pas vu en écrivant mon deuxième message mais oui c’était bien le problème que tu exposes! Je me suis également procuré le seul livre sur le sujet mais je ne trouve pas comment ajouter des namespace dans le xml généré par l’éditeur. Aurais-tu déjà rencontré ce problème ou une piste à suivre pour résoudre ça?
Olivier
En fait j’ai trouvé il faut faire un « Open with » > « Other » > « Workflow Model Editor ». Il reste à forcer l’ouverture du document par « Workflow Model Editor » et ça devrait être bon!
Bonjour knuck,
Merci de tes encouragements. J’ai tente de balayer le maximum de problématique mais il en reste encore une quantité à traiter et j’ai encore beaucoup de choses à apprendre.
Pour ton problème, ce fonctionnement est normal car un éditor est associé généralement à un extension d’un fichier. Si tu regarde le fichier plugin.xml qui definit l’editor (voir extension org.eclipse.ui.editors), tu as extensions= »workflow » qui indique que l’editor de workflow n’est proposé que pour les fichiers *.workflow. Tu peux essayer de mettre extensions= »workflow xml » pour proposer l’editor a la fois pour les fichiers *.workflow et *.xml. Cependant ca me parait un peu dangereux de faire ca car ca signifiera que tous les fichiers XML peuvent être ouvert par cet editor de workflow.
Si tu ne changes pas l’extensions, tu peux quand même ouvrir le fichier de workflow My.xml a l’aide du menu Open With/Other…
Angelo
Salut,
Tout d’abord merci pour ce tuto que je trouve assez complet
Je rencontre cependant certaines difficultés et notamment l’enregistrement des fichiers en xml.
Après avoir repris ton archive « org.example.workflow_step5.zip » je lance le plugin, crée un nouveau fichier au format « .workflow » , je renomme ensuite en « .xlm » mais mes vues « Workflow Model Editor » ne sont plus disponibles comme tu peux le voir ici : http://dj-olivierh.com/emf/xml.jpg
As-tu une idée du problème ? Merci par avance pour ta réponse