Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Eclipse EMF Validation Framework
Previous Page Home Next Page

Tutorial: EMF Validation General

Contents

Overview

The EMF validation framework provides a means to evaluate and ensure the well-formedness of EMF models. Validation comes in two forms: batch and live. While batch validation allows a client to explicitly evaluate a group of EObjects and their contents, live validation can be used by clients to listen on change notifications to EObjects to immediately verify that the change does not violate the well-formedness of the model.

Client contexts can be specified to ensure that certain constraints do not get executed outside of a certain context. A context is defined by the client to set up their own boundaries from other clients.

[ back to top]

References

This tutorial assumes that the reader has a knowledge of EMF, and the eclipse PDE development environment. It is essential that the reader understands the basic reflective mechanisms of EMF as well as its adapter/notifier system for broadcasting events.

For reference, the full example for this tutorial is available.

[ back to top]

Introduction

In order to demonstrate EMF Validation, we will be making use of the library metamodel. This metamodel is a variant of the standard EMF example metamodel used in many of its tutorials.

For those readers who are not familiar with this metamodel, it models a library with books and writers. The most important aspect of the library metamodel for this tutorial is the fact that books are modeled as EObjects whose EClass is Book and they contain a EStructuralFeature called pages that stores an integer number of pages contained in the book.

The goal of this tutorial is to create a batch/live EMF validation constraint that will catch cases where there is no unique identifier for the book,library or writer. Also, this tutorial will demonstrate how to create a client context allowing a client to run the batch/live constraints on a group of EObjects or Notifications.

[ back to top]

Constructing a Batch Model Constraint

A model constraint is written as a subclass of the AbstractModelConstraint that overrides the validate() method. The validate method has the task of taking the input from the validation context and returning either a ctx.createSuccessStatus() or ctx.createFailureStatus().

Our batch constraint takes the following form:

public class NonEmptyNamesConstraint
	extends AbstractModelConstraint {

	public IStatus validate(IValidationContext ctx) {
		EObject eObj = ctx.getTarget();
		EMFEventType eType = ctx.getEventType();
		
		// In the case of batch mode.
		if (eType == EMFEventType.NULL) {
			String name = null;
			if (eObj instanceof Writer) {
				name = ((Writer)eObj).getName(); 
			} else if (eObj instanceof Library) {
				name = ((Library)eObj).getName();
			} else if (eObj instanceof Book) {
				name = ((Book)eObj).getTitle();
			}
			
			if (name == null || name.length() == 0) {
				return ctx.createFailureStatus(new Object[] {eObj.eClass().getName()});
			}
		}
		
		return ctx.createSuccessStatus();
	}

}

The target EObject is retrieved from the IValidationContext by calling the getTarget() method. In order to rule out live validation cases, we check to ensure that the getEventType() is EMFEventType.NULL. If our constraint provider extension is written correctly, we should never be called to perform a live validation.

The constraint checks each type of object to ensure that its identifier is not null or empty. In the case of a failure, it returns a failure status and places the name of the EObject's EClass to be formatted as part of the failure message. Read the following section for more information on the failure message.

[ back to top]

Creating the Constraint Provider Extension

   <extension
         point="org.eclipse.emf.validation.constraintProviders">
      <category
            name="Library Constraints"
            id="org.eclipse.emf.validation.pde.example.general.ui.library"/>
      <constraintProvider cache="true">
         <package namespaceUri="https:///org/eclipse/emf/metamodel/example/pde/library.ecore/1.0.0"/>
         <constraints categories="org.eclipse.emf.validation.pde.example.general.ui.library">
            <constraint
                  lang="Java"
                  class="org.eclipse.emf.validation.examples.constraints.NonEmptyNamesConstraint"
                  severity="ERROR"
                  mode="Batch"
                  name="Non-Empty Names"
                  id="org.eclipse.emf.validation.pde.example.general.ui.NameNotEmpty"
                  statusCode="1">
               <description>
                  All items in a library model should have some unique identifier or name.
               </description>
               <message>
                  A {0} has been found to have no unique identifier (name or title).
               </message>
               <target class="Library"/>
               <target class="Writer"/>
               <target class="Book"/>
               </target>
            </constraint>
         </constraints>
      </constraintProvider>
   </extension>

The above extension registers a validation category and activates the NonEmptyNamesConstraint in the validation service. Categories can be nested and constraints can be placed into one or more of these categories by their category identifier. It is worth noting that categories can be defined as mandatory, which means that any constraints in that category cannot be enabled/disabled by the client or user. A constraint must only be a member of one mandatory category to become mandatory itself even if it is contained in multiple categories.

The constraintProvider node refers specifically to the URI of the library EPackage so that references in the extension to EClasses and EStructuralFeatures can be properly imported by the service. Finally, the constraints node registers our constraint node into the validation service into our category.

