Loading from the stream

For as long as I have been searching for, I have not found anything like a JarInputStreamClassLoader. That is, or at least that would have been in my intent, a ClassLoader that takes a stream corresponding to a Jar file and load classes from that. Why would I need something like that?, you may ask. Because I want to send a jar file to a server (via a servlet, a web service, or whatever is reachable by TCP/IP) as a stream of bytes, and have the server dynamically load classes from it (why do I want to do that? I will explain later). Maybe it really does not exist anywhere or, more probably, I have not searched hard enough. If you know about anything like that, please let me know in the comments.

However, the fact is: I decided to build it myself since I was actually doing this for training and learning purposes and, most important, I took it as a challenge: aren’t you really preventing me from doing it, Java, are you?

Actually there is the very useful java.net.URLClassLoader that works perfectly with the URL of a jar file. So, if you can receive the stream on the server and save it locally as a jar file, you are done. The problem was that I wanted to accomplish the task in a Google Application Engine webapp, an environment that does not let you to write on the local file system (unless you use their storage API, but it is basically your database interface and I did not want to use it for this task). So I needed to do the thing in memory. I can better explain the feature that I wanted to obtain with a JUnit test case:

	@Test
	public void testLoadStupidClass() throws FileNotFoundException, IOException, ClassNotFoundException
	{
		String pathname = "/Temp/stupidDTO-lib.jar";
		File inputFile = new File(pathname);
		JarInputStream jarStream = new JarInputStream(new FileInputStream(inputFile));
		JarInputStreamClassLoader loader = new JarInputStreamClassLoader(jarStream);
		Class<?> clazz = loader.loadClass("org.xxx.stupiddto.StupidPOJO");
		assertTrue(clazz != null);
	}

Imagine that instead of loading the stream from a file you already have a stream, in the form of an input stream of a servlet request, and that’s it:

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
	{
		StringBuilder builder = new StringBuilder("loading...");
		JarInputStream jarStream = null;
		try
		{
			jarStream = new JarInputStream(new ByteArrayInputStream(ServletUtils.getHttpRequestBody(req.getInputStream())));
...

of course, it takes a bit of work to extract the jar data from the http request InputStream, and the work is performed by the ServletUtils.getHttpRequestBody (I’m not gonna show you the implementation here, since I have done that in a quite dirty way and I am not proud of it. I will return on that later).
The JarInputStreamClassLoader itself is quite simple once you have uderstood how to cycle on the entries of a JarInputStream and to properly load the raw bytes of each class. The hard thing, for me, has actually been extracting the jar data from the InputStream of the request.
Here is the complete code of the JarInputStreamClassLoader that I implemented:

package org.xxx.mockgen.web.classloader;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * Loads a class from a byte array representing the content of a jar file
 *
 */
public class JarInputStreamClassLoader extends ClassLoader
{
	private JarInputStream inputStream;
	protected RawClassList rawClasses;

	protected RawClassList getRawClasses()
	{
		return rawClasses;
	}

	protected void setRawClasses(RawClassList rawClasses)
	{
		this.rawClasses = rawClasses;
	}

	protected JarInputStream getInputStream()
	{
		return inputStream;
	}

	protected void setInputStream(JarInputStream inputStream)
	{
		this.inputStream = inputStream;
	}

	public JarInputStreamClassLoader(JarInputStream jarStream) throws IOException
	{
		JarEntry entry = null;
		// JarInputStream stream = getInputStream();
		setRawClasses(new RawClassList());
		while ((entry = jarStream.getNextJarEntry()) != null)
		{
			String entryName = entry.getName();
			int lastIndexOf = entryName.lastIndexOf(".class");
			String classCandidateName = "";
			if(lastIndexOf != -1)
			{
				classCandidateName = entryName.replace('/', '.').substring(0, entryName.lastIndexOf(".class"));
			}
			if (!classCandidateName.isEmpty())
			{
				ByteArrayOutputStream classBytesStream = new ByteArrayOutputStream();
				byte[] read = new byte[256];
				while (jarStream.read(read) != -1)
				{
					classBytesStream.write(read);
				}

				byte[] rawClassBytes = classBytesStream.toByteArray();
				RawClass rawClass = new RawClass(rawClassBytes, classCandidateName);
				getRawClasses().add(rawClass);
			}
		}
	}

	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException
	{
		Class<?> clazz = null;

		for (RawClass rawClass : getRawClasses())
		{
			String className = rawClass.getName();
			if (name.equals(className))
			{
				byte[] rawClassBytes = rawClass.getRawClassBytes();
				clazz = defineClass(name, rawClassBytes, 0, rawClassBytes.length);
				break;
			}
		}
		if (clazz == null)
		{
			throw new ClassNotFoundException("Class " + name + " not found.");
		}
		return clazz;
	}

	public List getAvailableClasses()
	{
		return getRawClasses().getClassesNames();
	}
}

The RawClass class is a very simple one: it stores the name and the raw bytes of each class entry found in the jar input stream. The RawClassList class is nothing more than a typed wrapper of ArrayList.
Lines from 43 to 65 of JarInputStreamClassLoader are the key: it iterates through the entries of the jar input stream and, if it finds that the entry is a java class, it stores it in a RawClass object for future use. The remainder of the class (method findClass) is just standard Java class loading.

Advertisements

Where do you come from?

Sometimes it’s hard to figure out where a Java class has been loaded from. In a typical Enterprise System, there could be tens of web applications, maybe contained in an enterprise portal, running on several kinds of web and application servers, clustered over hundreds of servers. The common library used at run time can contain thousands of jars. A Java web application can load classes from several sources (his private WEB-INF/lib, the web server common library, some custom shared library, etc…), each of which can be associated with a different ClassLoader. Then it is no surprise that, if you work for years in an environment like this, you come to hate violently the class loading mechanism of Java, and every NoClassDefFoundError is seen as a voodoo curse.

Here’s a trick that I usually use when I work with Eclipse or an IDE that derives from it (the infamous RAD, for example). You have to be able to debug your program in order to perform the trick on the fly.

Place a breakpoint in an appropriate position (for example, just after some “new” instruction of an object whose class you want to know about). Then open the “Display” view. To do so, go to Windows ->Show view -> Other… . Then select the “Display” view (see figures below).

Cattura

Cattura2

The “Display” view lets you input Java instructions that are interpreted interactively and affect the execution of the program. Now you can discover where a certain class definition is taken from. Just type something like

objectIWantToKnowAbout.getClass().getProtectionDomain().getCodeSource().getLocation()

ctrl+a (select all) and ctrl+shift+d (Display), and you will get the output of the invokation:

(java.net.URL) file:/C:/Users/currentUser/workspace/myProject/bin/

This always gives you the exact location (i.e. the folder, as in this example, or the jar file) from which the class definition of the object has been loaded. You can also discover a bunch of fancy information about your object, such as the ClassLoader that has been used to load the class.

It might look like a very basic thing that every Java developer should know, but it is not. In fact, I cannot recall all the times that this activity saved me a lot of troubles and made me look like a smartass.

If you cannot debug the application that gives you problems then, well, i guess your only option is to insert some smart debug trace and wait for the next production release.