juillet
2009
Dans le billet précédant [step3] nous avons mis en place les Translator WST pour les élements state et action et leur attribut name. Les attributs fromState et toState des actions qui font références a des states n’ont pas encore été mis en place. Dans ce billet, nous allons :
- Mettre en place les Translator WST pour les attributs fromState et toState de action.
- Ajouter une page Source avec un TextEditor qui utilisera StructuredTextEditor de WST.
Vous pouvez télécharger le projet org.example.workflow_step4.zip présenté dans ce billet.
Translator fromState – toState
A l’étape du billet précédant [step3], les translators pour gérer les attributs fromState et toSate n’ont pas été mis en place. Pour effectuer ce test, ouvrez le fichier My.workflow dans l’editor Workflow Model Editor puis ouvrez ce même fichier dans l’éditor de text (Open With Text Editor) et mettez ces 2 éditors côte à côte :
Vous pouvez constater que la vue properties n’affiche pas les 2 states s1 et s2. Ceci s’explique par le fait que les Translator n’ont pas été mis en place pour les attributs fromState et toState et que par conséquent l’instance EMF ActionType n’est pas synchronisée avec les 2 attributs.
WorkflowTranslator
Pour gérer les 2 attributs fromState et toState nous devons modifier WorkflowTranslator pour gérer les 2 cas suivants :
- la mise à jour de l’attribut name d’un state doit :
- mettre à jour l’instance StateType avec la valeur de l’attribut name de state (ceci est déja fait)
- indiquer que l’attribut name est un ID EMF (via la Resource EMF)
Ceci s’effectue à l’aide de la classe MyIDTranslator que nous allons créer.
- l’attribut fromState est mappé avec le name d’un StateType retrouve par son ID EMF (name de StateType). Ceci s’effectue à l’aide de la classe HRefTranslator que nous allons créer.
StateType – MyIDTranslator
En EMF, il est possible de mettre en ID une instance EMF via la Resource EMF de type org.eclipse.emf.ecore.xmi.XMLResource comme ceci :
XMLResource resource = (XMLResource)stateType.eResource();
resource.setID(stateType, "MyId");
et retrouver l’instance EMF par ID comme ceci :
StateType stateType = (StateType)resource.getEObject("MyId");
et retrouver l’ID d’une instance EMF comme ceci :
XMLResource resource = (XMLResource)stateType.eResource();
String id = resource.getID(stateType);
Nous dans notre cas, nous voulons identifier l’instance EMF StateType par son nom, autrement dit faire :
XMLResource resource = (XMLResource)stateType.eResource();
resource.setID(stateType, stateType.getName());
WST propose une classe org.eclipse.wst.common.internal.emf.resource.IDTranslator qui d’après son code est capable de mettre
en ID une instance EMF avec le nom XML id. Je n’ai pas bien compris l’utilité de ce translator car :
- le nom XML de l’attribut est forcé à id. Dans notre cas nous voulons mettre name et pas id
- ce translator gère uniquement l’ID EMF de l’instance EMF (dans la Resource EMF) et PAS la mise à jour de l’instance EMFavec la valeur de l’id (dans notre cas la mise à jour de name de StateType)
Autrement dit quand l’attribut name de state change nous devons exécuter le code suivant :
// 1. Here we update emfObject with value
StateType stateType = ...;
stateType.setName(valueOfAttrStateName);
// 2. Here we update resources ID
XMLResource resource = (XMLResource)stateType.eResource();
resource.setID(stateType, valueOfAttrStateName);
Créer la classe org.example.workflow.model.util.MyIDTranslator comme suit (j’ai repris tel quel le code de IDTranslator et j’ai ajouté l’étape 1. Here we update emfObject with value) et j’ai commenté la méthode getMOFValue (pour qu’elle utilise le name de StateType et pas son ID car lors d’une création d’un StateType on ne met pas à jour l’ID et cette méthode retourne toujours null) :
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.xmi.XMIResource;
import org.eclipse.wst.common.internal.emf.resource.Translator;
/**
* Same code than
* {@link org.eclipse.wst.common.internal.emf.resource.IDTranslator} but update
* emfObject with value
*
*/
public class MyIDTranslator extends Translator {
public class NoResourceException extends RuntimeException {
public NoResourceException() {
super();
}
public NoResourceException(String s) {
super(s);
}
}
public MyIDTranslator(String domNameAndPath, EStructuralFeature aFeature,
int style) {
super(domNameAndPath, aFeature, style);
}
public void setMOFValue(EObject emfObject, Object value) {
// 1. Here we update emfObject with value
super.setMOFValue(emfObject, value);
// 2. Here we update resources ID
XMIResource res = (XMIResource) emfObject.eResource();
if (res == null)
throw new NoResourceException();
String id = res.getID(emfObject);
if (id == null && value == null)
return;
if ((id != null && !id.equals(value))
|| (value != null && !value.equals(id)))
res.setID(emfObject, (String) value);
}
// public Object getMOFValue(EObject emfObject) {
// if (emfObject == null)
// throw new NoResourceException();
// XMIResource res = (XMIResource) emfObject.eResource();
// if (res == null)
// throw new NoResourceException();
// return res.getID(emfObject);
// }
public boolean featureExists(EObject emfObject) {
return true;
}
public boolean isIDMap() {
return true;
}
}
Modifier ensuite la méthode WorkflowTranslator#createStateTranslator() pour utiliser maintenant notre classe MyIDTranslator (et plus Translator) comme ceci :
GenericTranslator translator = new GenericTranslator("state",
WorkflowPackage.eINSTANCE.getWorkflowType_State());
translator.setChildren(new Translator[] {
new MyIDTranslator("name", WorkflowPackage.eINSTANCE
.getStateType_Name(), Translator.DOM_ATTRIBUTE)
});
return translator;
}
ActionType – HRefTranslator
A cette étape les StateType peuvent être récupérés par leur name en utilisant les ID EMF via la Resource EMF. Nous pouvons gérer les translator pour fromState et toState qui doivent rechercher les StateType par ID correspondant au nom des attributs fromState/toState. Pour gérer ceci, créer la classe org.example.workflow.model.util.HRefTranslator (je me suis inspiré de la classe org.eclipse.wst.common.componentcore.internal.util.HRefTranslator et je l’ai simplifié) comme suit :
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.wst.common.internal.emf.resource.Translator;
import org.eclipse.wst.common.internal.emf.resource.TranslatorPath;
public class HRefTranslator extends Translator {
public HRefTranslator(String domNameAndPath, EClass eClass) {
super(domNameAndPath, eClass);
}
public HRefTranslator(String domNameAndPath, EStructuralFeature aFeature) {
super(domNameAndPath, aFeature);
}
public HRefTranslator(String domNameAndPath, EStructuralFeature aFeature,
EClass eClass) {
super(domNameAndPath, aFeature, eClass);
}
public HRefTranslator(String domNameAndPath, EStructuralFeature aFeature,
TranslatorPath path) {
super(domNameAndPath, aFeature, path);
}
public HRefTranslator(String domNameAndPath, EStructuralFeature aFeature,
TranslatorPath[] paths) {
super(domNameAndPath, aFeature, paths);
}
public HRefTranslator(String domNameAndPath, EStructuralFeature aFeature,
int style) {
super(domNameAndPath, aFeature, style);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.wst.common.internal.emf.resource.Translator#convertStringToValue
* (java.lang.String, org.eclipse.emf.ecore.EObject)
*/
public Object convertStringToValue(String strValue, EObject owner) {
if (strValue == null)
return null;
Resource resource = owner.eResource();
if (resource == null)
return null;
return resource.getEObject(strValue);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.wst.common.internal.emf.resource.Translator#convertValueToString
* (java.lang.Object, org.eclipse.emf.ecore.EObject)
*/
public String convertValueToString(Object value, EObject owner) {
Resource resource = owner.eResource();
if (resource == null)
return null;
return resource.getURIFragment((EObject)value);
}
}
Modifier ensuite la méthode WorkflowTranslator#createActionTranslator() pour utiliser maintenant notre classe HRefTranslator pour gérer ls translators avec les attributs fromState et toState : :
GenericTranslator translator = new GenericTranslator("action",
WorkflowPackage.eINSTANCE.getWorkflowType_Action());
translator.setChildren(new Translator[] {
new Translator("name", WorkflowPackage.eINSTANCE
.getActionType_Name(), Translator.DOM_ATTRIBUTE),
new HRefTranslator("fromState", WorkflowPackage.eINSTANCE
.getActionType_FromState(), Translator.DOM_ATTRIBUTE),
new HRefTranslator("toState", WorkflowPackage.eINSTANCE
.getActionType_ToState(), Translator.DOM_ATTRIBUTE), });
return translator;
}
Vous pouvez ensuite relancer le plugin, et constater que l’instance EMF est bien synchronisée avec les attributs XML fromState et toState:
Modifier l’attribut XML toState avec s1 et vous pourrez constater que l’instance EMF se synchronise avec l’attribut XML toState (sans être obligé de sauvegarder) :
Modifier l’instance EMF via la vue properties en sélectionnant State Type s2 et vous pourrez constater que l’attribut XML toState se synchronise avec l’instance EMF avec la valeur s2 (sans être obligé de sauvegarder) :
Modifier l’attribut XML toState avec s3 qui n’est pas un state existant et vous pourrez constater que l’instance EMF a un StateType null :
Page Source – StructuredTextEditor
WST fournit un editeur texte qui est capable de gérer un modèle SSE org.eclipse.wst.sse.ui.StructuredTextEditor qui éténd la classe org.eclipse.ui.editors.text.TextEditor (éditeur texte de base). L’éditeur XML de WST est basé sur org.eclipse.wst.sse.ui.StructuredTextEditor. Nous allons ajouter une page source dans notre éditeur de workflow à l’aide de cette classe pour donner la possibilité de saisir le contenu XML du workflow :
Dependance StructuredTextEditor
Pour utiliser org.eclipse.wst.sse.ui.StructuredTextEditor il faut tout d’abord ajouter la dépendance org.eclipse.wst.sse.ui : ce plugin contient les classes org.eclipse.wst.sse.ui.StructuredTextEditor.
Ajout StructuredTextEditor dans WorkflowEditor
La classe WorkflowEditor doit être modifiée :
- ajout champs sourcePage :
private StructuredTextEditor sourcePage = null;
- Ajout de la méthode createAndAddSourcePage :
private void createAndAddSourcePage() {
sourcePage = new StructuredTextEditor();
sourcePage.setEditorPart(this);
try {
int index = addPage(sourcePage, getEditorInput());
setPageText(index, "Source");
} catch (PartInitException e) {
e.printStackTrace();
}
} - Appel de la méthode createAndAddSourcePage dans la méthode createPages qui permet de créer les pages de l’éditeur multi-page WorkflowEditor :
public void createPages() {
...
// Add source page
createAndAddSourcePage();
...
Voici le code de WorkflowEditor :
...
private StructuredTextEditor sourcePage = null;
...
/**
* This is the method used by the framework to install your own controls.
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated NOT
*/
@Override
public void createPages() {
...
int pageIndex = addPage(viewerPane.getControl());
setPageText(pageIndex, getString("_UI_TreeWithColumnsPage_label"));
}
// Add source page
createAndAddSourcePage();
...
}
...
private void createAndAddSourcePage() {
sourcePage = new StructuredTextEditor();
sourcePage.setEditorPart(this);
try {
int index = addPage(sourcePage, getEditorInput());
setPageText(index, "Source");
} catch (PartInitException e) {
e.printStackTrace();
}
}
...
}
ATTENTION a bien mettre les @generated NOT pour ne pas écraser les modifications effectuées lors de la prochaine re-génération.
Gestion dirty
A cette étape si on modifie le contenu XML dans l’éditeur texte de la page source, cette opération n’influe pas sur l’état dirty de l’éditeur de workflow. En effet si vous tappez un caractère dans la page source, l’état dirty (qui devrait se mettre a true) de l’éditeur est toujours à false. L’éditeur considère qu’il n’y donc pas eu de modification et il est impossible de sauvegarder le nouveau contenu XML :
L’état dirty est gérée par la méthode WorkflowEditor#isDirty() dont voici son code :
return ((BasicCommandStack) editingDomain.getCommandStack()).isSaveNeeded();
}
Je parlerai plus en détail de ce code dans le billet suivant step5. Ce code signifie que l’état dirty est géré en fonction de l’état isSaveNeeded d’une stack de commandes. Cette stack de commande est en faite mise à jour lorsque le modèle EMF est modifié via toutes les pages (Selection, Parent, List….) et la vue properties. Par exemple si vous modifiez le nom d’un StateType via la vue properties, une commande EMF de type org.eclipse.emf.edit.command.SetCommand est créée et ajouté à la stack. Cette commande permet de modifier le nom de l’instance StateType sélectionée. En d’autres termes la vue properties ne modifie pas directement le name de l’instance StateType, mais elle ajoute une commande EMF à la stack de commande de l’editing domain EMF. Cette command est ensuite executée et c’est elle qui met à jour l’instance EMF. Ce mécanisme de stack de commande permet de gérer le undo/redo de l’éditeur de workflow. Seul les pages qui modifie l’instance EMF via l’edting domain bénéficie des opérations undo/redo. La page source qui est composée d’un éditor StructuredTextEditor ne met jamais à jour cette stacke de commande quand l’utilisateur saisit du contenu XML dans l’éditeur texte. Pour prendre en compte l’état dirty de l’éditor StructuredTextEditor, il faut modifier le code comme suit :
return ((BasicCommandStack) editingDomain.getCommandStack()).isSaveNeeded() || sourcePage.isDirty();
}
Ce code signifie que l’éta dirty vaut true si :
- Il y a eu une modification de l’instance EMF via l’edting domain.
- OU via la page source (éditeur de texte à dirty).
Cependant si on souhaite dans le futur ajouter une nouvelle page qui contient un autre éditeur (ex : éditeur GEF graphique), il faudra modifier ce code. Il est possible de dire que l’on souhaite effectuer ce mécanisme pour toutes les pages de type org.eclipse.ui.IEditorPart (comme StructuredTextEditor) en appelant MultiPageEditorPart#isDirty() (WorkflowEditor éténd de MultiPageEditorPart) :
Voici le code de MultiPageEditorPart#isDirty() :
// use nestedEditors to avoid SWT requests; see bug 12996
for (Iterator i = nestedEditors.iterator(); i.hasNext();) {
IEditorPart editor = (IEditorPart) i.next();
if (editor.isDirty()) {
return true;
}
}
return false;
}
Par conséquent modiifier le code WorkflowEditor#isDirty() comme ceci :
* This is for implementing {@link IEditorPart} and simply tests the command stack.
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated NOT
*/
@Override
public boolean isDirty() {
return ((BasicCommandStack) editingDomain.getCommandStack()).isSaveNeeded() || super.isDirty();
}
ATTENTION a bien mettre les @generated NOT pour ne pas écraser les modifications effectuées lors de la prochaine re-génération.
On se retrouve avec le même code que l’éditeur de JSF Webtools.
Gestion sauvegarde
A cette étape la sauvegarde ne fonctionne toujours pas. Nous avons toujours l’exception :
at org.eclipse.wst.xml.core.internal.emf2xml.EMF2DOMSSERenderer.doSave(EMF2DOMSSERenderer.java:250)
at org.eclipse.wst.common.internal.emf.resource.TranslatorResourceImpl.doSave(TranslatorResourceImpl.java:180)
at org.eclipse.wst.common.internal.emf.resource.TranslatorResourceImpl.save(TranslatorResourceImpl.java:160)
at org.example.workflow.presentation.WorkflowEditor$18.execute(WorkflowEditor.java:1489)
at org.eclipse.ui.actions.WorkspaceModifyOperation$1.run(WorkspaceModifyOperation.java:104)
at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:1800)
at org.eclipse.ui.actions.WorkspaceModifyOperation.run(WorkspaceModifyOperation.java:116)
at org.eclipse.jface.operation.ModalContext$ModalContextThread.run(ModalContext.java:121)
Je n’ai pas apronfondi la question concernant La ligne de code EMF2DOMSSERenderer.java:250 qui pose problème :
ResourceSetWorkbenchEditSynchronizer synchronizer = (ResourceSetWorkbenchEditSynchronizer) ((ProjectResourceSet) resource.getResourceSet()).getSynchronizer();
Pour régler ce problème nous allons déléguer la sauvegarde à l’editeur de texte StructuredTextEditor de la page source. Je sais que ce choix fera hurler plus d’un qui préféreait sauvegarder l’instance EMF, mais j’ai fait le choix de sauvegarder ce que saisit l’utilisateur quitte à ce que le contenu XML saisi ne soit pas valide.
Pour cela modifier la méthode WorkflowEditor#doSave(IProgressMonitor progressMonitor) comme suit :
* This is for implementing {@link IEditorPart} and simply saves the model file.
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated NOT
*/
@Override
public void doSave(IProgressMonitor progressMonitor) {
// Save only resources that have actually changed.
//
final Map<Object, Object> saveOptions = new HashMap<Object, Object>();
saveOptions.put(Resource.OPTION_SAVE_ONLY_IF_CHANGED, Resource.OPTION_SAVE_ONLY_IF_CHANGED_MEMORY_BUFFER);
// Do the work within an operation because this is a long running activity that modifies the workbench.
//
WorkspaceModifyOperation operation =
new WorkspaceModifyOperation() {
// This is the method that gets invoked when the operation runs.
//
@Override
public void execute(IProgressMonitor monitor) {
// Save the resources to the file system.
//
boolean first = true;
for (Resource resource : editingDomain.getResourceSet().getResources()) {
if ((first || !resource.getContents().isEmpty() || isPersisted(resource)) && !editingDomain.isReadOnly(resource)) {
try {
long timeStamp = resource.getTimeStamp();
//resource.save(saveOptions);
if (resource.getTimeStamp() != timeStamp) {
savedResources.add(resource);
}
}
catch (Exception exception) {
resourceToDiagnosticMap.put(resource, analyzeResourceProblems(resource, exception));
}
first = false;
}
}
}
};
updateProblemIndication = false;
try {
// This runs the options, and shows progress.
//
new ProgressMonitorDialog(getSite().getShell()).run(true, false, operation);
sourcePage.doSave(progressMonitor);
// Refresh the necessary state.
//
((BasicCommandStack)editingDomain.getCommandStack()).saveIsDone();
firePropertyChange(IEditorPart.PROP_DIRTY);
}
catch (Exception exception) {
// Something went wrong that shouldn't.
//
WorkflowEditorPlugin.INSTANCE.log(exception);
}
updateProblemIndication = true;
updateProblemIndication();
}
Cette modification consite à :
- mettre en commentaire la sauvegarde de l’instance EMF :
//resource.save(saveOptions);
- déleguer la sauvegarde à l’éditeur de texte de la page source :
sourcePage.doSave(progressMonitor);
JSF Webtools fonctionne de la même manière.
La sauvegarde de l’éditeur fonctionne à nouveau.
Conclusion
On peut constater que fonctionnelement les Translator apporte beaucoup mais au niveau codage, ils sont assez pénible à mettre en place car il n’utilise pas les information meta object de l’Ecore : les ID EMF ont dû être gérés via les classes MyIDTranslator et HRefTranslator.
Il existe à cette étape encore un problème de undo/redo. En effet si vous tappez du contenu XML dans l’éditeur de texte puis si vous allez dans une autre page de l’éditeur puis vous cliquer sur Ctrl+Z, vous pourrez constater que le undo ne fonctionne pas (et inversement). Le undo fonctionne que si on l’effectue dans la page source. Ceci s’explique par le fait que l’editeur de texte gère sa stack de commande et que les autres pages gérent une autre stack de commande (l’editingDomain). Je tenetrais dans le prochain billet d’apporter une solution pour règler ce problème.
2 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
Bonjour knuck,
Je n’ai jamais intégré un editeur de type DOM-SSE dans une application RCP. je ne sais pas si c’est possible de faire ca. Je croies (mais je ne suis pas sur) que l’API se base sur le Plug-In Resources d’Eclipse et la il faudra surement importer ce Plug-In mais je n’ai jamais fait.
Bon courage!
Angelo
Bonjour,
J’ai un soucis avec la vue « Source ». Je crée mon éditeur via EMF avec toutes les options par défaut excepté « Rich Client Platform » que je passe à true.
Dans la méthode WorkflowEditor :: createPages j’ajoute ton code (ainsi que la dépendence):
/************************************/
/* Adding a source page*/
{
StructuredTextEditor sourcePage = new StructuredTextEditor();
sourcePage.setEditorPart(this);
try {
int pageIndex = addPage(sourcePage, getEditorInput());
setPageText(pageIndex, « Source »);
} catch (PartInitException e) {
e.printStackTrace();
}
}
/************************************/
à l’exécution j’obtiens une exception:
org.eclipse.ui.PartInitException: Text editor does not have a document provider
at org.eclipse.ui.texteditor.AbstractTextEditor.internalInit(AbstractTextEditor.java:3148) …..
L’onglet source n’apparait même pas.
As-tu une idée d’où peut venir le problème ?
Merci pour ta réponse,
Olivier