Creating a download tracker for Domino

function displayServletCode() {
var e = document.getElementById(‘servlet_code’);
e.style.visibility=’visible’;
e.style.display=’block’;
}

When doing a download tracker for binary content there is some issues that must be addressed – one is the MIME type of the file being downloaded. The MIME type determines how the calling browser determines what to do with the response sent by the download tracker. Since the main content being downloaded is going to be images I need to change the MIME type since the browser would otherwise simply display the image. The easy solution is to set the MIME type to application/octet-stream since it will cause the browser to ask where to save the file.

Another obstacle when handling binary data from an agent in Domino is that the default agent output you can obtain from the AgentBase class in Domino is a java.io.PrintWriter. PrintWriter is for character data and therefore not of much use to me.

You obtain the agent output writer by using the getAgentOutput() method of the AgentBase class from which all agents inherit:

import lotus.domino.*;
import java.io.PrintWriter;

public class JavaAgent extends AgentBase {

   public void NotesMain() {

      try {
         Session session = getSession();
         AgentContext agentContext = session.getAgentContext();

         PrintWriter pw = this.getAgentOutput();

      } catch(Exception e) {
         e.printStackTrace();
      }
   }
}

The getAgentOutput()-method is the only method to agent output which is discussed in the Domino 6.5.x help database. As mentioned above a PrintWriter isn’t of much use to me – what I really needed was a way to obtain a java.io.OutputStream implementation. Although not mentioned in the documentation I thought Lotus might have added a way to get a such so I wrote an agent to peek at the available methods as reported by the JVM using reflection:

import lotus.domino.*;
import java.io.PrintWriter;
import java.lang.reflect.*;

public class JavaAgent extends AgentBase {

   public void NotesMain() {

      try {
         Session session = getSession();
         AgentContext agentContext = session.getAgentContext();

         Method[] methods = this.getClass().getMethods();
         for (int i=0; i<methods.length; i++) {
            // get info about the method
            Class[] param = methods[i].getParameterTypes();
            Class rt = methods[i].getReturnType();
            Class[] exceptions = methods[i].getExceptionTypes();
            int mod = methods[i].getModifiers();

            System.out.print(Modifier.toString(mod));
            if (rt.equals(Void.class)) {
               System.out.print(" void ");
            } else {
               System.out.print(" " + rt.getName() + " ");
            }
            System.out.print(methods[i].getName());
            System.out.print("(");
            for (int j=0; j<param.length; j++) {
               if (j>0) System.out.print(", ");
               System.out.print(param[j].getName());
            }
            System.out.print(")");
            for (int j=0; j<exceptions.length; j++) {
               if (j>0) System.out.print(", ");
               System.out.print(exceptions[j].getName());
            }
            System.out.println(";");

         }

      } catch(Exception e) {
         e.printStackTrace();
      }
   }
}

As one might have guessed there is a method called getAgentOutputStream() (highlighted in bold) that returns an OutputStream as shown in the snippet below:

public void NotesMain();
public final void startup(lotus.domino.AgentInfo);
public final void runNotes()lotus.domino.NotesException;
public lotus.domino.Session getSession();
public static lotus.domino.Session getAgentSession();
public boolean isRestricted();
public java.io.PrintWriter getAgentOutput();
public java.io.OutputStream getAgentOutputStream();
public void setDebug(boolean);
public void setTrace(boolean);
...
...

Well this was great so I went ahead and coded the actual agent. Getting at the correct document and attachment based on supplied parameters is straight forward using the Domino Java classes. Once I had a hold on the correct attachment as an EmbeddedObject object I used the getInputStream() method to get an InputStream for the attachment. Until this point all is well and good.

From here on it should be quite simple to set the MIME type via the Content-Type HTTP header and read the bytes from the InputStream and writing them to the OutputStream. Or so I thought…

import lotus.domino.*;
import java.io.*;

public class JavaAgent extends AgentBase {

   public void NotesMain() {

      try {
         Session session = getSession();
         AgentContext agentContext = session.getAgentContext();
         EmbeddedObject o = null;

         // code to get at the correct attachment as an EmbeddedObject
         // object has been left out

         // get output stream and set mime type
         OutputStream agent_out = this.getAgentOutputStream();
         agent_out.write("Content-Type: application/octet-streamn".getBytes());

         // get an input stream for the attachment
         InputStream o_in = o.getInputStream();

         // write bytes from input stream to output stream
         byte[] bytes = new byte[1];
         while (o_in.read(bytes) > -1) {
            agent_out.write(bytes);
         }

         // flush and close
         agent_out.flush();
         agent_out.close();

      } catch(Exception e) {
         e.printStackTrace();
      }
   }
}

While the above code compiles and run just fine there is a problem. For some reason I can’t figure out not all the bytes actually reach the browser. Substituting the OutputStream from Domino (agent_out) with a FileOutputStream I can write the attachment to disk just fine so reading and writing is well. The problem must lie with the Domino OutputStream.

Well I’ll save you more ramblings about my debugging attempts but after a LOT of troubleshooting I ended up rewriting the code as a servlet. Once I ported the above code to a servlet it works like a charm when running it from Tomcat (click here to display the code).

This just strengthened my observation that the problem was with the Domino OutputStream implementation. Again using reflection I could find out exactly which kind of OutputStream Lotus was using:

...
// get output stream and set mime type
OutputStream agent_out = this.getAgentOutputStream();
System.out.println(agent_out.getClass().getName());
Class parent = agent_out.getClass().getSuperclass();
while (null != parent) {
   System.out.println(parent.getName());
   parent = parent.getSuperclass();
}
...

The result is a PrintOutputStream as shown by the result below:

java.io.PrintStream
java.io.FilterOutputStream
java.io.OutputStream
java.lang.Object

A java.io.PrintStream will use the default encoding when printing characters so it might be because of this my output data gets corrupted.

Conclusion
Well while the solution using a servlet works it isn’t perfect since it is so much easier to configure an agent than a servlet. Looking at the bright side a servlet provided superiour performance so it isn’t so bad I guess.

To summarize I was happy when I found the getAgentOutputStream() method but was equally disapointed when I found out that it didn’t work. To be fair I don’t know whether it is my code or whether it is something in the obtained OutputStream that teases me. I just hope the above will save someone else from spending precious time fiddeling around with binary data from a Domino web agent and that they will go directly to the servlet.

3 thoughts on “Creating a download tracker for Domino”

  1. Well sure I could have done the redirecting solution and I was seriously considing it due to its simplicity but then there is the issues of cleaning up after the downloads etc. The reason why I opted for the servlet approach was to minimize the housecleaning chores needed.

    Like

  2. Hello from germany and Thanks !

    I am fighting with the same Problems at the moment. Your article let me save time … the agent-solution is buggy
    and the servlet-solution is not a workaround for my customer … F… NOTES …

    Like

Comments are closed.