DFS challenge

Yesterday I read interesting opinion about DFS on ECN:

I would strongly advise against using DFS, you should check if the REST API fits your use case: DFS is built on outdated libraries and it is “difficult” to make it work in certain (most) environments and with any java version newer than 6.0

Unfortunately, it is only a half of truth – at current moment there are no reliable options to exchange data between Documentum and other systems, and the fact that DFS is unreliable does not make REST API robust. So, what is wrong with DFS? I believe “DFS is built on outdated libraries” means following: DFS client libraries depend on ancient version of jaxws-rt, i.e. 2.1.7, and when we try to run DFS client against recent version of jaxws-rt we get something like:

java.lang.NoSuchMethodError: com.sun.xml.ws.api.message.Message.getHeaders()Lcom/sun/xml/ws/api/message/HeaderList;
at com.emc.documentum.fs.rt.impl.pipe.DfsTube.processRequest(DfsTube.java:89)
at com.sun.xml.ws.api.pipe.Fiber.__doRun(Fiber.java:1136)
at com.sun.xml.ws.api.pipe.Fiber._doRun(Fiber.java:1050)
at com.sun.xml.ws.api.pipe.Fiber.doRun(Fiber.java:1019)
at com.sun.xml.ws.api.pipe.Fiber.runSync(Fiber.java:877)
at com.sun.xml.ws.client.Stub.process(Stub.java:463)
at com.sun.xml.ws.client.sei.SEIStub.doProcess(SEIStub.java:191)
at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:108)
at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:92)
at com.sun.xml.ws.client.sei.SEIStub.invoke(SEIStub.java:161)
at com.sun.proxy.$Proxy41.get(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.emc.documentum.fs.rt.context.impl.compat.ClientReflectionServiceInvokerCompat60.invoke(ClientReflectionServiceInvokerCompat60.java:56)
at com.emc.documentum.fs.rt.context.impl.UcfClientInvocationHandler.invoke(UcfClientInvocationHandler.java:52)
at com.emc.documentum.fs.rt.context.impl.SoapClientInvocationHandler.invoke(SoapClientInvocationHandler.java:60)
at com.emc.documentum.fs.rt.context.impl.MtomCompatHandler60SP1.invoke(MtomCompatHandler60SP1.java:60)
at com.emc.documentum.fs.rt.context.impl.HttpSessionInvocationHandler.invoke(HttpSessionInvocationHandler.java:88)
at com.emc.documentum.fs.rt.context.impl.RemoteServiceInterfaceInvocationHandler.invoke(RemoteServiceInterfaceInvocationHandler.java:30)
at com.emc.documentum.fs.rt.context.impl.ReturnedContentTransformationHandler.invoke(ReturnedContentTransformationHandler.java:45)
at com.emc.documentum.fs.rt.context.impl.OperationOptionsHandler.invoke(OperationOptionsHandler.java:74)
at com.emc.documentum.fs.rt.context.impl.ContextThreadLocalInvocationHandler.invoke(ContextThreadLocalInvocationHandler.java:48)
at com.emc.documentum.fs.rt.context.impl.ServiceContextInvocationHandler.invoke(ServiceContextInvocationHandler.java:30)
at com.emc.documentum.fs.rt.context.impl.FileRegistryCleanupHandler.invoke(FileRegistryCleanupHandler.java:24)
at com.sun.proxy.$Proxy25.get(Unknown Source)

which could be fixed via following code (technically it just recompiles the problem com.emc.documentum.fs.rt.impl.pipe.DfsTube#processRequest method agains recent version of jaxws-rt):

/**
 * @author Andrey B. Panfilov <andrey@panfilov.tel>
 */
public class FixDfsTube {

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        for (Class cls : new Class[] {ServiceContextAdapter.class,
            ThreadLocalContextStorage.class, Header.class, StringUtils.class,
            DOMHeader.class, ContentTransferModeUtil.class }) {
            pool.importPackage(cls.getPackage().getName());
        }

        String cls = DfsTube.class.getName();
        CtClass cc = pool.get(cls);
        CtMethod m = cc.getDeclaredMethod("processRequest");
        StringBuilder body = new StringBuilder();
        body.append("{\n");
        body.append("ServiceContextAdapter contextAdapter = (ServiceContextAdapter) ThreadLocalContextStorage\n");
        body.append("        .get();\n");
        body.append("String serviceName = $1.endpointAddress.getURL().getFile();\n");
        body.append("if (serviceName.endsWith(CONTEXT_REGISTRY_SERVICE_NAME)\n");
        body.append("        || serviceName.endsWith(AGENT_SERVICE_SERVICE_NAME)\n");
        body.append("        || contextAdapter == null) {\n");
        body.append("    return super.processRequest($1);\n");
        body.append("}\n");
        body.append("Header header;\n");
        body.append("if (StringUtils.isNotBlank(contextAdapter.getConsolidatedContext()\n");
        body.append("        .getToken())) {\n");
        body.append("    header = new DOMHeader(addTokenToHeader(contextAdapter\n");
        body.append("            .getConsolidatedContext().getToken()));\n");
        body.append("    $1.getMessage().getHeaders().add(header);\n");
        body.append("}\n");
        body.append("\n");
        body.append("if (!contextAdapter.isDeltaEmpty()) {\n");
        body.append("    header = new DOMHeader(\n");
        body.append("            createDeltaContextElement(contextAdapter.getDeltaContext()));\n");
        body.append("    $1.getMessage().getHeaders().add(header);\n");
        body.append("}\n");
        body.append("if (ContentTransferModeUtil.isMtomTransfer(contextAdapter)) {\n");
        body.append("    enableMtom($1);\n");
        body.append("} else {\n");
        body.append("    disableMtom($1);\n");
        body.append("}\n");
        body.append("return super.processRequest($1);\n");
        body.append("}");
        m.setBody(body.toString());

        String resourceName = DfsTube.class.getName().replace(".", "/")
                + ".class";
        URL resourceURL = DfsTube.class.getResource("/" + resourceName);
        JarURLConnection connection = (JarURLConnection) resourceURL
                .openConnection();
        URL jarURL = connection.getJarFileURL();
        String fileName = jarURL.getFile();
        ZipFile zipFile = new ZipFile(fileName);
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(fileName
                + ".patched"));
        for (Enumeration e = zipFile.entries(); e.hasMoreElements();) {
            ZipEntry entryIn = (ZipEntry) e.nextElement();
            if (!entryIn.getName().equals(resourceName)) {
                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);
                }
            } else {
                zos.putNextEntry(new ZipEntry(resourceName));
                zos.write(cc.toBytecode());
            }
            zos.closeEntry();
        }
        zos.close();
    }

}

Enjoy 🙂