Documentum and Ajax, Part 2

December 31, 2008 11:03 by Brian

Overview

As a follow up to my previous blog entry in which I demonstrated how to build a JSON Serializer for Documentum, I thought it would be interesting to build a service that allows you to enter a DQL query and have the results returned as a array of JSON objects. The Object Broker that was developed in my previous post in this series, was a service which was RESTFUL in nature and  leveraged the JSON Serializer for Documentum to expose object metadata to consumer applications.

The goal of this post is to demonstrate how the JSON Serializer for Documentum can be further leveraged to provide a DQL query service that can be invoked asynchronously. It allows you to perform DQL queries from any application that supports JSON and web requests. Similar to the Object Broker Service, the Query Service allows you to perform object queries 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.

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 TEXTAREA control for the query and an Execute button that when clicked makes the asynchronous request for the query results.

The following figure shows what the user sees upon clicking the Execute button, but before the results are returned to the user.

DCTMSerializer2-Figure1

The following screen shows what the user sees once the results are received by the client.

Figure 2: Screen displayed after asynchronous resonse is processed 

Since the request is made asynchronously, the screen prints don’t do this demonstration justice. The key benefit is that the web page is never “refreshed”. The results (JSONArray containing multiple JSONObject’s) are displayed by dynamically creating a table using JavaScript and DOM once the asynchronous response is received.

Technical Objectives

The primary objective is to build a lightweight solution to return an array of JSON Objects for the specified DQL query. With this goal in mind, we will:

  1. Build a server-side interface (Servlet) that returns a JSON Array containing the results of the query specified in the query argument
  2. 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)
  3. Build a sample HTML web page that demonstrates how the asynchronous call is made to the server to execute the DQL query. It also demonstrates how to process the JSON Array via JavaScript scripting to display the DQL query results

Extending the Repository Service

The first step is to add a method to the RepositoryService class created in the first part of this series. This method accepts a single argument queryString which contains the DQL query to  execute. It returns an ArrayList containing IDfTypedObject for each row in the query results. I used generics, but if you are using an older version of Java prior to 1.5 (when generic support was added), simply remove the type safety statements.

public ArrayList<IDfTypedObject> getQueryResults(String queryString) {
        ArrayList<IDfTypedObject> results = new ArrayList<IDfTypedObject> ();
        IDfSessionManager sessionMgr = null;
        IDfSession session = null;
        IDfCollection col = null;
        try {
            // Get session manager
            sessionMgr = this.getSessionManager();
            if (sessionMgr == null) {
                return results;
            }
            // Get repository session
            session = sessionMgr.getSession(this.getRepositoryName());
            // Get query object
            IDfClientX clientX = this.getClientX();
            IDfQuery query = clientX.getQuery();
            // Initialize query object
            query.setDQL(queryString);
            // Execute query
            col = query.execute(session, IDfQuery.DF_EXECREAD_QUERY);
            // Iterate through results and add IDfTYpedObject for each row
            // ArrayList that is returned
            while (col.next()) {
                IDfTypedObject curObj= col.getTypedObject();
                results.add(curObj);
            }
        } catch (DfException e) {
            e.printStackTrace();
        }
        finally {
            // Close collection
            try {
                col.close();
            } catch (DfException e) {}
            if (session != null && sessionMgr != null) {
                sessionMgr.release(session);
            }
        }
        return results;
    }

 

 

Extending the JSON Documentum Serializer

As I was developing this Query Service, I thought it would be useful to extend the JSON Serializer for Documentum (TypedObjectJsonSerializer class) that I developed in the previous post in this series to support attribute metadata (e.g. attribute data type, attribute repeating flag). Previously, it simply returned an array containing the names of all attributes. I enhanced it to now provide the attribute data type and true/false if the attribute is repeating. This moderately increases the size of the server response, but since it is JSON, it should be manageable.

Below is the complete listing of the revised TypedObjectJsonSerializer class:

