Accueil > Eclipse RCP, Spring, Spring Data JPA > Eclipse RCP/RAP with Spring DM, Spring Data JPA and Remoting [step8]

Eclipse RCP/RAP with Spring DM, Spring Data JPA and Remoting [step8]


In [step7] we have seen how to use Spring Data JPA to add custom methods to the Dao to manage criteria and pagination.

In this article we will implement a Rich Client with RCP Application to display User list in a SWT Table :

We will see how to we can consume the UserService by using Spring DM by using the SpringExtensionFactory from Martin Lippert.

Download

You can download eclipsespring_step8.zip which contains the following explained projects :

  • fr.opensagres.domain : OSGi bundle domain which hosts fr.opensagres.domain.User class.
  • fr.opensagres.dao : OSGi bundle Dao API which hosts fr.opensagres.dao.UserDao interface.
  • fr.opensagres.dao.mock : OSGi bundle Dao Mock Implementation (Dao implemented with Java Map) which hosts a Spring file which declares the implementation of UserDao in a Spring bean and publish it the OSGi services registry with Spring DM <osgi:service.
  • fr.opensagres.dao.jpa the bundle implementation of UserDao with JPA by using Spring Data JPA to avoid coding the JPAUserDao class.
  • fr.opensagres.dao.jpa.eclipselink the fragment which configures the JPA fr.opensagres.dao.jpa to use EclipseLink and Deby as dialect.
  • fr.opensagres.data.datasource the bundle which publishes the Derby Datasource used by the fr.opensagres.dao.jpa.
  • fr.opensagres.services : OSGi bundle Services API which hosts fr.opensagres.services.UserService interface.
  • fr.opensagres.services.impl : OSGi bundle Services Implementation which hosts a Spring file which declares the implementation of UserService in a Spring bean and publish it the OSGi services registry with Spring DM <osgi:service.
  • fr.opensagres.data.injector : OSGi bundle Data Injector which hosts a Spring file which declares a DataInjector in a Spring bean. This DataInjector consumes the UserService injected by Spring which is retrieved from the OSGi services registry with Spring DM <osgi:reference and call UserService#saveUser(User user) to inject User data.
  • fr.opensagres.simpleclient : OSGi bundle simple client which hosts a Spring file which declares a thread in a Spring bean. This thread consumes the UserService injected by Spring which is retrieved from the OSGi services registry with Spring DM <osgi:reference.
  • fr.opensagres.richclient :RCP client which displays in a SWT Table the User list. The ViewPart consumes the UserService injected by Spring which is retrieved from the OSGi services registry with Spring DM <osgi:reference by using the SpringExtensionFactory.
  • fr.opensagres.config.log4j : OSGi fragment which configures log4j.
  • TargetPlatform: simple project which hosts the Spring DM JARS, the target definition and launch.

To use this zip, unzip it :

  1. import those projects in a workspace.
  2. open the TargetPlatform/eclipsespring.target file and click on Set as Target Platform.
  3. select TargetPlatform/launch/Simple OSGi client.launch – Mock Dao to test the launch with Mock Dao and Run it.
  4. select TargetPlatform/launch/Simple OSGi client.launch – JPA Dao to test the launch with JPA Dao and Run it.
  5. select TargetPlatform/launch/RCP Client – Mock Dao.launch to test the RCP Application with Mock Dao and Run it.
  6. select TargetPlatform/launch/RCP Client – JPA Dao.launch to test the RCP Application with JPA Dao and Run it.

RCP Application with a view

In this section we will generate a RCP application with the PDE template RAP Hello World.

Generate RCP Application with a view

RCP application is an Eclipse Plugin. To create a RCP Application we need to create an Eclipse Plugin. To do that, go to the File/New/Other… menu :

Select Plug-in Development/Plug-in Project node :

