Accueil > DynaResume, Eclipse RCP, OSGi > Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step12]

Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step12]


Dans le billet précédant [step11] nous avons vu mis en place Spring Dynamic Module coté client et serveur. Dans ce billet et le suivant nous allons nous concentrer sur la mise en place d’un client Eclipse RCP qui consommera le service UserService pour afficher la liste des Users dans une View.

Dans ce billet nous allons initialiser le client RCP avec une View qui affichera une liste statique de Users (sans faire appel au service UserService) :

Autrement dit, ici nous ne ferons pas appel au service UserService (Full Client/ Client- Server) via Spring DM pour récupérer la liste des User. Notre application RCP dans ce billet dépendra uniquement de notre bundle Domain org.dynaresume.domain qui contient la classe User.

Dans le billet suivant nous expliquerons comment appeler le service UserService comme ce que nous avons fait dans le client simple OSGi org.dynaresume.simpleosgiclient. Ceci permettra d’expliquer comment utiliser Spring DM dans une application RCP.

Eclipse RCP

Eclipse RCP (Rich Client Platform) permet de développer ses propres applications de type clients riches en s’appuyant sur les composants d’Eclipse (mais tout en étant indépendant de l’IDE Eclipse) comme :

  • SWT la bibliothèque graphique de base de l’IDE Eclipse
  • JFace une bibliothèque graphique de plus haut niveau basée sur SWT.
  • Eclipse Workbench qui est la dernière couche graphique permettant de manipuler des composants tels que des vues, des éditeurs, des perspectives

Une application RCP est un workbench constitué d’une perspective par défaut. L’IDE Eclipse est en fait une application RCP qui à un workbench constitué de la perspective par défaut « Resources ». Si vous ne connaissez pas Eclipse RCP, je vous conseille vivement de lire :

Création de notre 1er Application RCP

PDE permet de générer une application RCP à travers divers wizards. Dans notre cas nous souhaitons afficher une View qui affiche une liste de Users. Pour cela nous utiliserons le template PDE RCP application with a view pour générer notre client RCP. Nous décortiquerons dans un premier temps le code généré par PDE puis nous modifierons le code pour afficher notre liste de Users.

Vous pouvez télécharger org.dynaresume_step12.zip qui contient les projets expliqués dans ce billet :

  • org.dynaresume.domain le bundle OSGi Domain qui contient la classe User.
  • org.dynaresume.simplercpclient le client RCP qui affiche une liste statique de Users.
  • dynaresume-launch projet simple Eclipse qui contient le launch DynaResume – org.dynaresume.simplercpclient.application.launch qui permet de lancer l’application RCP et qui est celui expliqué tout au long de ce billet.

Si vous importez les 3 projets, votre workspace ressemblera à ca :

