Degradation of Documentum developers

Pro Documentum

About two months ago I was talking with my former colleague, and he was complaining that “modern” documentum developers fails to perform basic CS routines like creating/modifying jobs or acls using IAPI scripts – instead of leveraging functionality provided by IAPI/IDQL they are relying on functionality provided by Composer or xCP designer, and in most cases results what they get do not conform their expectations (just because both Composer or xCP designer are poor tools). What do you think what is the reason of such degradation? In my opinion it is caused by the fact that EMC has stopped to publish Content Server API Reference Manual

View original post

javassist

Six months ago Marian Caikovski have shared his experience with java decompilers and advertised CFR decompiler: Decompiling jars obfuscated with AspectJ (e.g. D2FS4DCTM-WEB-4.5.0.jar or dfc.jar) – in free clicks from CFR decompiler page (FAQ -> Procyon / Java Decompiler -> Konloch/bytecode-viewer) you may find another cool project indented to provide universal GUI for java decompilers – Konloch/bytecode-viewer:

Well, why is this blogpost named “javassist”? The problem is I do not trust decompilers because some of them produce completely wrong results, for example, I have noticed that a lot of people do like jd-gui decompiler, because it has cool GUI, but try to decompile following code in jd-gui:

public final class Test { 
    public static void main(String[] argv) throws Exception {
        int i = 0;
        while (++i > 0) {
            try {
                if (i < 10) {
                    throw new Exception("xxx");
                }
                System.out.print("xxx");
                break;
            } catch (Exception e) {
                if (!(i < 10)) {
                    throw e;
                }
            }
        }
    }
}

and you will get something like:

public final class Test
{
  public static void main(String[] paramArrayOfString)
    throws Exception
  {
    int i = 0;
    while (true) { i++; if (i <= 0) break;
      try {
        if (i < 10) {
          throw new Exception("xxx");
        }
        System.out.print("xxx");
      }
      catch (Exception localException) {
        if (i >= 10)
          throw localException;
      }
    }
  }
}

which is completely wrong because decompiled code contains infinite loop while original one does not 😦 Moreover, in general, if we want to changefix the behaviour of buggy class we do not need to reconstruct the original source code of the whole class – in the worst case we just need to reconstruct the source code of buggy method, and in the most cases we do not need to reconstruct source code at all, and javassist helps a lot there, let me provide some examples.

When displaying objects in a grid Webtop likes to throw annoying exceptions like:

at com.documentum.fc.client.impl.docbase.DocbaseExceptionMapper.newException(DocbaseExceptionMapper.java:49)
 at com.documentum.fc.client.impl.connection.docbase.MessageEntry.getException(MessageEntry.java:39)
 at com.documentum.fc.client.impl.connection.docbase.DocbaseMessageManager.getException(DocbaseMessageManager.java:137)
 at com.documentum.fc.client.impl.connection.docbase.netwise.NetwiseDocbaseRpcClient.checkForMessages(NetwiseDocbaseRpcClient.java:310)
 at com.documentum.fc.client.impl.connection.docbase.netwise.NetwiseDocbaseRpcClient.applyForObject(NetwiseDocbaseRpcClient.java:653)
 at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection$8.evaluate(DocbaseConnection.java:1370)
 at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.evaluateRpc(DocbaseConnection.java:1129)
 at com.documentum.fc.client.impl.connection.docbase.DocbaseConnection.applyForObject(DocbaseConnection.java:1362)
 at com.documentum.fc.client.impl.docbase.DocbaseApi.parameterizedFetch(DocbaseApi.java:107)
 at com.documentum.fc.client.impl.objectmanager.PersistentDataManager.fetchFromServer(PersistentDataManager.java:201)
 at com.documentum.fc.client.impl.objectmanager.PersistentDataManager.getData(PersistentDataManager.java:92)
 at com.documentum.fc.client.impl.objectmanager.PersistentObjectManager.getObjectFromServer(PersistentObjectManager.java:355)
 at com.documentum.fc.client.impl.objectmanager.PersistentObjectManager.getObject(PersistentObjectManager.java:311)
 at com.documentum.fc.client.impl.session.Session.getObject(Session.java:946)
 at com.documentum.fc.client.impl.session.SessionHandle.getObject(SessionHandle.java:652)
 at com.documentum.webcomponent.library.actions.ExportAction.queryExecute(Unknown Source)
 at com.documentum.web.formext.action.ActionService.queryExecute(Unknown Source)
 at com.documentum.web.formext.control.action.ActionMultiselect.getMultiselectActionExecuteTable(ActionMultiselect.java:615)
 at com.documentum.web.formext.control.action.ActionMultiselectTag.renderEnd(ActionMultiselectTag.java:185)
 at com.documentum.web.form.ControlTag.doEndTag(Unknown Source)

