Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

Eclipse Validators


December, 2004: Eclipse Validators

Authoring documents that conform to Standards

Lawrence is a software developer in the IBM Rational Software Division working on XML web services tooling, a committer for the Eclipse Web Service Validation Tools project, and a contributor for the Eclipse Web Tools Platform project. Lawrence can be contacted at [email protected].


Open-Source Eclipse Validators


Specifications bodies such as OASIS and W3C continue to release open standards for a myriad of problem domains. While important, useful, and necessary, these standards are often confusing, which means that creating documents that conform to standards, such as the Web Services Description Language (WSDL), can be difficult for developers. "Validators" can act as educated friends, checking your work while you create instance documents. Eclipse provides a plugable architecture that makes it easy for you to add tools such as validators. In this article, I show how to create an Eclipse validator plug-in that provides a menu contribution to invoke the validator and that uses Eclipse markers and dialog boxes to display problems. While I create a validator for WSDL, only a few simple changes (which I will point out as I go) are required to produce a validator with validation logic based on a different specification.

Creating the Validator Plug-In

The WSDL validator plug-in I present here is called "org.eclipse.wsdl.validate." Every Eclipse plug-in requires some basic information, which is stored in a plug-in descriptor file named "plugin.xml." The plug-in descriptor (Example 1) contains the name and version number of the WSDL validator plug-in as displayed in the Eclipse plug-in registry, the name of the runtime library, and a list of plug-in dependencies. The descriptor specifies the name "WSDL Validator Plug-in," the version number 1.0.0, and that the runtime library will be wsdlvalidator.jar for the WSDL validator. The WSDL validator plug-in is dependent on three other plug-ins:

  • org.eclipse.ui because it is going to make user-interface contributions (through the pop-up menu, problems markers, and dialogs).
  • org.eclipse.core.runtime because it is a plug-in that runs as part of Eclipse (as opposed to a plug-in just contributing functionality that other plug-ins consume, such as org.junit).
  • org.eclipse.core.resources because it acts on files, a type of resource, in the workspace.

The Validation Logic

The guts of the validator are found in the class org.eclipse.wsdl.validate.WSDLValidator, which contains the validation code and provides methods to validate an instance document and get any error messages resulting from the validation. The validation logic of the WSDL validator is limited to checking that the root element of the file is the definitions element from the WSDL 1.1 namespace http://schemas.xmlsoap.org/wsdl/. (For information on fully implemented Eclipse validators; see the accompanying text box entitled "Open-Source Eclipse Validators.")

Listing One is the WSDLValidator class. The validate method contains the validation logic and is the method that your custom validation logic should live in or be called from. The validate method creates a standard XML parser that it uses to obtain a document object model (DOM) of a WSDL instance document, the location of which is specified when the validate method is called. The parser is set to understand XML namespaces, which WSDL 1.1 uses, and to be a nonvalidating parser, as the WSDL validator is only concerned with the model the parser creates. A custom error handler of the type WSDLErrorHandler, which is declared as an internal class because it will only be used by this validator, is registered with the parser to catch any critical XML parsing errors that occur while creating the model. WSDLErrorHandler then adds the critical XML parsing errors to the error list.

After the document model is returned from the parser, the validation logic is run. The root element is checked to ensure that the local name is "definitions" and the namespace URI is http://schemas.xmlsoap .org/wsdl/. If either of these checks fails, a new org.eclipse.wsdl.validate.ErrorMessage is created that holds the error message string and line location information. Listing Two is the ErrorMessage class. The getErrors method provides a way for the calling method to get the list of error messages created by the WSDLValidator.

Running Validation in Eclipse

To run the WSDL validator in Eclipse, the validator contributes a menu item that appears on the Eclipse pop-up menu, which is displayed when you right-click on a file in your workspace. To add the menu item, the plug-in must declare that it contains a pop-up menu action. To declare this, the plug-in descriptor extends the org.eclipse.ui.popupMenus extension point. The extension contains two parts:

  • An objectContribution element to specify for what files the menu item should be visible.
  • An action element that declares what to do when the menu item is selected.