/* The Class TypedObjectJsonSerializer serializes a EMC Documentum IDfTypedObject 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 attributes = 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();
                    boolean curIsRepeating = false;
                    String curDataType = "";                       
                    // Append attribute-type specific value to JSON object
                    switch (curType) {
                        // If boolean
                        case IDfAttr.DM_BOOLEAN:
                            curDataType = "boolean";
                            curIsRepeating = curAttr.isRepeating();
                            // 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:
                            curDataType = "string";
                            curIsRepeating = curAttr.isRepeating();
                            // 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:
                            curDataType = "id";
                            curIsRepeating = curAttr.isRepeating();
                            // 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:
                            curDataType = "integer";
                            curIsRepeating = curAttr.isRepeating();
                            // 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:
                            curDataType = "time";
                            curIsRepeating = curAttr.isRepeating();
                            // 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:
                            curDataType = "double";
                            curIsRepeating = curAttr.isRepeating();
                            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:
                            curDataType = "unknown";
                            curIsRepeating = false;
                            json.put(curName, "");
                    } // switch
                    // Add attribute
                    JSONObject curAttribute = new JSONObject();
                    curAttribute.put("name", curName);
                    curAttribute.put("type", curDataType);
                    curAttribute.put("repeating", curIsRepeating);
                    attributes.put(curAttribute);                   
                } // if
            } // for loop
            json.put("attributes", attributes);
        } 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:

  1. The Servlet supports both the POST and GET operations to provide maximum flexibility, although POST is strongly recommended to avoid to query string limitations and the escaping of the query on the client side.
  2. 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.
  3. The Servlet expects a single argument, query
  4. The Servlet returns either an empty string or the string representation of the array of JSON objects
  5. The Servlet reads the repository credentials from the RepositoryName, RepositoryUser, and RepositoryPassword configuration parameters in the web.xml

Similar to the Object Broker Servlet, the processRequest method does most of the work. The processRequest method simply retrieves the query results from the RepositoryService using the new getQueryResults method. The ArrayList returned by the getQueryResults is iterated through and a JSONObject is created for each row. A JSONArray containing the JSONObject objects is then serialized to a String and returned to the consumer application.

The full listing of the QueryServlet is shown below:

public class QueryServlet extends HttpServlet {
    public RepositoryService m_repositorySvc = null;

    /**
     * Gets the repository service.
     *
     * @return the repository service
     */
    public 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;
    }
    @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);
    }

    @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 queryString= req.getParameter("query");
        JSONArray jsonArray = new JSONArray();
        // Initialize repository service
        RepositoryService repositorySvc = this.getRepositoryService();
        // If not initialized or connected return null
        if (repositorySvc == null || !repositorySvc.isValid()) {
            return jsonStr;
        }

        // Create serializer
        TypedObjectJsonSerializer serializer = new TypedObjectJsonSerializer();

        // Iterate through all query results
        ArrayList<IDfTypedObject> results = repositorySvc.getQueryResults(queryString);
        if (results != null && results.size() >0) {
            Iterator<IDfTypedObject> objIter = results.iterator();
            while (objIter.hasNext()) {
                IDfTypedObject curObj = objIter.next();
                JSONObject jsonObj = serializer.serialize(curObj);
                jsonArray.put(jsonObj);
            }
        }

        jsonStr = jsonArray.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.

As in the previous post in this series, XmlHttpRequest, which is supported by most modern browsers including Internet Explorer and Firefox, is used to make the asynchronous request.

An asynchronous request is made in the onExecute event handler which is fired when the Execute button is clicked. The query specified in the queryText TEXTAREA control is passed to the asynchronous request.

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. The eval JavaScript function is used to build the JavaScript array containing the query result objects from the JSON string. The query results are passed to the showQueryResults function which dynamically generates an HTML table containing the query results. The JavaScript code is fairly straightforward and uses DOM Scripting to build the HTML table.

