Because I often have read that it is not possible to access „Old School“ Java elements in XPages, but never understood the reason why, I have written my own class loader for fun to demonstrate how to do this:
1. I have created a simple Java library:
2. The library contains a single class „LoadedClass„:
The class is very simple. When it is instantiated, it just prompts the current timestamp to the server console:
package ch.hasselba.test;
public class LoadedClass {
public LoadedClass(){
System.out.println("Time: " + System.currentTimeMillis() );
}
}
3. If you inspecting the Java design element, you will notice a attachment with the name %%object.jar%%. That’s the compiled library:
And here is the UNID of the design element for accessing it
4. Now it is time for the class loader (the modified code originally came from Laurentiu Cristofor):
package ch.hasselba.demo;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Hashtable;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
/**
*
* This class implements a simple class loader that can be used to load at
* runtime classes contained in a JAR file.
*
* (P)2000 Laurentiu Cristofor
*
* Modified 2014 by Sven Hasselbach
*
*/
public class JarClassLoader extends ClassLoader {
private Hashtable jarContents;
/**
* Creates a new JarClassLoader that will allow the loading of classes
* stored in a jar file.
*
* @param bos
* ByteArrayOutputStream containing a Jar
* @exception IOException
* an error happened while reading the contents of the jar
* file
*/
public JarClassLoader(ByteArrayOutputStream bos) throws IOException {
// second get contents of the jar file and place each
// entry in a hashtable for later use
jarContents = new Hashtable();
JarInputStream jis = new JarInputStream(new ByteArrayInputStream(bos
.toByteArray()));
JarEntry je;
while ((je = jis.getNextJarEntry()) != null) {
// make sure we have only slashes, we prefer unix, not windows
String name = je.getName().replace('\\', '/');
// get entry size from the entry or from our hashtable
int size = bos.size();
// read the entry
byte[] ba = new byte[size];
int bytes_read = 0;
while (bytes_read != size) {
int r = jis.read(ba, bytes_read, size - bytes_read);
if (r < 0)
break;
bytes_read += r;
}
jarContents.put(name, ba);
}
jis.close();
}
/**
* Looks among the contents of the jar file (cached in memory) and tries to
* find and define a class, given its name.
*
* @param className
* the name of the class
* @return a Class object representing our class
* @exception ClassNotFoundException
* the jar file did not contain a class named
* <code>className</code>
*/
public Class findClass(String className) throws ClassNotFoundException {
// transform the name to a path
String classPath = className.replace('.', '/') + ".class";
byte[] classBytes = (byte[]) jarContents.get(classPath);
if (classBytes == null)
throw new ClassNotFoundException();
return defineClass(className, classBytes, 0, classBytes.length);
}
}
The trick is to ignore the size of the JarEntries and use the size of the whole ByteArrayOutputStream.
5. Now we need to load the design element, extract the Jar and load our test class:
package ch.hasselba.demo;
import ch.hasselba.demo.JarClassLoader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import lotus.domino.Database;
import lotus.domino.Document;
import lotus.domino.EmbeddedObject;
import lotus.domino.Session;
public class JavaLoader {
public JavaLoader(Session session) {
try {
Database db = session.getCurrentDatabase();
// get the design document
Document doc = db
.getDocumentByUNID("6783F550459C81B1C1257C7E007A5D44");
// get the attachment
EmbeddedObject embObj = doc.getAttachment("%%object%%.jar");
FileInputStream fis = (FileInputStream) embObj.getInputStream();
// convert the FileInputStream to a ByteArrayOutputStream
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int data;
while ((data = fis.read()) != (-1)) {
bos.write(data);
}
bos.flush();
// load the Jar with the class loader
JarClassLoader jcl = new JarClassLoader(bos);
// get the class
Class loadedClass = jcl.findClass("ch.hasselba.test.LoadedClass");
// and instantiate it
Object obj = loadedClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
6. Last but not least, a XPage for demonstrating how to use the code:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:button value="Label" id="button1">
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action>
<![CDATA[#{javascript:
importPackage( ch.hasselba.demo );
new ch.hasselba.demo.JavaLoader(session);}]]>
</xp:this.action>
</xp:eventHandler>
</xp:button>
</xp:view>
7. If you open the XPage and click the button…
… you can see the result on the server console:
If you want to access the methods and/or properties of the loaded object, you have do do this with reflection.
P.S. Keep in mind that this article has been posted in the “Quick-n-Dirty” category.
This was a missing spot in the OpenNTF API! We had a classloader that covered the XPages, Java resources, and Jar resources, but I hadn’t yet added Java script libraries. I’ve made good on it now, so in the next release you’ll be able to have a unified classloader to cover all of the Java class stores in the DB.