the root cause of such behaviour is following: webtop selects a list of objects to display and after that it calculates preconditions, the problem is when webtop calculates preconditions the list of objects is already not actual – somebody might delete some objects or change permissions and, hence, some preconditions throw an exception. How to fix this behaviour? I do think it is obvious (but not for talented team) that queryExecute method of ActionService class must return false instead of throwing exceptions, but if we try to decompile ActionService class we will find that it contains about 500 lines of code and, moreover, decompilers do not produce a “beautiful” code – it will take a couple of hours to reconstruct original source code of ActionService class, meanwhile the javassist solution looks extremely robust and simple:

public class ActionServiceFix {

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        for (Class cls : new Class[] {Trace.class }) {
            pool.importPackage(cls.getPackage().getName());
        }

        CtClass cc = pool.get(ActionService.class.getName());
        for (Class cls : new Class[] {DfException.class }) {
            pool.importPackage(cls.getPackage().getName());
        }
        CtMethod queryExecute = cc.getDeclaredMethod("queryExecute",
                pool.get(new String[] {ActionService.ActionDef.class.getName(),
                    ArgumentList.class.getName(), Context.class.getName(),
                    Component.class.getName(), boolean.class.getName() }));
        queryExecute.addCatch("Trace.error(null,t);\n;return false;",
                pool.get(Throwable.class.getName()), "t");
        File f = File.createTempFile(ActionService.class.getSimpleName(), ".class");
        FileOutputStream os = new FileOutputStream(f);
        os.write(cc.toBytecode());
        os.close();
        System.out.println(f.getAbsolutePath());
    }

}

Another concurrency issue I already complained about: implementation of aspects in DFC causes DM_OBJ_MGR_E_UNABLE_TO_FETCH_CONSISTENT_OBJECT_SNAPSHOT errors:

public class DfcFix {

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        for (Class cls : new Class[] {}) {
            pool.importPackage(cls.getPackage().getName());
        }

        String POM = PersistentObjectManager.class.getName().replace(".", "/")
                + ".class";
        URL resourceURL = DfcFix.class.getResource("/" + POM);
        JarURLConnection connection = (JarURLConnection) resourceURL
                .openConnection();
        URL jarURL = connection.getJarFileURL();
        String fileName = jarURL.getFile();
        ZipFile zipFile = new ZipFile(fileName);
        String out = fileName + ".patched";
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(out));
        for (Enumeration e = zipFile.entries(); e.hasMoreElements();) {
            ZipEntry entryIn = (ZipEntry) e.nextElement();
            if (entryIn.getName().equals(POM)) {
                zos.putNextEntry(new ZipEntry(POM));
                zos.write(getPOMFixRename(pool));
            } else {
                zos.putNextEntry(new ZipEntry(entryIn.getName()));
                InputStream is = zipFile.getInputStream(entryIn);
                byte[] buf = new byte[1024];
                int len;
                while ((len = is.read(buf)) > 0) {
                    zos.write(buf, 0, len);
                }
            }
            zos.closeEntry();
        }
        zos.close();
        System.out.println(out);
    }

    private static byte[] getPOMFixRename(ClassPool pool) throws Exception {
        CtClass cc = pool.get(PersistentObjectManager.class.getName());
        for (Class cls : new Class[] {DfException.class, DfExceptions.class, }) {
            pool.importPackage(cls.getPackage().getName());
        }

        CtMethod original = cc.getDeclaredMethod("getObject");
        CtMethod newMethod = CtNewMethod.make(AccessFlag.PUBLIC,
                original.getReturnType(), original.getName(),
                original.getParameterTypes(), original.getExceptionTypes(),
                null, original.getDeclaringClass());
        original.setName("doGetObject");
        original.setModifiers(AccessFlag.setPrivate(original.getModifiers()));
        StringBuilder body = new StringBuilder();
        body.append("{\n");
        body.append("DfException ex = null;\n");
        body.append("for (int i = 0; i < 10; i++) {\n");
        body.append("    try {\n");
        body.append("        return doGetObject($$);\n");
        body.append("    } catch (DfException e) {\n");
        body.append("        ex = e;\n");
        body.append("        if (DfExceptions.isFetchSoft(e)) {\n");
        body.append("            DfLogger.debug(this, \"Got soft exception \"\n");
        body.append("                    + \"on {0} iteration\", new Object[] {i + 1 }, e);\n");
        body.append("            continue;\n");
        body.append("        }\n");
        body.append("        throw ex;\n");
        body.append("    }\n");
        body.append("}\n");
        body.append("throw ex;\n");
        body.append("\n}");
        newMethod.setBody(body.toString());
        cc.addMethod(newMethod);
        return cc.toBytecode();

    }

}