BPEL Validator

The BPEL validator is designed to validate BPEL 2.0 source files. In addition the BPEL validator checks the executable BPEL processes - support for abstract processes is not implement.

The validator code is in the plugin org.eclipse.bpel.validator which must be present for validation support to be included.

Workbench integration

The validator plugin implements 2 extension points which allow it to be executed automatically when BPEL resources are modified. These are:
  1. The BPEL "builder". Here the project must be created with the builder defined in the .project file. The builder id is org.eclipse.bpel.validator.builder
  2. The WST validation. Here, a new validator (BPEL Validator) is registered with the WST validation extensions.

In either situation, the validator is run "on save" of a BPEL resource and it produces a list of markers as a result on the resources which have errors in them (which are primarily the BPEL resources).

The validator plugin defines a "BPEL marker" which is essentially a problem marker.

As all markers in Eclipse these are displayed in the Problem View . The resulting markers are shown in text editors and in the BPEL editor and doubleclicking the error in the Problem View will navigate to the problem location.

Under the covers: INode, Act 1

The general approach was to write the validator with no direct dependency on Eclipse, the EMF BPEL model, or other workbench things. The reason was that it might be useful in other places - so no direct dependency ...

Having said that, the stake in the ground was a simple INode interface to the model nodes - your basic "tree" node type of concept.

This adapts very easily to DOM nodes (into which BPEL source is transformed inititially), and to a slightly lesser extend to EMF object nodes, since they do, for the most part, follow that pattern. The leap of faith here is that any BPEL entity model would follow that type of a pattern ("toma-toe" vs. "tomato") and that suitable adapters can be build if needed.

Validation then becomes a two pass walk through this adapted model tree where validators are written to the specific BPEL model nodes (so there is a VariableValidator , an UntilValidator , a WhileValidator , etc). The input to the validator is a starting node and something we call IModelQuery (explained later), and the output is a list or problems that have been found.

Why a 2 pass walk ? Because walking is simple and predictable and the 1 st pass captures certain state that is only useful on the 2 nd pass. The code is actually so simple, that it might as well be included here. Take a look at org.eclipse.bpel.validator.model.Runner

				
public class Runner {

	/** The root node from where validation should be started */
	INode fRoot;
	
	/** The query interface to our model */
	IModelQuery fModelQuery;
	
	/** empty list of problems */
	IProblem[] fProblems = {};

	
	/**
	 * Return a brand new shiny instance of a runner. The runner's purpose is to validate
	 * the nodes in the tree starting at node passed.
	 * 
	 * @param query the model query
	 * @param root the root node.
	 */
	
	public Runner ( IModelQuery query, INode root) {

		fRoot = root ;
		fModelQuery = query;
	}
	
	
	/**
	 * Perform the validation run.
	 * 
	 * @return list of problems found.
	 */
	
	
	public IProblem[] run () {

		// Create a depth first iteration from the root node
		ArrayList iteratorList = new ArrayList(32);
		ArrayList validatorList = new ArrayList(256);
		
		// start at the root node
		iteratorList.add(fRoot);
		
		// 1. Generate the list of validators to call, in order
		while (iteratorList.size() > 0) {
			
			INode nextNode = iteratorList.remove(0);
			
			Validator validator = nextNode.nodeValidator();
			
			if (validator != null) {				
				validatorList.add(validator);
				validator.setModelQuery(fModelQuery);
			}
			
			// the facaded object will tell us what children to include in the walk
			List children = nextNode.children();			
			if (children.size() > 0) {
				iteratorList.addAll(0, children);
			}			
		}
		
		
		// Pass 1
		for(Validator validator : validatorList) {
			validator.validate(Validator.PASS1);
		}
		
		ArrayList problems = new ArrayList( 64 );
		
		// Pass 2
		// On the validators that have been run, perform the final rule pass.
		for(Validator validator : validatorList) {
			validator.validate(Validator.PASS2);
			
			for(IProblem problem : validator.getProblems() ) {
				problems.add (problem);
			}
		}

		return problems.toArray( fProblems );
	}
}

			

Writing Rules: IModelQuery, Act 2

Some checks can be made using only this simple INode model. For example, can this node have that node as a child, and are certain attributes set on a given node.

Other checks are a little more tricky. For example, how do you determine the type of a variable ? Well, you have to lookup it up someplace. So the question becomes where and how ?

From the perspective of the validator, this becomes an interface that it will query against the actual implementation model (we called it IModelQuery ). So the validator code would say, lookup this XSD type, and the implementor of the interface will perform the query against the prevailing models. The return of the "lookup this XSD type" is an INode which adapts the actual model node (either EMF or DOM). The validator code can then answer simple questions about this returned node via its INode interface. For example: are you resolved , or do you exist ?

Type compatibility checks are also delegated to IModelQuery and are answered by the implementing model using whatever means it has.

XPath expressions are a little tricker in that you have to walk the XPath expression trees. In path expressions, you may know what variable is being used (and hence you can query its type from the model), then walk the path expression. Again an abstraction of that concept is present in the model query interface so the validator code says: can I make the next step while the implementation of it will say yes or no .

Once a problem is found, a message is generated and the appropriate information is put into a problem and then the issue becomes how to pin the problem to the right location. Once again this is delegated to the IModelQuery interface which will fill the appropriate items like EMF paths, XPaths, line numbers, etc, that will help pin the problem to a particular location in the entity model.

Rule exposed: Annotations, Act 3

Rules are just special methods on a validator object which may be annotated and are discovered once (by reflection). The only 2 run-time things that are interesting about a rule method on a validator object are 2 items:

  1. rule index (order of execution)
  2. rule tag name