In the objectContribution element, the plug-in descriptor specifies that the menu item is visible for objects of type org.eclipse.core.resource.IFile (which represents files in the Eclipse workspace) with the extension .wsdl. It is important to restrict your menu contributions to certain object types. If you do not specify an object type, your menu item is visible on all pop-up menus, regardless of whether your validator is applicable for the object selected. Adding menu items without specifying an associated object type causes workspace clutter and makes it more difficult to find relevant actions.

The plug-in descriptor specifies an action element with four attributes: ID, label, class, and enablesFor. The ID attribute is given a value org.eclipse.wsdl.validate.wsdlaction, which is a unique identifier of the WSDL validate action among all registered Eclipse actions. The label attribute that specifies the text that appears on the menu is set to "Validate WSDL File"; see Figure 1. The class attribute is the name of the class that implements org.eclipse.ui.IActionDelegate and runs the WSDL validate action. The WSDL validator uses the delegate class org.eclipse.wsdl.validate.ValidateWSDLActionDelegate. The enablesFor attribute lets you specify the number of selected resources for which your action will be available. This WSDL validator only supports validation of a single file, so the descriptor specifies that the WSDL validate action is enabled when a single file is selected. You can specify that your action is enabled for any number of selected files or resources. The pop-up menu extension point that has just been added is in Example 2.

The class org.eclipse.wsdl.validate.ValidateWSDLActionDelegate implements IActionDelegate and provides an implementation for the descriptor's abstract definition of the menu item. IActionDelegate requires the two methods:

  • selectionChanged, which is called when you change the selection on the pop-up menu; that is, move the cursor from one item to the next.
  • run, which is called when you click the menu item. The run method does not get passed any information on the file that you select, so the validating class needs to store the file that is selected when the selectionChanged method is called. The run method performs a few checks to ensure the selection is an IFile (the object type the plug-in descriptor specified that the menu contribution should be available for). If it is, the run method calls the validate action for validation to occur on that file.

Listing Three is the ValidateWSDLActionDelegate class.

The org.eclipse.wsdl.validate.ValidateAction class (Listing Four) performs the action of running validation. The run method (the standard way to invoke Eclipse actions) calls the validate method. In the validate method, a new WSDLValidator is created. Any existing markers on the IFile are removed by the clearMarkers method, and any new errors produced by the validator are displayed in the Problems view in your workspace by the createMarkers method. Each change to your workspace, such as adding and removing markers, requires that the workspace be refreshed to reflect the changes. Refreshing the workspace is a slow action so adding or removing many markers one at a time will cause the results of your validation to be displayed very slowly. Eclipse provides a mechanism, through the org.eclipse.core.resource.IWorkspaceRunnable interface, to make a group of changes and only refresh your workspace once. The IWorkspaceRunnable interface requires the run method. The actions of clearing existing markers and creating new markers in the validate method are therefore run in the IWorkspaceRunnable run method. To use the WSDLValidator both inside and outside of the IWorkspaceRunnable run method, the WSDLValidator must be declared as final.

Validation markers are created for any validation errors that occur during validation, and existing markers from previous validation runs are removed. The createMarkers and clearMarkers methods perform these two tasks.

createMarkers takes a list of ErrorMessages, iterates through the list, and creates a marker on the IFile for each ErrorMessage. To add a marker to the IFile, an IMarker representation of the error is created. For the error message, a Problem marker is created by specifying a marker of type IMarker.Problem. Two arrays are created to hold the marker attributes. The first, attNames, contains the names of the attributes. The second, attValues, contains their values. There are four attributes that are assigned to a WSDL validator marker.

The owner attribute specifies an identifier for the WSDL validator. This attribute is used in the clearMarkers method to find markers that were assigned to an IFile so they can be removed.

The IMarker.LINE_NUMBER and IMarker.MESSAGE attributes assign, as expected, the line the marker is located at (which will show in the Problems view and can be displayed in editors as well) and the message to display for the marker.

The IMarker.SEVERITY attribute is used to set the marker as an error marker. The attributes are assigned to the marker using the two arrays. After being assigned, the markers appear in the Problems view, as in Figure 2.

clearMarkers finds all of the markers stored on the IFile and removes those markers that the WSDL validator created. This method determines which markers were created by the validator by checking the owner attribute of each marker to see if its value is the ID the validator sets when it creates markers. The deleteMarkers array stores all of the markers that should be removed from the IFile. The deleteMarkers array is populated by iterating through all of the markers stored on the IFile and selecting only those that were created by the WSDL validator. The workspace is then asked to delete the specified markers.

