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

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


In [step8] we have developed a Rich Client with RCP Application to display User list in a SWT Table by consuming the UserService by using Spring DM by using the SpringExtensionFactory from Martin Lippert.

In this article we will display the User List with pagination by using Nebula Pagination Control.

The sort of column will be done by the UserService by using Spring Data Sort Structure:

Download

You can download eclipsespring_step9.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. If you have problem with Target Definition, please read Problem with Install with Nebula (Incubation) P2.
  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.

Install Nebula Pagination Control

In this section we will download the Nebula Pagination Control Plug-In. This widget belongs to the Nebula Incubation Latest. As you can see in the download Nebula widgets, you can install Nebula (Incubation) Plug-Ins :

Install with Nebula (Incubation) P2

In our case we have a Target Definition, so we will use Nebula (Incubation) P2.

Open the eclipsespring.target that we have created on [step1] and click on Add… button:

This action opens the Add Content wizard :

Select Software Site and click on Next button:

Fill

  1. Work with field with the P2 URL http://download.eclipse.org/technology/nebula/incubation/snapshot
  2. select Nebula Pagination Control.
  3. select Include all environments.

And click on Finish Button. The Nebula Pagination Control is added to the Target Definition. Click on Set As Target Platform, to use it :

Problem with Install with Nebula (Incubation) P2

As Pagination Control belongs to the Nebula (Incubation) P2. This P2 is rebuild as soon as there is some other widgets are rebuilt. So when you will open next time the Target Definition eclipsespring.target, you can have this error after the Target Definition is resolved:

This error comes from that the Target Definition contains an old version of Pagination Control. To resolve this problem, click on Edit button to open the Edit Content wizard and select the Pagination Control :

Click on Finish Button to resolve the Target Definition. Click on Set As Target Platform, to use it.

Nebula Pagination with org.eclipse.nebula.widgets.pagination.collections.PageResult

By default, the Nebula Pagination Control works with org.eclipse.nebula.widgets.pagination.collections.PageResult structure to manage pagination:

package org.eclipse.nebula.widgets.pagination.collections;

import java.util.List;

/**
 * 
 * Page result used to store pagination result information :
 * 
 * <ul>
 * <li>the total elements</li>
 * <li>the paginated list</li>
 * </ul>
 * 
 * @param <T>
 *            the type item of the paginated list.
 */
public class PageResult<T> {

	private final List<T> content;
	private final long totalElements;

	/**
	 * Constructor with the given paginated list and total elements.
	 * 
	 * @param content
	 * @param totalElements
	 */
	public PageResult(List<T> content, long totalElements) {
		this.totalElements = totalElements;
		this.content = content;
	}

	/**
	 * Returns the total amount of elements.
	 * 
	 * @return the total amount of elements
	 */
	public long getTotalElements() {
		return totalElements;
	}

	/**
	 * Returns the page content as {@link List}.
	 * 
	 * @return
	 */
	public List<T> getContent() {
		return content;
	}
}

So we need to adapt the Spring Data org.springframework.data.domain.Page structure with Nebula pagination Structure org.eclipse.nebula.widgets.pagination.collections.PageResult.

View class

