Documentum D2, as IBM Content Navigator, provides the availability to override some native functionalities. To do this, we have to develop custom plugins, but there is a couple of things to keep in mind when doing this.
Documentum D2 Plugin
When end users needs to customize D2 functionalities, it can be easily achieved by developers with D2 plugins, written in Java, and working similarly as listeners. When a native action is done in D2, for example a document checkout, the functionality can be override using Java code. If there is only one application running on D2 instance, there is no change to have major issues when triggering D2 custom code. But when there is more than one applications, it can be a little more complicated…
The reflection
D2 actually use the Spring framework, which is a very powerful framework. Those classes provides us the capacity to dynamically list every classes implementing a specific interface. This is what I will use to reach my objective.
Oracle define the reflection feature as the following sentence :
Reflection is a feature in the Java programming language. It allows an executing Java program to examine or “introspect” upon itself, and manipulate internal properties of the program. For example, it’s possible for a Java class to obtain the names of all its members and display them. The ability to examine and manipulate a Java class from within itself may not sound like very much, but in other programming languages this feature simply doesn’t exist. For example, there is no way in a Pascal, C, or C++ program to obtain information about the functions defined within that program. One tangible use of reflection is in JavaBeans, where software components can be manipulated visually via a builder tool. The tool uses reflection to obtain the properties of Java components (classes) as they are dynamically loaded.
The algorithm
Using the reflection, we will be able to keep only one listener declared in D2, for a specific functionality. This listener will keep in memory each classes with my functionality specific implementation.
- because the main class, D2CheckoutServicePlugin, is implementing ID2fsPlugin, this class will act as our listener, from D2 point of view ;
- on D2CheckoutServicePlugin constructor, we will use the reflection to list every class implementing our custom interface D2CheckoutServiceLogic, and instanciating it ;
- each implementation of D2CheckoutServiceLogic will register itself to D2CheckoutServicePlugin ;
- on specific override, D2CheckoutServiceLogic will browse it’s list of registered logic and will determine the logic to use (related to D2 context) ;
- before / after initial call, our specific D2CheckoutServicePlugin listener will invoke, on the determine logic, specific methods.
The code
D2CheckoutServicePlugin
package org.company.plugin; public class D2CheckoutServicePlugin extends D2CheckoutService implements ID2fsPlugin { private static final Map<String, D2CheckoutServiceLogic> LOGICS = new HashMap<>(); public D2CheckoutServicePlugin() { BeanDefinitionRegistry bdr = new SimpleBeanDefinitionRegistry(); ClassPathBeanDefinitionScanner cpbds = new ClassPathBeanDefinitionScanner(bdr, false); TypeFilter tf = new AssignableTypeFilter(D2CheckoutServiceLogic.class); cpbds.addIncludeFilter(tf); cpbds.setIncludeAnnotationConfig(false); cpbds.scan("org.company"); String[] beanNames = bdr.getBeanDefinitionNames(); for (String beanName : beanNames) { ClassUtils.getClass(bdr.getBeanDefinition(beanName).getBeanClassName()).getDeclaredConstructor().newInstance(); } } public static void registerComponent(String componentName, D2CheckoutServiceLogic logic) { LOGICS.put(componentName, logic); } @Override public boolean checkout(Context context, String id) { D2CheckoutServiceLogic logic = null; for (Map.Entry<String, D2CheckoutServiceLogic> entry : LOGICS.entrySet()) { if (entry.getKey().equalsIgnoreCase(((D2fsContext) context).getConfigApplicationName())) { logic = entry.getValue(); } } if (logic != null) { logic.beforeCheckout((D2fsContext) context, id); } boolean checkout = super.checkout(context, id); if (logic != null) { logic.afterCheckout((D2fsContext) context, id); } return checkout; } }
D2CheckoutServiceLogic
package org.company.logic; public interface D2CheckoutServiceLogic { public void beforeCheckout(D2fsContext d2fsContext, String id) throws Exception; public void afterCheckout(D2fsContext d2fsContext, String id) throws Exception; }
Custom application A
package org.company.appliA.services; public class CustomApplicationA implements D2CheckoutServiceLogic { public CustomApplicationA() { D2CheckoutServicePlugin.registerComponent("ApplicationNameA", this); } @Override public void beforeCheckout(D2fsContext d2fsContext, String id) throws Exception { // Custom Application A functionality } @Override public void afterCheckout(D2fsContext d2fsContext, String id) throws Exception { // Custom Application A functionality } }
Custom Application B
package org.company.appliB.services; public class CustomApplicationB implements D2CheckoutServiceLogic { public CustomApplicationA() { D2CheckoutServicePlugin.registerComponent("ApplicationNameB", this); } @Override public void beforeCheckout(D2fsContext d2fsContext, String id) throws Exception { // Custom Application B functionality } @Override public void afterCheckout(D2fsContext d2fsContext, String id) throws Exception { // Custom Application B functionality } }
Conclusion
With this kind of process, D2 will only trigger one listener instead of 2, which is more consistent with D2. We are only executing custom code according to the execution context, and not additional code, which is more reliable and side-effect proof.