Click on Next button, the wizard page which gives the choice to create OSGi Bundle or Eclipse Plugin is displayed :

  • fill in the Project name field with fr.opensagres.richclient.
  • select the radio button Eclipse version because we would like to develop RCP application which is an Eclipse Plugin.

Click on Next button, the wizard page which configure the Eclipse Plugin is displayed :

  • ID field is the Bundle identifier (Bundle-SymbolicName: fr.opensagres.richclient).
  • Version field is the Bundle version (Bundle-Version: 1.0.0.qualifier).
  • Name field is the Bundle name (Bundle-Name: Richclient).
  • Execution Environment sets the minimal version of the JRE in order to execute the bundle (Bundle-RequiredExecutionEnvironment: JavaSE-1.6).
  • you can unselect generate an activator, a Java class that controls the plug-in’s life cycle to avoid generating a Bundle Activator
  • select This plug-in will make contributions to the UI because we want to generate RCP application
  • select yes for Would you like create a rich client application? because we want to generate a RCP application.

Click on Next button, the wizard page which show several PDE templates is displayed :

Select RCP Application with a view template to generate RCP application with a view.

Click on Next button, the wizard page which gives you the capability to customize package names, class names of the RCP project to generate is displayed :

When generation is done, your workspace looks like this :

Launch RCP Application

In this section we can start the generated RCP Application. To do that open MANIFEST.MF and click on Overview tab.

Click on Launch an Eclipse Application link on Testing section. This action starts the RCP fat client application :

SpringExtensionFactory

The generated Application contains a fr.opensagres.richclient.View class which host a SWT Table to display One, two, Three. In this article we will modify this View class to display the User list coming from the UserService. T

he classic mean to consume the UserService from the OSGi services registry is to use an OSGi ServiceTracker. But a preferred mean is used Dependency Injection to inject the UserService to the View class (like our FindAllUsersThread class):

public class View extends ViewPart {
	public static final String ID = "fr.opensagres.richclient.view";

	private UserService userService;

	public void setUserService(UserService userService) {
		this.userService = userService;
	}
...
}

And consume the userService to refresh the TableViewer with User list :

private void refreshUsers() {
	try {
		viewer.setInput(userService.findAll());
	} catch (Throwable e) {
		Status status = new Status(IStatus.ERROR, "My Plug-in ID", 0,
				"Status Error Message", e);
		ErrorDialog.openError(viewer.getTable().getShell(),
				"UserService Error", e.getMessage(), status);
	}
}

The problem with Eclipse RCP is that View class instance is managed by an extension point :

   <extension
         point="org.eclipse.ui.views">         
      <view
            name="View"
            class="fr.opensagres.richclient.View"
            id="fr.opensagres.richclient.view">
      </view>
   </extension>

and we need declare the fr.opensagres.richclient.View class in a Spring bean to benefit from Spring DM (retrieves the UserService from the OSGi regsitry services) and use Dependency Injection (inject the UserService):

<bean id="fr.opensagres.richclient.view" class="fr.opensagres.richclient.View"
	scope="prototype">
	<property name="userService" ref="userService"></property>
</bean>

Eclipse E4 provides the Dependency Injection with @Inject annotation (see Eclipse 4.0: Inject your OWN Objects for more information), but in our project we have not used Eclipse E4 because Eclipse RAP doesn’t support it today. As soon as Eclipse RAP will support Eclipse E4, we will migrate to it.

With RCP Application, the Dependency Injection with Spring can be done with SpringExtensionFactory from Martin Lippert. The idea is to declare the view in the extension point like this :

   <extension
         point="org.eclipse.ui.views">
      <view
            name="View"
            class="org.eclipse.springframework.util.SpringExtensionFactory"
            id="fr.opensagres.richclient.view">
      </view>         
   </extension>

And you can after declare a bean with id fr.opensagres.richclient.view like this :

<bean id="fr.opensagres.richclient.view" class="fr.opensagres.richclient.View"
	scope="prototype">
	<property name="userService" ref="userService"></property>
