JFace Data Binding/JSR303BeanJFaceDatabindingValidation

From Eclipsepedia

Jump to: navigation, search

Contents

Target

Work is underway to support JSR-303 Bean Validation with JFace Databinding Validators. This support was created after reading the Validating JFace Databinding with JSR-303 article.

You can find several plug-in projects from JSR-303 XDocReport Git which provides JSR-303 support for JFace Databinding Validators:

  • org.eclipse.core.databinding.validation.jsr303 : JSR-303 support for JFace Databinding Validators source.
  • org.eclipse.core.databinding.validation.jsr303.samples : JSR-303 support for JFace Databinding Validators sample with Java main.
  • org.eclipse.core.databinding.validation.jsr303.samples.rcp : JSR-303 support for JFace Databinding Validators with Eclipse RCP (in an OSGi context).

JSR-303 support for JFace Databinding Validators

JSR-303 Overview

@Annotations for JSR-303

JSR-303 Bean Validation gives you the capability to declare with annotation your validation constraints in your Java Pojo model. Here an example with Person class to set validation constraints:

  • "name" property as required.
  • "email" property as email pattern
package org.eclipse.core.databinding.validation.jsr303.samples.model;
 
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
 
public class Person {
 
	@Size(min = 1)
	private String name;
 
	@Size(min = 1)
	@Pattern(regexp = ".+@.+\\.[a-z]+")
	private String email;
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public String getEmail() {
		return email;
	}
 
	public void setEmail(String email) {
		this.email = email;
	}
}

Those validation constraints can be used in any context :

  • UI form could display errors when constraints are not respected. Ex : UI Text field email doesn't contains a well formatted email like this : JSR303JFaceDatabindingSamples Jsr303PersonSnippetUI.png
  • ORM like Hibernate can use thoses constraints validation when Pojo model must be saved in the Database.

So JSR-303 gives a commons means to declare your constraint validation that you can used in several context of your application (in your UI form, in your DAO when Pojo must be saved, etc....)

Validator API for JSR-303

JSR-303 Bean Validation provides an API to validate property, value by using JSR-303 constraints validation declared with annotations. Here a sample code to validate the value "XXX" by using the annotations declared in the "email" property of the Person class :

...
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
...
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Set<ConstraintViolation<Person>> violations = factory.getValidator().validateValue(Person.class, "email", "XXX");
for (ConstraintViolation<Person> violation : violations) {
  System.err.println(violation.getMessage());
}

NOTE: this code works only if there is in the ClassPath an implementation of JSR-303 Bean Validation. If you have not an implementation, you will have this error :

Exception in thread "main" javax.validation.ValidationException: Unable to find a default provider
	at javax.validation.Validation$GenericBootstrapImpl.configure(Validation.java:264)
	at javax.validation.Validation.buildDefaultValidatorFactory(Validation.java:111)

Otherwise, with Apache implementation, teh console will display:

must match the following regular expression: .+@.+\.[a-z]+

Validator Implementation for JSR-303

JSR-303 Bean Validation provides an API but not an implementation. To execute validation with JSR-303 you need JSR-303 implementation. It exists several implementation like:

Provided samples with JFace Databinding support for JSR-303 use Hibernate Validator and Apache Bean Validation.

JSR-303 JFace Databinding Validator Overview

JFace Databinding Validator support for JSR-303 provides the org.eclipse.core.databinding.validation.jsr303.Jsr303BeanValidator an implementation of org.eclipse.core.databinding.validation.IValidator which uses Validator API for JSR-303.

Create Jsr303BeanValidator

If you wish create a JFace Databinding Validator to validate "email" property of the Person class :

public class Person {
 
  ...
  @Size(min = 1)
  @Pattern(regexp = ".+@.+\\.[a-z]+")
  private String email;
  ...
}

You can do that :

...
import org.eclipse.core.databinding.validation.jsr303;
import org.eclipse.core.databinding.validation.IValidator;
...
 
IValidator validator = new Jsr303BeanValidator(Person.class, "email");

Use Jsr303BeanValidator

At this step you can use the validator in your DatabindingContext by creating an instanceof of UpdateValueStrategy  :