The WSDL validator uses two Eclipse dialog boxes to indicate the two possible validation outcomes. But why is it necessary to notify of the results of validation if they are shown in the Problems view? There are three reasons:

  • The Problems view may not be visible.
  • The Problems view may have problems from other sources or resources that may make if difficult to tell whether the file is valid according to the WSDL validator.
  • Showing a dialog provides confirmation to you that the action that you requested, validating your file, has completed.

The validate method launches a dialog. All dialogs require a shell that displays them, a title, and a message. The shell you should use can be retrieved from the method Display.getDefault().getActiveShell(). The title and message can be anything you think appropriate for your validator. For the WSDL validator, if the file is valid, an information dialog is displayed stating that the file is valid. There is no Eclipse valid dialog box so valid results will display an information dialog stating that the file is valid using the MessageDialog.openInformation method. If the file is not valid, an error dialog will be displayed stating that the file is not valid. The error dialog will be displayed with the MessageDialog.openError method. The valid and not valid dialogs are in Figures 3 and 4, respectively.

Functionally Part of the Workbench

By creating markers and reusing the Problems view, you not only get to display your error messages but you get some extra cool functionality that lets your validator be integrated across the Workbench. When a marker is assigned to an IFile, the Eclipse workspace takes a look at all of the markers, takes the icon of the most severe marker (error markers being most severe), and displays the icon on the file icon in the navigator view. This lets you easily see the status of all the files in your workspace. The workspace also adds the markers, at the correct lines, to the margin of the text editor (and most other source editors follow this convention as well). One more piece of functionality that is included for free is the ability to select a marker in the Problems view and have an editor open to the exact location of the problem. This extra functionality allows your validator to fit right in with the Eclipse Workbench and seamlessly integrate with the functionality already available in Eclipse.

Conclusion

Eclipse provides the mechanisms required to extend the platform and create your own validator for any open standard. In this article I have shown how to create an Eclipse validator plug-in from the ground up. I created a validator for WSDL 1.1 that exposes its functionality through an Eclipse pop-up menu contribution, reports errors resulting from validation using Eclipse markers and the Problems View, and shows the results of the validation in a dialog. It is up to you to create more standards validators. Promote the use of open standards by providing validators that help you create documents that conform to specifications.

DDJ



Listing One

package org.eclipse.wsdl.validate;

import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class WSDLValidator
{
  private List errorList = new ArrayList();
  public void validate(String fileLocation)
  {
    errorList.clear();
    try
    {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setNamespaceAware(true);
      factory.setValidating(false);
      DocumentBuilder docBuilder = factory.newDocumentBuilder();
      docBuilder.setErrorHandler(new WSDLErrorHandler());
      Document doc = docBuilder.parse(fileLocation);
      Element rootElement = doc.getDocumentElement();
      if (!(rootElement.getLocalName().equals("definitions") 
            && rootElement.getNamespaceURI().equals(
              "http://schemas.xmlsoap.org/wsdl/")))
      {
        errorList
            .add(new ErrorMessage("The root element of the WSDL document "
              + "is not definitions from the namespace "
              + "http://schemas.xmlsoap.org/wsdl/.", 1));
      }
    }
    catch (Exception e)
    {
    }
  }
  public List getErrors()
  {
    return errorList;
  }
  private class WSDLErrorHandler implements ErrorHandler
  {
    public void error(SAXParseException exception)
        throws SAXException
    {
      errorList.add(new ErrorMessage(exception.getMessage(),
                exception.getLineNumber()));
    }
    public void fatalError(SAXParseException exception)
        throws SAXException
    {
      error(exception);
    }
    public void warning(SAXParseException exception)
        throws SAXException
    {
      error(exception);
    }
  }
}
Back to article


Listing Two
package org.eclipse.wsdl.validate;
public class ErrorMessage 
{
  private String message;
  private int lineNumber;
  
  public ErrorMessage(String message, int lineNumber)
  {
    this.message = message;
    this.lineNumber = lineNumber;
  }
  public String getMessage()
  {
    return message;
  }
  public int getLineNumber()
  {
    return lineNumber;
  }
}
Back to article


