Pages

Friday, January 13, 2017

Invoking the dm_method from an action plugin to access the manifest of the jar encoding the method

Suppose the jar implemting some functionality is placed somewhere in WEB-INF/lib of some war deployed in some server. And there is a need for the jar to know the contents of its own manifest.mf. Standard methods such as Class.getResource("/META-INF/MANIFEST.MF") can only be chance might return the right url. Usually it will be the manifest of the file that was loaded first by the classloader.

To illustrate how the right manifest could be recovered, I will consider the setup where a dm_method installed as a jar in Documentum Java Methods Server. This jar is supposed to function as an important method. Additionally, administrators need to be able to easily access its description, e.g. build number or time, contained in manifest. For example, this information can be displayed in a popup invoked by clicking on a custom menu item in D2.

When a method jar depends on other classes in the classpath, the jar should be placed in WEB-INF/lib of DmMethods.war. Some people put method jars into dba/java_methods. They work only if the jars have no dependencies in the classpath.

The following class load the manifest specifically from the hosting jar. Obviously the manifest attributes should be filtered so that only pertinent values, such as commit hash or build time, are returned.

public class ManifestLoader {

    // escaped new line so that new lines can be passed to javascript
    public static String NEW_LINE = "\\n";
    private static ManifestLoader instance = new ManifestLoader();

    public static ManifestLoader getInstance() {
        return instance;
    }

    public String getVersionInfo() {
        Attributes manifestAttrs=loadManifestAttributes();
        StringBuilder sb = new StringBuilder();
        // return all attributes, but normally here should be some filter for the pertinent attributes
        for (Object o : manifestAttrs.keySet()) {
            sb.append(o + ": " + manifestAttrs.get(o) + NEW_LINE);
        }
        return sb.toString();
    }
    
    Attributes manifestAttrs;

    Attributes loadManifestAttributes() {
        // load manifest only once per runtime
        if (manifestAttrs == null) {
            manifestAttrs = getPersonalManifestInJBoss().getMainAttributes();
        }
        return manifestAttrs;
    }

    Manifest getPersonalManifestInJBoss() { // works only in JBOSS
        Manifest manifest = new Manifest();
        try {
            // determine the url of this jar
            URL thisJarUrl = getClass().getResource(getClass().getSimpleName() + ".class");
            // convert the url into the filename
            String jarFileName = thisJarUrl.toString().replaceFirst("vfs:/", "jar:file:/");
            String jarExtention = ".jar/";
            jarFileName = jarFileName.substring(0, jarFileName.indexOf(jarExtention) + jarExtention.length()) + "!/";
            // open the jar to extract its contents
            URL jarUrl = new URL(jarFileName);
            JarURLConnection jarConnection = (JarURLConnection) jarUrl.openConnection();
            // here we need only manifest
            manifest = jarConnection.getManifest();
        } catch (IOException ex) {
        }
        return manifest;
    }
}

Below is the simplified class implementing IDmMethod so that the jar can be registered as Documentum dm_method. Note, unlike retrieving the manifest value, the principal skipped here long running functions should by executed asynchronously in this dm_method. When the method is invoked by D2 custom action plugin, it returns the contents of the manifest as an error message. Note, throwing an exception is the only way to pass a message from the invoked dm_method to the invoking dql statement. In the dql statement result collection the message will be stored as error_message attribute value.

public class InvokeMethodFromPlugin implements IDmMethod {

    public static final String INFO_KEY = "info";

    public void execute(Map params, OutputStream out) throws Exception {
        String[] infos = (String[]) params.get(INFO_KEY);
        if (infos != null) {
            throw new RuntimeException(ManifestLoader.getInstance().getVersionInfo());
        }
    }
}

Last, let's consider a simplified D2 action plugin that invokes the method and relays the message to D2 where it can be displayed in javascript alert.

public class LaunchMethodPlugin implements IPluginAction, ID2fsPlugin {

    public static final String INFO_KEY = "info";

    public List<Attribute> getInfo(D2fsContext context) throws D2fsException, DfException {
        IDfSession curSession = context.getSession();
        String dql = "EXECUTE do_method WITH METHOD='MethodName', ARGUMENTS='-" + INFO_KEY + " true'";
        String msg = executeDql(dql, curSession);
        List<Attribute> result = new ArrayList<Attribute>();
        result.add(AttributeUtils.createAttribute("result", msg));
    }

    String invokeMethod(String dql, IDfSession session) throws DfException {
        String msg = null;
        IDfCollection col = null;
        try {
            col = new DfQuery(dql).execute(session, DfQuery.DF_EXEC_QUERY);
            while (col.next()) {
                msg = col.getString("error_message");
            }
        } finally {
            if (col != null) {
                col.close();
            }
        }
        return msg;
    }
}

No comments:

Post a Comment