IValidator validator = new Jsr303BeanValidator(Person.class, "email");
UpdateValueStrategy updateValueStrategy = new UpdateValueStrategy().setAfterConvertValidator(validator);

Here the full code to bind SWT Text with the "email" property of Person instance :

Text emailText = ....
Person model = ...
IObservableValue swtEmailTextObservableValue = SWTObservables.observeText(emailText, SWT.Modify);
IObservableValue modelEmailObserveValue = PojoObservables.observeValue(model, "email");
dataBindingContext.bindValue(
		swtEmailTextObservableValue,
		modelEmailObserveValue,
		new UpdateValueStrategy()
				.setAfterConvertValidator(new Jsr303BeanValidator(
						Person.class, "email")), null);

Use Jsr303UpdateValueStrategyFactory

You can simplify the creation of the JSR-303 UpdateValueStrategy. Instead of doing that:

UpdateValueStrategy updateValueStrategy = new UpdateValueStrategy().setAfterConvertValidator(new Jsr303BeanValidator(Person.class, "email"));

You can write :

UpdateValueStrategy updateValueStrategy = Jsr303UpdateValueStrategyFactory.create(Person.class, "email");

Here the full code to bind SWT Text with the "email" property of Person instance :

Text emailText = ....
Person model = ...
IObservableValue swtEmailTextObservableValue = SWTObservables.observeText(emailText, SWT.Modify);
IObservableValue modelEmailObserveValue = PojoObservables.observeValue(model, "email");
dataBindingContext.bindValue(
		swtEmailTextObservableValue,
		modelEmailObserveValue,
		Jsr303UpdateValueStrategyFactory.create(Person.class, "email"), null);

Use Jsr303BeansUpdateValueStrategyFactory

If you are using PojoObservables or BeansObservable, you can simplify more code, where you can avoid setting the Person.class and "email"to create teh well UpdateValueStrategy. When you do :

IObservableValue modelEmailObserveValue = PojoObservables.observeValue(model, "email")

You can know the Class type and the property name by using the IObservableValue. That's why you can simplify your code if you are using PojoObservables or BeansObservables by creating an instanceof of UpdateValueStrategy configured with JSR-303 by using information of IObservableValue with Jsr303BeansUpdateValueStrategyFactory like this :

IObservableValue modelEmailObserveValue = PojoObservables.observeValue(model, "email");
UpdateValueStrategy updateValueStrategy = Jsr303BeansUpdateValueStrategyFactory .create(modelEmailObserveValue);

Here the full code to bind SWT Text with the "email" property of Person instance :

Text emailText = ....
Person model = ...
IObservableValue swtEmailTextObservableValue = SWTObservables.observeText(emailText, SWT.Modify);
IObservableValue modelEmailObserveValue = PojoObservables.observeValue(model, "email");
dataBindingContext.bindValue(
		swtEmailTextObservableValue,
		modelEmailObserveValue,
		Jsr303BeansUpdateValueStrategyFactory .create(modelEmailObserveValue), null);

Java main sample - org.eclipse.core.databinding.validation.jsr303.samples

The Jsr303PersonSnippet from the org.eclipse.core.databinding.validation.jsr303.samples project bind Person model :

public class Person {
 
  Size(min = 1)
  private String name;
  ...
  @Size(min = 1)
  @Pattern(regexp = ".+@.+\\.[a-z]+")
  private String email;
  ...
}

with UI SWT Text and manage validation with JSR-303 support for JFace Databinding Validator.

JSR303JFaceDatabindingSamples Jsr303PersonSnippetUI.png

Workspace

Here a screen of required projects to launch the Jsr303PersonSnippet :

JSR303JFaceDatabindingSamples Workspace.png

The org.eclipse.core.databinding.validation.jsr303.samples project provides several launches in the /launch folder :

  • Jsr303PersonSnippet_ApacheBvalValidator.launch: launch Jsr303PersonSnippet by using Apache Bean Validator. This launch add the org.apache.bval.org.apache.bval.bundle project in the classpath to use the Apache Bean Validator.
  • Jsr303PersonSnippet_HibernateValidator.launch: launch Jsr303PersonSnippet by using Hibernate Validator. This launch add the org.hibernate.validator (and sfl4j) project in the classpath to use the Hibernate Validator.
  • Jsr303PersonSnippet_WithoutImplementationOfValidator.launch: launch Jsr303PersonSnippet without JSR-303 Bean Validation implementation to see the problem (validation can be occurred).

