Pages

Sunday, January 1, 2017

Documentum D2 external widget. How to nicely use the current user's session

The code of many sample D2 widgets is available on EMC website. The widgets accessing documentum need a session or credentials to create one. The provided samples use tickets generated by D2 to create a session each time user activates the widget.

I used a simpler way - I reused the current user's session used by D2. To do this, the widget must be included in D2 application, which is not a problem. The same as with plugins, the widget jar should be placed into D2/WEB-INF/lib folder. Then both the static resources and the servlet have the same context root as D2 application and, therefore, can access httpSession of the current user. In D2 all the documentum sessions created for the current user are stored in http session attribute "CONTEXT". The attribute value is a map containing D2 session ids as key and the credentials for corresponding documentum sessions as values.

Project file layout

My sample widget comprises static resources such as html template, javascript and css stylesheet, and a servlet together with auxiliary classes required to generate the output.

myWidget.html

It is a simple file with DIV placeholder for the dynamically generated content. Additionally, the file load two standard scripts enabling the interaction with OpenAjaxHub, which act as a event bus in D2 application. The third script myWidget.js listens to D2 events and updates the html with the content generated by servlet MyWidgetServlet.java.

<html>
  <head>
    <title>My widget</title>
    <script language='javascript' src="container/external-api/OpenAjaxManagedHub-all.js"></script>
    <script language='javascript' src="container/external-api/D2-OAH.js"></script>
    <script src="myWidget.js"></script>
    <link rel="stylesheet" type="text/css" href="myWidgetStyles.css">
  </head>
  <body  onload="myWidget.loadEventHandler()">    
    <div id="myPlaceHolderForHTMLContent"></div>
  </body>
</html>
myWidget.js

The third script myWidget.js connects to ajaxHub, subscribes to D2_EVENT_SELECT_OBJECT event, and calls the servlet whenever the user selects an object. Additionally, when the user select an object visualized in the widget, the script issues event D2_ACTION_LOCATE_OBJECT so that this object is selected in D2 also. Essentially, the script is a relay ensuring the two-way communication between D2 ajaxHub and the widget. When the servlet is accessed it is passed two parameters: the selected object id and D2 session id (mere the D2-specific id of the documentum session used by the current user). The servlet response is inserted into the placeholder div. Then in method attachEventListeners onClick listeners are attached to the displayed objects and the new content is additionally styled and positioned (this code is not shown).

var myWidget = {
  clickedObjectId: "", // last selected object id
  widgetIsOn: false, // widget is active

  // Application initializes in response to document load event  
  loadEventHandler: function () {
    console.log("Iframe loaded ");
    myWidget.ajaxHub = new D2OpenAjaxHub();
    myWidget.ajaxHub.connectHub(myWidget.connectCompleted, myWidget.onInitWidget, myWidget.onActiveWidget);
  },
  connectCompleted: function (hubClient, success, error) {
    if (success) {
      console.log("Hub client connected");
      myWidget.subscribeEvents();
    } else
      console.log("Failed to connect");
  },
  // Callback that is invoked upon widget activation 
  onActiveWidget: function (bActiveFlag) {
    console.log("onActiveWidget: " + bActiveFlag);
    // set the internal flag
    myWidget.widgetIsOn = bActiveFlag;
  },
  onInitWidget: function (message) {
    console.log("onWidgetInit");
  },
  // the widget will react to selection of an object in D2, selectObjectCallback will be invoked
  subscribeEvents: function () {
    console.log("subscribeEvents");
    myWidget.ajaxHub.subscribeToChannel("D2_EVENT_SELECT_OBJECT", myWidget.selectObjectCallback, false);
  },
  // invoked when an object is selected in D2
  selectObjectCallback: function (name, msg) {
    var id=msg.get("oam_id");
    console.log("selectObjectCallback id: " + id);
    // check that the widget is active
    if (!myWidget.widgetIsOn) {
      return;
    }
    // react only if the newly selected object is not the same as the currently selected 
    if (myWidget.myClickedObjectId !== id) {
      console.log("selectObjectCallback processing: " + id);
      var xmlhttp = new XMLHttpRequest();
      xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
          console.log("selectObjectCallback received response " + xmlhttp.status);
          // display the generated html
          document.getElementById("myPlaceHolderForHTMLContent").innerHTML = xmlhttp.responseText;
          console.log("selectObjectCallback inserted response ");
           // the html can display some objects that are related to the selected object. For example, 
           // if versions or objects linked by relations are visualized, then one would expect that clicking on an object
           // would trigger something, for example, the clicked object will be selected in D2. 
              myWidget.attachEventListeners();
              console.log("selectObjectCallback attached listeners");
            }
          };
    
          // sent not only object id but also session id so that it can be recovered by the servlet
          xmlhttp.open("GET", "myWidgetServlet?id=" + id +  "&uid=" + msg.get("oam_cuid"), true);
          xmlhttp.send();
          console.log("selectObjectCallback sent ajax request");
          myWidget.myClickedObjectId = "";
        }
      },
      // optionally attach listeners to your generated html or modify html or do anything else
      attachEventListeners: function () {
      },
      // a methods that could be used together with the method above to trigger selection of the object in D2
      // the method sends D2_ACTION_LOCATE_OBJECT event together with the object id to D2 AjaxHub
      displayInD2ObjectSelectedInWidget: function (id) {
        console.log("displayInD2ObjectSelectedInWidget: " + id);
        var messageToSend = new OpenAjaxMessage();
        messageToSend.put("oam_id", id);
        myWidget.ajaxHub.sendMessage("D2_ACTION_LOCATE_OBJECT", messageToSend);
        return messageToSend;
      }
    };
