WSDL Parsing with Eclipse’s metamodel for WSDL

I recently had to work with the WSDL metamodel from Eclipse WTP.
I thought it may be useful to compare it with other WSDL parsing solutions. Notice that I will focus on Java solutions here.

A quick introduction to WSDL

WSDL stands for Web Services Description Language.
It is a W3C specification to describe the interface / contract of a service. A WSDL contract is defined in a *.wsdl file. This file describes the service operations, their input and output parameters, the declaration of services, their reach location and the communication protocol to use to interact with them. A *.wsdl file may import other WSDL and also XML schemas (they contain the type definition of operation parameters).

There are two versions of WSDL that may be worked with: WSDL 1.1 and WSDL 2.0.
Although WSDL 2.0 is conceptually better (in particular because of MEP – Message Exchange Patterns), WSDL 1.1 is the most used. There are very few tools and libraries libraries which support it (Axis 2 supports it, CXF does not, JAX-WS did not plan its support). And it must be said that the two versions are different in the approach and some concepts do not match at all.

And in WSDL 1.1, there are some distinctions, such as the style (document / wrapped, xml-rpc…).
Information about this can be found on the internet.

Parsing solutions

For WSDL 1.1, the most famous solution is WSDL4j.
This library provides an easy (and light!) solution to parse WSDL definitions. Its only limitation in my opinion, is that it is not easy to introspect XML schemas with it.

For WSDL 2.0, there is Apache Woden.
This library allows to parse WSDL (2.0) definitions and to convert WSDL (1.1) definitions to WSDL 2.0 (again, both versions are very different).

Then, there is a more recent project called EasyWSDL.
The versions 1.x and 2.x aimed at providing a single API to parse and edit WSDL 1.1 and WSDL 2.0 definitions. Unfortunately, this API makes an important use of Java generics. However, it is easy to navigate in XML schema with it. The version 3.x is still in incubation but took a very different turn. To simplify the API, and because it was ambiguous, it was decided to separate the API for the various versions of the specification. For the moment, EasyWSDL 3.x only supports WSDL 1.1. The API is much more simple and it is still easy to navigate in XML schemas. Besides, it can execute XPath queries on a macro document (the main document and all the import as a single virtual document). However, it misses some helpers to work with imports (writing XPath queries is not very user-friendly).

Eventually, there is the EMF metamodel for WSDL.
The metamodel is provided by the WTP project at Eclipse. It can only parse WSDL 1.1. In fact, I found out that it relies on and extends WSDL4j. One of the main improvements is that it can introspect XML schemas. To use it, you have to put the EMF libraries and some WTP libraries in your dependencies:

  • org.eclipse.xsd: the metamodel for XML schemas.
  • org.eclipse.wst.wsdl: the metamodel for WSDL.
  • javax.wsdl: a wrapper for WSDL4j.

Reading a WSDL with the WSDL metamodel

/**
 * Finds all the port types declared in the WSDL or in its imported documents.
 * @param emfUri an EMF URI pointing to a WSDL definition
 */
List<PortType> findAllPortTypes( URI emfUri ) {
	
	// Register the basic elements of the specification
	ResourceSet resourceSet = new ResourceSetImpl();
	resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put( "xml", new XMLResourceFactoryImpl());
	resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put( "xsd", new XSDResourceFactoryImpl());
	resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put( "wsdl", new WSDLResourceFactoryImpl());
	resourceSet.getPackageRegistry().put( XSDPackage.eNS_URI, XSDPackage.eINSTANCE );
	resourceSet.getPackageRegistry().put( WSDLPackage.eNS_URI, WSDLPackage.eINSTANCE );

	// Register the required extensions
	// None here
	
	// Load the main document
	Resource resource = resourceSet.getResource( emfUri, true );
    Definition def = (Definition) resource.getContents().iterator().next();

	// Add the initial definition
	Set<Definition> definitions = new HashSet<Definition> ();
	definitions.add( alreadyLoadedDefinition );

	// Process the imports
	processImports( alreadyLoadedDefinition.getImports(), definitions );
	
	// Get all the port types
	List<PortType> portTypes = new ArrayList<PortType> ();
	for( Definition def : definitions ) {
		for( Object o : def.getPortTypes().values())
			portTypes.add((PortType) o );
	}
	
	return portTypes;
}

/**
 * Finds the definitions from imports and processes them recursively.
 * @param imports a map of imports (see {@link Definition#getImports()})
 * @param definitions a list of definitions, found from import declarations
 */