Vous pourrez lancer l’application RCP qui affiche la liste statique de Users en utilisant le launch DynaResume – org.dynaresume.simplercpclient.application.launch (en faisant Run Configurations…le launch doit apparaître dans le noeud Eclipse Application :

Sélectionnez ce launch et cliquez ensuite sur le bouton Run.

Création Application RCP

Ici nous allons créer l’application RCP (projet Plug-in) org.dynaresume.simplercpclient. Pour cela sélectionnez le menu File/New/Other puis Plug-in Development/Plug-in Project :

Cliquez sur le bouton Next, puis :

  • renseignez le champs Project name par org.dynaresume.simplercpclient.
  • laissez sélectionner le bouton radio Eclipse Version car nous souhaitons développer un Plug-in Eclipse.

Cliquez sur le bouton Next, le wizard qui permet de configurer le Plug-in s’ouvre en pré-remplissant la plupart des champs. Voici une copie d’écran de ce wizard où j’ai mis en rouge ce que j’ai modifié :

  • le champs ID est l’identifiant de notre Bundle. Il est pré-rempli en utilisant le nom du projet.
  • le champs Version est la version de notre Bundle. Il est pré-remplit avec 1.0.0.qualifier.
  • le champs Name est le nom de notre Bundle. Cette information n’est pas très importante. Dans ce billet je l’ai renseigné avec DynaResume RCP Client
  • le champs Provider est généralement le nom de la société qui créé le Bundle. Cette information n’est pas très importante.
  • le champs Execution Environment indique la version minimale de la JRE pour que le Bundle puisse être éxécuté.
  • l’option generate an activator, a Java class that controls the plug-in’s life cycle est cochée par défaut. Nous la laissons cochée pour générer une classe Activator dans ce Bundle (dans le package internal), même si au final nous n’en n’aurons pas vraiment besoin pour ce Bundle .
  • cochez la case This plug-in will make contributions to the UI
  • cochez yes a la question Would like to create a rich client application? car nous souhaitons développer une application RCP.

Cliquez sur le bouton Next puis sélectionnez le template RCP application with a view :

Cliquez sur le bouton Finish pour générer le projet Application RCP org.dynaresume.simplercpclient dans votre workspace..

Run – Application RCP

Avant d’expliquer le contenu de l’application RCP générée par PDE, nous allons lancer l’application. Pour cela ouvrez le fichier MANIFEST.MF ou plugin.xml du projet org.dynaresume.simplercpclient. Ceci ouvre l’Editor PDE et cliquez sur l’onglet Overview :

Cliquez sur le lien Launch an Eclipse Application qui lancera l’application RCP et qui affichera la fenêtre suivante :

Cette action a généré un launch qui utilise l’ID de l’application RCP (dans notre cas org.dynaresume.simplercpclient.application). Pour y accéder vous pouvez allez dans le menu Run :

Contenu Application RCP créé

Voici le contenu du projet Application RCP générée par PDE :

Un projet RCP est constitué :

Un projet application RCP est un projet bundle OSGi qui décrit ces dépendances, son Identifiant… via le fichier MANIFEST.MF. Les autres informations qui concerne généralement l’Interface Utilisateur (Menu, Perspective, View…), sont décrites dans le fichier plugin.xml.

MANIFEST.MF

Voici le contenu du fichier META-INF/MANIFEST.MF généré par PDE :

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: DynaResume RCP Client
Bundle-SymbolicName: org.dynaresume.simplercpclient; singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: org.dynaresume.simplercpclient.internal.Activator
Require-Bundle: org.eclipse.ui,
 org.eclipse.core.runtime
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6

On peut remarquer qu’une application RCP est un bundle OSGi. Les méta-données OSGi intéressantes dans le fichier MANIFEST.MF sont :

  • Bundle-SymbolicName contient l’information singleton:=true :
    Bundle-SymbolicName: org.dynaresume.simplercpclient; singleton:=true

    Ceci signifie qu’un application RCP est un singleton, autrement dit il n’est pas possible de faire cohabiter dans le conteneur OSGi plusieurs versions d’une même applications RCP. Ceci s’explique par le fait qu’il est possible d’enrichir l’application RCP avec d’autres plugins pour ajouter des menus par exemple (comme lorsque l’on développe un Plug-in pour l’IDE Eclipse). Les plugins qui enrichirait l’application RCP s’applique sur UNE application RCP donnée.

  • Require-Bundle est renseigné comme suit :
    Require-Bundle: org.eclipse.ui,
     org.eclipse.core.runtime

    Ceci signifie qu’une application RCP dépend (au minimum) des 2 bundles org.eclipse.ui et org.eclipse.core.runtime.

  • Bundle-ActivationPolicy est positionné à lazy :
    Bundle-ActivationPolicy: lazy

    Cette méta donnée qui fait partie de la spécification OSGi remplace l’ancienne méta donnée Eclipse-LazyStart. Elle permet de charger les classes du Bundle que si celui-ci est sollicité. Dans un Plug-In Eclipse, Bundle-ActivationPolicy est très intéressante car il permet d’améliorer le temps de démarrage de l’IDE Eclipse constitué d’une multitude de Plug-In. Les classes des Plug-In sont chargées uniquement à la demande lorsque le Plug-In est sollicité par exemple par une action utilisateur qui souhaite utiliser un Plug-in. Dans notre cas ou on souhaite lancer l’application RCP; Bundle-ActivationPolicy ne sert pas à grand chose.

    Pour plus d’information veuillez consulter :

    Classes Java RCP

    Une application RCP doit définir au minimum 2 concepts :

    • le point d’entrée de l’application qui s’occupe d’instancier le workbench. Ceci s’effectue en implémentant l’interface org.eclipse.equinox.app.IApplication qui impose d’implémenter les méthodes IApplication#start(IApplicationContext context) et IApplication#stop(). Dans notre cas, PDE a généré la classe org.dynaresume.simplercpclient.Application. qui implémente org.eclipse.equinox.app.IApplication.
    • la perspective par défaut à utiliser dans le workbench créé. PDE a généré une classe org.dynaresume.simplercpclient.Perspective qui est en fait une factory de Perspetvive qui implémente org.eclipse.ui.IPerspectiveFactory. Cette classe ajoute la View (qui affiche la liste des Users) dans la perspective.

    Nous allons voir plus comment tout ceci s’articule en détaillant le code généré par PDE.

    Application

    Voici le code de la classe générée par PDE org.dynaresume.simplercpclient.Application :

    package org.dynaresume.simplercpclient;
    
    import org.eclipse.equinox.app.IApplication;
    import org.eclipse.equinox.app.IApplicationContext;
    import org.eclipse.swt.widgets.Display;
    import org.eclipse.ui.IWorkbench;
    import org.eclipse.ui.PlatformUI;
    
    public class Application implements IApplication {
    
    	public Object start(IApplicationContext context) {
    		Display display = PlatformUI.createDisplay();
    		try {
    			int returnCode = PlatformUI.createAndRunWorkbench(display, new ApplicationWorkbenchAdvisor());
    			if (returnCode == PlatformUI.RETURN_RESTART) {
    				return IApplication.EXIT_RESTART;
    			}
    			return IApplication.EXIT_OK;
    		} finally {
    			display.dispose();
    		}
    	}
    
    	public void stop() {
    		final IWorkbench workbench = PlatformUI.getWorkbench();
    		if (workbench == null)
    			return;
    		final Display display = workbench.getDisplay();
    		display.syncExec(new Runnable() {
    			public void run() {
    				if (!display.isDisposed())
    					workbench.close();
    			}
    		});
    	}
    }

    Cette classe qui implémente l’interface org.eclipse.equinox.app.IApplication est le point d’entrée de l’application RCP. Elle implémente la méthode IApplication#start(IApplicationContext context) comme ceci :

    public Object start(IApplicationContext context) {
    	Display display = PlatformUI.createDisplay();
    	try {
    		int returnCode = PlatformUI.createAndRunWorkbench(display, new ApplicationWorkbenchAdvisor());
    		if (returnCode == PlatformUI.RETURN_RESTART) {
    			return IApplication.EXIT_RESTART;
    		}
    		return IApplication.EXIT_OK;
    	} finally {
    		display.dispose();
    	}
    }

    Le code

    PlatformUI.createAndRunWorkbench(display, new ApplicationWorkbenchAdvisor());

    créé une instance de workbench en lui fournissant une instance de la classe générée org.dynaresume.simplercpclient.ApplicationWorkbenchAdvisor qui éténd la classe org.eclipse.ui.application.WorkbenchAdvisor. ApplicationWorkbenchAdvisor permet entre autres d’indiquer l’ID de la perspective par défaut à utiliser dans le workbench.

    Nous verrons que la méthode stop() n’est jamais appelé dans un arrêt normal de l’application. Je n’ai cependant pas trouvé à ce jour de cas ou celle-ci est appelée (que signifie arrêt NON normal?). J’ai lancé une question sur [Eclipse RCP] org.eclipse.equinox.app.IApplication#stop() jamais appelé?.

    ApplicationWorkbenchAdvisor

    Voici le code de la classe générée par PDE org.dynaresume.simplercpclient.ApplicationWorkbenchAdvisor :

    package org.dynaresume.simplercpclient;
    
    import org.eclipse.ui.application.IWorkbenchWindowConfigurer;
    import org.eclipse.ui.application.WorkbenchAdvisor;
    import org.eclipse.ui.application.WorkbenchWindowAdvisor;
    
    public class ApplicationWorkbenchAdvisor extends WorkbenchAdvisor {
    
    	private static final String PERSPECTIVE_ID = "org.dynaresume.simplercpclient.perspective";
    
    	public WorkbenchWindowAdvisor createWorkbenchWindowAdvisor(
    			IWorkbenchWindowConfigurer configurer) {
    		return new ApplicationWorkbenchWindowAdvisor(configurer);
    	}
    
    	public String getInitialWindowPerspectiveId() {
    		return PERSPECTIVE_ID;
    	}
    
    }

    Cette classe qui éténd la classe org.eclipse.ui.application.WorkbenchAdvisor permet d’indiquer au workbench créé :

    • l’ID de la perspective à utiliser par défaut en implémentant la méthode WorkbenchAdvisor#getInitialWindowPerspectiveId(). Dans notre cas l’ID de la perspective est org.dynaresume.simplercpclient.perspective. Cette ID est utilisé pour retrouver la bonne perspective définit dans le fichier plugin.xml:
      <perspective
                  name="Perspective"
                  class="org.dynaresume.simplercpclient.Perspective"
                  id="org.dynaresume.simplercpclient.perspective">
      </perspective>

      Cette déclaration indique que la classe org.dynaresume.simplercpclient.Perspective sera utilisée en tant que Perspective.

    • la configuration à utiliser pour la fenêtre (globale) du workbench (comme les menus à afficher, le titre de la fénêtre…) en surchargeant la méthode WorkbenchAdvisor#createWorkbenchWindowAdvisor(IWorkbenchWindowConfigurer configurer). Dans notre cas ceci s’effectuera via une instance de la classe générée org.dynaresume.simplercpclient.ApplicationWorkbenchWindowAdvisor.
    ApplicationWorkbenchWindowAdvisor

    Voici le code de la classe générée par PDE org.dynaresume.simplercpclient.ApplicationWorkbenchWindowAdvisor :

    package org.dynaresume.simplercpclient;
    
    import org.eclipse.swt.graphics.Point;
    import org.eclipse.ui.application.ActionBarAdvisor;
    import org.eclipse.ui.application.IActionBarConfigurer;
    import org.eclipse.ui.application.IWorkbenchWindowConfigurer;
    import org.eclipse.ui.application.WorkbenchWindowAdvisor;
    
    public class ApplicationWorkbenchWindowAdvisor extends WorkbenchWindowAdvisor {
    
    	public ApplicationWorkbenchWindowAdvisor(IWorkbenchWindowConfigurer configurer) {
    		super(configurer);
    	}
    
    	public ActionBarAdvisor createActionBarAdvisor(
    			IActionBarConfigurer configurer) {
    		return new ApplicationActionBarAdvisor(configurer);
    	}
    
    	public void preWindowOpen() {
    		IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
    		configurer.setInitialSize(new Point(400, 300));
    		configurer.setShowCoolBar(false);
    		configurer.setShowStatusLine(false);
    		configurer.setTitle("RCP Application");
    	}
    }

    Cette classe qui éténd la classe org.eclipse.ui.application.WorkbenchWindowAdvisor permet de configurer la fenêtre du workbench comme :

    • sa taille, son titre… qui s’effectue dans la méthode ApplicationWorkbenchWindowAdvisor#preWindowOpen() :
      public void preWindowOpen() {
      	IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
      	configurer.setInitialSize(new Point(400, 300));
      	configurer.setShowCoolBar(false);
      	configurer.setShowStatusLine(false);
      	configurer.setTitle("RCP Application");
      }
    • sa barre de menu qui s’effectue dans la méthode ApplicationWorkbenchWindowAdvisor#createActionBarAdvisor(IActionBarConfigurer configurer) qui doit retourner une instance de org.eclipse.ui.application.ActionBarAdvisor :
      public ActionBarAdvisor createActionBarAdvisor(IActionBarConfigurer configurer) {
      	return new ApplicationActionBarAdvisor(configurer);
      }

      Dans notre cas la barre de menu de notre application RCP est géré par la classe org.dynaresume.simplercpclient.ApplicationActionBarAdvisor qui permet d’afficher un menu File/Exit.

    ApplicationActionBarAdvisor

    Voici le code de la classe générée par PDE org.dynaresume.simplercpclient.ApplicationActionBarAdvisor :

    package org.dynaresume.simplercpclient;
    
    import org.eclipse.jface.action.IMenuManager;
    
    public class ApplicationActionBarAdvisor extends ActionBarAdvisor {
    
    	private IWorkbenchAction exitAction;
    
    	public ApplicationActionBarAdvisor(IActionBarConfigurer configurer) {
    		super(configurer);
    	}
    
    	protected void makeActions(final IWorkbenchWindow window) {
    		exitAction = ActionFactory.QUIT.create(window);
    		register(exitAction);
    	}
    
    	protected void fillMenuBar(IMenuManager menuBar) {
    		MenuManager fileMenu = new MenuManager("&File",
    				IWorkbenchActionConstants.M_FILE);
    		menuBar.add(fileMenu);
    		fileMenu.add(exitAction);
    	}
    
    }

    Cette classe s’occupe de créer la barre de menu File/Exit du workbench autrement dit :

    • ActionBarAdvisor#makeActions(final IWorkbenchWindow window) créée l’action Exit.
    • ActionBarAdvisor#fillMenuBar(IMenuManager menuBar) créée le menu et ajoute l’action Exit à la barre de menu.
    Perspective

    Voici le code de la classe générée par PDE org.dynaresume.simplercpclient.Perspective :

    package org.dynaresume.simplercpclient;
    
    import org.eclipse.ui.IPageLayout;
    import org.eclipse.ui.IPerspectiveFactory;
    
    public class Perspective implements IPerspectiveFactory {
    
    	public void createInitialLayout(IPageLayout layout) {
    		String editorArea = layout.getEditorArea();
    		layout.setEditorAreaVisible(false);
    		layout.setFixed(true);
    		
    		layout.addStandaloneView(View.ID,  false, IPageLayout.LEFT, 1.0f, editorArea);
    	}
    
    }

    La perspective ajoute la View d’ID org.dynaresume.simplercpclient.view :

    layout.addStandaloneView(View.ID,  false, IPageLayout.LEFT, 1.0f, editorArea);

    La classe à utilisé pour la View est org.dynaresume.simplercpclient.View. En effet la View est retrouvé par son ID dans la déclaration du plugin.xml :

    <extension point="org.eclipse.ui.views">
        <view
              name="View"
              class="org.dynaresume.simplercpclient.View"
              id="org.dynaresume.simplercpclient.view">
        </view>
    </extension>
    View

    Voici le code de la classe générée par PDE org.dynaresume.simplercpclient.View :

    package org.dynaresume.simplercpclient;
    
    import org.eclipse.jface.viewers.IStructuredContentProvider;
    import org.eclipse.jface.viewers.ITableLabelProvider;
    import org.eclipse.jface.viewers.LabelProvider;
    import org.eclipse.jface.viewers.TableViewer;
    import org.eclipse.jface.viewers.Viewer;
    import org.eclipse.swt.SWT;
    import org.eclipse.swt.graphics.Image;
    import org.eclipse.swt.widgets.Composite;
    import org.eclipse.ui.ISharedImages;
    import org.eclipse.ui.PlatformUI;
    import org.eclipse.ui.part.ViewPart;
    
    public class View extends ViewPart {
    	public static final String ID = "org.dynaresume.simplercpclient.view";
    
    	private TableViewer viewer;
    
    	class ViewContentProvider implements IStructuredContentProvider {
    		public void inputChanged(Viewer v, Object oldInput, Object newInput) {
    		}
    
    		public void dispose() {
    		}
    
    		public Object[] getElements(Object parent) {
    			return new String[] { "One", "Two", "Three" };
    		}
    	}
    
    	class ViewLabelProvider extends LabelProvider implements
    			ITableLabelProvider {
    		public String getColumnText(Object obj, int index) {
    			return getText(obj);
    		}
    
    		public Image getColumnImage(Object obj, int index) {
    			return getImage(obj);
    		}
    
    		public Image getImage(Object obj) {
    			return PlatformUI.getWorkbench().getSharedImages().getImage(
    					ISharedImages.IMG_OBJ_ELEMENT);
    		}
    	}
    
    	public void createPartControl(Composite parent) {
    		viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
    				| SWT.V_SCROLL);
    		viewer.setContentProvider(new ViewContentProvider());
    		viewer.setLabelProvider(new ViewLabelProvider());
    		viewer.setInput(getViewSite());
    	}
    
    	public void setFocus() {
    		viewer.getControl().setFocus();
    	}
    }

    La classe générée View étend org.eclipse.ui.part.ViewPart. Cette View est constituée d’une Table SWT créé dans la méthode ViewPart#createPartControl(Composite parent) :

    public void createPartControl(Composite parent) {
       viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
       ...
    }

    Le TableViewer créé en interne une Table SWT. Le viewer s’occupe d’afficher une liste d’items et pour cela il a besoin de 2 concepts :

    • org.eclipse.jface.viewers.IContentProvider qui joue le rôle de « fournisseur de contenu ». Dans notre cas le fournisseur de contenu est une liste de String [« One », « Two », « Three »] qui est représenté par la classe interne ViewContentProvider :
      class ViewContentProvider implements IStructuredContentProvider {
              ...
      	public Object[] getElements(Object parent) {
      		return new String[] { "One", "Two", "Three" };
      	}
      }

      Le « fournisseur de contenu » ViewContentProvider est renseigné au viewer TableViewer après sa creation comme suit :

      viewer.setContentProvider(new ViewContentProvider());
    • org.eclipse.jface.viewers.IBaseLabelProvider qui joue le rôle de « fournisseur de libéllé ». L’implémentation de cette interface indique comment le contenu (provenant du « fournisseur de contenu ») doit s’afficher dans la Table SWT. Dans notre cas voici la classe interne ViewLabelProvider qui est utilisé:
      class ViewLabelProvider extends LabelProvider implements ITableLabelProvider {
      	public String getColumnText(Object obj, int index) {
      		return getText(obj);
      	}
      	...
      }

      La méthode appelée LabelProvider#getText(Object obj) effectue un toString() sur l’objet passé en paramètre.Le « fournisseur de libéllé » de TableViewer est initialisé après sa creation comme suit :

      viewer.setLabelProvider(new ViewLabelProvider());

    plugin.xml

    Voici le contenu du fichier plugin.xml généré par PDE :

    <?xml version="1.0" encoding="UTF-8"?>
    <?eclipse version="3.4"?>
    <plugin>
    
       <extension
             id="application"
             point="org.eclipse.core.runtime.applications">
          <application>
             <run
                   class="org.dynaresume.simplercpclient.Application">
             </run>
          </application>
       </extension>
       <extension
             point="org.eclipse.ui.perspectives">
          <perspective
                name="Perspective"
                class="org.dynaresume.simplercpclient.Perspective"
                id="org.dynaresume.simplercpclient.perspective">
          </perspective>
       </extension>
       <extension
             point="org.eclipse.ui.views">
          <view
                name="View"
                class="org.dynaresume.simplercpclient.View"
                id="org.dynaresume.simplercpclient.view">
          </view>
       </extension>
    
    </plugin>

    Ce fichier XML permet de déclarer les 3 composants nécéssaires pour notre application RCP constitué d’une View :

    • la classe Application à utiliser qui créé le workbench.
    • la classe Perspective à utiliser qui ajoute la View.
    • la classe View qui s’occupe d’affiche la liste de String.

    Cycle de vie – Application RCP

    Ici nous allons afficher des traces avec System.out pour suivre dans la console le cycle de vie de notre application RCP. Pour cela, modifiez la classe org.dynaresume.simplercpclient.internal.Activator en ajoutant les traces System.out suivantes dans les méthodes Activator#start(BundleContext context) et Activator#stop(BundleContext context) :

    public class Activator extends AbstractUIPlugin {
            ...
    	public void start(BundleContext context) throws Exception {
    		super.start(context);
    		plugin = this;
    		System.out.println("Start Bundle [" + context.getBundle().getSymbolicName() + "]");
    	}
    
    	public void stop(BundleContext context) throws Exception {
    		plugin = null;
    		super.stop(context);
    		System.out.println("Stop Bundle [" + context.getBundle().getSymbolicName() + "]");
    	}
            ...
    }

    Modifiez la classe org.dynaresume.simplercpclient.Application en ajoutant les traces System.out suivantes dans les méthodes Application#start(IApplicationContext context) et Application#stop() :

    public class Application implements IApplication {
    
    	public Object start(IApplicationContext context) {
    		System.out.println("Start RCP Application");
                    ...
    	}
    
    	public void stop() {
    		System.out.println("Stop RCP Application");
                    ...
    	}
    }

    Relancez l’application RCP et la console affiche :

    Start Bundle [org.dynaresume.simplercpclient]
    Start RCP Application

    Arrêtez l’application (en fermant la fenêtre), la console affiche :

    Stop Bundle [org.dynaresume.simplercpclient]

    On peut remarquer que la méthode stop de Application n’est jamais appelée.

    Console OSGi

    Une application RCP est un bundle OSGi, pour le prouver Relancez l’application RCP et tappez ss dans la console puis entrée. Rien ne se passe. En effet par défaut la console OSGi n’est pas accéssible. Pour la rendre accéssible il faut configurer les arguments du launch. Pour cela éditez le launch (Run Configurations…) puis accédez à l’onglet Arguments et ajoutez dans la textarea Program Arguments le paramètre -console (pensez à mettre un espace après le dernier paramètre).

    Relancez l’application RCP et la console (OSGi cette fois) affiche le contenu suivant :

    osgi> Start Bundle [org.dynaresume.simplercpclient]
    Start RCP Application

    On peut remarquer que la trace commence par osgi> ce qui indique que nous sommes dans une console OSGi.

    Tappez ss puis entree, la console OSGi affiche :

    ss

    Framework is launched.

    id State Bundle
    0 ACTIVE org.eclipse.osgi_3.5.0.v20090520
    ...
    29 ACTIVE org.dynaresume.simplercpclient_1.0.0.qualifier

    Nous voyons que notre aplication RCP d’ID org.dynaresume.simplercpclient s’affiche dans la liste des bundles OSGi avec l’état ACTIVE.

    View – Liste User

    Ici nous allons afficher la liste des Users. Notre application RCP doit avoir accès à la classe org.dynaresume.domain.User. Pour cela nous devons (après avoir ouvert le fichier MANIFEST.MF de notre application RCP) importer le package org.dynaresume.domain dans notre application RCP.

    Après avoir importé le package, le fichier MANIFEST.MF a le contenu suivant :

    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: DynaResume RCP Client
    Bundle-SymbolicName: org.dynaresume.simplercpclient; singleton:=true
    Bundle-Version: 1.0.0.qualifier
    Bundle-Activator: org.dynaresume.simplercpclient.internal.Activator
    Require-Bundle: org.eclipse.ui,
     org.eclipse.core.runtime
    Bundle-ActivationPolicy: lazy
    Bundle-RequiredExecutionEnvironment: JavaSE-1.6
    Import-Package: org.dynaresume.domain

    A ce stade nous avons accès à la classe User dans notre application RCP. Nous allons construire une liste statique de User dans la classe View puis nous la passerons au « fournisseur de contenu » à l’aide de la méthode TableViewer#setInput(object o).

    Pour cela modifiez la méthode View#createPartControl(Composite parent) de la classe org.dynaresume.simplercpclient.Viewcomme suit :

    public void createPartControl(Composite parent) {
    ...		
    		List<User> users = new ArrayList<User>();
    		users.add(new User("user1", "pwd1"));
    		users.add(new User("user2", "pwd2"));
    		users.add(new User("user3", "pwd2"));
    		
    		viewer.setInput(users);
    }

    Le « fournisseur de contenu » doit être modifié pour récupérer la liste des Users. Pour cela modifiez la classe ViewContentProvider (interne à la classe View) comme suit :

    class ViewContentProvider implements IStructuredContentProvider {
    ...
    	@SuppressWarnings("unchecked")
    	public Object[] getElements(Object parent) {
    		return ((Collection<User>)parent).toArray();
    	}
    }

    Ce qui donne :

    package org.dynaresume.simplercpclient;
    
    import java.util.ArrayList;
    
    public class View extends ViewPart {
    	public static final String ID = "org.dynaresume.simplercpclient.view";
    
    	private TableViewer viewer;
    
    	class ViewContentProvider implements IStructuredContentProvider {
    		public void inputChanged(Viewer v, Object oldInput, Object newInput) {
    		}
    
    		public void dispose() {
    		}
    
    		@SuppressWarnings("unchecked")
    		public Object[] getElements(Object parent) {
    			return ((Collection<User>)parent).toArray();
    		}
    	}
    
    	class ViewLabelProvider extends LabelProvider implements
    			ITableLabelProvider {
    		public String getColumnText(Object obj, int index) {
    			return getText(obj);
    		}
    
    		public Image getColumnImage(Object obj, int index) {
    			return getImage(obj);
    		}
    
    		public Image getImage(Object obj) {
    			return PlatformUI.getWorkbench().getSharedImages().getImage(
    					ISharedImages.IMG_OBJ_ELEMENT);
    		}
    	}
    
    	public void createPartControl(Composite parent) {
    		viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
    				| SWT.V_SCROLL);
    		viewer.setContentProvider(new ViewContentProvider());
    		viewer.setLabelProvider(new ViewLabelProvider());
    		
    		List<User> users = new ArrayList<User>();
    		users.add(new User("user1", "pwd1"));
    		users.add(new User("user2", "pwd2"));
    		users.add(new User("user3", "pwd2"));
    		
    		viewer.setInput(users);
    	}
    
    	public void setFocus() {
    		viewer.getControl().setFocus();
    	}
    }

    Run – Application RCP – Liste Users

    A ce stade pour que l’application RCP fonctionne, le bundle org.dynaresume.domain doit être lancé dans le conteneur OSGi.

    Relancez l’application RCP et vous verrez le dialogue d’erreur suivant :

    Cliquez sur le bouton Yes, in a Editor ce qui ouvre un éditor avec le message d’erreur suivant :

    ...
    java.lang.RuntimeException: Application "org.dynaresume.simplercpclient.application" could not be found in the registry. The applications available are: org.eclipse.equinox.app.error.
    ...
    !ENTRY org.eclipse.osgi 2 0 2010-01-04 16:34:21.859
    !MESSAGE One or more bundles are not resolved because the following root constraints are not resolved:...
    !MESSAGE Missing imported package org.dynaresume.domain_0.0.0.
    ...

    Cette erreur indique que le package org.dynaresume.domain n’a pas pu être trouvé ce qui est normal car notre bundle Domaine org.dynaresume.domain n’est pas hébergé dans le conteneur OSGi.

    Afficher erreur dans Console

    Cette erreur ne s’affiche pas dans la console OSGi. Pour afficher les erreurs (les logs en général) dans la console OSGi, il faut modifiez le launch. Pour cela éditez le launch (Run Configurations…) puis accédez à l’onglet Arguments et ajoutez dans la textarea Program Arguments le paramètre -consoleLog (pensez à mettre un espace après le dernier paramètre):

    Relancez l’application RCP et vous verrez le même message d’erreur dans la console OSGi.

    Validate Plugins

    Il est possible de détecter cette erreur de dépendances à l’aide du bouton Validate Plugins du launch. Pour cela éditez le launch:

    Sélectionnez le launch et vous pourrez voir le bouton Validate Plugins apparaître:

    Cliquez sur le bouton Validate Plugins, la fenêtre de dialogue s’affiche et montre les dépendances manquantes :

    Resolution probleme

    Pour résoudre l’erreur de dépendance, sélectionnez le bundle org.dynaresume.domain dans le launch:

    Relancez l’application RCP, l’application RCP s’affiche avec la liste des Users (sous forme de String) :

    Pour afficher le login du User, modifiez la classe ViewLabelProvider comme suit :

    class ViewLabelProvider extends LabelProvider implements
    			ITableLabelProvider {
    		public String getColumnText(Object obj, int index) {
    			User user = (User)obj;
    			switch(index) {
    			case 0:
    				return user.getLogin();
    			}
    			throw new IllegalArgumentException("bad index!");
    		}
    ...
    }

    Relancez l’application RCP, l’application RCP s’affiche avec la liste des login des Users :

    Conclusion

    Dans ce billet nous avons vu comme il est simple de créer une application RCP à l’aide de PDE. Nous avons vu qu’il est important aussi de rajouter les 2 paramètres -console -consoleLog aux arguments du launch. Dans ce billet la liste des Users est construite dans la View. Nous verrons dans le prochain billet comment récupérer le service UserService via Spring DM dans la classe View, autrement dit comment injecter le service UserService à la classe View.

    Vous pouvez lire le billet suivant [step13].

