Vaadin Web Application with Jersey REST

Introduction

It was a pleasure to learn about Google Web Toolkit (GWT) and its ability to remove some of the drudgery from program the presentation layer.  It was even more satisfying to look into Vaadin with it rich CSS and DHTML magic.  This article just skims the surface of Vaadin’s offerings as it is used to implement the web front end in the integration of the REST client  API developed in the previous article: Jersey REST Client.

Implementation

Vaadin was very easy to configure.  All the HTTP requests are mapped through the Vaadin GWT servlet:

From the web.xml

 <servlet>
  	<servlet-name>Wow Application</servlet-name>
  	<servlet-class>
  	com.vaadin.terminal.gwt.server.ApplicationServlet</servlet-class>
  	<init-param>
  		<description>
  		Vaadin application class to start</description>
  		<param-name>application</param-name>
  		<param-value>com.persistent.app.WowApplication</param-value>
  	</init-param>
  </servlet>

  <servlet-mapping>
  	<servlet-name>Wow Application</servlet-name>
  	<url-pattern>/*</url-pattern>
  </servlet-mapping>

Integrating the REST API is just a matter of including the gclient dependency in the POM file and utilizing the RestCompanyDirectory class. This class relies on the Jersey REST API and there is no need for any Spring configuration in this application.

From pom.xml

<dependency>
	<groupId>com.persistent</groupId>
		<artifactId>gclient</artifactId>
		<version>1.0-SNAPSHOT</version>
	</dependency>
</dependencies>

The Vaadin API is well constructed and they have both an excellent demo page and downloadable book on the framework. I am just presenting a short exemplar of their products ease of use.

The resulting application looks as follows:

 

The link below contains the code required to render the user interface shown above as well as interact with the REST web service.

package com.persistent.app;

import java.util.ArrayList;
import java.util.Collection;
import java.util.StringTokenizer;

import org.apache.commons.lang.StringUtils;

import com.persistent.dto.Company;
import com.persistent.dto.Person;
import com.persistent.rest.client.RestCompanyDirectory;
import com.vaadin.Application;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.util.IndexedContainer;
import com.vaadin.ui.Button;
import com.vaadin.ui.Component;
import com.vaadin.ui.DefaultFieldFactory;
import com.vaadin.ui.Field;
import com.vaadin.ui.Form;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.NativeSelect;
import com.vaadin.ui.SplitPanel;
import com.vaadin.ui.Table;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.Button.ClickEvent;

public class WowApplication extends Application {
	private static final long serialVersionUID = 2192808513028842236L;
	private Collection<Company> companies;
	private Table contactList = new Table();
	private Form contactEditor = new Form();
	private HorizontalLayout bottomLeftCorner = new HorizontalLayout();
	private Button contactRemovalButton;
	private IndexedContainer companyDirectoryData = retrieveCompanyDirectoryData();
	private ArrayList<Company> companyArray = null;
	private Collection<Person> persons = null;

	boolean updateFlag = false;

	public enum Column {
		id("Id"), first("First Name"), last("Last Name"), age("Age"), company(
				"Company"), companyId("CompanyId");
		private static Column[] visible = { first, last, company };
		private String displayName;

		public static String[] getVisibleColumnNames() {
			String[] visibleColumnNames = new String[visible.length];
			int i = 0;
			for (Column column : visible) {
				visibleColumnNames[i++] = column.displayName;
			}
			return visibleColumnNames;
		}
		Column(String displayName) {
			this.displayName = displayName;
		}
		public String getDisplayName() {
			return displayName;
		}
		public static Column[] getVisibleColumns() {
			return visible;
		}
	}

	private NativeSelect getCompaniesNativeSelect() {
		final NativeSelect companies = new NativeSelect("Company");
		companies.setWidth("30em");
		if (companyArray == null) {
			Collection<Company> theCompanies = getCompanies();
			companyArray = new ArrayList<Company>(theCompanies);
		}
		for (Company company : companyArray) {
			companies.addItem(company.getName());
		}
		return companies;
	}

	@Override
	public void init() {
		initLayout();
		initContactAddRemoveButtons();
		initAddressList();
		initFilteringControls();
	}

	private void initLayout() {
		SplitPanel splitPanel = new SplitPanel(
				SplitPanel.ORIENTATION_HORIZONTAL);
		setMainWindow(new Window("Address Book", splitPanel));
		// Left Side
		VerticalLayout left = new VerticalLayout();
		left.setSizeFull();
		left.addComponent(contactList);
		contactList.setSizeFull();
		left.setExpandRatio(contactList, 1);
		splitPanel.addComponent(left);
		bottomLeftCorner.setWidth("100%");
		left.addComponent(bottomLeftCorner);
		// other side
		splitPanel.addComponent(contactEditor);
		contactEditor.setSizeFull();
		contactEditor.getLayout().setMargin(true);
		contactEditor.setImmediate(true);
		contactEditor.setFormFieldFactory(new PersonFactory());
	}

	@SuppressWarnings("serial")
	private void initContactAddRemoveButtons() {
		// New item button
		bottomLeftCorner.addComponent(new Button("+",
				new Button.ClickListener() {
					public void buttonClick(ClickEvent event) {
						Object id = contactList.addItem();
						contactList.setValue(id);
					}
				}));

		// Remove item button
		contactRemovalButton = new Button("-", new Button.ClickListener() {
			public void buttonClick(ClickEvent event) {
				final Item item = contactList.getItem(contactList.getValue());
				final String personId = (String) item.getItemProperty(Column.id.displayName)
						.getValue();
				final RestCompanyDirectory companyDirectory = new RestCompanyDirectory();
				companyDirectory.deletePerson(Long.parseLong(personId));
				contactList.removeItem(contactList.getValue());
				contactList.select(null);
			}
		});
		contactRemovalButton.setVisible(false);
		bottomLeftCorner.addComponent(contactRemovalButton);
	}

	@SuppressWarnings("serial")
	private void initAddressList() {
		contactList.setContainerDataSource(companyDirectoryData);
		contactList.setVisibleColumns(Column.getVisibleColumnNames());
		contactList.setSelectable(true);
		contactList.setImmediate(true);

		contactList.addListener(new Property.ValueChangeListener() {
			public void valueChange(ValueChangeEvent event) {
				Object id = contactList.getValue();
				updateFlag = true;
				contactEditor.setItemDataSource(id == null ? null : contactList
						.getItem(id));
				contactRemovalButton.setVisible(id != null);
				updateFlag = false;
			}
		});
	}

	@SuppressWarnings("serial")
	private void initFilteringControls() {
		for (final Column column : Column.getVisibleColumns()) {

			if (!column.equals(Column.company)) {
				final TextField sf = new TextField();
				bottomLeftCorner.addComponent(sf);
				sf.setWidth("100%");
				sf.setInputPrompt(column.displayName);
				sf.setImmediate(true);
				bottomLeftCorner.setExpandRatio(sf, 1);
				sf.addListener(new Property.ValueChangeListener() {
					public void valueChange(ValueChangeEvent event) {
						companyDirectoryData
								.removeContainerFilters(column.displayName);
						if (sf.toString().length() > 0
								&& !column.displayName.equals(sf.toString())) {
							companyDirectoryData.addContainerFilter(
									column.displayName, sf.toString(), true,
									false);
						}
						getMainWindow().showNotification(
								"" + companyDirectoryData.size() + " matches found");
					}
				});
			} else {
				final NativeSelect ns = getCompaniesNativeSelect();
				bottomLeftCorner.addComponent(ns);
				ns.setImmediate(true);
				ns.setCaption("");
				ns.addListener(new Property.ValueChangeListener() {
					public void valueChange(ValueChangeEvent event) {
						companyDirectoryData
								.removeContainerFilters(column.displayName);
						if (ns.getValue() != null) {
							companyDirectoryData.addContainerFilter(
									column.displayName, (String) ns.getValue(),
									true, false);
						}
						getMainWindow().showNotification(
								"" + companyDirectoryData.size() + " matches found");
					}

				});
			}
		}
	}

	@SuppressWarnings("serial")
	private class PersonFactory extends DefaultFieldFactory {

		public PersonFactory() {
		}

		@Override
		public Field createField(final Item item, Object propertyId,
				Component uiContext) {
			Field result = null;
			if (Column.company.displayName.equals(propertyId)) {
				final NativeSelect companies = new NativeSelect(
						Column.company.displayName);
				companies.setWidth("30em");
				Collection<Company> theCompanies = getCompanies();
				ArrayList<Company> a = new ArrayList<Company>(theCompanies);
				for (Company company : a) {
					companies.addItem(company.getName());
				}
				companies.setValue(uiContext);
				result = companies;
			} else {
				result = super.createField(item, propertyId, uiContext);
				if (Column.id.displayName.equals(propertyId)
						|| Column.companyId.displayName.equals(propertyId)) {
					result.setVisible(false);
				}
			}
			result.addListener(new Property.ValueChangeListener() {
				public void valueChange(ValueChangeEvent event) {
					if (!updateFlag && isValid(item)) {
						updateFlag = true;
						getMainWindow().showNotification(
								"Updated Record: "
										+ (String) item.getItemProperty(
												Column.id.displayName)
												.getValue());
						WowApplication.this.updateDB(item);
						updateFlag = false;
					}
				}
			});
			return result;
		}
	}

	public boolean isValid(Item item) {
		String first = (String) item.getItemProperty(Column.first.displayName)
				.getValue();
		String last = (String) item.getItemProperty(Column.last.displayName)
				.getValue();
		String company = (String) item.getItemProperty(
				Column.company.displayName).getValue();
		String age = (String) item.getItemProperty(Column.age.displayName)
				.getValue();
		if (!StringUtils.isNumeric(age)) {
			getMainWindow().showNotification("Age must be numeric",
					Window.Notification.TYPE_ERROR_MESSAGE);
		}
		return StringUtils.isNotBlank(first) && StringUtils.isNotBlank(last)
				&& StringUtils.isNotBlank(company)
				&& StringUtils.isNotBlank(age) && StringUtils.isNumeric(age);
	}

	private IndexedContainer retrieveCompanyDirectoryData() {
		Collection<Person> persons = getPersons();
		ArrayList<Person> aperson = new ArrayList<Person>(persons);
		IndexedContainer ic = new IndexedContainer();

		for (Column p : Column.values()) {
			ic.addContainerProperty(p.displayName, String.class, "");
		}
		for (Person person : aperson) {
			Object id = ic.addItem();
			StringTokenizer tokenizer = new StringTokenizer(person.getName());
			if (tokenizer.hasMoreElements()) {
				ic.getContainerProperty(id, Column.first.displayName).setValue(
						tokenizer.nextToken());
				if (tokenizer.hasMoreElements()) {
					ic.getContainerProperty(id, Column.last.displayName)
							.setValue(tokenizer.nextToken());
				}
			}
			ic.getContainerProperty(id, Column.company.displayName).setValue(
					person.getCompany().getName());
			ic.getContainerProperty(id, Column.id.displayName).setValue(
					person.getId());
			ic.getContainerProperty(id, Column.companyId.displayName).setValue(
					person.getCompany().getId());
			ic.getContainerProperty(id, Column.age.displayName).setValue(
					person.getAge());
		}
		return ic;
	}

	public void updateDB(final Item item) {
		RestCompanyDirectory companyDirectory = new RestCompanyDirectory();
		String sid = (String) item.getItemProperty(Column.id.displayName)
				.getValue();
		String companyName = (String) item.getItemProperty(
				Column.company.displayName).getValue();
		if (StringUtils.isNotBlank(sid)) {
			Long id = Long.parseLong(sid);
			Person person = companyDirectory.getPerson(id);
			populatePerson(item, person);
			if (!companyName.equals(person.getCompany().getName())) {
				Company company = getCompany(companyName);
				if (company != null) {
					person.setCompany(company);
				}
			}
			companyDirectory.updatePerson(person);
		} else {
			Person person = new Person();
			populatePerson(item, person);
			Company company = getCompany(companyName);
			person.setCompany(company);
			Person newPerson = companyDirectory.createPerson(person);
			item.getItemProperty(Column.id.displayName).setValue(
					newPerson.getId());
		}
	}

	private Company getCompany(String companyName) {
		Company selectedCompany = null;
		for (Company company : companyArray) {
			if (company.getName().equals(companyName)) {
				selectedCompany = company;
				break;
			}
		}
		return selectedCompany;
	}

	private void populatePerson(Item item, Person person) {
		String first = (String) item.getItemProperty(Column.first.displayName)
				.getValue();
		String last = (String) item.getItemProperty(Column.last.displayName)
				.getValue();
		String age = (String) item.getItemProperty(Column.age.displayName)
				.getValue();
		person.setName(first + " " + last);
		person.setAge(Short.parseShort(age));
	}

	private Collection<Person> getPersons() {
		if (persons == null) {
			RestCompanyDirectory companyDirectory = new RestCompanyDirectory();
			persons = companyDirectory.getPersons();
		}
		return persons;
	}

	private Collection<Company> getCompanies() {
		if (companies == null) {
			RestCompanyDirectory companyDirectory = new RestCompanyDirectory();
			companies = companyDirectory.getCompanies();
		}
		return companies;
	}
}

Installation

There are three steps to install this application:

  1. Starting the REST Web Service
  2. Build REST Client Code
  3. The Vaadin Application

Installing the REST Web Service

The REST Webservice was developed in previous articles.  It can be downloaded by this link.  Uncompress and run the following maven command:

clean install jetty:run -Djetty.port=9444

This command will download dependencies, compile the code and deploy the application in Jetty at port 9444.

We need to export the client API from the service to build the client code.  This is done after the build through a script available in the src/main/resource directory called: installit.sh

This bash script simply installs the client jar into the appropriate location to be utilized in other programs.  For more information see Simple Spring-WS Application.

 

Building REST Client Code

This is described in the previous article Jersey REST Client.

 

Building the Vaadin Project

Download the code linked here.

 To build unzip and from the root directory type:

mvn clean install

A target directory will be created and within it there will be a war file called: vaadin_one.war

Take this war and deploy it in your web server.  I am working with tomcat 6.0.20.

The Vaadin web application URL is: http://localhost:8080/vaadin_one

Enjoy!  If you have any questions just send me a note.

 

About The Author

David Sells is a computer consultant in Toronto, Ontario who specializes in Java Enterprise Development. He has  provided innovative solutions for clients including: IBM, Goldman Sachs, Merrill Lynch and Deutsche Bank.

He also holds the following certifications:

  • Sun Certified Enterprise Architect for the Java Platform, Enterprise Edition 5 (2009)
  • Sun Certified Web Component Developer
  • Sun Certified Java Programmer.

Contact: david@persistentdesigns.com

 

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>