Chapter 9. The ClassLoading Layer

JBoss has always had a unique way of dealing with classloading, and the new classloading layer that comes with the Microcontainer is no exception ClassLoading is an optional add-on that you can use when you want non-default classloading. With the rising demand for OSGi-style classloading, and a number of new Java classloading specifications on the horizon, the changes to the ClassLoading layer of EAP 5.1 are useful and timely.
The Microcontainer ClassLoading layer is an abstraction layer. Most of the details are hidden behind private and package-private methods, without compromising the extensibility and functionality available through public classes and methods that make the API. This means that you code against policy and not against classloader details.
The ClassLoader project is split into 3 sub-projects
  • classloader
  • classloading
  • classloading-vfs
classloader contains a custom java.lang.ClassLoader extension without any specific classloading policy. A classloading policy includes knowledge of where to load from and how to load.
Classloading is an extension of Microcontainer’s dependency mechanisms. Its VFS-backed implementation is classloading-vfs. See Chapter 8, The Virtual File System for more information on VFS.

9.1. ClassLoader

The ClassLoader implementation supports pluggable policies and is itself a final class, not meant to be altered. To write your own ClassLoader implementations, write a ClassLoaderPolicy which provides a simpler API for locating classes and resources, and for specifying other rules associated with the classloader.
To customize classloading, instantiate a ClassLoaderPolicy and register it with a ClassLoaderSystem to create a custom ClassLoader. You can also create a ClassLoaderDomain to partition the ClassLoaderSystem.
The ClassLoader layer also includes the implementation of things like DelegateLoader model, classloading, resource filters, and parent-child delegation policies.
The run-time is JMX enabled to expose the policy used for each classloader. It also provides classloading statistics and debugging methods to help determine where things are loaded from.

Example 9.1. ClassLoaderPolicy Class

The ClassLoaderPolicy controls the way your classloading works.
public abstract class ClassLoaderPolicy extends BaseClassLoaderPolicy {
    public DelegateLoader getExported()

        public String[] getPackageNames()

        protected List<? extends DelegateLoader> getDelegates()

        protected boolean isImportAll()
        protected boolean isCacheable()
        protected boolean isBlackListable()

        public abstract URL getResource(String path);

    public InputStream getResourceAsStream(String path)

        public abstract void getResources(String name, Set<URL> urls) throws IOException;

    protected ProtectionDomain getProtectionDomain(String className, String path)
        public PackageInformation getPackageInformation(String packageName)
        public PackageInformation getClassPackageInformation(String className, String packageName)

        protected ClassLoader isJDKRequest(String name)
        }
}
			
			
			

The following two examples of ClassLoaderPolicy. The first one retrieves resources based on regular expressions, while the second one handles encrypted resources.

Example 9.2. ClassLoaderPolicy with Regular Expression Support

public class RegexpClassLoaderPolicy extends ClassLoaderPolicy {
    private VirtualFile[] roots;
    private String[] packageNames;

    public RegexpClassLoaderPolicy(VirtualFile[] roots)
    {
	this.roots = roots;
    }

    @Override
	public String[] getPackageNames()
    {
	if (packageNames == null)
	    {
		Set<String> exportedPackages = PackageVisitor.determineAllPackages(roots, null, ExportAll.NON_EMPTY, null, null, null);
		packageNames = exportedPackages.toArray(new String[exportedPackages.size()]);
	    }
	return packageNames;
    }

    protected Pattern createPattern(String regexp)
    {
	boolean outside = true;
	StringBuilder builder = new StringBuilder();
	for (int i = 0; i < regexp.length(); i++)
	    {
		char ch = regexp.charAt(i);
		if ((ch == '[' || ch == ']' || ch == '.') && escaped(regexp, i) == false)
		    {
			switch (ch)
			    {
			    case '[' : outside = false; break;
			    case ']' : outside = true; break;
			    case '.' : if (outside) builder.append("\\"); break;
			    }
		    }

		builder.append(ch);
	    }
	return Pattern.compile(builder.toString());
    }

    protected boolean escaped(String regexp, int i)
    {
	return i > 0 && regexp.charAt(i - 1) == '\\';
    }

    public URL getResource(String path)
    {
	Pattern pattern = createPattern(path);
	for (VirtualFile root : roots)
	    {
		URL url = findURL(root, root, pattern);
		if (url != null)
		    return url;
	    }
	return null;
    }