Modify 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.nebula.widgets.pagination.IPageLoader;
import org.eclipse.nebula.widgets.pagination.PageableController;
import org.eclipse.nebula.widgets.pagination.collections.PageResult;
import org.eclipse.nebula.widgets.pagination.table.PageableTable;
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 org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

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;

	private PageableTable paginationTable;

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

	public void createPartControl(Composite parent) {

		Composite body = new Composite(parent, SWT.NONE);
		body.setLayout(new GridLayout(2, false));

		// 1) create pageable table
		int pageSize = 5;
		paginationTable = new PageableTable(body, SWT.NONE, SWT.MULTI
				| SWT.H_SCROLL | SWT.V_SCROLL, pageSize);

		// 2) initialize table viewer
		TableViewer viewer = paginationTable.getViewer();
		viewer.setContentProvider(ArrayContentProvider.getInstance());

		// 3) initialize page loader
		paginationTable.setPageLoader(new IPageLoader<PageResult<User>>() {
			public PageResult<User> loadPage(PageableController controller) {
				int page = controller.getCurrentPage();
				int size = controller.getPageSize();
				Pageable pageable = new PageRequest(page, size);
				Page<User> usersPage = userService.findAll(pageable);
				return new PageResult<User>(usersPage.getContent(), usersPage
						.getTotalElements());
			}
		});

		// 4) initialize table
		Table table = viewer.getTable();
		table.setHeaderVisible(true);
		table.setLinesVisible(true);

		table.setLayoutData(new GridData(GridData.FILL_BOTH));

		// 5) create table columns
		createColumns(viewer);

		// 6) 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 {
			paginationTable.refreshPage(true);
		} catch (Throwable e) {
			Status status = new Status(IStatus.ERROR, "My Plug-in ID", 0,
					"Status Error Message", e);
			ErrorDialog.openError(paginationTable.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 last name
		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() {
		paginationTable.setFocus();
	}

}

Here some explanation of this code:

  • The Nebula Pageable Table (which wraps SWT Table and TableViewer) is created to display 5 items per page:
    // 1) create pageable table
    int pageSize = 5;
    paginationTable = new PageableTable(body, SWT.NONE, SWT.MULTI| SWT.H_SCROLL | SWT.V_SCROLL, pageSize);
    
  • Pageable table must be initialized with an instance of IPageLoader which is called when the table must be loaded and when page index changes, sort changed. In this section we create a Pageable by using information about pagination coming from the PageableController. Spring Data Page Structure coming from the UserService is used to create the Nebula PageResult structure:
    // 3) initialize page loader
    paginationTable.setPageLoader(new IPageLoader<PageResult<User>>() {
    	public PageResult<User> loadPage(PageableController controller) {
    		int page = controller.getCurrentPage();
    		int size = controller.getPageSize();
    		Pageable pageable = new PageRequest(page, size);
    		Page<User> usersPage = userService.findAll(pageable);
    		return new PageResult<User>(usersPage.getContent(), usersPage.getTotalElements());
    	}
    });
    
  • the refreshUsers method use now the PageableTable#refreshPage(boolean reset) method to refresh the page with the paginated User list :
    private void refreshUsers() {
    	try {
    		paginationTable.refreshPage(true);
    	} catch (Throwable e) {
    		Status status = new Status(IStatus.ERROR, "My Plug-in ID", 0, "Status Error Message", e);
    		ErrorDialog.openError(paginationTable.getShell(), "UserService Error", e.getMessage(), status);
    	}
    }
    

MANIFEST.MF

We need modify the META-INF/MANIFEST.MF of the fr.opensagres.richclient Plug-In to add

  • import org.springframework.data.domain package
  • required org.eclipse.nebula.widgets.pagination bundle.

Modify the META-INF/MANIFEST.MF of the fr.opensagres.richclient Plug-In 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,
 org.eclipse.nebula.widgets.pagination
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Import-Package: fr.opensagres.domain,
 fr.opensagres.services,
 org.eclipse.springframework.util,
 org.springframework.data.domain

Run

At this step we can modify our RCP launches to add the org.eclipse.nebula.widgets.pagination Plug-In and run it.

RCP Client – Mock Dao

Modify the RCP Client – Mock Dao by adding the org.eclipse.nebula.widgets.pagination Plug-In and run it. You will see this RCP Application where you can play with page links to paginate to the User list (coming from Java Map) :

RCP Client – JPA Dao

Modify the RCP Client – JPA Dao by adding the org.eclipse.nebula.widgets.pagination Plug-In and run it. You will see this dialog error at first (UserService is not available) :

Close it, and after you will see this RCP Application with empty table:

Click on Refresh button and play with page links to paginate to the User list (coming from Database Derby with JPA) :

Nebula Pagination with org.springframework.data.domain.Page

It’s possible to use Nebula Pagination Control directly with Spring Data org.springframework.data.domain.Page structure instead of Nebula Pagination structure org.eclipse.nebula.widgets.pagination.collections.PageResult structure. To do that we must implement :

  • org.eclipse.nebula.widgets.pagination.PageableController which uses Spring Data Structure Page.
  • org.eclipse.nebula.widgets.pagination.IPageContentProvider which uses the Spring Data PageableController. IPageContentProvider is used to adapt any pagination structure with Nebula Pagination Control.

You can find the adapater of Nebula Pagination of Spring Data in the project org.eclipse.nebula.widgets.pagination.springdata in on our git. But in this article I have copy/paste those 2 classes.

SpringDataPageableController

Create org.eclipse.nebula.widgets.pagination.springdata.SpringDataPageableController class in the fr.opensagres.richclient Plug-In like this:

/*******************************************************************************
 * Copyright (C) 2011 Angelo Zerr <angelo.zerr@gmail.com>, Pascal Leclercq <pascal.leclercq@gmail.com>
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Angelo ZERR - initial API and implementation
 *     Pascal Leclercq - initial API and implementation
 *******************************************************************************/
package org.eclipse.nebula.widgets.pagination.springdata;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.nebula.widgets.pagination.PageableController;
import org.eclipse.swt.SWT;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;

/**
 * Extension of pagination controller {@link PageableController} to implement
 * Spring Data {@link Pageable}.
 * 
 * @see http://www.springsource.org/spring-data
 * 
 */
public class SpringDataPageableController extends PageableController implements
		Pageable {

	private static final long serialVersionUID = 245254414099137504L;

	// Spring Data Sort dooesn't support property null
	private static final String EMPTY_PROPERTY_SORT = "empty";

	// Cache to retrieve Spring Data sort instance by property name.
	private final Map<String, Sort> sortCache;

	// The current sort
	private Sort sort;

	/**
	 * Constructor with page size.
	 * 
	 * @param pageSize
	 *            size of the page (number items displayed per page).
	 */
	public SpringDataPageableController(int pageSize) {
		super(pageSize);
		this.sortCache = new HashMap<String, Sort>();
		this.sort = null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.data.domain.Pageable#getOffset()
	 */
	public int getOffset() {
		return getPageOffset();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.data.domain.Pageable#getPageNumber()
	 */
	public int getPageNumber() {
		return getCurrentPage();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.data.domain.Pageable#getSort()
	 */
	public Sort getSort() {
		return sort;
	}

	@Override
	public void setSort(String propertyName, int sortDirection) {
		// Sort is applied, update the current Spring Data instance sort.
		this.sort = getSort(propertyName, sortDirection);
		super.setSort(propertyName, sortDirection);
	}

	/**
	 * Returns the Spring Data instance {@link Sort} according the given
	 * property name to sort and direction.
	 * 
	 * @param propertyName
	 *            the sort property name.
	 * @param sortDirection
	 *            the sort direction {@link SWT.UP}, {@link SWT.DOWN}.
	 * @return
	 */
	protected Sort getSort(String propertyName, int sortDirection) {
		String key = getKey(propertyName, sortDirection);
		Sort sort = sortCache.get(key);
		if (sort == null) {
			Direction direction = getDirection(sortDirection);
			// Spring Data Sort dooesn't support property null,
			// EMPTY_PROPERTY_SORT is set if the property is null
			sort = new Sort(direction, propertyName != null ? propertyName
					: EMPTY_PROPERTY_SORT);
			sortCache.put(key, sort);
		}
		return sort;
	}

	/**
	 * Returns the Spring Data {@link Direction} according the given sort
	 * direction {@link SWT.UP} orf {@link SWT.DOWN}.
	 * 
	 * @param sortDirection
	 *            the sort direction {@link SWT.UP}, {@link SWT.DOWN}.
	 * @return
	 */
	protected Direction getDirection(int sortDirection) {
		switch (sortDirection) {
		case SWT.UP:
			return Direction.ASC;
		case SWT.DOWN:
			return Direction.DESC;
		}
		return null;
	}

	/**
	 * Returns the key.
	 * 
	 * @param propertyName
	 *            the sort property name.
	 * @param sortDirection
	 *            the sort direction {@link SWT.UP}, {@link SWT.DOWN}.
	 * @return
	 */
	private String getKey(String propertyName, int sortDirection) {
		StringBuilder key = new StringBuilder();
		key.append(propertyName);
		key.append('_');
		key.append(sortDirection);
		return key.toString();
	}

}

SpringDataPageContentProvider

Create org.eclipse.nebula.widgets.pagination.springdata.SpringDataPageContentProvider class in the fr.opensagres.richclient Plug-In like this:

/*******************************************************************************
 * Copyright (C) 2011 Angelo Zerr <angelo.zerr@gmail.com>, Pascal Leclercq <pascal.leclercq@gmail.com>
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Angelo ZERR - initial API and implementation
 *     Pascal Leclercq - initial API and implementation
 *******************************************************************************/
package org.eclipse.nebula.widgets.pagination.springdata;

import java.util.List;

import org.eclipse.nebula.widgets.pagination.IPageContentProvider;
import org.springframework.data.domain.Page;

/**
 * Implementation of {@link IPageContentProvider} to retrieves pagination
 * information (total elements and paginated list) from the pagination structure
 * Spring Data {@link Page}.
 * 
 */
public class SpringDataPageContentProvider implements IPageContentProvider {

	private static final IPageContentProvider INSTANCE = new SpringDataPageContentProvider();

	public static IPageContentProvider getInstance() {
		return INSTANCE;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.nebula.widgets.pagination.IPageContentProvider#createController
	 * (int)
	 */
	public SpringDataPageableController createController(int pageSize) {
		return new SpringDataPageableController(pageSize);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.nebula.widgets.pagination.IPageContentProvider#getTotalElements
	 * (java.lang.Object)
	 */
	public long getTotalElements(Object page) {
		return ((Page<?>) page).getTotalElements();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.nebula.widgets.pagination.IPageContentProvider#getPaginatedList
	 * (java.lang.Object)
	 */
	public List<?> getPaginatedList(Object page) {
		return ((Page<?>) page).getContent();
	}

}

View class

Now we can use the Nebula Pagination adapter for Spring Data. Modify 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.nebula.widgets.pagination.IPageLoader;
import org.eclipse.nebula.widgets.pagination.PageableController;
import org.eclipse.nebula.widgets.pagination.springdata.SpringDataPageContentProvider;
import org.eclipse.nebula.widgets.pagination.table.PageableTable;
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 org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

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;

	private PageableTable paginationTable;

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

	public void createPartControl(Composite parent) {

		Composite body = new Composite(parent, SWT.NONE);
		body.setLayout(new GridLayout(2, false));

		// 1) create pageable table
		int pageSize = 5;
		paginationTable = new PageableTable(body, SWT.NONE, SWT.MULTI
				| SWT.H_SCROLL | SWT.V_SCROLL, pageSize,
				SpringDataPageContentProvider.getInstance());

		// 2) initialize table viewer
		TableViewer viewer = paginationTable.getViewer();
		viewer.setContentProvider(ArrayContentProvider.getInstance());

		// 3) initialize page loader
		paginationTable.setPageLoader(new IPageLoader<Page<User>>() {
			public Page<User> loadPage(PageableController controller) {
				Pageable pageable = (Pageable) controller;
				return userService.findAll(pageable);
			}
		});
		paginationTable.setLayoutData(new GridData(GridData.FILL_BOTH));

		// 4) initialize table
		Table table = viewer.getTable();
		table.setHeaderVisible(true);
		table.setLinesVisible(true);

		table.setLayoutData(new GridData(GridData.FILL_BOTH));

		// 5) create table columns
		createColumns(viewer);

		// 6) 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 {
			paginationTable.refreshPage(true);
		} catch (Throwable e) {
			Status status = new Status(IStatus.ERROR, "My Plug-in ID", 0,
					"Status Error Message", e);
			ErrorDialog.openError(paginationTable.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 last name
		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() {
		paginationTable.setFocus();
	}

}

Here some explanation about this code:

  • The pageable table is created by setting the SpringDataPageContentProvider instance:
    // 1) create pageable table
    int pageSize = 5;
    paginationTable = new PageableTable(body, SWT.NONE, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL, pageSize, SpringDataPageContentProvider.getInstance());
    
  • By using SpringDataPageContentProvider, it’s possible now to use Spring Data Page as structure directly in the IPageLoader:
    // 3) initialize page loader
    paginationTable.setPageLoader(new IPageLoader<Page<User>>() {
    	public Page<User> loadPage(PageableController controller) {
    		Pageable pageable = (Pageable) controller;
    		return userService.findAll(pageable);
    	}
    });
    

Sort with Nebula Pagination

Nebula Pagination gives you the capability to manage the sort by calling the UserServive. To do that, it’s very simply, you must just add listener org.eclipse.nebula.widgets.pagination.table.SortTableColumnSelectionListener to sort the column like this :

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();
		}
	});
	// Sort the first name by using the UserService
	col.getColumn().addSelectionListener(new SortTableColumnSelectionListener("firstName"));

	// Second column is for the last name
	col = createTableViewerColumn(viewer, "Last Name", 150);
	col.setLabelProvider(new ColumnLabelProvider() {
		@Override
		public String getText(Object element) {
			User user = (User) element;
			return user.getLastName();
		}
	});
	col.getColumn().addSelectionListener(new SortTableColumnSelectionListener("lastName"));
}

The following code :

col.getColumn().addSelectionListener(new SortTableColumnSelectionListener("firstName"));

means that when the column will fire sort (when user click on the column), UserService will be called to sort the User list by « firstName »:

View

Modify 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.nebula.widgets.pagination.IPageLoader;
import org.eclipse.nebula.widgets.pagination.PageableController;
import org.eclipse.nebula.widgets.pagination.springdata.SpringDataPageContentProvider;
import org.eclipse.nebula.widgets.pagination.table.PageableTable;
import org.eclipse.nebula.widgets.pagination.table.SortTableColumnSelectionListener;
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 org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

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;

	private PageableTable paginationTable;

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

	public void createPartControl(Composite parent) {

		Composite body = new Composite(parent, SWT.NONE);
		body.setLayout(new GridLayout(2, false));

		// 1) create pageable table
		int pageSize = 5;
		paginationTable = new PageableTable(body, SWT.NONE, SWT.MULTI
				| SWT.H_SCROLL | SWT.V_SCROLL, pageSize,
				SpringDataPageContentProvider.getInstance());

		// 2) initialize table viewer
		TableViewer viewer = paginationTable.getViewer();
		viewer.setContentProvider(ArrayContentProvider.getInstance());

		// 3) initialize page loader
		paginationTable.setPageLoader(new IPageLoader<Page<User>>() {
			public Page<User> loadPage(PageableController controller) {
				Pageable pageable = (Pageable) controller;
				return userService.findAll(pageable);
			}
		});
		paginationTable.setLayoutData(new GridData(GridData.FILL_BOTH));

		// 4) initialize table
		Table table = viewer.getTable();
		table.setHeaderVisible(true);
		table.setLinesVisible(true);

		table.setLayoutData(new GridData(GridData.FILL_BOTH));

		// 5) create table columns
		createColumns(viewer);

		// 6) 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 {
			paginationTable.refreshPage(true);
		} catch (Throwable e) {
			Status status = new Status(IStatus.ERROR, "My Plug-in ID", 0,
					"Status Error Message", e);
			ErrorDialog.openError(paginationTable.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();
			}
		});
		// Sort the first name by using the UserService
		col.getColumn().addSelectionListener(
				new SortTableColumnSelectionListener("firstName"));

		// Second column is for the last name
		col = createTableViewerColumn(viewer, "Last Name", 150);
		col.setLabelProvider(new ColumnLabelProvider() {
			@Override
			public String getText(Object element) {
				User user = (User) element;
				return user.getLastName();
			}
		});
		col.getColumn().addSelectionListener(
				new SortTableColumnSelectionListener("lastName"));
	}

	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() {
		paginationTable.setFocus();
	}

}

Mock Dao

For Mock Dao implementation, the sort is done by using reflection. The code is not very interesting for this article, but you can find a clean project which manages that in our Git in the org.springframework.data.domain.collections project.

Here I have just copy/paste Java classes that we need to manage sort with Mock Dao.

BeanComparator

Create fr.opensagres.dao.mock.BeanComparator class like this :

/*******************************************************************************
 * Copyright (C) 2011 Angelo Zerr <angelo.zerr@gmail.com>, Pascal Leclercq <pascal.leclercq@gmail.com>
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Angelo ZERR - initial API and implementation
 *     Pascal Leclercq - initial API and implementation
 *******************************************************************************/
package fr.opensagres.dao.mock;

import java.util.Comparator;

import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;

/**
 * Implementation of {@link Comparator} to compare POJO by using Spring Data
 * {@link Sort}.
 * 
 */
@SuppressWarnings("rawtypes")
public class BeanComparator implements Comparator {

	private final Sort sort;

	public BeanComparator(Sort sort) {
		this.sort = sort;
	}

	public int compare(Object o1, Object o2) {
		if ((o1 instanceof Comparable) && (o2 instanceof Comparable)) {
			// Compare simple type like String, Integer etc
			Comparable c1 = ((Comparable) o1);
			Comparable c2 = ((Comparable) o2);
			return compare(c1, c2);
		}
		for (Order order : sort) {
			o1 = BeanUtils.getValue(o1, order.getProperty());
			o2 = BeanUtils.getValue(o2, order.getProperty());
			if ((o1 instanceof Comparable) && (o2 instanceof Comparable)) {
				// Compare simple type like String, Integer etc
				Comparable c1 = ((Comparable) o1);
				Comparable c2 = ((Comparable) o2);
				return compare(c1, c2);
			}
		}
		return 0;
	}

	private int compare(Comparable c1, Comparable c2) {
		for (Order order : sort) {
			if (order.isAscending()) {
				return c2.compareTo(c1);
			}
			return c1.compareTo(c2);
		}
		return 0;
	}

}

BeanUtils

Create fr.opensagres.dao.mock.BeanUtils class like this :

/*******************************************************************************
 * Copyright (C) 2011 Angelo Zerr <angelo.zerr@gmail.com>, Pascal Leclercq <pascal.leclercq@gmail.com>
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Angelo ZERR - initial API and implementation
 *     Pascal Leclercq - initial API and implementation
 *******************************************************************************/
package fr.opensagres.dao.mock;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * Utilities to retrieves values of POJO with the property name.
 * 
 * @author azerr
 * 
 */
public class BeanUtils {

	/**
	 * Returns the value of the given property for the given bean.
	 * 
	 * @param source
	 *            the source bean
	 * @param property
	 *            the property name to retrieve
	 * @return the value of the given property for the given bean.
	 */
	public static Object getValue(Object source, String property) {
		if (property == null) {
			return source;
		}
		if (property.indexOf('.') == -1) {
			if (source == null) {
				return null;
			}
			PropertyDescriptor propertyDescriptor = getPropertyDescriptor(
					source.getClass(), property);
			return getValue(source, propertyDescriptor);
		}

		String[] properies = property.split("[.]");
		for (int i = 0; i < properies.length; i++) {
			source = getValue(source, properies[i]);
		}
		return source;
	}

	/**
	 * Returns the value of the given property for the given bean.
	 * 
	 * @param source
	 *            the source bean
	 * @param propertyDescriptor
	 *            the property to retrieve
	 * @return the contents of the given property for the given bean.
	 */
	private static Object getValue(Object source,
			PropertyDescriptor propertyDescriptor) {
		try {
			Method readMethod = propertyDescriptor.getReadMethod();
			if (readMethod == null) {
				throw new IllegalArgumentException(propertyDescriptor.getName()
						+ " property does not have a read method."); //$NON-NLS-1$
			}
			if (!readMethod.isAccessible()) {
				readMethod.setAccessible(true);
			}
			return readMethod.invoke(source, null);
		} catch (InvocationTargetException e) {
			/*
			 * InvocationTargetException wraps any exception thrown by the
			 * invoked method.
			 */
			throw new RuntimeException(e.getCause());
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Returns the property descriptor of the given bean class and the given
	 * property.
	 * 
	 * @param beanClass
	 * @param propertyName
	 * @return the PropertyDescriptor for the named property on the given bean
	 *         class
	 */
	private static PropertyDescriptor getPropertyDescriptor(Class beanClass,
			String propertyName) {
		if (!beanClass.isInterface()) {
			BeanInfo beanInfo;
			try {
				beanInfo = Introspector.getBeanInfo(beanClass);
			} catch (IntrospectionException e) {
				// cannot introspect, give up
				return null;
			}
			PropertyDescriptor[] propertyDescriptors = beanInfo
					.getPropertyDescriptors();
			for (int i = 0; i < propertyDescriptors.length; i++) {
				PropertyDescriptor descriptor = propertyDescriptors[i];
				if (descriptor.getName().equals(propertyName)) {
					return descriptor;
				}
			}
		} else {
			try {
				PropertyDescriptor propertyDescriptors[];
				List pds = new ArrayList();
				getInterfacePropertyDescriptors(pds, beanClass);
				if (pds.size() > 0) {
					propertyDescriptors = (PropertyDescriptor[]) pds
							.toArray(new PropertyDescriptor[pds.size()]);
					PropertyDescriptor descriptor;
					for (int i = 0; i < propertyDescriptors.length; i++) {
						descriptor = propertyDescriptors[i];
						if (descriptor.getName().equals(propertyName))
							return descriptor;
					}
				}
			} catch (IntrospectionException e) {
				// cannot introspect, give up
				return null;
			}
		}
		throw new IllegalArgumentException(
				"Could not find property with name " + propertyName + " in class " + beanClass); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * Goes recursively into the interface and gets all defined
	 * propertyDescriptors
	 * 
	 * @param propertyDescriptors
	 *            The result list of all PropertyDescriptors the given interface
	 *            defines (hierarchical)
	 * @param iface
	 *            The interface to fetch the PropertyDescriptors
	 * @throws IntrospectionException
	 */
	private static void getInterfacePropertyDescriptors(
			List propertyDescriptors, Class iface)
			throws IntrospectionException {
		BeanInfo beanInfo = Introspector.getBeanInfo(iface);
		PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
		for (int i = 0; i < pds.length; i++) {
			PropertyDescriptor pd = pds[i];
			propertyDescriptors.add(pd);
		}
		Class[] subIntfs = iface.getInterfaces();
		for (int j = 0; j < subIntfs.length; j++) {
			getInterfacePropertyDescriptors(propertyDescriptors, subIntfs[j]);
		}
	}

}

MockDaoHelper

Modify fr.opensagres.dao.mock.MockDaoHelper class like this :

package fr.opensagres.dao.mock;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import fr.opensagres.domain.User;

public class MockDaoHelper {

	public static String getCriteria(String criteria) {
		if (criteria == null) {
			return null;
		}
		if (criteria.endsWith("%")) {
			criteria = criteria.substring(0, criteria.length() - 1);
		}
		if (criteria.length() == 0) {
			return null;
		}
		return criteria;
	}

	public static Page createPage(List<?> list, Pageable pageable) {

		Sort sort = pageable.getSort();
		if (sort != null) {
			Collections.sort(list, new BeanComparator(sort));
		}

		int totalSize = list.size();
		int pageSize = pageable.getPageSize();
		int pageIndex = pageable.getOffset();

		int fromIndex = pageIndex;
		int toIndex = pageIndex + pageSize;
		if (toIndex > totalSize) {
			toIndex = totalSize;
		}
		List content = list.subList(fromIndex, toIndex);
		return new PageImpl(content, pageable, totalSize);

	}

	public static Page<User> createPage(Collection<?> list, Pageable pageable) {
		return createPage(new ArrayList(list), pageable);
	}

}

JPA Dao

Here we have nothing to do. Thanks to Spring Data JPA!

Run Pagination&Sort

Now you can run the 2 RCP launches and play with sort :

Conclusion

In this article we have seen how to use Eclipse Nebula Pagination to display the User List in a SWT Table with pagination. We have seen too how to adapt Nebula Pagination to work with Spring Data structure and how to manage sort.

In the next article [step10], we will adapt our RCP Application to use it in WEB mode by using RAP framework. Our RCP Application will support the 2 modes (called Single Sourcing) :

  • Desktop mode with RCP.
  • WEB mode with RAP.