|
|
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
Page 2 of 5
Java has had java.net.URLs since version 1.0 and has supported several useful schemes (file:, http:, etc). It's not hard to implement a scheme of your own design: you need a custom subclass of java.net.URLStreamHandler and a matching subclass of java.net.URLConnection (see white paper and bug report in Resources for full details). However, installing the scheme in a JVM is annoyingly difficult even to this date: you either need to
use URL.setURLStreamHandlerFactory() or append a custom package prefix to the global list in the java.protocol.handler.pkgs system property. The first method can only be called once in the JVM's lifetime, and the second option requires enough security
privileges and cooperation from all components in the JVM. Neither one has much of a chance working for modular code (e.g.,
Enterprise JavaBeans (EJB) components). To make matters worse, custom URL handlers are not looked up via thread context classloaders
by analogy with Java API for XML Parsing (JAXP) or Java Naming and Directory Interface (JNDI) object factories (see "Find a Way Out of the ClassLoader Maze" for more on this). Coupled with the fact that java.net.URL is a final class, this all but kills any aspirations of a budding custom URL Java programmer.
Working around these problems is possible, but requires a little foresight. Specifically, you can instantiate all URLs in your code using the following factory API instead of calling URL constructors directly (once the URL is constructed, however, it can be passed freely into anything, including core Java APIs):
public abstract class URLFactory
{
public static final String HANDLER_MAPPING_RESOURCE_NAME = "url.properties";
/**
* A factory method for constructing a java.net.URL out of a plain string.
* Stock URL schemes will be tried first, followed by custom schemes with
* a mapping in the union of all {@link #HANDLER_MAPPING_RESOURCE_NAME}
* resources found at the time this class is initialized.
*
* @param url external URL form [may not be null].
* @return java.net.URL corresponding to the scheme in 'url'.
*
* @throws MalformedURLException if 'url' does not correspond to a known
* stock or custom URL scheme.
*/
public static URL newURL (final String url)
throws MalformedURLException
{
// Try already installed protocols first:
try
{
return new URL (url);
}
catch (MalformedURLException ignore)
{
// Ignore: try our handler list next.
}
final int firstColon = url.indexOf (':');
if (firstColon <= 0)
{
throw new MalformedURLException ("no protocol specified: " + url);
}
else
{
final Map handlerMap = getHandlerMap ();
final String protocol = url.substring (0, firstColon);
final URLStreamHandler handler;
if ((handlerMap == null) ||
(handler = (URLStreamHandler) handlerMap.get (protocol)) == null)
throw new MalformedURLException ("unknown protocol: " + protocol);
return new URL (null, url, handler);
}
}
/**
* Not synchronized by design. You will need to change this if you make
* HANDLERS initialize lazily (post static initialization time).
*
* @return scheme/stream handler map [can be null if static init failed].
*/
private static Map /* String->URLContentHandler */ getHandlerMap ()
{
return HANDLERS;
}
/*
* Loads a scheme/handler map that is a union of *all* resources named
* 'resourceName' as seen by 'loader'. Null 'loader' is equivalent to the
* application loader.
*/
private static Map loadHandlerList (final String resourceName,
ClassLoader loader)
{
if (loader == null) loader = ClassLoader.getSystemClassLoader ();
final Map result = new HashMap ();
try
{
// NOTE: using getResources() here.
final Enumeration resources = loader.getResources (resourceName);
if (resources != null)
{
// Merge all mappings in 'resources':
while (resources.hasMoreElements ())
{
final URL url = (URL) resources.nextElement ();
final Properties mapping;
InputStream urlIn = null;
try
{
urlIn = url.openStream ();
mapping = new Properties ();
mapping.load (urlIn); // Load in .properties format.
}
catch (IOException ioe)
{
// Ignore this resource and go to the next one.
continue;
}
finally
{
if (urlIn != null) try { urlIn.close (); }
catch (Exception ignore) {}
}
// Load all handlers specified in 'mapping':
for (Enumeration keys = mapping.propertyNames ();
keys.hasMoreElements (); )
{
final String protocol = (String) keys.nextElement ();
final String implClassName = mapping.getProperty (protocol);
final Object currentImpl = result.get (protocol);
if (currentImpl != null)
{
if (implClassName.equals (currentImpl.getClass ().getName ()))
continue; // Skip duplicate mapping.
else
throw new IllegalStateException ("duplicate " +
"protocol handler class [" + implClassName +
"] for protocol " + protocol);
}
result.put (protocol,
loadURLStreamHandler (implClassName, loader));
}
}
}
}
catch (IOException ignore)
{
// Ignore: an empty result will be returned.
}
return result;
}
/*
* Loads and initializes a single URL stream handler for a given name via a
* given classloader. For simplicity, all errors are converted to
* RuntimeExceptions.
*/
private static URLStreamHandler loadURLStreamHandler (final String className,
final ClassLoader loader)
{
final Class cls;
final Object handler;
try
{
cls = Class.forName (className, true, loader);
handler = cls.newInstance ();
}
catch (Exception e)
{
throw new RuntimeException ("could not load and instantiate" +
" [" + className + "]: " + e.getMessage ());
}
if (! (handler instanceof URLStreamHandler))
throw new RuntimeException ("not a java.net.URLStreamHandler" +
" implementation: " + cls.getName ());
return (URLStreamHandler) handler;
}
/**
* This method decides which classloader will be used by all
* resource/classloading in this class. At the very least, you should use 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.
*/
private static ClassLoader getClassLoader ()
{
return Thread.currentThread ().getContextClassLoader ();
}
private static final Map /* String->URLContentHandler */ HANDLERS;
private static final boolean DEBUG = true;
static
{
Map temp = null;
try
{
temp = loadHandlerList (HANDLER_MAPPING_RESOURCE_NAME,
getClassLoader ());
}
catch (Exception e)
{
if (DEBUG)
{
System.out.println ("could not load all" +
" [" + HANDLER_MAPPING_RESOURCE_NAME + "] mappings:");
e.printStackTrace (System.out);
}
}
HANDLERS = temp;
}
} // End of class.
Look at the newURL() method. It starts by attempting a plain URL(String) constructor. If that works, the URL string belongs to one of the core protocols, so the existing schemes just pass through
this method. Otherwise, the URL protocol prefix is determined, and the URL is constructed by coupling its source string with
a custom URLContentHandler implementation. All such handlers are kept in the HANDLERS map, which is populated at static initialization time. The protocol prefix-handler class mapping comes from a classloader
resource "url.properties". The custom URL result is constructed with a special three-parameter URL constructor, which is the ultimate reason for using
this factory. It is rather significant that this resource is retrieved using ClassLoader.getResources() and not ClassLoader.getResource(), something I will further comment on later.
Archived Discussions (Read only)