MyWidgetServlet.java

Note, this servlet works only in servers supporting servlet specification 3.0 and above.

The servlet receives the selected object id and the D2-specific documentum session id. Then the documentum session is extracted from http session, which is shared by D2 application and the widget. The session and the selected object id are passed to auxiliary method createPage rendering html. For example, the navigatable version tree of the object could be rendered.

@WebServlet("/myWidgetServlet")
public class MyWidgetServlet extends HttpServlet {

  @Override
  protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    try {
      // the selected object id
      String selectedObjectId = request.getParameter("id");

      Map<String, Object> contextMap = (Map) request.getSession().getAttribute("CONTEXT");

      // get the session of the current user
      Context c = (Context) contextMap.get(request.getParameter("uid"));
      D2fsContext d2fsContext = new D2fsContext(c, false);
      IDfSession session = d2fsContext.getSession();
       
      // use the session and selected object id to create HTML page 
      // for example you can visualize the version tree of the object
      String html = createPage(session, selectedObjectId);
      out.println(html);
    } catch (DfException | D2fsException ex) {
      ex.printStackTrace(out);
    } finally {
      out.close();
    }
  }

  String createPage(IDfSession session, String selectedObjectId) {
    // generate html displayed in the widget
    return html;
  }
}
Installation

After the project is built into jar file and placed into D2/WEB-INF/lib folder, the widget has to be enabled in D2 config:

  • create a new widget entry in D2 config
  • select ExternalWidget option in the Widget type list
  • check Bidirectional communication
  • enter myWidget.html?anything=11 (without an arbitrary parameter the relative url is misunderstood by D2) into the Widget url text input
  • click Save
  • enable the widget in the context matrix of D2 config
  • open D2 and select the widget in the widget gallery

7 comments:

  1. thank you for sharing !
    Jérémy

    ReplyDelete
  2. Thanks for the information. The information you provided is very helpful for Documentum Learners.
    https://mindmajix.com/documentum-training

    ReplyDelete
  3. Thank you, this is quite helpful.
    I have question, IDfSession we get from D2fsContext in MyWidgetServlet can remain unreleased?

    ReplyDelete
    Replies
    1. Hello
      Here, we only get a reference to an existing session class that was opened not by us, so it is not up to us to close it.

      Delete
    2. Hello,
      Thank you for the response.

      To me it still looks that if we create d2fsContext and ask for session
      "
      D2fsContext d2fsContext = new D2fsContext(c, false);
      IDfSession session = d2fsContext.getSession();
      "
      then we need in finally block to call d2fsContext.refease(true/false), this method will release session.

      This is how it is done in D2HttpServlet.
      The same thing is doing InjectSessionAspect, before every d2fs service call it creates D2fsContext object, then it is passed as argument to the service method execution, after that in finally block it calls release method on D2fsContext object.





      Delete