juillet
2009
Dans le billet précédant [step1] nous avons initialisé le modèle EMF du workflow et l’éditeur Eclipse à partir du schéma XML workflow.xsd. On peut créer des states et des actions à partir de l’éditeur (mais qui n’ont aucun lien entre eux). Maintenant nous souhaitons qu’une action face référence à deux state(s) (via les attributs fromState et toState). Dans ce billet nous allons gérer le lien entre les actions et les states en modifiant l’Ecore workflow.ecore. Nous étudierons comment gérer la sérialisation/de-sérialisation de l’instance EMF pour obenir le contenu XML suivant :
<workflow:workflow xmlns:workflow="http://www.example.org/workflow">
<workflow:state name="s1"/>
<workflow:state name="s2"/>
<workflow:action name="a1" fromState="s2" toState="s2"/>
</workflow:workflow>
qui décrit deux states et une action qui fait référence à ces deux states (par leur nom name) via les attributs fromState et toState.
Vous pouvez télécharger le projet org.example.workflow_step2.zip présenté dans ce billet.
Référence EAttribute fromState/toState
Avant de commencer le billet, étudions le problème des références action/state dans le projet du billet précédant [step1]. Si vous créez 2 states s1 et s2 et une action a1. Les propriétés fromState et toState (dans la vue Properties) sont des champs texte où l’on peut saisir ce que l’on souhaite alors que nous souhaitons avoir une référence sur des states existants (s1 ou s2) :
Ce comportement est normal car l’Ecore workflow.ecore définit fromState et toState en tant que EAttribute. Dans la suite de ce billet nous allons référencer fromSate et toState sur des org.example.workflow.model.StateType à l’aide de EReference.
Référence EReference fromState/toState
Le modèle Ecore workflow.ecore a été généré par le schéma XML workflow.xsd et je ne sais pas si il est possible de référéncer un type (dans notre cas state) dans la définition d’un attribut (dans notre cas fromState et toState) d’un schéma XML. A cette étape fromState et toState sont des attributs EAttribute :
Nous allons les supprimer et les remplacer par des EReference sur le type StateType. Pour cela sélectionnez l’attribut fromState puis appuyer sur Suppr. Ouvrez le menu contextuel pour créér un nouveau EReference :
Renseignez dans la vue Properties, les propriétés :
- Container: false
- Containment : false
- EType : StateType
- Name : fromState
Effectuez la même opération pour l’attribut toState. Regenérez tout (Generate Model Code, Generate Edit Code, Generate Editor Code) et relancez le plugin. L’éditeur de workflow propose maintenant une combo liste qui permet de sélectionner un state existant :
Si vous enregistrez le workflow vous pourrez voir le contenu XML suivant :
<workflow:workflow xmlns:workflow="http://www.example.org/workflow">
<workflow:state name="s1"/>
<workflow:state name="s2"/>
<workflow:action name="a1" fromState="#//@workflow/@state.0" toState="#//@workflow/@state.0"/>
</workflow:workflow>
On peut remarquer que le lien state vers action géré par l’attribut fromState a une syntaxe EMF (qui ressemble à XPath) comme ceci :
#//@workflow/@state.0
Cette syntaxe indique que fromState fait référence au premier noeud state du workflow. Cette sérialisation des liens peut être personnalisée et dépend de l’objet Ressource EMF utilisé (dans notre cas XMLResource) et de sa configuration (gérés par la Map options).
ID EMF
La syntaxe XPath-like d’EMF utilisé pour faire réference au state (dans les actions) ne nous convient pas dans notre cas, car nous souhaitons référencer le nom (name) de state à la place, car ce mode de réferencement est plus simple à saisir (ce qui sera utile quand nous aurrons intégrer un éditor Text qui permet de saisir le contenu XML).
Pour effectuer ce type de référencement, EMF propose la notion d’ID que l’on peut mettre sur un EAttribute pour identifier une instance EMF. Dans notre cas, nous allons mettre en ID le EAttribute name de StateType en modifiant le Ecore workflow.ecore :
Regenérer tout (Generate Model Code, Generate Edit Code, Generate Editor Code) et relancez le plugin. Lorsque vous sauvegarderez le workflow, vous obtiendrez le contenu XML suivant :
<workflow:workflow xmlns:workflow="http://www.example.org/workflow">
<workflow:state name="s1"/>
<workflow:state name="s2"/>
<workflow:action name="a1" fromState="#s2" toState="#s2"/>
</workflow:workflow>
Comme on peut le constater les références au state sont encodés (le charactère # est devant le nom du state). Dans notre cas, cet encodage ne nous convient toujours pas, car nous souhaitons utiliser directement le nom (name) de state. C’est ce que nous allons expliquer dans la section suivante.
ID EMF – Non encodé
Pour éviter d’avoir des références encodées dans le contenu XML, commentez dans la classe générée org.example.workflow.model.util.WorkflowResourceFactoryImpl le code de la méthode WorkflowResourceFactoryImpl#createResource(URI uri) :
//result.getDefaultSaveOptions().put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE);
Ce commentaire permet de supprimer l’encodage des références à la sérialisation (sauvegarde)/dé-sérialisation (chargement) de l’instance EMF.
Et modifier le commentaire @generated de la méthode WorkflowResourceFactoryImpl#createResource(URI uri) par @generated NOT. Ceci permet d’éviter d’écraser la modification effectuée ci dessus lors d’une regénération de code.
Voici le code entier de la méthode modifiée :
* Creates an instance of the resource.
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated NOT
*/
@Override
public Resource createResource(URI uri) {
XMLResource result = new WorkflowResourceImpl(uri);
result.getDefaultSaveOptions().put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
result.getDefaultLoadOptions().put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
result.getDefaultSaveOptions().put(XMLResource.OPTION_SCHEMA_LOCATION, Boolean.TRUE);
//result.getDefaultLoadOptions().put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE);
//result.getDefaultSaveOptions().put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE);
result.getDefaultLoadOptions().put(XMLResource.OPTION_USE_LEXICAL_HANDLER, Boolean.TRUE);
return result;
}
Relancez le plugin. Lorsque vous sauvegarderez le workflow, vous obtiendrez le contenu XML suivant :
<workflow:workflow xmlns:workflow="http://www.example.org/workflow">
<workflow:state name="s1"/>
<workflow:state name="s2"/>
<workflow:action name="a1" fromState="s2" toState="s2"/>
</workflow:workflow>
Les références aux states utilisent maintenant le nom des states sans encodage. Le billet est terminé, mais si vous ne connaissez pas EMF, les sections suivantes expliqueront comment fonctionne WorkflowResourceFactoryImpl (Factory de Resource EMF) et le @generated.
WorkflowResourceImpl – Resource EMF
Pour sérialiser/désérialiser une instance EMF (ex : WorkflowType) dans un contenu (XML, XMI….), EMF propose le mécanisme de Resource. Une org.eclipse.emf.ecore.resource.Resource EMF est un conteneur pour les objets persistés (Resource#save(…) et Resource#load()). Ces objets persistés sont accéssibles via Resource#getContents(). EMF propose par exemple les implémentations XMLResourceImpl (pour sérializer en XML) et XMIResourceImpl (pour sérializer en XMI).
Voici un exemple de code qui permet de sauvegarder une instance EMF workflow dans un format XML :
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.xmi.impl.XMLResourceImpl;
import org.example.workflow.model.WorkflowFactory;
import org.example.workflow.model.WorkflowType;
public class TestSaveWorflowIntoXML {
public static void main(String[] args) throws IOException {
// 1. Create EMF WorkflowType model
WorkflowType workflow = WorkflowFactory.eINSTANCE.createWorkflowType();
// 2. Create EMF XML Resource
Resource resource = new XMLResourceImpl();
// 3. Add EMF model to EMF Resource
resource.getContents().add(workflow);
// Save contents of EMF Resource into System.out
resource.save(System.out, null);
}
}
Si vous lancer le main de ce code, vous obtiendrez dans la console :
<workflow:WorkflowType xmlns:workflow="http://www.example.org/workflow"/>
Dans notre cas, la resource EMF utilisée est org.example.workflow.model.util.WorkflowResourceImpl qui éténd org.eclipse.emf.ecore.xmi.impl.XMLResourceImpl. Cette classe Resource a été générée car le paramètre Resource Type du genmodel est à XML.
WorkflowResourceFactoryImpl – Factory Resource EMF
Pour récupérer une instance de WorkflowResourceImpl, l’éditeur générée utilise la factory WorkflowResourceFactoryImpl qui est une factory de Resource EMF org.eclipse.emf.ecore.resource.Resource.Factory. Voici la même sérialisation de WorkflowType décrite ci dessus, mais avec l’utilisation de la factory de resource EMF WorkflowResourceFactoryImpl :
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.example.workflow.model.WorkflowFactory;
import org.example.workflow.model.WorkflowType;
import org.example.workflow.model.util.WorkflowResourceFactoryImpl;
public class TestSaveWorflowIntoXML2 {
public static void main(String[] args) throws IOException {
// 1. Create EMF WorkflowType model
WorkflowType workflow = WorkflowFactory.eINSTANCE.createWorkflowType();
// URI
URI uri = null;
// 2. Create EMF Resource factory
Resource.Factory factory = new WorkflowResourceFactoryImpl();
// 3. Create EMF Resource WorkflowResourceImpl from the factory
Resource resource = factory.createResource(uri);
// 4. Add EMF model to EMF Resource
resource.getContents().add(workflow);
// 5. Save contents of EMF Resource into System.out
resource.save(System.out, null);
}
}
Si vous lancer le main de ce code, vous obtiendrez dans la console :
<workflow:workflow_._type xmlns:workflow="http://www.example.org/workflow"/>
On peut remarquer que l’élement racine est workflow:workflow_._type, ceci est du au fait que l’options XMLResource.OPTION_EXTENDED_META_DATA est mis à true (je ne vais pas rentrer dans les détails de ce paramètre, mais l’idée général est d’utiliser les méta-données ExtendedMetaData du genmodel) :
result.getDefaultLoadOptions().put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
Si vous dé-commentez ces lignes vous obtiendrez le même résultat que la resource EMF XMLResourceImpl du premier test TestSaveWorflowIntoXML.
Extension 2 Factory
Il est possible d’indiquer que tous les fichiers se terminant par l’extension workflow doivent utilisés la factory WorkflowResourceFactoryImpl (c’est ce qui d’ailleurs est effectué dans le code généré). Pour cela il suffit d’enregistrer dans la Map de
Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap() la factory WorkflowResourceFactoryImpl avec la cle workflow :
Voici un exemple de code qui retrouve la factory WorkflowResourceFactoryImpl par l’extension workflow :
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.example.workflow.model.WorkflowFactory;
import org.example.workflow.model.WorkflowType;
import org.example.workflow.model.util.WorkflowResourceFactoryImpl;
public class TestSaveWorflowIntoXML3 {
public static void main(String[] args) throws IOException {
// 1. Create EMF WorkflowType model
WorkflowType workflow = WorkflowFactory.eINSTANCE.createWorkflowType();
// URI with workflow extension
URI uri = URI.createFileURI("tmp.workflow");
// *.workflow file must be loaded with WorkflowResourceFactoryImpl
Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(
"workflow", new WorkflowResourceFactoryImpl());
// 2. Get EMF Resource factory with *.workflow
Resource.Factory factory = Resource.Factory.Registry.INSTANCE
.getFactory(uri);
// 3. Create EMF Resource WorkflowResourceImpl from the factory
Resource resource = factory.createResource(uri);
// 4. Add EMF model to EMF Resource
resource.getContents().add(workflow);
// 5. Save contents of EMF Resource into System.out
resource.save(System.out, null);
}
}
plugin.xml
Si vous tentez de recherchez le code Java qui instancie la factory de resource EMF WorkflowResourceFactoryImpl, vous serez surpris de ne pas le trouver. En effet, cette factory est enregistrée via un un point d’extension org.eclipse.emf.ecore.extension_parser que vous pouvez trouver dans le fichier plugin.xml du projet org.example.workflow :
<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.
@generated
Comme nous avons pu le voir, il est possible de partir d’un schéma XML et de générer une grosse quantité de code Java et de RE-générer le code après avoir modifié par exemple le Ecore workflow.ecore. Ce que j’adore dans EMF, c’est qu’il est possible de modifier le code source Java généré et re-générer le code Java sans que le code modifié soit écrasé. Cette fonctionnalité se base sur les commentaires @generated.
En effet lorsque le code est généré, le commentaire @generated est inclus dans toutes les sources Java pour indiquer que ce code a été généré et que lors d’une re-génération ce code sera écrasé avec le nouveau contenu généré. Lorsque l’on souhaite personnaliser le code généré, comme ce que nous avons pu faire dans la section ID EMF – Non encodé il suffit d’ajouter NOT à la fin de @generated.
JMerge est le projet d’EMF qui est utilisé pour gérér le merge entre fichier Java.
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