The complete listing of this HTML page is shown below:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Query Service</title>
    <script type="text/javascript">
        var req;

        // Initialize XmlHttpRequest object
        function initializeXmlHttpRequest() {
            if (window.ActiveXObject) {
                req=new ActiveXObject('Microsoft.XMLHTTP');
            }
            else {
                req=new XMLHttpRequest();
            }
        }

        function showQueryResults(results) {
            if (results.length <= 0) {
                return;
            }
            var resultTable=document.createElement("table");
            resultTable.border=1;
            resultTable.cellSpacing=0;
            resultTable.cellPadding=0;
               for(i=0;i<results.length;i++) {
                   // Get current row
                   var curObj = results[i];
                // Add row
                var curRow = resultTable.insertRow(i);
                // Add cell for each attribute
                for(j=0; j < curObj.attributes.length; j++) {
                    var curAttr = curObj.attributes[j];
                    var curName = curAttr.name;                   
                    var curCell = curRow.insertCell(j);     

                    // If repeating
                    if (curAttr.repeating) {
                        var values = curObj[curName];
                        var valueStr = "";
                        for (v=0; v < values.length; v++) {
                            valueStr = valueStr + values[v] + "<br/>";
                        }
                        if (valueStr.length ==0) {
                            valueStr = "&nbsp;";
                        }
                        curCell.innerHTML = valueStr;
                    }
                    else {                   
                        var curValue = curObj[curName];
                        if (curValue == null || curValue.length == 0) {
                            curCell.innerHTML = "&nbsp;";
                        }
                        else {
                            curCell.innerHTML = curValue; 
                        }
                    }
                }
            }

            // Add header
            var thead = resultTable.createTHead();
               var headerRow = thead.insertRow(-1);
               headerRow.setAttribute("bgColor","#3399FF");
               var curObj = results[0];
               for(j=0; j < curObj.attributes.length; j++) {
                var curAttr = curObj.attributes[j];
                var curName = curAttr.name;

                var curCell = headerRow.insertCell(j);
                curCell.align = "center";
                curCell.style.color = "white";
                curCell.style.fontWeight = "bold";
                curCell.innerHTML = curName;
               }
               var dumpControl = document.getElementById("queryDump");
               dumpControl.innerHTML = "";              
               dumpControl.appendChild(resultTable);
               //alert(dumpControl.innerHTML);              
        }
        // 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 query data from repository.');
                    return;
                }

                // Get server response
                var responseData = req.responseText;
                // Store JSON object for global access
                var obj = eval('(' + responseData + ')');

                // Build table
                showQueryResults(obj);
            }
            catch(err) {
                alert(err.message);
            }
        }

        // Get post data
        function getPostData() {
            var data = "query=" + queryForm.queryText.value;
            return data;        
        }
        // Event handler for on click execute button
        function onExecute() {
            var queryControl = document.getElementById("queryText");
            if (queryControl == null) {
                alert('Query field is missing or null.');
                return;
            }

            // Get user-entered object id
            var query = queryControl.value;
            // Ensure valid object id specified (must be 16 characters in documentum)
            if (query.length == 0) {               
                return;
            }

            // Update object dump div tag to indicate that data is being retrieved
            var dumpControl = document.getElementById("queryDump");
            if (dumpControl != null) {
                dumpControl.innerHTML = "<span style='color:blue;font-style:italic'>Executing query: " + query + "</span>";
            }
            // 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='Executing query...';

                var postData = getPostData();
                // Open server request
                req.open('POST','/TestWebSite/queryservice',true);
                req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                req.setRequestHeader("Content-length", postData.length);
                // Send data
                req.send(postData);       
            }

            return;
        }

        // Event handler for on click cancel button
        function onCancel() {
            history.back(-1);
        }

    </script>
</head>
<body>
    <form id="queryForm">
        <table width="400px">           
            <tbody>
                <tr>
                    <td>
                        <span style="font-weight: bold">Query:</span>
                    </td>
                    <td>
                        <textarea rows="10" cols="50" id="queryText"></textarea>                       
                    </td>
                </tr>
                <tr>
                    <td colspan="2">
                        <input type="button" value="Cancel" onclick="onCancel()"/>&nbsp;
                        <input type="button" value="Execute" onclick="onExecute()"/>
                    </td>
                </tr>
            </tbody>
        </table>
        <div id="queryDumpLabel" style="font-weight:bold;margin-bottom:1em;">JSON Query Results</div>
        <div id="queryDump" ></div>
    </form>
</body>
</html>

Conclusion

This solution demonstrated how to asynchronously retrieve DQL query results from the client-side, how to serialize the query results 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.

As I mentioned in my previous post, this demonstration sample is only to show how the service can be consumed, but the possibilities for leveraging the new Query Service are endless. The DQL query can dynamically be built in JavaScript functions based on selected nodes, form field values, etc. The query results can then be used to build tables, list items, tree nodes, or any other dynamic HTML generated through JavaScript and DOM scripting.

As always, this code is more of a proof of concept and not production grade code. I recommend the following:

  • Evaluate whether the security approach is acceptable (storing credentials for account in the web.xml file).
  • Make sure the account being used has limited access (read-only) to the repository.
  • Add checks to ensure no updates or malicious queries are allowed. This require simple parsing of the DQL query in the RepositoryService class
  • 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.
  • If large volumes of data are used, perhaps it would be useful to add a flag to suppress the attribute metadata (data type, repeating, etc). It really depends on how the data will be consumed.

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


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Copyright (c) 2007-2009 Brian J. Stewart, Copyright Policy

Comments

Add comment


 

  Country flag

biuquote
  • Comment
  • Preview
Loading