Catégories :DynaResume, Eclipse RCP, OSGi
  1. Amine B.
    janvier 7, 2012 à 4:34

    Salut

    Merci Angelo, ce billet me fournit une excellente initiation pratique à Eclipse RCP !
    J’aime déjà beaucoup.

    Pour ce qui est de la méthode « stop » de « Application », je pense que tu dois être sur une fausse piste en cherchant sur le web quel évènement enclenche cette méthode.

    J’ai l’impression que la méthode « start » contient tout le code, avant et après fermeture de l’application.
    Par exemple, si tu mets ton « System.out.println(« Stop RCP Application »); » dans la méthode « start » et après la ligne « int returnCode = […] » tu obtiens l’effet que tu désirais avoir dans ce step12.
    A savoir qu’au démarrage on verra dans la console « start osgi bundle », puis « start application ». Cette méthode start est alors suspendue et attend la fin (normale ou anormale) de l’application.
    Puis quand on ferme la fenêtre, la méthode « start » reprend la main et on voit afficher dans la console « stop application », puis « stop osgi bundle »

    Donc à quoi sert la méthode « stop » ?
    Je pense, vu l’extrait de javadoc que tu as mis dans le forum de developpez.com, que ce n’est pas un listener mais qu’il permet à nous en tant que développeur de pouvoir forcer la fermeture de l’application de façon programmatique.
    Vu le code qu’il y a dedans ça concorde : Tu lances ce stop et ça enclenche la fin du workbench, puis par ricochet ça nous enverrait à nouveau dans la méthode « start », après la ligne « int returnCode = […] »

    Tu en penses quoi? Tu avais trouvé une réponse depuis le temps?

  2. janvier 7, 2012 à 4:58

    Salut Amine,

    Merci de ton post. Concernant la méthode stop, je n’ai jamais trouvé à quoi ca servait vraiment et je n’ai jamais eu besoin jusqu’à ce jour de l’implémenter.

    Bonne continuation dans tes billets:)

    Angelo

  3. janvier 10, 2013 à 1:58

    Hello Angelo. Sorry to post this in English, but my French is only sufficient to read your posts, not to write comments in it. Your blog is by far the best resource I found on the web for integration of a spring server using spring remoting and a RCP 3 client, thanks a lot for your efforts! Only one small remark on this post for step 12. It seems that for some earlier steps a jre 1.5 was required (for instance step 9, with the simple dynamic web module), but for me the RCP client didn’t start immediately, because it needed a jre 1.6. Maybe it’s worth mentioning this in your article. Do you know if writing a RCP 4 client would be easier or more difficult then the RCP 3.5 one you are describing here?

  4. janvier 10, 2013 à 2:14

    Hi Erik,

    No problem with your English post. I’m happy that my articles please you. I suggest you to read my english articles https://angelozerr.wordpress.com/about/eclipse_spring/ which uses Spring Data JPA and I explain how to manage remoting with Apache CXF and the application manage too Eclipse RAP (for Web context).

    For your JRE problem, I’m sorry, but I don’t remember.

    Eclipse E4 provides DI with @Inject, so you can inject easily your OSGi service to your Part. But I find that the big problem with E4 it’s that it doesn’t provide Editor or View clases. You must code your Editor at hand because a Part is simple Pojo.

    Regards Angelo

  1. janvier 5, 2010 à 1:12
  2. janvier 8, 2010 à 6:13

Laisser un commentaire