</bean>

SpringExtensionfactory & Target Definition

  1. Download springextensionfactory_1.0.1.zip from the blog SpringExtensionfactory.
  2. Unzip it and copy springextensionfactory_1.0.3/plugins/org.eclipse.springframework.util_1.0.3.jar to the TargetPlatform/lib.
  3. close the editor of TargetPlatform/eclispespring.target.
  4. reopen the editor of TargetPlatform/eclispespring.target. PDE will resolve the target platform and bundles that you have downloaded must appears in the Target Paltform.
  5. Activate the Target Platform with Set as Target Platform link.
  6. Display User list

    At this step we can use org.eclipse.springframework.util which is in the Target Platform.

    View

    Modify to consume UserService (userService.findAll()) retrieved by Dependency Injection to display User list, the fr.opensagres.richclient.View class like this :

    package fr.opensagres.richclient;
    
    import org.eclipse.core.runtime.IStatus;
    import org.eclipse.core.runtime.Status;
    import org.eclipse.jface.dialogs.ErrorDialog;
    import org.eclipse.jface.viewers.ArrayContentProvider;
    import org.eclipse.jface.viewers.ColumnLabelProvider;
    import org.eclipse.jface.viewers.TableViewer;
    import org.eclipse.jface.viewers.TableViewerColumn;
    import org.eclipse.swt.SWT;
    import org.eclipse.swt.events.SelectionAdapter;
    import org.eclipse.swt.events.SelectionEvent;
    import org.eclipse.swt.layout.GridData;
    import org.eclipse.swt.layout.GridLayout;
    import org.eclipse.swt.widgets.Button;
    import org.eclipse.swt.widgets.Composite;
    import org.eclipse.swt.widgets.Table;
    import org.eclipse.swt.widgets.TableColumn;
    import org.eclipse.ui.part.ViewPart;
    
    import fr.opensagres.domain.User;
    import fr.opensagres.services.UserService;
    
    public class View extends ViewPart {
    	public static final String ID = "fr.opensagres.richclient.view";
    
    	private UserService userService;
    
    	public void setUserService(UserService userService) {
    		this.userService = userService;
    	}
    
    	private TableViewer viewer;
    
    	public void createPartControl(Composite parent) {
    
    		Composite body = new Composite(parent, SWT.NONE);
    		body.setLayout(new GridLayout(2, false));
    
    		// 1) create table viewer
    		viewer = new TableViewer(body, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
    		viewer.setContentProvider(ArrayContentProvider.getInstance());
    
    		// 2) initialize table
    		Table table = viewer.getTable();
    		table.setHeaderVisible(true);
    		table.setLinesVisible(true);
    
    		table.setLayoutData(new GridData(GridData.FILL_BOTH));
    
    		// 3) create table columns
    		createColumns(viewer);
    
    		// 4) refresh viewer with User list
    		refreshUsers();
    
    		Button refreshButton = new Button(body, SWT.NONE);
    		refreshButton.setText("Refresh");
    		refreshButton.setLayoutData(new GridData(
    				GridData.VERTICAL_ALIGN_BEGINNING));
    		refreshButton.addSelectionListener(new SelectionAdapter() {
    			@Override
    			public void widgetSelected(SelectionEvent e) {
    				refreshUsers();
    			}
    		});
    	}
    
    	private void refreshUsers() {
    		try {
    			viewer.setInput(userService.findAll());
    		} catch (Throwable e) {
    			Status status = new Status(IStatus.ERROR, "My Plug-in ID", 0,
    					"Status Error Message", e);
    			ErrorDialog.openError(viewer.getTable().getShell(),
    					"UserService Error", e.getMessage(), status);
    		}
    	}
    
    	private static void createColumns(final TableViewer viewer) {
    
    		// First column is for the first name
    		TableViewerColumn col = createTableViewerColumn(viewer, "First Name",
    				150);
    		col.setLabelProvider(new ColumnLabelProvider() {
    			@Override
    			public String getText(Object element) {
    				User user = (User) element;
    				return user.getFirstName();
    			}
    		});
    
    		// Second column is for the adress
    		col = createTableViewerColumn(viewer, "Last Name", 150);
    		col.setLabelProvider(new ColumnLabelProvider() {
    			@Override
    			public String getText(Object element) {
    				User user = (User) element;
    				return user.getLastName();
    			}
    		});
    	}
    
    	private static TableViewerColumn createTableViewerColumn(
    			TableViewer viewer, String title, int bound) {
    		final TableViewerColumn viewerColumn = new TableViewerColumn(viewer,
    				SWT.NONE);
    		final TableColumn column = viewerColumn.getColumn();
    		column.setText(title);
    		column.setWidth(bound);
    		column.setResizable(true);
    		column.setMoveable(true);
    		return viewerColumn;
    	}
    
    	public void setFocus() {
    		viewer.getControl().setFocus();
    	}
    
    }
    

    You can notice that we have done try/catch when UserService is consuming, because the service can be sometimes not available. In this case a dialog error displays the problem.
    A Refresh Button is added to retry to call the UserService and refresh the table viewer.

    module-context.xml

    Create the META-INF/spring/module-context.xml like this:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    	<bean id="fr.opensagres.richclient.view" class="fr.opensagres.richclient.View"
    		scope="prototype">
    		<property name="userService" ref="userService"></property>
    	</bean>
    
    </beans>
    

    module-osgi-context.xml

    Create the META-INF/spring/module-osgi-context.xml like this:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi"
    	xsi:schemaLocation="http://www.springframework.org/schema/osgi  
           http://www.springframework.org/schema/osgi/spring-osgi-1.0.xsd
           http://www.springframework.org/schema/beans   
           http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    	<!-- Consume UserService from the OSGi services registry -->
    	<osgi:reference id="userService" interface="fr.opensagres.services.UserService"
    		cardinality="0..1" timeout="1000" />
    
    </beans>
    

    plugin.xml

    Here we must use SpringExtensionFactory to declare our View class in a Spring file module-context.xml :

    <extension
             point="org.eclipse.ui.views">
          <view
                name="View"
                class="org.eclipse.springframework.util.SpringExtensionFactory"
                id="fr.opensagres.richclient.view">
          </view>         
          <!--<view
                name="View"
                class="fr.opensagres.richclient.View"
                id="fr.opensagres.richclient.view">
          </view>-->
       </extension>
    

    Modify the plugin.xml like this :

    <?xml version="1.0" encoding="UTF-8"?>
    <?eclipse version="3.4"?>
    <plugin>
    
       <extension
             id="application"
             point="org.eclipse.core.runtime.applications">
          <application>
             <run
                   class="fr.opensagres.richclient.Application">
             </run>
          </application>
       </extension>
       <extension
             point="org.eclipse.ui.perspectives">
          <perspective
                name="Perspective"
                class="fr.opensagres.richclient.Perspective"
                id="fr.opensagres.richclient.perspective">
          </perspective>
       </extension>
       <extension
             point="org.eclipse.ui.views">
          <view
                name="View"
                class="org.eclipse.springframework.util.SpringExtensionFactory"
                id="fr.opensagres.richclient.view">
          </view>         
          <!--<view
                name="View"
                class="fr.opensagres.richclient.View"
                id="fr.opensagres.richclient.view">
          </view>-->
       </extension>
       <extension
             point="org.eclipse.ui.perspectiveExtensions">
          <perspectiveExtension
                targetID="*">
             <view
                   standalone="true"
                   minimized="false"
                   relative="org.eclipse.ui.editorss"
                   relationship="left"
                   id="fr.opensagres.richclient.view">
             </view>
          </perspectiveExtension>
       </extension>
       <extension
             point="org.eclipse.ui.menus">
          <menuContribution
                locationURI="menu:org.eclipse.ui.main.menu">
             <menu
                   label="File">
                <command
                      commandId="org.eclipse.ui.file.exit"
                      label="Exit">
                </command>
             </menu>
          </menuContribution>
       </extension>
    
    </plugin>
    

    MANIFEST.MF

    In the META-INF/MANIFEST-MF of the richclient plugin :

    • import the fr.opensagres.domain package.
    • import the fr.opensagres.services package.
    • import the org.eclipse.springframework.util package.

    Modify the META-INF/MANIFEST-MF of the fr.opensagres.richclient plugin like this:

    Manifest-Version: 1.0
    Bundle-ManifestVersion: 2
    Bundle-Name: Richclient
    Bundle-SymbolicName: fr.opensagres.richclient; singleton:=true
    Bundle-Version: 1.0.0.qualifier
    Require-Bundle: org.eclipse.core.runtime,
     org.eclipse.ui
    Bundle-RequiredExecutionEnvironment: J2SE-1.5
    Import-Package: fr.opensagres.domain,
     fr.opensagres.services,
     org.eclipse.springframework.util
    

    Run

    At this step we can create teh 2 launch (Mock Dao =JPA Dao) with our RCP Application.

    RCP Client – Mock Dao.launch

    Open the generated application launch and rename it to RCP Client – Mock Dao.

    Select the Workbench bundles :

    • fr.opensagres.config.log4j
    • fr.opensagres.dao
    • fr.opensagres.dao.mock and set Auto-Start to true
    • fr.opensagres.data.injector and set Auto-Start to true.
    • fr.opensagres.domain
    • fr.opensagres.services
    • fr.opensagres.services.impl and set Auto-Start to true.

    and select Target Platform bundles :

    • select Spring DM bundles :
      • org.springframework.osgi.core
      • org.springframework.osgi.extender and set Auto-Start to true and level to 5
      • org.springframework.osgi.io
    • select the org.springframework.data.commons bundle (which provides the org.springframework.data.repository.PagingAndSortingRepository interface) and click on Add required Bundles button to add missing bundles in the selection :

    Remove (why is there selected?) at hand some bundles which is not used in our context like:

    • com.springsource.org.apache.commons.pool
    • com.springsource.org.apache.commons.derby
    • org.apache.geronimo.specs.geronimo-specs-jpa_2_0_spec
    • org.eclipse.persistence.antlr
    • org.eclipse.persistence.asm
    • org.eclipse.persistence.core
    • org.eclipse.persistence.jpa

    Run the Mock Dao launch to open this RCP Application :

    RCP Client – JPA Dao.launch

    At this step we can create the OSGi launch to use the JPA Dao Implementation. To do that duplicate the RCP Client – Mock Dao:

    Rename the duplicated launch to RCP Client – JPA Dao:

    • unselect the fr.opensagres.dao.mock.
    • select the fr.opensagres.dao.jpa and set Auto-Start to true.
    • select the fr.opensagres.dao.jpa.eclipselink.
    • select the fr.opensagres.data.datasource and set Auto-Start to true.
    • click on Add Required Bundles button to add required bundles.

    Run the JPA Dao launch. As JPA Dao takes some times to be created (Derby Database must be created+JPA Dao created by Spring Data JPA at runtime), the View of the Eclipse RCP tries to consume the UserServive although it is not available. The error dialog (from the refreshUsers) will display this error :

    Close the error dialog, and you will see the SWT Table empty:

    Click on Refresh button to call the UserService and display User list (coming from Derby Database with JPA):

    Conclusion

    In this article we have seen how to use Dependency Injection in an Eclipse RCP with Spring to consume UserService coming from OSGi regsitry services by using the SpringExtensionFactory from Martin Lippert.

    In the next article [step9], we will improve the UI of the User List to display User with pagination in a SWT Table by using Eclipse Nebula Pagination.

Laisser un commentaire