The rule index is used to mark the order of execution of the rule and the tag name is used to distiguish between pass1 and pass2 rules.

The full rule annotation is shown below. The other fields of the annotation are used for generating completeness documentation from the validator code.

			package org.eclipse.bpel.validator.model;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)

public @interface ARule {
	
	/** The date in which this rule was added */
	String date()      default "01/01/1970";
	
	/** The static analysis reference code
	 * @return the static analysis code.
	 */
	
	int sa ()       default 0;
	
	/** Brief description of the rule */
	String desc()      default "No description";
	
	/** The author to bug about this */
	String author()    default "Unknown";	
	
	/** Tags are used for marked execution of rules */
	String tag ()      default "pass1";
	
	/** the order in which the rule will be executed */ 
	int order () default 0;
}

			
A VariableValidator segment is shown below. Here we check to make sure that a variable has at least a messageType, type, or element set. There is a rule to check the NCName of the variable (no dots) and a rule to check the message type.
			    ...
	/**
	 * Rule to check the name of the variable. 
	 */
	@ARule(
		author = "michal.chmielewski@oracle.com",
		date = "9/14/2006",
		desc = "Checks that variable NCName further does not contain a period (.) in the name.",
		sa = 24
	)	
	public void rule_CheckName_1 () {			
		
		// Must be a valid NCName ...
		mChecks.checkNCName(mNode, ncName, AT_NAME );
		
		IProblem problem ;
		// ... and not contain a .
		if (ncName.indexOf('.') >= 0) {
			problem = createError();
			problem.setAttribute(IProblem.CONTEXT, AT_NAME);
			problem.fill("BPELC_VARIABLE__NO_DOT", //$NON-NLS-1$
				ncName);
		}
	}
	
	
	
	/**
	 * Rule to check the type of the variable.
	 * 
	 * It can be either:
	 *   1) MessageType
	 *   2) Element
	 *   3) Type
	 *   
	 * Only one must be defined, more then one cannot be defined. 
	 *  
	 */
	@ARule(
		author = "michal.chmielewski@oracle.com",
		date = "9/14/2006",
		desc = "Variable type specification (either element, messaageType, or type).",
		sa = 25
	)
	
	public void rule_CheckType_2 () {
		
		int typeCount = 0;
		IProblem problem;
		
		// Check messageType 
		String messageType = mNode.getAttribute(AT_MESSAGE_TYPE);		 
		if (messageType != null) {
			typeCount += 1;
			fMessageTypeNode = mModelQuery.lookup(mNode, 
				  					IModelQuery.LookupNode.MESSAGE_TYPE, 
				  					messageType);			
		}
		
		
		// Check element
		String element = mNode.getAttribute(AT_ELEMENT);		
		if (element != null) {
			typeCount += 1;
			fElementNode = mModelQuery.lookup(mNode, 
							IModelQuery.LookupNode.XSD_ELEMENT,
							element );			
		}
		
		// Check Type (XMLType)
		String type = mNode.getAttribute ( AT_TYPE );			
		if (type != null) {
			typeCount += 1;
			fTypeNode = mModelQuery.lookup(mNode, 
										IModelQuery.LookupNode.XSD_TYPE,
										type );			
		}
		
		
		
		// Missing and too many types
		if (typeCount == 0) {
			problem = createError( );
			problem.setAttribute(IProblem.CONTEXT, AT_TYPE);
			problem.fill( "BPELC_VARIABLE__NO_TYPE", ncName); //$NON-NLS-1$
			
		} else if (typeCount > 1) {
			
			problem = createError( );
			problem.setAttribute(IProblem.CONTEXT, AT_TYPE);
			problem.fill( "BPELC_VARIABLE__NO_TYPE", ncName); //$NON-NLS-1$			
		}
	}
	

	/**
	 * Check message type node
	 */
	@ARule(
		sa = 10,
		desc = "Make sure that Message Type is visible from the import(s)",
		author = "michal.chmielewski@oracle.com",
		date = "01/25/2007"
	)
	
	public void rule_CheckMessageTypeNode_4 () {
		if (fMessageTypeNode == null) {
			return ;
		}
		
		mChecks.checkAttributeNode (mNode, fMessageTypeNode, AT_MESSAGE_TYPE, KIND_NODE );
		setValue("type",fMessageTypeNode);
	}
	...
			

Validator Creation: IFactory, Act 4

The validators used by the adapted model tree are created using factories which are registered in org.eclipse.bpel.validation.model.RuleFactory . Here are the few basic rules regarding creating, registering, and writing node validators.
  1. Validators are specific to nodes. So an assign activity will be queried for its list of validators. The copy node will do the same, and so will from and to (to use Assign as an exmaple).
  2. A factory for validator nodes may be registered. A factory must implement the IFactory interface, which is again very trivial. null should be returned if the factory does not supply validators for the given namespace.
    					package org.eclipse.bpel.validator.model;
    
    import javax.xml.namespace.QName;
    
    /**
     * @author Michal Chmielewski (michal.chmielewski@oracle.com)
     * 
     * @param  The of the factory
     * @date Jan 5, 2007
     *
     */
    public interface IFactory {
    	
    	/**
    	 * Generic factory to create something based on a QName.
    	 * 
    	 * @param qname the QName of the node.
    	 * @return the node validator for this QName 
    	 */
    	
    	T create ( QName qname ) ;	
    }
     					
  3. Validators can be chained . Meaning a several factories can add validators for the same node.
  4. All validators must extend org.eclipse.bpel.validator.model.Validator

Self Documentation

For a list of rules and coverage of the static analysis checks look here .

Graduation
This project has graduated!