What’s in your classpath?
Tuesday, June 20th, 2006On the mojo developper list, someone was searching for a way to find information about the jars available to a particular classloader. In particular, as all jars produced by maven contain a pom.properties file, it would be useful for debugging purposes to be able to fetch all these properties files.
This little blog entry presents JarInfo, a little class to help you retrieve this information. For the impatient, here’s a screenshot of the resulting information displayed in a JSP.
problem and solution
We first need to identify all jars available to the classloader. We cannot rely on the JVM classpath, available through the java.class.path System properties. While in very simple cases this classpath may contain what you are looking for, a more complex application, especially one involving some sort of container, will hide the real set of jars used by your application behind a classloader hierarchy.
Some particular ClassLoader implementations let you retrieve the information directly. An example is the getURLs() method in the URLClassLoader class. But of course, there’s no similar method in the ClassLoader abstract class: ClassLoaders have been designed with a more generic idea in mind! So we are going to need a little trick to find a generic solution.
The initial idea of the mojo developer was to use the ClassLoader#getResources() call to try to locate all pom.properties files. There are 2 major issues with that:
- the location of a
pom.propertiesin a jar file is project dependent. Maven currently stores them underMETA-INF/maven/groupId/artifactId/pom.properties - not all Jar files have a
pom.propertiesfile, so we will have limited classpath information
So we need to find another way. Fortunately there’s another file that fulfills these two criteria: the META-INF/MANIFEST.MF.
So if we ask the ClassLoader to list the Manifest files it knows, we will obtain an Enumeration of URLs. For all the manifest files located under a jar file, the URLs when turned into a String will have the following format:
jar:${jarurl}!/META-INF/MANIFEST.MF. Easy enough to parse to obtain the URL of the jar file itself.
So here we go:
public static Collection findJarUrls( ClassLoader classLoader )
throws java.io.IOException
{
java.util.Enumeration e = classLoader.getResources( MANIFEST_PATH );
List jarUrls = new ArrayList();
for ( ; e.hasMoreElements(); )
{
String path = e.nextElement().toString();
if ( path.startsWith( "jar:" ) )
{
String jarPath = path.substring( 4, path.indexOf( "!" ) );
jarUrls.add( new URL( jarPath ) );
}
}
return jarUrls;
}
public static Collection findJarUrls()
throws java.io.IOException
{
return findJarUrls( Thread.currentThread().getContextClassLoader() );
}
Now that we have a Jar url, we need to process the Jar. The SDK already contains some utility classes JarInputStream and JarFile that can help us in this task. But it lacks some features for our needs, in particular the ability to search the contents of the jar file. So let’s create a class to wrap all this information.
class JarInfo
{
private URL url;
public JarInfo( URL url )
throws URISyntaxException
{
this.url = url;
this.uri = url.toURI();
}
...
private JarFile getJarFile()
throws IOException, SecurityException
{
return new JarFile( new File( uri ) );
}
public Collection findResources( String resourceNamePattern )
throws ...
{
...
}
}
Now, we have to retrieve information from these jars, including the manifest contents and why not, the pom.properties file. A possible way to achieve this:
public Collection findResources( String resourceNamePattern )
throws IOException, SecurityException, URISyntaxException
{
URI str = url.toURI();
JarFile file = new JarFile( new File( str ) );
List result = new ArrayList();
Enumeration e = file.entries();
while ( e.hasMoreElements() )
{
JarEntry jarEntry = (JarEntry) e.nextElement();
if ( jarEntry.getName().matches( resourceNamePattern ) )
{
result.add( jarEntry.getName() );
}
}
return result;
}
Now given a JarEntry, all you need to do is getName() to get the path inside the jar.
Finally if ever you need to load a custom resource from a particular jar file, you may want to:
public InputStream getResourceAsStream( String resourcePath )
throws IOException
{
JarFile jarFile = getJarFile();
ZipEntry ze = jarFile.getEntry( resourcePath );
return jarFile.getInputStream( ze );
}
The jar is currently available at in my maven snapshot repository, but I will put the code online as soon as I get some time. In the mean time, browse the XRef code.
See also: