Overview
With the release of Documentum D6, developers now have access to a new programming
interface to the Content Server: DFS Web services. Regardless of whether you
are a veteran of Web services programming or new to the technology, you will no
doubt find DFS powerful and easy to use. Developers who are already familiar
with Web services will appreciate having all of the usual benefits of a
service-based architecture: flexibility, scalability, reusability,
“one-stop shopping”, and so forth. Newcomers to web services,
with little ramp-up, should find this model at least as natural to
develop with as conventional DFC programming. The purpose of this
article is to provide a practical example-based introduction to client
programming with the out-of-the-box DFS services.
The first section of the article gives an overview of Web services in
general and some tips for making sure your DFS environment is properly
configured and running. The latter section of the article contains an
example-based introduction to client-side programming with the DFS Web
services ObjectService using the java client library.
DFS Web Services Introduction
Why Web Services?
Over the last few years ‘Web services’ have matured beyond buzzword status
and gained wide acceptance in enterprise architectures mainly because they
ease technology integration headaches. The goal of service oriented
architectures (SOAs) is to provide a one-stop-shop for business service
clients by wrapping the proprietary protocols of heterogeneous systems
into easy-to-use web services that all speak the same language, namely
SOAP (Simple Object Access Protocol) over HTTP. The SOA model allows
for rapid client application development because software designers and
developers don’t have to ramp-up on the specifics of lower-level proprietary
technologies.
Because of their growing position in the corporate enterprise,
EMC/Documentum made the logical decision to expose almost all platform
functionalities in out-of-the-box Web services.
The DFS Web Services
There are four “core” DFS services that provide access to content repository
and other fundamental Documentum platform features:
- Object Service – This is the main service for dealing with the
properties and content associated with repository objects. This service makes
heavy use of the DFS data model for passing packages of objects between the
server and client (see The DFS Data Model). - Query Service – Provides functionality for running queries against
a content repository and managing their results efficiently. - Schema Service – Exposes access to the repository object model
(or data dictionary). For example, getting the descriptions of object types
and their attributes. - Version Control Service – This service is used for checking out and
checking in objects and otherwise accessing and manipulating object
version trees.
The DFS Data Model
The DFS architecture includes an object hierarchy specifically designed to
efficiently handle the transfer of multiple repository objects among clients and
services. For example, when a query result set is returned to a client application
by a DFS service, the objects are packaged using this data model. Likewise, when
a client creates or modifies repository objects, the new or modified objects are
packaged using this data model prior to transfer to the server. The data model
also has features which allow it to be tuned so that packages contain only the
desired content and metadata.
The DFS data model is comprised of the following elements:
- DataPackage – This is the top-level container that organizes objects
within the data model. It contains zero or more DataObjects. - DataObject – This is the primary means of representing a single repository
object in a service request or response. A DataObject object typically represents
a single persistent object in a Documentum repository. The DataObject type is itself
a container for one or more of the following object types: - ObjectIdentitySet – This object type is the container for
ObjectIdentity objects. - ObjectIdentity – This object type contains uniquely-identifying information
about a repository object. Identification criteria can be expressed in three ways:
an object id (r_object_id), an object path, or a DQL qualification (the DQL WHERE clause). Each ObjectIdentity should match
a single object in the repository. Use the QueryService when searching for multple objects. - PropertySet – Contains the property/attribute values for an object, each
stored in a separate Property object. By default a PropertySet contains all the
properties of an object, but this can be reduced via a PropertyProfile filter. - Property – Represents a single property of an object (subject, title, etc.).
- Content – Represents file content associated with a DataObject.
DataObjects may contain zero or more instances of the Content object. The
first instance is always the primary content for the object. The type and
extent of content contained in a Content object instance can be controlled
by an optional ContentProfile. - Permissions – The multiple Permissions objects associated with a DataObject
describe all of the current user’s permissions on the DataObject. These objects are
read-only and cannot be used to modify permissions (rather use ACL objects via the
ObjectService). By default DatObjects are not returned with any Permissions from
the ObjectService. To get an object’s permissions, you must populate and associate
a PermisionProfile with the ObjectService request. - Relationship – This type and its subtypes represent the relationships
(dm_relation objects) for a DataObject. This type is fairly complex and its
functionality goes beyond the scope of this article.
ServiceFactory, ServiceContext, and RepositoryIdentity
The ServiceFactory is used to construct service instance. As parameters,
the ServiceFactory requires the class name of the service to create, the URL to
the service WSDL, and a populated ServiceContext.
The ServiceContext object provides stateful information such as repository
credentials to a service instance. Once created, ServiceContext instances can be
optionally registered with the ContextRegistryService which allows them to be shared
among multiple service instances
A ServiceContext instance must be associated with at least one RepositoryIdentity
instance. A RepositoryIdentity instance stores a repository name and its login
credentials for the service
Web Service Concepts from a DFS Perspective
SOAP (Simple Object Access Protocol) – This is an XML protocol designed
specifically for interacting with Web services over HTTP. DFS uses SOAP for all
client-server communications. Developer’s may choose to implement DFS clients
using SOAP directly, but are more likely to use the provided Java Client Library
which abstracts the SOAP request and responses into a friendlier object-oriented
programming model. In fact, developers using the Java Client Library need not be
versed in SOAP technology at all (and might never even know they’re using it).
WSDL (Web Services Description Language) – WSDL is an XML-based protocol
that service consumers can use to ‘discover’ any SOAP-based service’s available requests
and responses. It can be said that WSDL provides the service-to-client ‘contract’ for
a given service.
DFS publishes WSDL for the core services by default on port 9080 of the content
server. For example, on a content server machine named “D6prepp”, the WSDLs for the
four “core” DFS services can be found at the following URLs:
- https://D6prepp:9080/services/core/ObjectService?wsdl
- https://D6prepp:9080/services/core/QueryService?wsdl
- https://D6prepp:9080/services/core/SchemaService?wsdl
- https://D6prepp:9080/services/core/VersionControlService?wsdl
Indeed, by directing an open source SOAP utility at the core service URL,
we can browse through all of the information published in the DFS WSDLs as shown
below:
Figure 1:
soapUI Connected to the DFS WSDL
Attaching a third-party client tool to the DFS WSDL is a good way to verify that
your Web services are running and available. WSDL requests will also display as XML
in a web browser that is directed to the WSDL’s URL.
Setting Up a DFS Development Environment
Setting up a DFS development environment is fairly simple. The services install
by default with the content server. For your DFS client project, you’ll just need to
reference a few DFS client jar files.
Content Server Machine
No special installation is needed to deploy DFS on the content server.
DFS is installed as part of the base Content Server installation. By default,
the WSDLs are published on port 9080.
Client Development Environment
To setup a DFS client development environment on a machine other than the
content server, you need simply unzip the DFS installation package somewhere
on your hard drive and add all of the jar files listed in "<unzip root>
client-jars.txt” to your project java build path
Note: Ensure that other versions of the jaxws-related jar files are not listed in
your build path. DFS is only compatible with the versions of the files that ship
with it.
Client Programming with the DFS Java Client Library
Overview
The following code example shows the end-to-end process of connecting and
authenticating with DFS and then retrieving a result set of objects (DataPackages)
from the repository and displaying each object’s properties. This example shows
the complete end-to-end process of DFS service instantiation, authentication,
and interaction in one simple class – an example the author was unable to
find anywhere else.
Note: Although not available at the time of writing, a .Net client library is forthcoming from EMC.
This code queries the repository for all documents that have an object name
that starts with ‘DFS’ and then outputs their subject and creation date attribute
values.
package com.ArgonDigitalgroup.sopapilla;
import java.util.Iterator;
import com.emc.documentum.fs.datamodel.core.*;
import com.emc.documentum.fs.rt.ServiceInvocationException;
import com.emc.documentum.fs.rt.context.ContextFactory;
import com.emc.documentum.fs.rt.context.IServiceContext;
import com.emc.documentum.fs.rt.context.ServiceFactory;
import com.emc.documentum.fs.services.core.client.IObjectService;
public class SimpleClient {
public static void main(String [] args) throws ServiceInvocationException {
// Set important variables
String repositoryName = "d6_test1";
String userName = "userName";
String userPassword = "passWord";
try
{
// Get a ContextFactory so that we can
// create a ServiceContext
System.out.println("Creating ContextFactory");
ContextFactory contextFactory = ContextFactory.getInstance();
// Use the ContextFactory to create
// a ServiceContext for a service
System.out.println("Creating ServiceContext");
IServiceContext serviceContext = contextFactory.newContext();
// Create a RepositoryIdentity to store the
// repository credentials on the ServiceContext
System.out.println("Creating RepositoryIdentity");
RepositoryIdentity repoId = new RepositoryIdentity();
// Populate the repository credentials
System.out.println("Populating RepositoryIdentity");
repoId.setRepositoryName(repositoryName);
repoId.setUserName(userName);
repoId.setPassword(userPassword);
// Add the populated RepositoryIdentity to the ServiceContext
System.out.println("Adding RepositoryIdentity to ServiceContext");
serviceContext.addIdentity(repoId);
// Get an ObjectService from the ServiceFactory
IObjectService objectService = ServiceFactory.getInstance().getRemoteService(IObjectService.class,
serviceContext,
"core",
"https://d6prepp:9080/services");
// Query the repository for objects matching our criteria
// Build object qualifications using the criteria expressed in DQL syntax
Qualification qualification1 = new Qualification("dm_document where object_name = 'DFS Test 1'");
Qualification qualification2 = new Qualification("dm_document where object_name = 'DFS Test 2'");
// Create ObjectIdentity objects with our DQL qualifications and repository name
ObjectIdentity targetObjectIdentity1 = new ObjectIdentity(qualification1, repositoryName);
ObjectIdentity targetObjectIdentity2 = new ObjectIdentity(qualification2, repositoryName);
// Create an ObjectIdentitySet that contains the ObjectIdentity objects
ObjectIdentitySet objIdSet = new ObjectIdentitySet();
objIdSet.addIdentity(targetObjectIdentity1);
objIdSet.addIdentity(targetObjectIdentity2);
// Create an empty OperationOptions object
OperationOptions opts = new OperationOptions();
// Create an empty DataPackage to hold the resulting objects
DataPackage dataPackage = new DataPackage();
// Create an empty PropertySet to hold the resulting object properties
PropertySet properties = new PropertySet();
// Execute ObjectService.get() which hopefully
// returns a populated DataPackage
dataPackage = objectService.get(objIdSet, opts);
// Iterate through the DataObject objects in the result DataPackage
Iterator iterator = dataPackage.getDataObjects().iterator();
while(iterator.hasNext()){
System.out.println("######## GETTING OBJECT PROPERTIES #########");
DataObject thisDataObject = (DataObject) iterator.next();
// Write out some properties for this DataObject
properties = thisDataObject.getProperties();
System.out.println("Object Name: " + properties.get("object_name").getValueAsString());
System.out.println("Creation Date: " + properties.get("r_creation_date").getValueAsString());
}
}
catch (Exception e)
{
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
Example Code Output
This is the output generated by the above example java program. Two documents were found
and returned based on the specified criteria:
Creating ContextFactory
Creating ServiceContext
Creating RepositoryIdentity
Populating RepositoryIdentity
Adding RepositoryIdentity to ServiceContext
######## GETTING OBJECT PROPERTIES #########
Object Name: DFS Test 1
Creation Date: Mon Aug 06 18:56:26 CDT 2007
######## GETTING OBJECT PROPERTIES #########
Object Name: DFS Test 2
Creation Date: Mon Aug 06 18:58:07 CDT 2007
3.4 Explanation of the Example Code
Step 1 – Create and Populate a Service Context
The first part of the example program creates an instance of a ServiceContext object and
populates it with the name and credentials of the target repository. A ServiceContext is always
necessary when creating a DFS service because it stores all of the service’s stateful information
like repository credentials. ServiceContext objects are created via a ContextFactory:
// Get a ContextFactory so that we can
// create a ServiceContext
ContextFactory contextFactory = ContextFactory.getInstance();
// Use the ContextFactory to create
// a ServiceContext for a service
IServiceContext serviceContext = contextFactory.newContext();
Once the ServiceContext instance has been created, it can be populated with the repository
credentials that will be used to authenticate to the repository. The client library provides
the RepositoryIdentity object for storing repository credentials. A ServiceContext may be
associated with multiple RepositoryIdentity objects, but only one per repository. Here we set
the repository name, user name, and password:
// Create a RepositoryIdentity to store the
// repository credentials on the ServiceContext
RepositoryIdentity repoId = new RepositoryIdentity();
// Populate the repository credentials
repoId.setRepositoryName(repositoryName);
repoId.setUserName(userName);
repoId.setPassword(userPassword);
Now that the RepositoryIdentity has been populated, it is added to the ServiceContext:
// Add the populated RepositoryIdentity to the ServiceContext
serviceContext.addIdentity(repoId);
Step 2 – Instantiate a DFS Service
Now that the ServiceContext has been populated, it can be passed to the
ServiceFactory for creation of a service instance, in this case an ObjectService.
These are the arguments to the getRemoteService() method:
- Service Class – This is the class of the type of service we
want to create. In this case, ObjectService. - Service Context – This is the ServiceContext instance we created and
populated with repository credentials in Part 1. - Service Module – This is the part of the service URL that appears
immediately after “/services”, in this case “core”. There is currently only
one DFS service module. - Context RootThis is the root of the service URL;
everything before the service module name, “/core”.
// Get an ObjectService from the ServiceFactory
IObjectService objectService =
ServiceFactory.getInstance().getRemoteService(IObjectService.class,
serviceContext,
"core",
"https://d6prepp:9080/services");
Step 3 – Get Objects from the Repository
The ObjectService instance is now instantiated and can be used to get repository objects.
Identifying the repository objects requires a Qualification to define the identification criteria.
In this case, we will use DQL qualifications to find documents having known object names.
// Build object Qualifications using the criteria expressed in DQL syntax
// Note that each qualification should match only one object in the repository!
Qualification qualification1 = new Qualification("dm_document where object_name = 'DFS Test 1'");
Qualification qualification2 = new Qualification("dm_document where object_name = 'DFS Test 2'");
In order to pass the qualifications to the ObjectService, we must first associate them with
ObjectIdentity instances and then add ObjectIdentity instances to the ObjectIdentitySet (which is the first
argument to the ObjectService’s get() command):
// Create ObjectIdentity objects with our DQL qualifications and repository name
ObjectIdentity targetObjectIdentity1 = new ObjectIdentity(qualification1, repositoryName);
ObjectIdentity targetObjectIdentity2 = new ObjectIdentity(qualification2, repositoryName);
// Create an ObjectIdentitySet that contains the ObjectIdentity objects
ObjectIdentitySet objIdSet = new ObjectIdentitySet();
objIdSet.addIdentity(targetObjectIdentity1);
objIdSet.addIdentity(targetObjectIdentity2);
Now we’ll create three empty object instances in preparation for the ObjectService get() call:
- OperationOptionsOperationOptions – This object contains any options, profiles,
or filters used to control the behavior of the ObjectService. We’ll just take the defaults
and thus pass an empty instance of this object since it is a required argument. - DataPackageDataPackage – This is the empty object collection that will be used
to contain the results of the ObjectService get() call. - PropertySetPropertySet – This is an empty properties container that will be assigned
to each object in the result set so that we can access the properties for each object.
// Create an empty OperationOptions object
OperationOptions opts = new OperationOptions();
// Create an empty DataPackage to hold the resulting objects
DataPackage dataPackage = new DataPackage();
// Create an empty PropertySet to hold the resulting object properties
PropertySet properties = new PropertySet();
Finally, it’s time to execute the ObjectService get() call:
// Execute ObjectService.get() which hopefully
// returns a populated DataPackage
dataPackage = objectService.get(objIdSet, opts);
Step 4 – Iterate Over the Results
The call to the ObjectService returns a DataPackage which must be iterated through to access each contained
DataObject. Each DataObject contains a PropertSet which an be used to access individual property values,
in this case by using the getValueAsString() method. Here we iterate through the returned DataObjects
and write out the object name and creation date for each:
// Iterate through the DataObject objects in the result DataPackage
Iterator iterator = dataPackage.getDataObjects().iterator();
while(iterator.hasNext()){
System.out.println("######## GETTING OBJECT PROPERTIES #########");
DataObject thisDataObject = (DataObject) iterator.next();
// Write out some properties for this DataObject
properties = thisDataObject.getProperties();
System.out.println("Object Name: " + properties.get("object_name").getValueAsString());
System.out.println("Creation Date: " + properties.get("r_creation_date").getValueAsString());
}
Conclusion
In this article, we’ve taken an introductory look at Web services and client programming with DFS.
Now that you’ve got the basics under your belt, try exploring DFS on your own. For more information
about developing with DFS, please see the EMC document, DFS Development Guide.
The code example in this article was developed and tested by the author.