Overview
D2 is the most popular client web application for Documentum platform. However, its users often need to customize its functionalities, which can be easily achieved using custom D2 listener plugins written in Java. For example, plugins could automatically modify, fill or restore some attributes of the selected objects, link them to some folders based on attribute values, or send emails to reviewers.
Whenever some menu item is clicked, the supporting D2 service is invoked in the backend. Let's consider several distinct menu items: View, Paste and Properties.
In D2 config we can see that clicking View translates into publishing action D2_ACTION_CONTENT_VIEW:
Paste item produces event D2_ACTION_PASTE:
Lastly, Properties option calls PropertiesDialog popup:
When one needs to modify some D2 functionality, one can perform the action in D2 user interface and then look into the D2 log to see what service class has been invoked. Plugins override existing services classes. Often D2 service classes have many methods, so additionally the name of the invoked method has to be recovered from the log. Note, invocation of services are logged at DEBUG logging level.
When View is clicked getDownloadUrls method of D2DownloadService is invoked.
When Properties is click getDialog of D2DialogService is invoked. Note, after you click OK in the Properties page popup, D2PropertyService service will be invoked to handle the introduced modifications.
When Paste menu item is selected, copy methods of D2MoveService is invoked.
As you see, regardless of the immediate settings in D2 config, eventually every activity is mediated by D2 service classes.
Plugin classes are complied into a jar file that is placed into D2/WEB-INF/lib directory or anywhere on the classpath. As it is evident from the log, when a service is invoked, D2 class com.emc.d2fs.dctm.aspects.InjectSessionAspect searches the classpath for the plugins overriding the service. If any overriding plugin is found, it is invoked instead of the native service method. Plugin classes are recognized by two distinctive features. All listener plugin classes implement interface ID2fsPlugin, and their class names are composed of the name of the target service class that is concataneted to the keywork Plugin.
The plugins that I developed or upgraded usually execute some code before calling the overridden service, call the service, and then execute again some custom code that sometimes uses the result of the native service. There is a difference between D2 3.1 and D2 4.2 plugins. In D2 4.2 and 4.5 plugins do not have onBefore and onAfter methods. I replace them with custom methods before and after. I illustrated this in the comments in the simplified code below. The examples contains some explanation in comments.
Below I describe several plugins:
Life Cycle Service Plugin
I start from D2 4.5 plugin that is invoked when a user changes lifecyle state of an object.
public class D2LifeCycleServicePlugin extends D2LifeCycleService implements ID2fsPlugin { // just for visualizing input arguments and debugging void printAttributes(List<Attribute> parameters) { for (Attribute a : parameters) { System.out.println("attrName/value: " + a.getName() + " " + a.getValue()); } } // just for visualizing input arguments and debugging void printParameters(D2fsContext d2fsContext) throws DfException, D2fsException { ParameterParser d2parameterparser = d2fsContext.getParameterParser(); for (Attribute a : d2parameterparser.getParameters()) { System.out.println("paramName/value: " + a.getName() + " " + a.getValue()); } } @Override public LifeCycleResult changeState(Context context, String docId, String targetState, String event, String operation, List<Attribute> parameters) throws Exception { System.out.println(">D2LifeCycleServicePlugin:changeState: docId=" + docId + "; targetState=" + targetState + "; event=" + event + "; operation=" + operation); // just for visualizing input arguments and debugging printAttributes(parameters); //D2fsContext contains current user and admin user sessions and lots of other values D2fsContext d2fsContext = (D2fsContext) context; // just for visualizing input arguments and debugging printParameters(d2fsContext); // execute custom code before executing the native code before(d2fsContext, docId, event); // call the native method LifeCycleResult result = super.changeState(context, docId, targetState, event, operation, parameters); // execute custom code before executing the native code, you can modify the returned value here after(d2fsContext, docId, event, targetState); return result; } // execute custom code before executing the native code public void before(D2fsContext d2context, String objectId, String event) throws DfException, D2fsException { IDfSession session = d2context.getSession(); IDfSession adminSession = d2context.getAdminSession(); IDfSysObject object = (IDfSysObject) session.getObject(new DfId(objectId)); IDfSysObject adminObj = (IDfSysObject) adminSession.getObject(object.getObjectId()); } // execute custom code before executing the native code, you can modify the returned value here public void after(D2fsContext d2context, String objectId, String event, String targetState) { } // two methods producing the plugin info that is shown in D2 About menu when plugin is installed @Override public String getFullName() { return new PluginVersion().getFullName(); } @Override public String getProductName() { return new PluginVersion().getProductName(); } }
Dialog service plugin (mass update)
There are many dialogs in D2. They are supported by dialog service. The D2 4.5 listener plugin example below shows how to apply custom modifications to the selected objects specifically after Mass Update dialog has been invoked from the context menu.
public class D2DialogServicePlugin extends D2DialogService implements ID2fsPlugin { @Override public Dialog validDialog(Context context, String id, String dialogName, List<Attribute> parameters) throws Exception { Dialog result; if (dialogName.equals("MassUpdateDialog")) { // Mass Update dialog D2fsContext d2fsContext = (D2fsContext) context; before(d2fsContext); // call the native method result = super.validDialog(context, id, dialogName, parameters); after(d2fsContext); } else { // not Mass Update dialog, do nothing except calling the native method result = super.validDialog(context, id, dialogName, parameters); } return result; } public void before(D2fsContext d2context) throws D2fsException, DfException { ParameterParser d2parameterparser = d2context.getParameterParser(); // Verify the mass update configuration name if (d2parameterparser.hasParameter("config_name")) { if (d2parameterparser.getStringParameter("config_name").equals("Distribution list")) { // get selected objects for (int i = 0; i < d2context.getObjectCount(); i++) { IDfSysObject bisObject = (IDfSysObject) d2context.getObject(i); // do something special to each selected object } } } } public void after(D2fsContext d2context) throws Exception { IDfSession session = d2context.getSession(); ParameterParser d2parameterparser = d2context.getParameterParser(); // do something to selected objects as in methods before } @Override public String getFullName() { return new PluginVersion().getFullName(); } @Override public String getProductName() { return new PluginVersion().getProductName(); } }
Creation service plugin
The most common type of D2 plugins are plugins modifying the properties of newly created objects. For example, values of some attributes can be filtered or somehow modified, some attributes can be assigned some values and the object can be linked to some particular folders based on some attribute values and emails can be sent to some reviewers.
Below is the example of Creation service listener plugin that will work with D2 4.2 and 4.5.
public class D2CreationServicePlugin extends D2CreationService implements ID2fsPlugin { @Override public String createProperties(Context context, List<Attribute> parameters) throws Exception { D2fsContext d2fsContext = (D2fsContext) context; Map<String, String> attributeMap = new HashMap<>(); for (Attribute a : parameters) { String name = a.getName(); String val = a.getValue(); attributeMap.put(name, val); // one can modify the attributes of the object to be created // for example, remove commas in repeating attribute authors if (name.equals("authors")) { String[] vals = val.split(AttributeUtils.SEPARATOR_VALUE); for (int i = 0; i < vals.length; i++) { vals[i] = vals[i].replace(",", ""); } a.setValue(ArrayUtil.join(vals, AttributeUtils.SEPARATOR_VALUE)); } } String objectType = attributeMap.get("r_object_type"); // execute some logic before creating the object of some particular type if (objectType.equals("custom_object_type")) { before(d2fsContext, parameters); } String result = super.createProperties(context, parameters); // execute some logic after the object of some particular type has been saved if (objectType.equals("custom_object_type")) { String objId = extractNewIDFromReturnString(result); after(d2fsContext, objId); } return result; } // exctact the id of the created object from the result string returned by the service method // <success d2_naming_config="false" new_id="080f42418001febd" locate="true"/> String extractNewIDFromReturnString(String s) throws DfException { String[] values = s.split(" "); for (String str : values) { if (str.startsWith("new_id")) { String newId = str.split("\"")[1]; return newId; } } throw new DfException("Cannot extract object id"); } // custom code to be executed before the native code void before(D2fsContext d2context, List<Attribute> parameters) throws DfException, D2fsException { IDfSession session = d2context.getSession(); // do something } // custom code to be executed after the native code void after(D2fsContext d2context, String id) throws D2fsException, DfException { IDfSession session = d2context.getSession(); IDfSysObject obj = (IDfSysObject) session.getObject(new DfId(id)); // do something } @Override public String getFullName() { return new PluginVersion().getFullName(); } @Override public String getProductName() { return new PluginVersion().getProductName(); } }
One important remark, D2CreationService.createProperties method does not link the created object to any folder. The containing folder id is stored in contentId attribute, though. The object is linked much later by D2CreationService.setTemplate method, which first removes all existing links. Before execution of that methods, the object in not linked anywhere except the home folder. So if your plugin uses the parent folder information, you must use the value of contentId parameter.
Property service plugin
Another quite common type of D2 plugins are plugins modifying objects after the object attribute values have been updated in D2 properties widget. For example, values of some attributes can be restored or somehow further modified, some attributes can be assigned some values and the object can be linked to particular folders depending on some attribute values, object life cycle stated can be changed, and emails can be sent to some reviewers.
Below is the example of Property service listener plugin that will work with D2 4.2 and 4.5.
public class D2PropertyServicePlugin extends D2PropertyService implements ID2fsPlugin { public XmlNode saveProperties(Context context) throws Exception { D2fsContext d2fsContext = (D2fsContext) context; ParameterParser d2parameterparser = d2fsContext.getParameterParser(); String objectId = d2parameterparser.getStringParameter("id"); IDfSession session = d2fsContext.getSession(); IDfSysObject obj = (IDfSysObject) session.getObject(new DfId(objectId)); // apply only to specific target type if (obj.getTypeName().equals("target_object_type")) { before(d2fsContext, objectId); } XmlNode r = super.saveProperties(context); // apply only to specific target type if (obj.getTypeName().equals("target_object_type")) { after(d2fsContext, objectId); } return r; } void before(D2fsContext d2context, String objectId) throws D2fsException, DfException, IOException { IDfSession session = d2context.getSession(); IDfSysObject obj = (IDfSysObject) session.getObject(new DfId(objectId)); // modify the object before it has been updated, for example backup some attribute values // and then save obj.save(); } void after(D2fsContext d2context, String objectId) throws D2WarningException, DfException, D2fsException, IOException, MessagingException { IDfSession session = d2context.getSession(); IDfSysObject obj = (IDfSysObject) session.getObject(new DfId(objectId)); // modify the object, for example link or fill some attributes // and then save obj.save(); } @Override public String getFullName() { return new PluginVersion().getFullName(); } @Override public String getProductName() { return new PluginVersion().getProductName(); } }
Download service plugin (overriding checkin method)
Often when a document has been checked in, some automatic modifications to the object are desired. The plugin could, for example, change life cycle stated of the object and send emails to reviewers.
In D2 4.5 the checkin functionality is mediated by checkin method of Download Service. Note, D2 3.1 and 4.2 have no service method for checkin, this functionality i mediated by com.emc.d2fs.dctm.servlets.upload.Checkin servlet.
The first example that I provide below is of the checkin listener plugin for D2 4.5 and the second for D2 4.2.
The example for D2 4.5:
public class D2DownloadServicePlugin extends D2DownloadService implements ID2fsPlugin { @Override public String checkin(Context context, String id, File uploadFile, long fileLength, String contentType, String logEntry, String checkinVersionT, boolean makeCurrent, boolean retainLock, boolean keepSymbolicLabel, boolean keepLogEntry, boolean queueRendition, String location, boolean asynchronous, boolean useBocs, Object contentMover) throws Exception { D2fsContext d2fsContext = (D2fsContext) context; String result = super.checkin(context, id, uploadFile, fileLength, contentType, logEntry, checkinVersionT, makeCurrent, retainLock, keepSymbolicLabel, keepLogEntry, queueRendition, location, asynchronous, useBocs, contentMover); after(d2fsContext, result); return result; } private void after(D2fsContext d2context, String objectId) throws DfException, D2fsException { IDfSession session = d2context.getSession(); IDfSysObject obj = (IDfSysObject) session.getObject(new DfId(objectId)); // apply only objects of specific type if (obj.getTypeName().equals("target_object_type")) { // modify the object } } @Override public String getFullName() { return new PluginVersion().getFullName(); } @Override public String getProductName() { return new PluginVersion().getProductName(); } }
The checkin listener plugin for D2 4.2 is quite different:
public class CheckinListener implements ID2PluginListener, ID2fsPlugin { @Override public XmlNode onBefore(HttpServletRequest request, HttpServletResponse response, D2HttpContext paramD2HttpContext) throws Exception { // do nothing return null; } @Override public XmlNode onAfter(HttpServletRequest request, HttpServletResponse response, D2HttpContext d2context, XmlNode xmlNode) throws Exception { IDfSession session = d2context.getSession(); String objectId = xmlNode.getFirstXmlNode("new").getAttribute("id").toString(); IDfSysObject obj = (IDfSysObject) session.getObject(new DfId(objectId)); //Specific behavior for C-Sox and for Policies and Procedures if ("target_object_type".equals(obj.getTypeName()) { // do something, for example send emails } return xmlNode; } @Override public String getFullName() { return new SampleVersion().getFullName(); } @Override public String getProductName() { return new SampleVersion().getProductName(); } }
Move service plugin (copy, cut, paste and link)
Copy, cut, paste and link context menu items are mediated by Move service. If you need to modify automatically the selected objects before or after the operation, you can install a listener plugin. The example for D2 4.5:
public class D2MoveServicePlugin extends D2MoveService implements ID2fsPlugin { @Override public boolean move(Context context, String targetId, String sourceId, String idChild) throws Exception { D2fsContext d2fsContext = (D2fsContext) context; boolean result = super.move(context, targetId, sourceId, idChild); afterMove(d2fsContext, targetId, idChild); return result; } void afterMove(D2fsContext d2context, String dest_id, String objIds) throws DfException, D2fsException { IDfSession session = d2context.getSession(); String[] ids = objIds.split(AttributeUtils.SEPARATOR_VALUE); for (String id : ids) { IDfSysObject obj = (IDfSysObject) session.getObject(new DfId(id)); // apply only to a spcific target type if (obj.getTypeName().equals("target_object_type")) { // modify the object } } } @Override public boolean copy(Context context, String targetId, String idChild) throws Exception { D2fsContext d2fsContext = (D2fsContext) context; boolean result = super.copy(context, targetId, idChild); afterCopy(d2fsContext, targetId, idChild); return result; } void afterCopy(D2fsContext d2context, String folderId, String objIds) throws DfException, D2fsException { IDfSession session = d2context.getSession(); String[] ids = objIds.split(AttributeUtils.SEPARATOR_VALUE); for (String id : ids) { IDfSysObject obj = (IDfSysObject) session.getObject(new DfId(id)); // modify the object } } @Override public boolean link(Context context, String targetId, String objIds) throws Exception { D2fsContext d2fsContext = (D2fsContext) context; boolean result = super.link(context, targetId, objIds); afterLink(d2fsContext, targetId, objIds); return result; } void afterLink(D2fsContext d2context, String dest_id, String objIds) throws DfException, D2fsException { IDfSession session = d2context.getSession(); String[] ids = objIds.split(AttributeUtils.SEPARATOR_VALUE); for (String id : ids) { IDfSysObject obj = (IDfSysObject) session.getObject(new DfId(id)); // apply only to a spcific target type if (obj.getTypeName().equals("target_object_type")) { // modify the object } } } @Override public String getFullName() { return new PluginVersion().getFullName(); } @Override public String getProductName() { return new PluginVersion().getProductName(); } }
Destroy service plugin (delete and unlink)
Delete and unlink context menu items are mediated by Destroy service. If you need to modify automatically the selected objects before or after the operation, you can install a listener plugin. The example for D2 4.5:
public class D2DestroyServicePlugin extends D2DestroyService implements ID2fsPlugin { @Override public Destroyresult destroy(Context context, String id, List<Attribute> attributes) throws D2FailureException, Exception { String deleteType = "undefined", parentId = "undefined"; for (Attribute a : attributes) { System.out.println(" " + a.getName() + "=" + a.getValue()); if (a.getName().equals("version")) { deleteType = a.getValue(); } else if (a.getName().equals("parentId")) { parentId = a.getValue(); } } D2fsContext d2fsContext = (D2fsContext) context; Destroyresult result = super.destroy(context, id, attributes); if (deleteType.equals("3")) { // 3 stands for unlink after(d2fsContext, id, parentId); } return result; } public void after(D2fsContext d2context, String objectId, String parentId) throws DfException, D2fsException { IDfSession session = d2context.getSession(); IDfSysObject obj = (IDfSysObject) session.getObject(new DfId(objectId)); if (obj.getTypeName().equals("target_object_type")) { // do something to the object, save folder name or send emails } } @Override public String getFullName() { return new PluginVersion().getFullName(); } @Override public String getProductName() { return new PluginVersion().getProductName(); } }
How can i start a D2 workflow on document via plugin code after import?
ReplyDeleteIs there any API or Documentation available for the same?
qs5korwu
ReplyDeletecialis 5 mg satın al
cialis 20 mg eczane
kamagra
sight care
viagra eczane
https://shop.blognokta.com/urunler/ereksiyon-haplari/cialis/cialis-100-mg-30-tablet-eczaneden-etkili-ereksiyon-saglayici-ilac/
glucotrust