Listing Three
package org.eclipse.wsdl.validate;

import org.eclipse.core.resources.IFile;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IActionDelegate;

public class ValidateWSDLActionDelegate implements IActionDelegate
{
  private ISelection selection;
  public void selectionChanged(IAction action, ISelection selection)
  {
    this.selection = selection;
  }
  public void run(IAction action)
  {
    if (!selection.isEmpty()
        && selection instanceof IStructuredSelection)
    {
      IStructuredSelection structuredSelection = 
        (IStructuredSelection) selection;
      Object element = structuredSelection.getFirstElement();
      if (element instanceof IFile)
      {
        ValidateAction validateAction = new ValidateAction((IFile) element);
        validateAction.run();
      }
    }
  }
}
Back to article


Listing Four
package org.eclipse.wsdl.validate;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;

public class ValidateAction extends Action
{
  private IFile iFile;
  public ValidateAction(IFile iFile)
  {
    this.iFile = iFile;
  }
  public void run()
  {
    validate(iFile);
  }
  protected void validate(final IFile file)
  {
    final WSDLValidator validator = new WSDLValidator();
    IWorkspaceRunnable op = new IWorkspaceRunnable()
    {
      public void run(IProgressMonitor progressMonitor)
          throws CoreException
      {
        clearMarkers(file);
        try
        {
          String location = file.getLocation().toFile()
                            .getCanonicalFile().getAbsolutePath();
          validator.validate(location);
        }
        catch (IOException e)
        {
        }
        createMarkers(file, validator.getErrors());
      }
    };
    try
    {
      ResourcesPlugin.getWorkspace().run(op, null);
      if (validator.getErrors().isEmpty())
      {
        String title = "Validation Succeeded";
        String message = "The WSDL file is valid.";
        MessageDialog.openInformation(Display.getDefault()
            .getActiveShell(), title, message);
      }
      else
      {
        String title = "Validation Failed";
        String message = "The WSDL file is not valid. "
                       + "Please see the problems view for errors.";
        MessageDialog.openError(Display.getDefault()
            .getActiveShell(), title, message);
      }
    }
    catch (Exception e)
    {
    }
  }
  private void createMarkers(IFile iFile, List errors)
  {
    Iterator errorIter = errors.iterator();
    while (errorIter.hasNext())
    {
      ErrorMessage errorMessage = (ErrorMessage) errorIter.next();
      int line = errorMessage.getLineNumber();
      String message = errorMessage.getMessage();
      try
      {
        IMarker marker = iFile.createMarker(IMarker.PROBLEM);
        String[] attNames = new String[4];
        Object[] attValues = new Object[4];
        attNames[0] = "owner";
        attValues[0] = "org.eclipse.wsdl.validate";
        attNames[1] = IMarker.LINE_NUMBER;
        attValues[1] = new Integer(line);
        attNames[2] = IMarker.SEVERITY;
        attValues[2] = new Integer(IMarker.SEVERITY_ERROR);
        attNames[3] = IMarker.MESSAGE;
        attValues[3] = message;
        marker.setAttributes(attNames, attValues);
      }
      catch (CoreException e)
      {
      }
    }
  }
  private void clearMarkers(IFile iFile)
  {
    try
    {
      IMarker[] markers = iFile.findMarkers(null, true, IResource.DEPTH_ZERO);
      IMarker[] deleteMarkers = new IMarker[markers.length];
      int deleteindex = 0;
      Object owner;
      for (int i = markers.length - 1; i >= 0; i--)
      {
        IMarker marker = markers[i];
        owner = marker.getAttribute("owner");

        if (owner != null && owner instanceof String) if (owner
            .equals("org.eclipse.wsdl.validate"))
        {
          deleteMarkers[deleteindex++] = markers[i];
        }
      }
      if (deleteindex > 0)
      {
        IMarker[] todelete = new IMarker[deleteindex];
        System.arraycopy(deleteMarkers, 0, todelete, 0, deleteindex);
        iFile.getWorkspace().deleteMarkers(todelete);
      }
    }
    catch (CoreException e)
    {
    }
  }
}
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.