The constraint node has a number of useful options. For the scope of this tutorial, we will be using the "Java" language. By specifying the Java language, we are indicating that the algorithm of the constraint will be written in the form of an AbstractModelConstraint class that can be loaded in order to perform the validation. The severity of our constraint is an error because it makes it very difficult to discern unnamed writers, libraries or books in our editor. We have chosen the batch mode for now because we only want users to discover the error if they run validation explicitly. The status code was chosen to be a unique number for this plugin.

The description and message provide communication to users. The description is used in parts of the user interface to list different constraints, provide enablement and to offer a description of the purpose of the constraint. The message could be presented to the user to give details of a constraint that failed on a particular EObject. The message may contain any number of {0}, {1}, {2} ... substitutions. These substitutions are replaced with the objects passed to the ctx.createFailureStatus(new Object[] {...}) call in order(see code above).

Finally, there are the target nodes. If no target nodes are specified then any EObject of any EClass will given to our validator. In our constraint, we are only interested in Writers, Books and Libraries from the library metamodel. These EClasses are qualified by the EPackage URI we provided in the package node mentioned above.

So far, this would be nearly sufficient to develop a new constraint into an application that is already using the validation service and has its own constraints. The only remaining item would be to ensure that this constraint gets bound to the client context of that application. More details on this in the following section.

[ back to top]

Creating a Client Context

Client contexts are used by application writers to define the context in which their validation will occur. This prevents constraints that were provided by a third-party for a third-party application from being executed in the wrong context. In our case, we don't want our constraint to be executed in any other context other than our own so we will create our own client context and bind our constraint category to our context. The following extension illustrates how this is done:

   <extension
         point="org.eclipse.emf.validation.constraintBindings">
      <clientContext
            default="false"
            id="org.eclipse.emf.validation.pde.example.general.ui.libraryContext">
         <selector class="org.eclipse.emf.validation.examples.constraints.ValidationDelegateClientSelector"/>
      </clientContext>
      <binding
            context="org.eclipse.emf.validation.pde.example.general.ui.libraryContext"
            category="org.eclipse.emf.validation.pde.example.general.ui.library"/>
   </extension> 

This extension registers the client context that is not a "default" context. Default contexts will accumulate constraints and categories that have not been bound to any context. We provide a unique identifier and a selector class for this context. The selector has the purpose of discovering whether an EObject belongs to the context. A context binding is set up to link our category with our client context. The selector is a simple static latch that we will latch whenever we call the validation service in the next section:

// NOTE: This is _NOT_ a recommended approach to writing a client selector.
//       Suggested approaches:
//           -Check the resource of the EObject either by identity or by URI
//            as long as this resource is somehow unique to this application
//           -Check the identity of the resource set to ensure that it is some
//            private object
//           -Check the identity of the EObject itself to see if it belongs to
//            some private collection
//           -Check the EClass of the EObject but only if the metamodel is private
//            to this application and will not be used by other contexts
public class ValidationDelegateClientSelector
	implements IClientSelector {

	public static boolean running = false;
	
	public boolean selects(Object object) {
		return running;
	}
}

Using a client selector is not the only approach that can be used to categorize an EObject as part of a client context. An alternative would be to use an XML enablement expression in an enablement node of the clientContext node in the constraint bindings extension. These expressions can be constructed in the XML to reflectively produce a true/false value when evaluated against a Java object. See the org.eclipse.core.expressions package for more details.

[ back to top]

Executing Batch Validation

Now that the constraint has been registered and the category has been bound to our client context we can call the validation service to perform a batch validation like this:

ValidationDelegateClientSelector.running = true;

IBatchValidator validator = (IBatchValidator)ModelValidationService.getInstance()
	.newValidator(EvaluationMode.BATCH);
validator.setIncludeLiveConstraints(true);

IStatus status = validator.validate(selectedEObjects);
ValidationDelegateClientSelector.running = false;

The first part of this code snippet enables the latch so that the validation service will determine that the provided EObjects belong to our client context. We requested a batch validation and asked that the batch validator include live validation constraints because live validation constraints are often written to handle the batch validation case. Finally, we validate the selected EObjects and are given back the status of the validation. This status obeys the regular IStatus rules and may be a composite or a non-composite status object. The status may have an ERROR/WARNING/OK code, which indicates the worst status code of all of the statuses given back by the evaluated constraints. In order to prevent our client context from interfering with other client contexts, we release the client selector latch. Once the batch validator has been constructed, it can be reused to perform validation at a later time.

[ back to top]

Transforming a Batch Constraint into a Live Constraint

Our constraint can be modified to be a live constraint so it can be used by the validation service to validate notifications coming from notifiers of an EMF model. Within the notification, information such as the modified structural feature, the old value and the new value is available to our constraint. Also, our extension may specify the exact structural features that we are interested in validating. Here is the modified constraint class and extension:

