Consuming CMIS WSDL in Visual Studio

As indicated previously, I’ve uploaded a CMIS v0.5 sample to EDN. This sample works with EMC Documentum CMIS EA2.

This CMIS sample is intentionally similar to a sample produced previously for DFS 6.5 SP1. The intent is to help you compare and contrast one set of service contracts from the other. In doing so, please keep in mind that CMIS is focused on basic library services for content management—common features across supporting repositories—while DFS is focused on the broader richness of the EMC Documentum ECM Platform.

It’s worth noting that in the case of its CMIS Repository service interaction, this sample EXE was also used by IBM during this week’s TC meeting against their P8-based WSDL endpoint—requiring only a binding configuration change (i.e. zero code changes).

I mentioned that the CMIS AtomPub service (introspection) document for EA2 is accessible as follows: <code>http://host:port/resources/cmis</code>. Let’s say your EA2 installation is running at localhost on 8080, then a request for this document will return the following type of response:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns3:service xmlns="http://www.w3.org/2005/Atom" xmlns:ns2="http://www.cmis.org/2008/05" xmlns:ns3="http://www.w3.org/2007/app">
  <ns3:workspace ns2:id="dfs">
    <title type="text">dfs</title>
    <ns2:repositoryInfo>
      <ns2:repositoryId>dfs</ns2:repositoryId>
      <ns2:repositoryName>dfs</ns2:repositoryName>
      <ns2:repositoryDescription>dfs</ns2:repositoryDescription>
      <ns2:vendorName>EMC</ns2:vendorName>
      <ns2:productName>Documentum</ns2:productName>
      <ns2:productVersion>6.5.0.033</ns2:productVersion>
      <ns2:rootFolderId>0c00302180000105</ns2:rootFolderId>
      <ns2:capabilities>
        <ns2:capabilityMultifiling>true</ns2:capabilityMultifiling>
        <ns2:capabilityUnfiling>false</ns2:capabilityUnfiling>
        <ns2:capabilityVersionSpecificFiling>true</ns2:capabilityVersionSpecificFiling>
        <ns2:capabilityPWCUpdateable>false</ns2:capabilityPWCUpdateable>
        <ns2:capabilityPWCSearchable>false</ns2:capabilityPWCSearchable>
        <ns2:capabilityAllVersionsSearchable>true</ns2:capabilityAllVersionsSearchable>
        <ns2:capabilityQuery>both</ns2:capabilityQuery>
        <ns2:capabilityJoin>inneronly</ns2:capabilityJoin>
        <ns2:capabilityFullText>fulltextandstructured</ns2:capabilityFullText>
      </ns2:capabilities>
      <ns2:cmisVersionsSupported>0.5</ns2:cmisVersionsSupported>
    </ns2:repositoryInfo>
    <ns3:collection href="http://localhost:8080/resources/cmis/repositories/dfs/objects/0c00302180000105/children" ns2:collectionType="root-children">
      <title type="text">root-children</title>
    </ns3:collection>
    <ns3:collection href="http://localhost:8080/resources/cmis/repositories/dfs/objects/0c00302180000105/descendants" ns2:collectionType="root-descendants">
      <title type="text">root-descendants</title>
    </ns3:collection>
    <ns3:collection href="http://localhost:8080/resources/cmis/repositories/dfs/types" ns2:collectionType="types-children">
      <title type="text">types-children</title>
    </ns3:collection>
    <ns3:collection href="http://localhost:8080/resources/cmis/repositories/dfs/types" ns2:collectionType="types-descendants">
      <title type="text">types-descendants</title>
    </ns3:collection>
    <ns3:collection href="http://localhost:8080/resources/cmis/repositories/dfs/checkedout" ns2:collectionType="checkedout">
      <title type="text">checkedout</title>
    </ns3:collection>
    <ns3:collection href="http://localhost:8080/resources/cmis/repositories/dfs/queries" ns2:collectionType="query">
      <title type="text">query</title>
    </ns3:collection>
  </ns3:workspace>
</ns3:service>

Your repository name and object identifiers will likely differ from the example references above, but hopefully you get the gist of the response payload.

Comparing the RESTful AtomPub binding to the SOAP binding in CMIS, one has to make two service requests to yield the same repository information (i.e. the block of data represented by <code>repositoryInfo</code> above) as follows:

repositories = repositoryService.getRepositories();
foreach (cmisRepositoryEntryType repository in repositories)
{
  cmisAnyXml repositorySpecificInformation;
  string repositoryId = repository.repositoryID,
         repositoryRelationship,
         repositoryDescription,
         vendorName,
         productName,
         productVersion,
         rootFolderId,
         cmisVersionsSupported;
  cmisRepositoryCapabilitiesType capabilities;
  XmlAttribute[] AnyAttr;
  XmlElement[] Any;
  repositoryService.getRepositoryInfo(ref repositoryId, out repositoryRelationship,
         out repositoryDescription, out vendorName, out productName,
         out productVersion, out rootFolderId, out capabilities,
         out cmisVersionsSupported, out repositorySpecificInformation,
         out Any, out AnyAttr);
  . . .
}

Comparing CMIS Repository service WSDL consumption with DFS Search service WSDL consumption, the same DFS-based consumer code is as follows:

Repository[] repositories = searchService.getRepositoryList(null);
foreach (Repository repository in repositories)
{
  . . .
}

To be clear, these examples are not provided for me to argue that one approach is better than another but rather to show how approaches differ based on domain model, use cases, etc.

I’ll leave it as an exercise for the reader to perform similar comparisons where query support and object support is concerned between DFS and CMIS. :-)

Perhaps it’s also useful to comment on how WS-Security header information is passed to CMIS WSDL endpoints, since CMIS WSDL doesn’t currently declare headers explicitly in the service contract.

This sample injects WS-Security headers via app.config-based declaration (versus programmatically):

<extensions>
  <behaviorExtensions>
    <add name="usernameToken" type="SecurityMessageInspector.UsernameTokenBehaviorExtensionElement, SecurityMessageInspector, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
  </behaviorExtensions>
</extensions>
<behaviors>
  <endpointBehaviors>
    <behavior name="UsernameTokenBehavior">
      <usernameToken username="Administrator" password="emc" passwordType="PasswordText"/>
    </behavior>
  </endpointBehaviors>
</behaviors>

Right away, understand that this is a sample—you will likely want to take a more secure approach to managing user credentials. Certainly, if you employed this approach, you should employ a secure transport (i.e. SSL). Please see the sample solution’s README file for more details if a more programmatic approach is desired (with or without SSL).

There are other ways to "wire" WS-Security header creation and passing into applications. This particular sample takes a more subtle (less in-your-face) approach to accomplish this concern. Regardless, implicit SOAP headers in a contract require extra coding on the part of a consumer.

-Craig
http://craigrandall.net/
@craigsmusings