
When you run an agent in Notes/Domino the agent class is loaded into memory by a classloader (for more information about what a classloader actually is please refer to this previous post). In the case of Notes and Domino this classloader is the AgentLoader class from the lotus.domino package (that is lotus.domino.AgentLoader).
This is also the classloader which is responsible for loading all the other classes your agent and auxillary classes may use during their execution.
You can query any class in Java about which classloader actually loaded the class into memory. You do this using the getClassLoader()-method of the class instance as shown below:
import lotus.domino.*;
public class JavaAgent extends AgentBase {
public void NotesMain() {
try {
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
// get class for the current object
Class clazz = this.getClass();
// get classloader that loaded this agent class
ClassLoader cl = clazz.getClassLoader();
System.out.println("Default ClassLoader: " + cl.getClass().getName());
} catch(Exception e) {
e.printStackTrace();
}
}
}
The output of this agent in the Java Debug Console will be:
Default ClassLoader: lotus.domino.AgentLoader
In Java every classloader may be part of a hierarchy and hence a classloader may have a parent classloader. The only classloader that never has a parent is the top classloader called the bootstrap classloader which is installed by the JVM when initialized. The bootstrap classloader cannot be substituted in the case of Notes/Domino.
If a classloader is asked to load a class and it cannot find it, the request is delegated to the parent classloader if one is present. To demonstrate this we first need an extra classloader – let’s write our own classloader that loads classes from outside the Notes/Domino system classpath. Writing ones own classloader is actually VERY simple. You simply have to extend the java.lang.ClassLoader class and override one method. The code below shows how to write a classloader that loads classes from the root of the C-drive:
import java.io.*;
public class MyClassLoader extends ClassLoader {
public MyClassLoader() {
super();
}
public MyClassLoader(ClassLoader cl) {
super(cl);
}
public Class findClass(String name) throws ClassNotFoundException {
try {
// use utility method to load class bytes from
// the root of the C-drive
byte[] b = this.loadClassData(name);
return this.defineClass(name, b, 0, b.length);
} catch (Exception e) {
throw new ClassNotFoundException();
}
}
private byte[] loadClassData(String name) throws Exception {
// open a the requested file and read the bytes into an array
InputStream in = new BufferedInputStream(
new FileInputStream("c:\" + name + ".class"));
byte[] raw_classdata = new byte[1024];
int length = in.read(raw_classdata);
// since the byte array must be exactly the
// correct length we copy the bytes into a
// new array
byte[] classdata = new byte[length];
for (int i=0; i<length; i++) {
classdata[i] = raw_classdata[i];
}
// return the byte array
return classdata;
}
}
As you can see it is quite easy and only the imagination sets limits as to where you could load classes from in your agents. You could actually write a classloader to load classes from a central class repository using some network protocol of your choosing.
Since we need some classes to load using our new classloader I have written two very simple classes called OutsideClasspath and OutsideClasspath2 as shown below. The classes should be compiled using the javac-tool or using Eclipse and placed in the root of your C-drive. If you do not want to compile the classes yourself you can download them using the links below.
public class OutsideClasspath { public OutsideClasspath() { ClassLoader cl = this.getClass().getClassLoader(); System.out.println("Constructor of OutsideClasspath class " + "(loaded by: " + cl.getClass().getName() + ")..."); // load another class OutsideClasspath2 oc2 = new OutsideClasspath2(); System.out.println(oc2.echo("Hello from OutsideClasspath")); } } public class OutsideClasspath2 { public OutsideClasspath2() { ClassLoader cl = this.getClass().getClassLoader(); System.out.println("Constructor of OutsideClasspath2 class " + "(loaded by: " + cl.getClass().getName() + ")..."); } public String echo(String echo) { return "Echo: " + echo; } }
Using our new classloader is also simple. We simply instantiate it and pass the bootstrap classloader as the parent classloader in the constructor.
import lotus.domino.*;
import java.util.*;
public class JavaAgent extends AgentBase {
public void NotesMain() {
try {
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
// get bootstrap classloader
ClassLoader cl = this.getClass().getClassLoader();
System.out.println("Default ClassLoader: " + cl.getClass().getName());
// get classloader of the agent and show class name
System.out.println("Agent class loaded by: " +
this.getClass().getClassLoader().getClass().getName());
// create our own classloader passing the
// bootstrap classloader in the constructor
cl = new MyClassLoader(cl);
Class c = cl.loadClass("OutsideClasspath");
Object o = c.newInstance();
// create a java.util.Date object using our own classloader
// to demonstrate the hierarchical nature and delegation
// model of classloaders
Class clazz_date = cl.loadClass("java.util.Date");
Date d = (Date)clazz_date.newInstance();
System.out.println(d);
} catch(Exception e) {
e.printStackTrace();
}
}
}
The output of the agent is as follows:
Default ClassLoader: lotus.domino.AgentLoader Agent class loaded by: lotus.domino.AgentLoader Constructor of OutsideClasspath class (loaded by: MyClassLoader)... Constructor of OutsideClasspath2 class (loaded by: MyClassLoader)... Echo: Hello from OutsideClasspath Sat Apr 01 22:13:49 CEST 2006
The output demonstrates a couple of things:
- The agent class is loaded by the bootstrap classloader.
- That we are able to load classes using our own classloader what is we can load and instantiate an instance of the OutsideClasspath class.
- That classes use the classloader that loaded them to load any classes they need. This is demonstrated by the fact that the OutsideClasspath2 class is automatically loaded by MyClassLoader when the OutsideClasspath class creates and instance of it.
- That classloaders delegate the loading of classes to their parent if they cannot find the class. This is demonstrated by the fact that the request to load java.util.Date is delegated to the bootstrap classloader.
I hope this post has demonstrated the power of classloaders and how you can easily write one that suits your purpose.
Please note:
- Even if we had not supplied the bootstrap classloader in the constructor of the MyClassLoader class a ClassNotFoundException exception would not have been thrown at runtime when asked to load the java.util.Date class. This is because the bootstrap classloader is always asked as a last resort. Setting the parent classloader is only important if you build your own hierarchy of classloaders as is the case in J2EE application servers.
- The MyClassLoader class should be part of the agent if you decide to try the code out for yourself in Domino Designer.
Interesting Read.
I had wondered what the difficulty would be in attempting to implement something like a class loader that would allow tomcat to access a replicated servlet/jsp application that is stored in Notes. It doesn’t seem like it would be that difficult now.
Well the classloaders used in Tomcat are just like the ones I describe in the post. The major difference is how the hierarchy of classloaders in an application server is structured compared to the flat nature of normal classloading in Notes/Domino agents.
One problem I have encountered when trying to play with class loaders in domino is that in earlier versions of the Notes client, the default security settings are too strict. On a 6.5.1 client, I get a SecurityException, java.lang.RuntimePermission “getClassLoader” when I try to execute Class.getClassLoader(). On a 6.5.4 client, the same code works fine.
It seems that the default security settings were changed between those two versions of the Notes client to allow access to the class loader.
You can get around this by editing the java.policy file on the client, but this is not practical for a large number of users.
By the way, I would love to be told I am wrong on this; it would really help me to be able to get the class loader!
Well I can argue it both ways as to whether I think it is okay to restrict access to the bootstrap classloader for agents for security reasons. Of cause it makes it very difficult to get a property file since you’ll normally use the classloader for that.
I think that there are some areas in the Java implementation in Notes/Domino that are somewhat questionable for Java programmers coming into Notes/Domino that “native” Notes programmers will never realize. Property file access for example is a good example since a Notes programmer would probably use a profile document for this kind of information.
The good thing about the newer releases of Notes/Domino is that it’s a “real” JVM so we can customize the policy file etc. to suit our likings – the problem is however, as you state, that it isn’t really deployable across a large user base without resorting to login scripts etc.
I’m not sure I understand your point about property files. Are your referring to java.util.Properties? I don’t see the connection with ClassLoaders…
It goes to whether it is possible to get a reference to the current ClassLoader. I normally get read a properties file by getting the ClassLoader and then use the getResourceAsStream() method to read populate the Property object as follows:
Class clazz = this.getClass(); InputStream in = clazz.getClassLoader().getResourceAs Stream("somefile.properties"); Properties props = new Properties(); props.load(in);If I’m not able to get at the ClassLoader I cannot use the above stunt.
Mikkel, I’m not sure if I’m missing the point in your latest comment above, but it’s possible to get the stream like this:
InputStream in = getClass().getResourceAs Stream(“somefile.properties”);
Can’t we use this if classloader is the problem? This method is different from java.lang.Class.getResourceAsStream, which uses a class loader.
More information on differences (a bit old though) here .
In LotusNotes will it be a good idea to have a custom design element under Shared resources (like image resources), to store the properties file as well as locale information for ResourceBundles?
We would then need a custom class to load those properties file from there. Wild idea? Wiered? what do you think?
Ok. One thing I forgot when I posted my previous comment is, the need to maintain the package hierarchy and resource files within it – may not work well within shared resources. I may need to go for a custom method, with documents storing property file and getting that as stream. It will get messy with document needing to maintain property files with package hierarchy (stored as a field value) etc and a custom code to lookup the view based on the package structure (package name being the key).
I wasn’t aware of the getResourceAsStream() method in the Class object – I have always been using the ClassLoader equivalent. Judging from the Javadoc the Class.getResourceAsStream() should be better since it doesn’t throw an exception although I guess it could just return null. I don’t know if it returns null if the access to the ClassLoader is restricted as in Notes.
As for maintaining properties files as a shared resource I don’t think it is a good idea since it it difficult to get at the contents. I would guess one would have to go through the same hoops as with image resources as described on this site previously. I have used another approach for ResourceBundles previously where I did an implementation that read the key/value pairs from a lookup view in Notes. That made it easy to maintain the values and add new ones.
There is another link worth reading here . I have not yet tested this correctly though. If I keep the properties file in Java Library, both the approach work just fine without the getClassLoader() runtime security permission.
Also, from this previous link I posted earlier, this quote is interesting:
“To slightly complicate the picture, java.lang.Class’s getResourceAsStream() instance method can perform package-relative resource searches. In general, there’s no need to use this method if you are not planning to use package-relative resource naming in code.”
Sorry for the confusion, classloader approach seems to be fine for most of the cases; anyway I think it’s good to know the options 🙂