Updated DFS Developer Guide on EDN

My colleague and technical writer, Joe Ferrie, just uploaded the second edition of the DFS 6.5 Development Guide to the EMC Developer Network (EDN) here. The information here is applicable to DFS 6.5 and DFS 6.5 SP1.

(I’m still working on this, but at least this content is available via EDN.)

Some highlights of this edition are as follows:

  • A new content transfer chapter, with practical information about uploading and downloading content using Base64 and MTOM, as well as using UrlContent to get content from Accelerated Content Services (ACS). Examples are provided for WSDL-based consumers, as well as productivity layer consumers. Unified Client Facilities (UCF) now gets its own chapter.
  • Details of Java productivity layer dependencies so that you can more easily understand what JAR files you need on your ClassPath given for your particular development scenario
  • A new chapter comparing DFC and DFS, which is expected to be useful for DFC developers who are ramping up with DFS and want to compare the general approach and specific operations
  • A revised explanation of the sometimes confusing restrictions on retrieving deep relationships in DataObjects.

Thanks, Joe!

Dynamic value assistance using DFS

Continuing my collaboration with John Sweeney (or his collaboration with me), today we’ll examine the DFS Schema service and do so in .NET as a direct-to-WSDL consumer. This tutorial builds upon a previous tutorial; so, if you’re not already familiar with it, please read this post and play with this sample. You’ll also need and be familiar with using Documentum Administrator (DA) and Documentum Composer 6.5 SP1.

Thanks to John for doing the legwork in this tutorial and for allowing me to share it all with you here. (Although, I’d really like to see John blog about this directly; he has a lot to share.)

Not only can the DFS Schema service return value-assist values, it also supports dynamic value assistance, including the handling of dependencies (e.g. one attribute value-assist dynamically built based on the value of another attribute).

This tutorial demonstrates how to use the DFS Schema service to perform dynamic value-assist tasks. Knowledge of Documentum Administrator, Documentum Composer 6.5 SP1, DFS 6.5 SP1, Visual Studio 2008 and WSDL-based consumption of DFS with .NET is assumed.

Before we begin, we’ll need to create users, groups and an object type to use as test data.

Step 1 – Create users and groups (to be performed with Documentum Administrator)

1. Create three groups named writers, reviewers and publishers in your repository.

2. Create six users. Name them appropriately, but for this tutorial, they will be referred to as User1–6. Put the users in the new groups, as specified in the following chart:

  Group   Members
  writers   User1, User2
  reviewers   User3, User4
  publishers   User5, User6

Step 2 – Create a new object type (to be performed with Documentum Composer 6.5 SP1)

1. Open Documentum Composer and create a new project.

2. Add a new type called kb_document with a supertype of dm_document.

3. Add a new attribute to the type named document_state (as shown in the following image).

4. Expand the document_state attribute and select Value mapping.

5. In the Conditional Assistance section, click New…

6. Add Draft,Review,Publish as values of Fixed List (as shown in the following image) and click OK.

Your Conditional Assistance section should now look like this:

7. Add another new attribute to the type named assigned_to as shown in the following image.

8. Expand the assigned_to attribute and select Value mapping.

9. In the Conditional Assistance section, click New…

10. Add the following query:

select users_names from dm_group where group_name = 'writers'

Set the other fields (as shown in the following image).

The value-assist list will be populated with users who are members of the writers group by default, when document_state does not have a value of Review or Publish.

11. In the Conditional Assistance section, click New…

12. Add the following query:

select users_names from dm_group where group_name = 'reviewers'

Set the other fields (as shown in the following image).

The value in the Expression field will cause the value-assist list to be populated with users who are members of the reviewers group, but only when document_state has a value of "Review."

13. In the Conditional Assistance section, click New…

14. Add the following query:

select users_names from dm_group where group_name = 'publishers'

Set the other fields (as shown in the following image).

The value in the Expression field will cause the value-assist list to be populated with users who are members of the publishers group, but only when document_state has a value of "Publish."

Your Conditional Assistance section should now look like the following:

15. Install the Documentum project to your repository.

Step 3 – Retrieve the value-assist list (using C# and the .NET Framework 3.0 or higher)

Note: the ServiceContextHeader class used in this tutorial comes from this prior sample, as noted above.

The following code snippet will return group members in the ValueAssist Values list. A PropertySet is created for document_state. The value of document_state will alter the dynamic-assist values for the assigned_to property.

// Get a reference to the DFS Schema service
SchemaServicePortClient schemaService =
    new SchemaServicePortClient("SchemaServicePort"); 

StringProperty sp = new StringProperty();
sp.name = "document_state";
// assigned_to will have different dynamic-assist values, 
// depending upon the value set here
sp.Value = "Publish"; // or Review or Draft

// Create a PropertySet for document_state, which assigned_to depends upon
PropertySet ps = new PropertySet();
ps.Properties = new Property[1];
ps.Properties[0] = sp; 

using (OperationContextScope scope =
           new OperationContextScope(schemaService.InnerChannel))
{
    // Add the ServiceContextHeader info to the outgoing request
    OperationContext.Current.OutgoingMessageHeaders.Add(
        new ServiceContextHeader(repositoryName, userName, password)); 

    // Get the values for the assigned_to property
    ValueAssist valueAssist = schemaService.getDynamicAssistValues(
        repositoryName, "DEFAULT", "kb_document", "assigned_to", ps, null); 

    // Display the values
    if (valueAssist != null || valueAssist.Values != null)
    {
        foreach (string s in valueAssist.Values)
            MessageBox.Show(s, "Value Assist Value");
    }
}

The following table shows the expected results:

  document_state value   assigned_to values
  Draft   User1, User2
  Review   User3, User4
  Publish   User5, User6

It is worth noting that if you execute SchemaService.getTypeInfo for kb_document, and examine the assigned_to property, you can determine that the property has dependencies. The returned TypeInfo.PropertyInfos[n].Dependencies will contain an entry for document_state. You can check this beforehand as a way of determining if setting one property (document_state) should trigger a refresh of the value list (calling getDynamicAssistValues again). This is very useful for a picklist property page where the values of one list depend on the value of another.

Here endeth the lesson… :-)