public class NonEmptyNamesConstraint
	extends AbstractModelConstraint {

	public IStatus validate(IValidationContext ctx) {
		EObject eObj = ctx.getTarget();
		EMFEventType eType = ctx.getEventType();
		
		// In the case of batch mode.
		if (eType == EMFEventType.NULL) {
			String name = null;
			if (eObj instanceof Writer) {
				name = ((Writer)eObj).getName(); 
			} else if (eObj instanceof Library) {
				name = ((Library)eObj).getName();
			} else if (eObj instanceof Book) {
				name = ((Book)eObj).getTitle();
			}
			
			if (name == null || name.length() == 0) {
				return ctx.createFailureStatus(new Object[] {eObj.eClass().getName()});
			}
		// In the case of live mode.
		} else {
			Object newValue = ctx.getFeatureNewValue();
			
			if (newValue == null || ((String)newValue).length() == 0) {
				return ctx.createFailureStatus(new Object[] {eObj.eClass().getName()});
			}
		}
		
		return ctx.createSuccessStatus();
	}

}

The changes to the class include a new case where it is invoked in live validation mode. In this case, the exact structural feature does not need to be checked because the extension below will register the constraint against only one specific identification feature for each EClass. Finally, it checks the new value of the identification feature to ensure that it is not null nor empty.

   <extension
         point="org.eclipse.emf.validation.constraintProviders">
      <category
            name="Library Constraints"
            id="org.eclipse.emf.validation.pde.example.general.ui.library"/>
      <constraintProvider cache="true">
         <package namespaceUri="https:///org/eclipse/emf/metamodel/example/pde/library.ecore"/>
         <constraints categories="org.eclipse.emf.validation.pde.example.general.ui.library">
            <constraint
                  lang="Java"
                  class="org.eclipse.emf.validation.examples.constraints.NonEmptyNamesConstraint"
                  severity="ERROR"
                  mode="Live"
                  name="Non-Empty Names"
                  id="org.eclipse.emf.validation.pde.example.general.ui.NameNotEmpty"
                  statusCode="1">
               <description>
                  All items in a library model should have some unique identifier or name.
               </description>
               <message>
                  A {0} has been found to have no unique identifier (name or title).
               </message>
               <target class="Library">
                  <event name="Set">
                     <feature name="name"/>
                  </event>
                  <event name="Unset">
                     <feature name="name"/>
                  </event>
               </target>
               <target class="Writer">
                  <event name="Set">
                     <feature name="name"/>
                  </event>
                  <event name="Unset">
                     <feature name="name"/>
                  </event>
               </target>
               <target class="Book">
                  <event name="Set">
                     <feature name="title"/>
                  </event>
                  <event name="Unset">
                     <feature name="title"/>
                  </event>
               </target>
            </constraint>
         </constraints>
      </constraintProvider>
   </extension>

The extension has changed in a few key places. We have modified its mode to be "Live." This constraint will still get called by the validation service for batch validation because we called validator.setIncludeLiveConstraints(true) when we perform batch validation in the case above (see Executing Batch Validation). We have augmented each of the target nodes to include specific notification events and structural features for each EClass. The validation service will only call our constraint if the notification matches the EClass, EStructuralFeature and event type we have specified.

[ back to top]

Executing Live Validation

Live validation is not called in the typical way that batch validation is called. We will be attaching an adapter to a group of notifiers in order to get notifications when those notifiers are changed. If the adapter is attached as a content adapter to a resource, then notifications will be given for all EObjects contained in that EObject:

Resource r = (Resource)i.next();

if (!resourceHasAdapter(r)) {
	EContentAdapter liveValidationContentAdapter = new LiveValidationContentAdapter();
	r.eAdapters().add(liveValidationContentAdapter);
}

The content adapter will have to modify the client context latch as we did in the batch validation. This will guarantee that our notifier EObjects will be included in our client context, which will allow the validation service to call our constraint:

class LiveValidationContentAdapter extends EContentAdapter {
	private ILiveValidator validator = null;

	LiveValidationContentAdapter() {
	}

	public void notifyChanged(final Notification notification) {
		super.notifyChanged(notification);
		
		if (validator == null) {
			ILiveValidator validator = 
				(ILiveValidator)ModelValidationService.getInstance().newValidator(EvaluationMode.LIVE);
			
		}
		
		ValidationDelegateClientSelector.running = true;
		
		IStatus status = validator.validate(notification);
		
		if (!status.isOK()) {
			if (status.isMultiStatus()) {
				status = status.getChildren()[0];
			}
			
			System.out.println("The current modification has violated one or more live constraints");
		}
		
		ValidationDelegateClientSelector.running = false;
	}
}

As in the case of the batch validation, the details of the validation failures/successes can be retrieved through the returned IStatus object.

[ back to top]

Summary

In this tutorial, we did the following:

  1. Developed a batch validation constraint.
  2. Created the constraint provider extension.
  3. Created a client context for use in our application.
  4. Ran validation against a collection of EObjects in batch mode.
  5. Transformed the batch validation constraint into a dual mode live/batch validation constraint.
  6. Modified the constraint provider extension appropriately.
  7. Developed a content adapter to provide live validation for a particular EMF resource.

[ back to top]


Copyright (c) 2000, 2007 IBM Corporation and others. All Rights Reserved.


 
 
  Published under the terms of the Eclipse Public License Version 1.0 ("EPL") Design by Interspire