Exposing Documentum Object data as a JSON Object
Overview
I wrote several articles on Ajax related technologies (refer to the Articles for links to those articles). Since I do a lot of consulting on the EMC Documentum platform, I thought it would be interesting to write an interface that exposes repository object metadata as a JSON object.
By using JSON and Ajax technologies, object metadata can be exposed in a non-proprietary format that is highly efficient and has minimal overhead. This provides for a very flexible and lightweight interface.
It allows you to expose object metadata without requiring the application consuming the data to have any knowledge or reference to the EMC Documentum libraries (e.g. DFC or DFS Client). The solution is RESTFUL in nature, thus making it very lightweight and re-usable.
Since JSON is more efficient than XML (in terms of processing and parsing), it has much less overhead than SOAP based web services. In addition, any language that supports JSON (I.e. Java, JavaScript, or C#) can be used.
There are many use cases for this type of interface.
For example, you can build an Ajax style popup control that displays object metadata when a user hovers over an object in a list. The service can be called asynchronously to retrieve the metadata on demand through simple JavaScript scripting.
Another example is to use the service to expose object metadata in order to integrate enterprise applications (without requiring any reference to the EMC Documentum libraries).
This solution leverages the JSON for Java library to build the JSON object on the server-side. For information on this library, please refer to JSON for Java.
The sample demonstration solution is a pure HTML and JavaScript solution to demonstrate the lightweight nature of the data service. The web page contains a single INPUT text field for the user to enter an object id. Upon losing focus (via the onBlur event) an asynchronous call is made to the server to retrieve the object metadata. The JavaScript eval function is then used to convert the string (JSON object) to a JavaScript object. The object metadata is then completely accessible in JavaScript functions.
Technical Objectives
The primary objective is to build a lightweight solution to serialize repository objects to JSON objects. With this goal in mind, we will:
- Build a server-side interface (Servlet) that returns a JSON object for the object specified in the objectId argument
- Use only basic DFC classes, methods, and operations since many enterprise systems are still based on the EMC Documentum 5.x platform (thus enabling the service to be used more broadly)
- Build a sample HTML web page that demonstrates how the asynchronous call is made to the server to retrieve the JSON object and how to process the server response in JavaScript scripting
Building the Repository Service
The first step is to build the data provider or class to access the EMC Documentum repository. The Repository Service is responsible for retrieving the object from a EMC Documentum repository. The service uses standard DFC calls to access the repository and thus requires minimal explanation. The code for the RepositoryService class with detailed inline comments is shown below:
/**
* The Class RepositoryService provides basic repository functionality, such as fetching objects from the repository.
*/
public class RepositoryService {
/** The repository name. */
private String m_repositoryName = null;
/** The user name. */
private String m_userName = null;
/** The user password. */
private String m_userPassword = null;
/** The client. */
private IDfClient m_client = null;
/** The session mgr. */
private IDfSessionManager m_sessionMgr = null;
/** The clientX. */
private DfClientX m_clientX = null;
/**
* Instantiates a new repository service.
*
* @param repositoryName the repository name
* @param userName the user name
* @param userPassword the user password
*/
public RepositoryService(String repositoryName, String userName, String userPassword) {
this.m_repositoryName = repositoryName;
this.m_userName = userName;
this.m_userPassword = userPassword;
}
/**
* Gets the clientx.
*
* @return the clientx
*/
public DfClientX getClientX() {
if (this.m_clientX == null) {
this.m_clientX = new DfClientX();
}
return this.m_clientX;
}
/**
* Gets the client.
*
* @return the client
*/
public IDfClient getClient() {
if (this.m_client == null) {
DfClientX clientX = this.getClientX();
if (clientX != null) {
try {
this.m_client = clientX.getLocalClient();
} catch (DfException e) {
e.printStackTrace();
}
}
}
return this.m_client;
}
/**
* Gets the session manager.
*
* @return the session manager
*/
public IDfSessionManager getSessionManager() {
if (this.m_sessionMgr == null) {
IDfClient client = this.getClient();
DfClientX clientX = this.getClientX();
if (client != null) {
this.m_sessionMgr = client.newSessionManager();
IDfLoginInfo identity = clientX.getLoginInfo();
identity.setUser(this.getUserName());
identity.setPassword(this.getUserPassword());
try {
this.m_sessionMgr.setIdentity(this.getRepositoryName(), identity);
} catch (DfServiceException e) {
e.printStackTrace();
}
}
}
return this.m_sessionMgr;
}
/**
* Gets the user name.
*
* @return the user name
*/
private String getUserName() {
return this.m_userName;
}
/**
* Gets the user password.
*
* @return the user password
*/
private String getUserPassword() {
return this.m_userPassword;
}
/**
* Gets the repository name.
*
* @return the repository name
*/
private String getRepositoryName() {
return this.m_repositoryName;
}
/**
* Checks if the RepositoryService is valid (i.e. connection can be made to repository)
*
* @return True if a valid credentials were specified and connection can be made to the repository
*/
public boolean isValid () {
boolean isValid = false;
IDfSessionManager sessionMgr = null;
IDfSession session = null;
try {
// Get session manager
sessionMgr = this.getSessionManager();
if (sessionMgr == null) {
return isValid;
}
// Get repository session
session = sessionMgr.getSession(this.getRepositoryName());
// If session is not null and is connected
if (session != null && session.isConnected()) {
isValid = true;
}
} catch (DfIdentityException e) {
e.printStackTrace();
} catch (DfAuthenticationException e) {
e.printStackTrace();
} catch (DfPrincipalException e) {
e.printStackTrace();
} catch (DfServiceException e) {
e.printStackTrace();
} catch (DfException e) {
e.printStackTrace();
}
finally {
if (session != null && sessionMgr != null) {
sessionMgr.release(session);
}
}
return isValid;
}
/**
* Gets the object.
*
* @param objectId the id of object to retrieve
*
* @return the object
*/
public IDfTypedObject getObject(IDfId objectId) {
IDfTypedObject obj = null;
IDfSessionManager sessionMgr = null;
IDfSession session = null;
try {
// Get session manager
sessionMgr = this.getSessionManager();
if (sessionMgr == null) {
return obj;
}
// Get repository session
session = sessionMgr.getSession(this.getRepositoryName());
// If session is null return null;
if (session != null) {
// Fetch object
obj = session.getObject(objectId);
}
} catch (DfIdentityException e) {
e.printStackTrace();
} catch (DfAuthenticationException e) {
e.printStackTrace();
} catch (DfPrincipalException e) {
e.printStackTrace();
} catch (DfServiceException e) {
e.printStackTrace();
} catch (DfException e) {
e.printStackTrace();
}
finally {
if (session != null && sessionMgr != null) {
sessionMgr.release(session);
}
}
return obj;
}
/**
* Gets the object.
*
* @param objectId the id of object to retrieve
*
* @return the object
*/
public IDfTypedObject getObject(String objectId) {
IDfClientX clientX = this.getClientX();
IDfId id = clientX.getId(objectId);
return this.getObject(id);
}
}
The next step is to build the serializer, TypedObjectJsonSerializer, to serialize the IDfTypedObject or IDfPeristentObject object to a JSON object. The serializer needs to support single and repeating attributes, as well as the various Documentum data types (e.g. String, Boolean, Double, Integer, ID, Date/Time). The code is fairly straightforward, repeating attributes are stored as a JSONArray. The serializer checks the IDfAttr for each attribute, to handle the different Documentum data types correctly. As mentioned, the JSONObject is built using the JSON for Java library. The TypedObjectJsonSerializer class with detailed inline comments is shown below:
/*
* The Class TypedObjectJsonSerializer serializes a EMC Documentum IDfTypedObject or IDfPersistentObject to a JSON object
*/
public class TypedObjectJsonSerializer {
/**
* Instantiates a new typed object json serializer.
*/
public TypedObjectJsonSerializer() {
}
public String serializeToString(IDfTypedObject obj) {
JSONObject jsonObj = this.serialize(obj);
if (jsonObj != null) {
return jsonObj.toString();
}
return "";
}
public JSONObject serialize(IDfPersistentObject obj) {
IDfTypedObject typedObj = (IDfTypedObject) obj;
JSONObject json = this.serialize(typedObj);
try {
// If json object returned
if (json != null) {
json.put("r_object_id", obj.getObjectId().toString());
json.put("r_object_type", obj.getType().getName());
} // if json not null
} catch (DfException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return json;
}
/**
* Serialize IDfTypedObject to JSON object.
*
* @param obj Object to serialize
*
* @return JSON object
*/
public JSONObject serialize(IDfTypedObject obj) {
// Create JSON object
JSONObject json = new JSONObject();
JSONArray attributeNames = new JSONArray();
try {
// Get attribute count
int attrCount = obj.getAttrCount();
// Iterate through all attributes
for (int curIndex = 0; curIndex < attrCount; curIndex++) {
// Get current attribute
IDfAttr curAttr = obj.getAttr(curIndex);
// If attribute not null
if (curAttr != null) {
int curType = curAttr.getDataType();
String curName = curAttr.getName();
attributeNames.put(curName);
// Append attribute-type specific value to JSON object
switch (curType) {
// If boolean
case IDfAttr.DM_BOOLEAN:
// If attribute is repeating
if (curAttr.isRepeating()) {
JSONArray boolArray = this.getRepeatingBooleans(obj,curAttr);
json.put(curName, boolArray);
}
else {
boolean boolValue = this.getBooleanValue(obj,curAttr);
json.put(curName, boolValue);
}
break;
// If string
case IDfAttr.DM_STRING:
// If attribute is repeating
if (curAttr.isRepeating()) {
JSONArray strArray = this.getRepeatingStrings(obj,curAttr);
json.put(curName, strArray);
}
else {
String strValue = this.getStringValue(obj,curAttr);
json.put(curName, strValue);
}
break;
// If id
case IDfAttr.DM_ID:
// If attribute is repeating
if (curAttr.isRepeating()) {
JSONArray idArray = this.getRepeatingIds(obj,curAttr);
json.put(curName, idArray);
}
else {
String idValue = this.getIdValue(obj,curAttr);
json.put(curName, idValue);
}
break;
// If integer
case IDfAttr.DM_INTEGER:
// If attribute is repeating
if (curAttr.isRepeating()) {
JSONArray intArray = this.getRepeatingIntegers(obj,curAttr);
json.put(curName, intArray);
}
else {
int intValue = this.getIntegerValue(obj,curAttr);
json.put(curName, intValue);
}
break;
// If time
case IDfAttr.DM_TIME:
// If attribute is repeating
if (curAttr.isRepeating()) {
JSONArray timeArray = this.getRepeatingDates(obj,curAttr);
json.put(curName, timeArray);
}
else {
String dateValue = this.getDateValue(obj,curAttr);
json.put(curName, dateValue);
}
break;
// If double
case IDfAttr.DM_DOUBLE:
if (curAttr.isRepeating()) {
JSONArray dblArray = this.getRepeatingDoubles(obj,curAttr);
json.put(curName, dblArray);
}
else {
double dblValue = this.getDoubleValue(obj,curAttr);
json.put(curName, dblValue);
}
break;
case IDfAttr.DM_UNDEFINED:
json.put(curName, "");
} // switch
} // if
} // for loop
json.put("attributes", attributeNames);
} catch (DfException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
// Return JSON object
return json;
}
/**
* Gets the repeating doubles.
*
* @param obj the object
* @param attr the attr
*
* @return the repeating doubles
*
* @throws DfException the df exception
* @throws JSONException the JSON exception
*/
private JSONArray getRepeatingDoubles(IDfTypedObject obj, IDfAttr attr) throws DfException, JSONException {
JSONArray array = new JSONArray();
if (obj == null || attr == null) {
return array;
}
// Get attribute name
String attrName = attr.getName();
// Iterate through all attribute values
for (int index = 0; index < obj.getValueCount(attrName); index++) {
array.put(obj.getRepeatingDouble(attrName, index));
}
return array;
}
/**
* Gets the repeating dates.
*
* @param obj the object
* @param attr the attr
*
* @return the repeating dates
*
* @throws DfException the df exception
*/
private JSONArray getRepeatingDates(IDfTypedObject obj, IDfAttr attr) throws DfException {
JSONArray array = new JSONArray();
if (obj == null || attr == null) {
return array;
}
// Get attribute name
String attrName = attr.getName();
// Iterate through all attribute values
for (int index = 0; index < obj.getValueCount(attrName); index++) {
IDfTime time = obj.getRepeatingTime(attrName, index);
if (time != null && !time.isNullDate()) {
array.put(time.asString(IDfTime.DF_TIME_PATTERN18));
}
else {
array.put("");
}
}
return array;
}
/**
* Gets the repeating integers.
*
* @param obj the object
* @param attr the attr
*
* @return the repeating integers
*
* @throws DfException the df exception
*/
private JSONArray getRepeatingIntegers(IDfTypedObject obj, IDfAttr attr) throws DfException {
JSONArray array = new JSONArray();
if (obj == null || attr == null) {
return array;
}
// Get attribute name
String attrName = attr.getName();
// Iterate through all attribute values
for (int index = 0; index < obj.getValueCount(attrName); index++) {
array.put(obj.getRepeatingInt(attrName, index));
}
return array;
}
/**
* Gets the repeating ids.
*
* @param obj the object
* @param attr the attr
*
* @return the repeating ids
*
* @throws DfException the df exception
*/
private JSONArray getRepeatingIds(IDfTypedObject obj, IDfAttr attr) throws DfException {
JSONArray array = new JSONArray();
if (obj == null || attr == null) {
return array;
}
// Get attribute name
String attrName = attr.getName();
// Iterate through all attribute values
for (int index = 0; index < obj.getValueCount(attrName); index++) {
IDfId objId = obj.getRepeatingId(attrName, index);
String curValue = null;
if (objId != null) {
curValue = objId.toString();
}
array.put(curValue);
}
return array;
}
/**
* Gets the repeating booleans.
*
* @param obj the object
* @param attr the attr
*
* @return the repeating booleans
*
* @throws DfException the df exception
*/
private JSONArray getRepeatingBooleans(IDfTypedObject obj, IDfAttr attr) throws DfException {
JSONArray array = new JSONArray();
if (obj == null || attr == null) {
return array;
}
// Get attribute name
String attrName = attr.getName();
// Iterate through all attribute values
for (int index = 0; index < obj.getValueCount(attrName); index++) {
array.put(obj.getRepeatingBoolean(attrName, index));
}
return array;
}
/**
* Gets the repeating strings.
*
* @param obj the object
* @param attr the attr
*
* @return the repeating strings
*
* @throws DfException the df exception
*/
private JSONArray getRepeatingStrings(IDfTypedObject obj, IDfAttr attr) throws DfException {
JSONArray array = new JSONArray();
if (obj == null || attr == null) {
return array;
}
// Get attribute name
String attrName = attr.getName();
// Iterate through all attribute values
for (int index = 0; index < obj.getValueCount(attrName); index++) {
array.put(obj.getRepeatingString(attrName, index));
}
return array;
}
/**
* Gets the date value.
*
* @param obj the object
*
* @return the date value
* @throws DfException
*/
private String getDateValue(IDfTypedObject obj, IDfAttr attr) throws DfException {
if (obj == null || attr == null) {
return null;
}
// Get attribute name
String attrName = attr.getName();
IDfTime time = obj.getTime(attrName);
if (time != null && !time.isNullDate()) {
return time.asString(IDfTime.DF_TIME_PATTERN18);
}
else {
return null;
}
}
/**
* Gets the double value.
*
* @param obj the object
*
* @return the double value
* @throws DfException
*/
private double getDoubleValue(IDfTypedObject obj, IDfAttr attr) throws DfException {
if (obj == null || attr == null) {
return 0;
}
// Get attribute name
String attrName = attr.getName();
return obj.getDouble(attrName);
}
/**
* Gets the integer value.
*
* @param obj the object
*
* @return the integer value
* @throws DfException
*/
private int getIntegerValue(IDfTypedObject obj, IDfAttr attr) throws DfException {
if (obj == null || attr == null) {
return 0;
}
// Get attribute name
String attrName = attr.getName();
return obj.getInt(attrName);
}
/**
* Gets the id value.
*
* @param obj the object
*
* @return the id value
* @throws DfException
*/
private String getIdValue(IDfTypedObject obj, IDfAttr attr) throws DfException {
if (obj == null || attr == null) {
return null;
}
// Get attribute name
String attrName = attr.getName();
IDfId objId = obj.getId(attrName);
if (objId == null) {
return null;
}
return objId.toString();
}
/**
* Gets the string value.
*
* @param obj the object
*
* @return the string value
* @throws DfException
*/
private String getStringValue(IDfTypedObject obj, IDfAttr attr) throws DfException {
if (obj == null || attr == null) {
return null;
}
// Get attribute name
String attrName = attr.getName();
return obj.getString(attrName);
}
/**
* Gets the boolean value.
*
* @param obj the object
*
* @return the boolean value
* @throws DfException
*/
private boolean getBooleanValue(IDfTypedObject obj, IDfAttr attr) throws DfException {
if (obj == null || attr == null) {
return false;
}
// Get attribute name
String attrName = attr.getName();
return obj.getBoolean(attrName);
}
}
Building the Servlet
The Servlet is fairly straightforward. The key characteristics of the Servlet are:
- The Servlet supports both the POST and GET operations to provide maximum flexibility
- The Servlet uses the singleton pattern, to only instantiate a single instance of the RepositoryService class. Upon instantiation the singleton class can be used for each subsequent request. This avoids having to connect to the repository for each asynchronous request.
- The Servlet expects a single argument, objectId
- The Servlet returns either an empty string or the string representation of the object (JSON)
- The Servlet reads the repository credentials from the RepositoryName, RepositoryUser, and RepositoryPassword configuration parameters in the web.xml
The processRequest method does most of the work. The processRequest method simply retrieves the object from the repository using the RepositoryService. It then serializes the object to JSON using TypedObjectJsonSerializer serializer class. Finally, it converts the JSON object to a string using the toString method of the JSONObject instance.
The complete ObjectBrokerServlet class with detailed comments is shown below:
/**
* The Class ObjectBrokerServlet is a servlet that takes an object id and returns a JSON representation
* of the object stored in a Documentum repository.
*/
public class ObjectBrokerServlet extends HttpServlet{
public static RepositoryService m_repositorySvc = null;
/**
* Gets the repository service.
*
* @return the repository service
*/
public static RepositoryService getRepositoryService() {
if (this.m_repositorySvc == null) {
// Get repository credentials from web.xml
ServletContext context = this.getServletContext();
String repositoryName = context.getInitParameter("RepositoryName");
String userName = context.getInitParameter("RepositoryUser");
String userPassword = context.getInitParameter("RepositoryPassword");
this.m_repositorySvc = new RepositoryService(repositoryName, userName, userPassword);
}
return this.m_repositorySvc;
}
/* (non-Javadoc)
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Get json response
String jsonResponse = this.processRequest(req, resp);
// Write response
PrintWriter out = resp.getWriter();
out.println(jsonResponse);
}
/* (non-Javadoc)
* @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Get json response
String jsonResponse = this.processRequest(req, resp);
// Write response
PrintWriter out = resp.getWriter();
out.println(jsonResponse);
}
/**
* Process request.
*
* @param req the req
* @param resp the resp
*
* @return the string
*/
private String processRequest(HttpServletRequest req, HttpServletResponse resp) {
String jsonStr = "";
String objectId = req.getParameter("objectId");
// If object is null or not valid. Documentum object id's are 16 characters in length
if (objectId == null || objectId.length() != 16) {
return jsonStr;
}
// Initialize repository service
RepositoryService repositorySvc = ObjectBrokerServlet.getRepositoryService();
// If not initialized or connected return null
if (repositorySvc == null || !repositorySvc.isValid()) {
return jsonStr;
}
// Fetch object from repository
IDfPersistentObject persistObj = (IDfPersistentObject) repositorySvc.getObject(objectId);
// Serialize object to JSON
TypedObjectJsonSerializer serializer = new TypedObjectJsonSerializer();
JSONObject jsonObj = serializer.serialize(persistObj);
// Serialize JSON to string
if (jsonObj != null) {
jsonStr = jsonObj.toString();
}
return jsonStr;
}
}
Building the HTML Page
The final step is to demonstrate how to use the new service using standard HTML and JavaScript scripting.
The XmlHttpRequest, supported by most modern browsers including Internet Explorer and Firefox is used to make the asynchronous request.
An asynchronous request is made in the onIdChange event handler for the objectId INPUT control, which is fired when the objectId control loses focus (via onBlur event). The objectId INPUT text box value is passed with the asynchronous request to the Object Broker Servlet.
The server response is handled by the onServerResponse function. The server response is checked to make sure no error occurred and that the server response is complete. Within this function, two things occur.
First, the “object dump” tag is populated with the data (JSON string representation).
Second, the eval JavaScript function is used to build the JavaScript object from the JSON string. It sets the global variable obj which is then globally accessible to any element on the page. This can be wrapped in a JavaScript object to make it more reusable.
To demonstrate how specific attributes can be accessed. The attributeSelection SELECT control is then populated with all of the attribute names. The JavaScript (a.k.a. JSON) object contains an attribute that is named attributes which is an array containing all attribute names for the object. When the user selects an attribute in attributeSelection SELECT control, the corresponding value is displayed in a JavaScript alert via the onChangeAttribute function (event handler for onchange event of the attributeSelection SELECT control).
The complete listing of this HTML page is shown below:
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Object Service</title>
<script type="text/javascript">
var req;
var obj;
// Initialize XmlHttpRequest object
function initializeXmlHttpRequest() {
if (window.ActiveXObject) {
req=new ActiveXObject('Microsoft.XMLHTTP');
}
else {
req=new XMLHttpRequest();
}
}
// Event handler for asynchronous request
function onServerResponse() {
try {
// If not finished, then return
if(req.readyState!=4) {
return;
}
// If an error occurred notify user and return
if(req.status != 200) {
alert('An error occurred retrieving object data from repository.');
return;
}
// Get server response
var responseData = req.responseText;
// Update object dump div tag to contain JSON string representation
var dumpControl = document.getElementById("objectDump");
if (dumpControl != null) {
dumpControl.innerHTML = responseData;
}
// Store JSON object for global access
obj = eval('(' + responseData + ')');
// Populate 'attributes' select list
populateAttributeSelectControl(obj.attributes);
}
catch(err) {
alert(err.message);
}
}
// Get post data
function getPostData() {
var data = "objectId=" + objectDumpForm.objectId.value;
return data;
}
// Event handler for on id text box value change
function onIdChange(idControl) {
if (idControl == null || idControl == undefined) {
alert('Object id field is missing or null.');
return;
}
// Get user-entered object id
var idValue = idControl.value;
// Ensure valid object id specified (must be 16 characters in documentum)
if (idValue.length != 16) {
alert('Invalid object id ' + idValue + 'specified.');
return;
}
// Update object dump div tag to indicate that data is being retrieved
var dumpControl = document.getElementById("objectDump");
if (dumpControl != null) {
dumpControl.innerHTML = 'Retrieving data for ' + idValue + '...';
}
// Initialize XmlHttpRequest object
initializeXmlHttpRequest();
if (req == null) {
alert ('Unable to initialize XmlHttpRequest object.');
return;
}
// Build request
if (req!=null) {
req.onreadystatechange=onServerResponse;
// Set window status
window.status='Retrieving data for ' + idValue + '...';
var postData = getPostData();
// Open server request
req.open('POST','/TestWebSite/objectbroker',true);
req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
req.setRequestHeader("Content-length", postData.length);
// Send data
req.send(postData);
}
return;
}
// Populate attribute selection list dynamically
function populateAttributeSelectControl(dataValues) {
var curControl = document.getElementById('attributeSelection');
// If control not found, return null
if (curControl == null) return;
// Append blank option
var blankOption= new Option('','',false,true);
curControl.options[curControl.options.length]=blankOption;
// Iterate through data value array
for (var i=0;i<dataValues.length;i++) {
// Create option
var newOption= new Option(dataValues[i],dataValues[i],false,false);
// Add option to control options
curControl.options[curControl.options.length]=newOption;
}
}
// Event handler for on click save button
function onSave() {
}
// Event handler for on click cancel button
function onCancel() {
}
// Event handler for on change
function onChangeAttribute(curControl) {
// If control not found, return null
if (curControl == null) return;
var selectedValue = curControl.value;
alert(obj[selectedValue]);
}
</script>
</head>
<body>
<form id="objectDumpForm">
<table width="400px">
<tbody>
<tr>
<td>
<span style="font-weight: bold">Object Id:</span>
</td>
<td>
<input type="text" id="objectId" onblur="onIdChange(this)"/>
</td>
</tr>
<tr>
<td colspan="2">
<input type="button" value="Cancel" onclick="onCancel()"/>
<input type="button" value="Save" onclick="onSave()"/>
</td>
</tr>
<tr>
<td colspan="2">
<select id="attributeSelection" onchange="onChangeAttribute(this)" />
</td>
</tr>
</tbody>
</table>
<div id="objectDumpLabel" style="font-weight:bold;margin-bottom:1em;">JSON Object</div>
<div id="objectDump" style="color:blue;width:90%;height:200px;overflow:auto;"></div>
</form>
</body>
</html>
Conclusion
This solution demonstrated how to make asynchronous requests from the client-side, how to serialize a repository object to JSON, and finally how to process/access the server response in JavaScript scripting. It could easily be consumed in most modern languages. Please refer to http://www.json.org for a list of libraries that support JSON.
Obviously this code is not production-grade code for the following reasons:
- There is no authentication for the Repository Service, as the credentials are read from the web.xml. As mentioned previously, this was done to make the asynchronous call as fast as possible (avoid having to authenticate to Documentum on every asynchronous request). Since the service would be useless without knowing valid object ids, it might be enough of a deterrent. Nonetheless, below are a few suggestions related to security.
- If a similar solution is used in a production scenario, make sure the account being used has limited access (read-only) to the repository.
- Possibly use security and filtering to only enable the service to be accessed by certain hosts through IP Filtering or Digital Signatures
- Potentially modify the RepositoryService and Servlet to use a ticketed login, but this assumes the consuming application is able to obtain a repository session and generate a login ticket
- Potentially modify the RepositoryService and Servlet to store the repository session in a JSP session variable
- If you are integrating this interface into an existing WDK application, you can modify the RepositoryService and Servlet to retrieve the current repository session from SessionManagerHttpBinding
- The error handling needs to be more robust.
- Although I did some unit testing, additional testing is required, as this is more of a proof-of-concept.
In the future, I plan to post additional blogs on leveraging this solution by building an AJax enabled WDK control that works well with the Object Broker Service. I am also considering additional asynchronous functions that can exposed. Currently I am thinking it would be useful to return a collection of objects. For example, perhaps a search page can be built that upon clicking a button, asynchronously retrieves a collection of objects matching the search criteria. A status message can be displayed while the query is being executed asynchronously on the server. JavaScript scripting can be used to populate a table upon receiving the server response.
I would be interested in knowing if anyone finds this useful, as well as hearing about possible ideas and extensions for this solution. Please feel free to contact me or comment on this blog entry. If this is useful, I will post additional blog entries related to this topic.
If the demand and interest is there, perhaps this can be turned into an Open Source project.
Related Recommended Books