Run Jsr303PersonSnippet

Here a screen with Apache Bean Validator implementation (Jsr303PersonSnippet_ApacheBvalValidator.launch launch) which display error when "email" field is not valid :

JSR303JFaceDatabindingSamples Jsr303PersonSnippetUI.png

You will notice that in your console some trace are displayed :

JSR-303 Bean Support available?: true
OSGi context?: false
JSR-303 Bean Support ValidatorFactory Implementation: org.apache.bval.jsr303.ApacheValidatorFactory
JSR-303 Bean Support strategy resolver?: NoOSgi

Those trace use Jsr303BeanValidationSupport :

System.out.println("JSR-303 Bean Support available?: " + Jsr303BeanValidationSupport.isAvailable());
System.out.println("OSGi context?: " + Jsr303BeanValidationSupport.isOSGi());
System.out.println("JSR-303 Bean Support ValidatorFactory Implementation: " + Jsr303BeanValidationSupport.getValidatorFactoryClassName());
System.out.println("JSR-303 Bean Support strategy resolver?: " + Jsr303BeanValidationSupport.getStrategy());

Here a screen when there is no JSR-303 implementation (Jsr303PersonSnippet_WithoutImplementationOfValidator.launch launch) which display "Unable to find a default provider" because there none implementation :

JSR303JFaceDatabindingSamples Jsr303PersonSnippetUIWithoutImpl.png


You will notice that in your console some trace are displayed :

JSR-303 Bean Support available?: false
OSGi context?: false
JSR-303 Bean Support ValidatorFactory Implementation: null
JSR-303 Bean Support strategy resolver?: Unavailable

Java code Jsr303PersonSnippet

Here the full Java code of Jsr303PersonSnippet :

package org.eclipse.core.databinding.validation.jsr303.samples;
 
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.PojoObservables;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.validation.jsr303.Jsr303BeanValidationSupport;
import org.eclipse.core.databinding.validation.jsr303.Jsr303BeansUpdateValueStrategyFactory;
import org.eclipse.core.databinding.validation.jsr303.samples.model.Person;
import org.eclipse.jface.databinding.fieldassist.ControlDecorationSupport;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
 
public class Jsr303PersonSnippet {
 
	public static void main(String[] args) {
		// Display state of JSR-303 Bean Support
		System.out.println("JSR-303 Bean Support available?: "
				+ Jsr303BeanValidationSupport.isAvailable());
		System.out.println("OSGi context?: "
				+ Jsr303BeanValidationSupport.isOSGi());
		System.out
				.println("JSR-303 Bean Support ValidatorFactory Implementation: "
						+ Jsr303BeanValidationSupport
								.getValidatorFactoryClassName());
		System.out.println("JSR-303 Bean Support strategy resolver?: "
				+ Jsr303BeanValidationSupport.getStrategy());
 
		// Create UI+Binding
		final Display display = new Display();
		Realm.runWithDefault(SWTObservables.getRealm(display), new Runnable() {
			public void run() {
				final Shell shell = new Shell(display);
 
				shell.setLayout(new FillLayout());
 
				Composite parent = new Composite(shell, SWT.NONE);
				parent.setLayout(new GridLayout(2, false));
				parent.setLayoutData(new GridData(GridData.FILL_BOTH));
				Person model = new Person();
 
				// UI Name
				Label nameLabel = new Label(parent, SWT.NONE);
				nameLabel.setText("Name:");
				Text nameText = new Text(parent, SWT.BORDER);
				nameText.setLayoutData(new GridData(GridData.FILL_BOTH));
 
				// UI Email
				Label emailLabel = new Label(parent, SWT.NONE);
				emailLabel.setText("Email:");
				Text emailText = new Text(parent, SWT.BORDER);
				emailText.setLayoutData(new GridData(GridData.FILL_BOTH));
 
				DataBindingContext dataBindingContext = new DataBindingContext(
						SWTObservables.getRealm(display));
 
				// Binding Name
				IObservableValue nameTextObserveTextObserveWidget = SWTObservables
						.observeText(nameText, SWT.Modify);
				IObservableValue modelNameObserveValue = PojoObservables
						.observeValue(model, "name");
 
				Binding binding = dataBindingContext.bindValue(
						nameTextObserveTextObserveWidget,
						modelNameObserveValue,
						Jsr303BeansUpdateValueStrategyFactory
								.create(modelNameObserveValue), null);
				ControlDecorationSupport.create(binding, SWT.LEFT, parent);
 
				// Binding Email
				IObservableValue emailTextObserveTextObserveWidget = SWTObservables
						.observeText(emailText, SWT.Modify);
				IObservableValue modelEmailObserveValue = PojoObservables
						.observeValue(model, "email");
 
				binding = dataBindingContext.bindValue(
						emailTextObserveTextObserveWidget,
						modelEmailObserveValue,
						Jsr303BeansUpdateValueStrategyFactory
								.create(modelEmailObserveValue), null);
				ControlDecorationSupport.create(binding, SWT.LEFT, parent);
 
				shell.setSize(200, 100);
				shell.open();
 
				// The SWT event loop
				Display display = Display.getCurrent();
				while (!shell.isDisposed()) {
					if (!display.readAndDispatch()) {
						display.sleep();
					}
				}
			}
		});
	}
}