Update 2/6/2009: In order for you to spend more time understanding dynamic value assistance with DFS, I’ve uploaded a Visual Studio project to EDN here. Enjoy.

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.

Consuming DFS WSDL in Visual Studio

Earlier today I posted to EDN a new sample project that demonstrates consuming DFS web services directly from their WSDL in a Visual Studio 2008 environment. Right up front, I want to credit my colleague John Sweeney, who really should start blogging, with providing the original work contained in this sample. My subsequent contribution was mostly UI/UX-related. Thanks, John!

After downloading and extracting the sample to your local development machine, open the solution in Visual Studio 2008. The default code assumes that you’re running the DFS web services locally (i.e. IP 127.0.0.1 and port 8080). You should, of course, modify the IP address and port to match your particular development environment.

Main sample UI

The main intent of this sample is to demonstrate various ways to authenticate via DFS. It’s worth noting that this sample doesn’t cover single sign-on (SSO) support in DFS, nor does it cover WS-Security header-based authentication. Good demos to be certain…

As the user interface (UI) above tries to clearly state upfront, content repository (aka Docbase) name cannot be null or empty, regardless of authentication approach. Given authentication via ServiceContext header, user name and password cannot be null or empty. Given authentication via ContextRegistry runtime service, when token is null or empty, user name and password cannot be null or empty, and when user name and password are both null or empty, token cannot be null or empty.

One thing that is still a bit obscure in the current UI is that authentication via DFS can be a two-step or one-step process, depending on the situation. If you choose "Via ContextRegistry…" and a Context Registry token isn’t provided to DFS, two runtime service invocations will be made. In all other cases, there is one service invocation.

Assuming that your connection details are valid given your authentication approach, this sample will (a) retrieve a repository list, (b) retrieve cabinets, and (c) retrieve content (i.e. transfer MenuItemTemplate.ini to your desktop).

A previous direct-to-WSDL DFS consumer sample I posted, targeted .NET 3.0 and WCF "v1." This sample targets .NET 3.5 and WCF "v2" instead, along with support for Silverlight 2. It accomplishes this change via LINQ to XML (e.g. leverages System.Xml.Linq.XDocument instead of System.Xml.XmlDocument).

If you need to target this sample at .NET 3.0, in addition to adjusting the project build properties, removing the project reference to the System.Xml.Linq.dll assembly, and removing the System.Xml.Linq namespace declaration in ServiceContextHeader.cs, you’ll need to change the relevant code within OnWriteHeaderContents. For example:

  XDocument doc = null;
  using (StringWriter sw = new StringWriter(CultureInfo.CurrentCulture))
  {
      XmlSerializer xs = new XmlSerializer(typeof(ServiceContext));
      xs.Serialize(sw, serviceContext);
      doc = XDocument.Load(new StringReader(sw.ToString()));
  }

would become:

  XmlDocument doc = null;
  using (StringWriter sw = new StringWriter(CultureInfo.CurrentCulture))
  {
      XmlSerializer xs = new XmlSerializer(typeof(ServiceContext));
      xs.Serialize(sw, serviceContext);
      doc = XmlDocument.LoadXml(new StringReader(sw.ToString()));
  }

etc.

Although, it’s not currently employed in the sample, there is also code to add a ContentTransferProfile to the ServiceContext header.

As always, consult the DFS Developer Guide for complete details on everything that may be passed in the ServiceContext header.

I do expect that this sample will be incorporated into the DFS SDK and DFS technical publications content; however, I wanted to get this into your hands sooner rather than later. Cheers…

How do you say CMIS?

While I’ve been saying “see em eye ess” for CMIS up to this point, I’ve discovered that others are pronouncing CMIS “see miss” instead.

  • One’s pronunciation of SQL has been offered in support of pronouncing CMIS (i.e. “see qul” versus “ess queue el”). (I say “see qul;” so perhaps I should be saying “see miss.”)
  • Using the shortest pronunciation possible as measured by number of syllables has been offered in support of “see miss” (i.e. two versus four syllables).
  • Conjunctives like CMIS-enabled, CMIS-ready, etc. has been offered in consideration of “see miss.” On other hand, “see em eye ess” may be better in conversation with someone not yet familiar with the standards effort.
  • One’s locale and native language has also been offered in support of “see em eye ess” (e.g. easier to understand in conversation with non-native speakers by avoiding use of two common English words, which can be hard to parse in the middle of a sentence for someone not already used to the meaning).
  • Pop culture features shows like CSI, SVU and NCIS. Initialism applies to these shows (e.g. you don’t hear “see sci” or “en sis”). Therefore, …

So, how do you say CMIS? :-)

P.S. Yes, this is completely unimportant in every technical aspect where the standards effort around CMIS is concerned. Nevertheless, consistent identity, including pronunciation matters, and the standards effort is just getting started…

P.P.S. Happy Thanksgiving!