Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

URLs: Smart resource identifiers

Keep resource names and resource loading strategies separate

  • Print
  • Feedback

Page 3 of 5

Example: a custom URL scheme for classloader lookup

As an example of what URLFactory can do, let's add a protocol that maps resource names to classloader resources:

    "clsloader:"<resource name as for getResourceAsStream()>


This is actually quite easy. The complete implementation is shown here:

public class ClassLoaderResourceHandler extends URLStreamHandler
{
    public static final String PROTOCOL = "clsloader";
    
    
    protected URLConnection openConnection (final URL url) throws IOException
    {
        return new ClassLoaderResourceURLConnection (url);
    }
    
    /**
     * This method should return a parseable string form of this URL.
     */
    protected String toExternalForm (final URL url)
    {
        return PROTOCOL.concat (":").concat (url.getFile ());
    }
    
    /**
     * Must override to prevent default parsing of our URLs as HTTP-like URLs
     * (the base class implementation eventually calls setURL(), which is tied
     * to HTTP URL syntax too much).
     */
    protected void parseURL (final URL context, final String spec,
                             final int start, final int limit)
    {
        final String resourceName =
            combineResourceNames (context.getFile (), spec.substring (start));
        
        setURL (context, context.getProtocol (), "", -1, resourceName, "");
    }
    
    /*
     * The URLConnection implementation used by this scheme. 
     */
    private static final class ClassLoaderResourceURLConnection
        extends URLConnection
    {
        public void connect ()
        {
            // Do nothing, as we will look for the resource in getInputStream().
        }
        
        public InputStream getInputStream () throws IOException
        {
            // This always uses the current thread's context loader. A better
            // strategy is to use techniques shown in
            // http://www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html.
            final ClassLoader loader = Thread.currentThread ().getContextClassLoader ();
            
            // Don't be fooled by our calling url.getFile(): it is just a string,
            // not necessarily a real filename.
            String resourceName = url.getFile ();
            if (resourceName.startsWith ("/"))
                resourceName = resourceName.substring (1);
            
            final InputStream result = loader.getResourceAsStream (resourceName);
            
            if (result == null)
                throw new IOException ("resource [" + resourceName + "] could "
                + "not be found by classloader [" + loader.getClass ().getName ()
                + "]");
            
            return result; 
        }
        protected ClassLoaderResourceURLConnection (final URL url)
        {
            super (url);
        }
    } // End of nested class.
    
    
    private static String combineResourceNames (String base, String relative)
    {
        if ((base == null) || (base.length () == 0)) return relative;
        if ((relative == null) || (relative.length () == 0)) return base;
        
        if (relative.startsWith ("/"))
            // 'relative' is actually absolute in this case.
            return relative.substring (1);
        
        if (base.endsWith ("/"))
            return base.concat (relative);
        else
        {
            // Replace the name segment after the last separator:
            final int lastBaseSlash = base.lastIndexOf ('/');
            
            if (lastBaseSlash < 0)
                return relative;
            else
                return base.substring (0, lastBaseSlash).concat ("/")
                    .concat (relative);
        }
    }
} // End of class.


There are a few subtle points in the above code. For historical reasons, java.net.URL design is very biased towards HTTP-like URLs. Although openConnection() is the only method that is a mandatory override from java.net.URLStreamHandler, I also override toExternalForm() and parseURL(). This is necessary to prevent the input string from being parsed as an HTTP-like URL. I need to keep intact the part of the input string after the colon (this is the "opaque" part of my URL scheme). I accomplish that by masquerading all of it as the "file" part in the constructed URL. This is done by calling the protected setURL() handler method, which in turn dispatches to the protected and otherwise inaccessible URL.set() method. (Why is URL.set() a protected method in a final class? Your guess is as good as mine.)

  • Print
  • Feedback

Resources