Here the full Java code of Person :

package org.eclipse.core.databinding.validation.jsr303.samples.model;
 
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
 
public class Person {
 
	@Size(min = 1)
	private String name;
 
	@Size(min = 1)
	@Pattern(regexp = ".+@.+\\.[a-z]+")
	private String email;
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public String getEmail() {
		return email;
	}
 
	public void setEmail(String email) {
		this.email = email;
	}
}

Eclipse RCP (OSGi context) sample - org.eclipse.core.databinding.validation.jsr303.samples.rcp

The org.eclipse.core.databinding.validation.jsr303.samples.rcp project is an Eclipse RCP which uses JSR-303 JFace Databinding Validators support in an OSGi context to bind Person model :

public class Person {
 
  Size(min = 1)
  private String name;
  ...
  @Size(min = 1)
  @Pattern(regexp = ".+@.+\\.[a-z]+")
  private String email;
  ...
}

with UI SWT Text from a FormEditor and manage validation with JSR-303 support for JFace Databinding Validator :

JSR303JFaceDatabindingSamples EclipseRCPFragmentConfigUI.png

Workspace

Here a screen of required projects to launch the Eclipse RCP :

JSR303JFaceDatabindingEclipseRCPSamples Workspace.png

The org.eclipse.core.databinding.validation.jsr303.samples.rcp project provides several launches in the /launch folder :

  • Jsr303RCP_ApacheBValValidator_cocoa.launch: launch Eclipse RCP (cocoa OS) by using Apache Bean Validator. This launch configure the Apache Bean Validator as JSR-303 implementation with OSGi Fragment javax.validation.osgi.config.fragment.apachebval.
  • Jsr303RCP_ApacheBValValidator_win32.launch: launch Eclipse RCP (win32 OS) by using Apache Bean Validator. This launch configure the Apache Bean Validator as JSR-303 implementation with OSGi Fragment javax.validation.osgi.config.fragment.apachebval.
  • Jsr303RCP_HibernateValidator_cocoa.launch: launch Eclipse RCP (cocoa OS) by using Hibernate Validator. This launch configure the Hibernate Validator as JSR-303 implementation with OSGi Fragment javax.validation.osgi.config.fragment.hibernatevalidator.
  • Jsr303RCP_HibernateValidator_win32.launch: launch Eclipse RCP (win32 OS) by using Hibernate Validator. This launch configure the Hibernate Validator as JSR-303 implementation with OSGi Fragment javax.validation.osgi.config.fragment.hibernatevalidator.
  • Jsr303RCP_XValidator_BundleConfig_cocoa.launch: launch Eclipse RCP (cocoa OS) by using Apache Bean Validator or Hibernate Validator. This launch configure the 2 JSR-303 implementation with OSGi Bundles javax.validation.osgi.config.bundle.apachebval and javax.validation.osgi.config.bundle.hibernatevalidator. You can change at runtime of JSR-303 implementation, just by stopping/starting the previous bundle.
  • Jsr303RCP_XValidator_BundleConfig_win32.launch: launch Eclipse RCP (win32 OS) by using Apache Bean Validator or Hibernate Validator. This launch configure the 2 JSR-303 implementation with OSGi Bundles javax.validation.osgi.config.bundle.apachebval and javax.validation.osgi.config.bundle.hibernatevalidator. You can change at runtime of JSR-303 implementation, just by stopping/starting the previous bundle.

Configure JSR-303 Implementation in an OSGi context

This application is sample which shows you how to manage JSR-303 in an OSGi context. There are 2 means to do that:

  • OSGi Fragment linked to javax.validation bundle which set the JSR-303 implementation to use.
  • OSGi bundles which register the well JSR-303 ValidationFactory implementation in the OSGi registry services.

With OSGi fragment

In this section we will study how to configure Hibernate Validator in an OSGi context with OSGi fragment mean. With OSGi fragment, you force the JSR-303 implementation to use and use just one JSR-303 implementation. The Jsr303RCP_HibernateValidator_*.launch use this mean. If you see Plugins used for this launch :

JSR303JFaceDatabindingEclipseRCPSamples HibernateValidatorLaunch.png

you can notice that :

  • com.springsource.javax.validation is selected. It's the JSR-303 API.
  • org.hibernate.validator, slf4j.api, slf4j.simple bundles are selected. It's the JSR-303 Hibernate Validator Implementation.
  • javax.validation.osgi.config.fragment.hibernatevalidator is selected. It's teh OSGi fragment which configures the JSR-303 API with Hibernate Validator.

This OSGi fragment configure JSR-303 with Hibernate Validator like this:

  1. this OSGi fragment is linked to javax.validation bundle (com.springsource.javax.validation) declared in the MANIFEST.MF (Fragment-Host: com.springsource.javax.validation;bundle-version="1.0.0").
  2. this OSGi fragment configure with SPI provider the Hibernate Validator with the file META-INF/services/javax.validation.spi.ValidationProvider which contains org.hibernate.validator.HibernateValidator.
  3. this fragment require org.hibernate.validator.HibernateValidator, that's why it imports the package org.hibernate.validator.

Here the full MANIFEST.MF file of this OSGi fragment :

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Hibernatevalidator
Bundle-SymbolicName: javax.validation.osgi.config.fragment.hibernatevalidator
Bundle-Version: 1.0.0.qualifier
Fragment-Host: com.springsource.javax.validation;bundle-version="1.0.0"
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Bundle-ClassPath: .
Import-Package: org.hibernate.validator

With OSGi bundle

In this section we will study how to configure Hibernate Validator and Apache Bean Validator in an OSGi context with OSGi bundle mean. With OSGi bundle, you can have several JSR-303 implementation and switch to another implementation at runtime just by starting/stopping well bundle. The Jsr303RCP_XValidator_*.launch use this mean. If you see Plugins used for this launch :

JSR303JFaceDatabindingEclipseRCPSamples XValidatorLaunch.png

you can notice that :

  • com.springsource.javax.validation is selected. It's the JSR-303 API.
  • org.hibernate.validator, slf4j.api, slf4j.simple bundles are selected. It's the JSR-303 Hibernate Validator Implementation.
  • org.apache.bval.org.apache.bval.bundle bundle is selected. It's the JSR-303 Apache Bean Validator Implementation.
  • javax.validation.osgi.config.bundle.hibernatevalidator is selected. It's the OSGi bundle which registers in the registry services the Hibernate Validator instance of javax.validation.ValidatorFactory.
  • javax.validation.osgi.config.bundle.apachebval is selected. It's the OSGi bundle which registers in the registry services the Apache Bean Validator instance of javax.validation.ValidatorFactory.
Run

If you launch Jsr303RCP_XValidator_*.launch you will this screen :

JSR303JFaceDatabindingSamples EclipseRCPBundleConfigUI.png

You can change at runtime the JSR-303 implementation (just start/stop the well bundle javax.validation.osgi.config.bundle.hibernatevalidator or javax.validation.osgi.config.bundle.apachebval).