    private URL findURL(VirtualFile root, VirtualFile file, Pattern pattern)
    {
	try
	    {
		String path = AbstractStructureDeployer.getRelativePath(root, file);
		Matcher matcher = pattern.matcher(path);
		if (matcher.matches())
		    return file.toURL();

		List<VirtualFile> children = file.getChildren();
		for (VirtualFile child : children)
		    {
			URL url = findURL(root, child, pattern);
			if (url != null)
			    return url;
		    }

		return null;
	    }
	catch (Exception e)
	    {
		throw new RuntimeException(e);
	    }
    }

    public void getResources(String name, Set<URL> urls) throws IOException
    {
	Pattern pattern = createPattern(name);
	for (VirtualFile root : roots)
	    {
		RegexpVisitor visitor = new RegexpVisitor(root, pattern);
		root.visit(visitor);
		urls.addAll(visitor.getUrls());
	    }
    }

    private static class RegexpVisitor implements VirtualFileVisitor
    {
	private VirtualFile root;
	private Pattern pattern;
	private Set<URL> urls = new HashSet<URL>();

	private RegexpVisitor(VirtualFile root, Pattern pattern)
	{
	    this.root = root;
	    this.pattern = pattern;
	}

	public VisitorAttributes getAttributes()
	{
	    return VisitorAttributes.RECURSE_LEAVES_ONLY;
	}

	public void visit(VirtualFile file)
	{
	    try
		{
		    String path = AbstractStructureDeployer.getRelativePath(root, file);
		    Matcher matcher = pattern.matcher(path);
		    if (matcher.matches())
			urls.add(file.toURL());
		}
	    catch (Exception e)
		{
		    throw new RuntimeException(e);
		}
	}

	public Set<URL> getUrls()
	{
	    return urls;
	}
    }
}
			
			
			

RegexpClassLoaderPolicy uses a simplistic mechanism to find matching resources. Real-world implementations would be more comprehensive and elegant.
public class RegexpService extends PrintService {
    public void start() throws Exception
    {
	System.out.println();

	ClassLoader cl = getClass().getClassLoader();
	Enumeration<URL> urls = cl.getResources("config/[^.]+\\.[^.]{1,4}");
	while (urls.hasMoreElements())
	    {
		URL url = urls.nextElement();
		print(url.openStream(), url.toExternalForm());
	    }
    }
}
			
			
			

The regexp service uses the regular expression pattern config/[^.]+\\.[^.]{1,4} to list resources under the config// directory. The suffix length is limited, such that file names such as excluded.properties will be ignored.

Example 9.3. ClassLoaderPolicy with Encryption Support

public class CrypterClassLoaderPolicy extends VFSClassLoaderPolicy {
    private Crypter crypter;

    public CrypterClassLoaderPolicy(String name, VirtualFile[] roots, VirtualFile[] excludedRoots, Crypter crypter) {
        super(name, roots, excludedRoots);
        this.crypter = crypter;
    }

    @Override
    public URL getResource(String path) {
        try
            {
                URL resource = super.getResource(path);
                return wrap(resource);
            }
        catch (IOException e)
            {
                throw new RuntimeException(e);
            }
    }

    @Override
    public InputStream getResourceAsStream(String path) {
        InputStream stream = super.getResourceAsStream(path);
        return crypter.crypt(stream);
    }

    @Override
    public void getResources(String name, Set<URL> urls) throws IOException {
        super.getResources(name, urls);
        Set<URL> temp = new HashSet<URL>(urls.size());
        for (URL url : urls)
            {
                temp.add(wrap(url));
            }
        urls.clear();
        urls.addAll(temp);
    }

    protected URL wrap(URL url) throws IOException {
        return new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile(), new CrypterURLStreamHandler(crypter));
    }
}
			
			
			

Example 9.3, “ClassLoaderPolicy with Encryption Support” shows how to encrypt JARs. You can configure which resources to encrypt by specifying a proper filter. Here, everything is encrypted except for the contents of the META-INF/ directory.
public class EncryptedService extends PrintService {
    public void start() throws Exception
    {
	ClassLoader cl = getClass().getClassLoader();

	URL url = cl.getResource("config/settings.txt");
	if (url == null)
	    throw new IllegalArgumentException("No such settings.txt.");

	InputStream is = url.openStream();
	print(is, "Printing settings:\n");

	is = cl.getResourceAsStream("config/properties.xml");
	if (is == null)
	    throw new IllegalArgumentException("No such properties.xml.");

	print(is, "\nPrinting properties:\n");
    }
}
			
			
			

This service prints out the contents of two configuration files. It shows that decryption of any encrypted resources is hidden behind the classloading layer.
To properly test this, either encrypt the policy module yourself or use an existing encrypted one. To put this into action, you need to properly tie EncryptedService to ClassLoaderSystem and deployers.
Partitioning ClassLoaderSystem is discussed later in this chapter.