private static void processImports( Map<?,?> imports, Collection<Definition> definitions ) {

	for( Object o : imports.values()) {

		// Case "java.util.list"
		if( o instanceof List<?> ) {
			for( Object oo : ((List<?>) o)) {
				Definition d = ((Import) oo).getEDefinition();
				if( d != null && ! definitions.contains( d )) {
					definitions.add( d );
					processImports( d.getImports(), definitions );
				}
			}
		}

		// Case "org.eclipse.wst.Definition"
		else if( o instanceof Definition ) {
			Definition d = (Definition) o;;
			if( ! definitions.contains( d )) {
				definitions.add( d );
				processImports( d.getImports(), definitions );
			}
		}
	}
}

XML schema can be accessed with this solution.
This is possible thanks to the metamodel for XML schemas.
As an example, here is how to parse a XML schema with this metamodel. Note that this metamodel is not only an EMF library, it is also bridged with a DOM model. In fact, this is also true for the WSDL metamodel (generally, this is not the case with EMF metamodels).

/**
 * Loads a XML schema.
 * @param emfUri an EMF URI
 * @return an instance of {@link XSDSchema}
 * <p>
 * This object already supports inclusions, which means there is no need to
 * get the imports and parse them.
 * </p>
 */
public static XSDSchema loadXmlSchema( URI emfUri ) {

	// Register the basic elements
	ResourceSet resourceSet = new ResourceSetImpl();
	resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put( "xml", new XMLResourceFactoryImpl());
	resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put( "xsd", new XSDResourceFactoryImpl());
	resourceSet.getPackageRegistry().put( XSDPackage.eNS_URI, XSDPackage.eINSTANCE );

	// Load the resource
	Resource resource = resourceSet.getResource( emfUri, true );
	return (XSDSchema) resource.getContents().iterator().next();
}

Creating a WSDL with the WSDL metamodel

Like most of standards, WSDL supports extensions.
In this sample, I use an extension for BPEL.

Definition createWsdlArtifact( String newWsdlUrl ) {

	// Initial data we got from another WSDL definition
	PortType portType = this.portTypePage.getPortType();
	Definition businessDefinition = (Definition) portType.eContainer();
		
	// The new definition to create
	Definition artifactsDefinition = WSDLFactory.eINSTANCE.createDefinition();
	artifactsDefinition.setTargetNamespace( businessDefinition.getTargetNamespace() + "Artifacts" );

	// Hack for the role: we need to define manually the name space prefix for the TNS of the business WSDL
	artifactsDefinition.getNamespaces().put( "tns", businessDefinition.getTargetNamespace());

	// WSDL import
	Import wsdlImport = WSDLFactory.eINSTANCE.createImport();
	wsdlImport.setLocationURI( newWsdlUrl );
	wsdlImport.setNamespaceURI( businessDefinition.getTargetNamespace());
	artifactsDefinition.addImport( wsdlImport );

	// Partner Link Type
	PartnerLinkType plType = PartnerlinktypeFactory.eINSTANCE.createPartnerLinkType();
	plType.setName( portType.getQName().getLocalPart() + "PLT" );

	Role plRole = PartnerlinktypeFactory.eINSTANCE.createRole();
	plRole.setName( portType.getQName().getLocalPart() + "Role" );
	plRole.setPortType( portType );
	plType.getRole().add( plRole );
	plType.setEnclosingDefinition( artifactsDefinition );
		
	// This is an extension, here is how to add it
	artifactsDefinition.getEExtensibilityElements().add( plType );
	return artifactsDefinition;
}

And here is how to write it.

void writeDefinition( Definition def, File targetFile ) {
	
	// Register the basic elements of the specification
	ResourceSet resourceSet = new ResourceSetImpl();
	resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put( "xml", new XMLResourceFactoryImpl());
	resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put( "xsd", new XSDResourceFactoryImpl());
	resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put( "wsdl", new WSDLResourceFactoryImpl());
	resourceSet.getPackageRegistry().put( XSDPackage.eNS_URI, XSDPackage.eINSTANCE );
	resourceSet.getPackageRegistry().put( WSDLPackage.eNS_URI, WSDLPackage.eINSTANCE );

	// Register the required extensions
	resourceSet.getPackageRegistry().put( PartnerlinktypePackage.eNS_URI, PartnerlinktypePackage.eINSTANCE );
	
	// Create a resource...
	URI emfUri = URI.createFileURI( targetFile.getAbsolutePath());
	Resource resource = resourceSet.createResource( emfUri );
	resource.getContents().add( def );
		
	// ... and save it
	// See @link{org.eclipse.emf.ecore.xmi.XMLResource} for examples of SAVE options
	Map<Object,Object> saveOptions = new HashMap<Object,Object> ();
	resource.save( saveOptions );
}

Conclusion

I have been working with WSDL for several years. And to be honest, I had never thought about using the WSDL metamodel outside Eclipse. I knew it was there, I knew how to work with EMF. But for me, it was more a tool than a real library. And somehow, this is true. But the fact is that it is not complicated to use. And it can also work in standalone applications, not only in Eclipse.

So, this may be a solution to consider, depending on your needs.


About this entry