Hi,
In an earlier message I said that since java.net.URLClassLoader was based on
SecureClassLoader which depends on some 1.2 features not yet implemented in
Classpath it would not be I good idea to use it for loading Locales.
But that did not stop me from trying to implement it. (But I wise that I had
followed my own advise since it depends on a lot of tricky issues with
ClassLoaders, Security and Jar Manifests :)
Attached is a hopefully fairly complete and not to buggy implementation.
But it is not tested since there is no 1.2 support yet. (There are also some
notes in the comments about issues I was not completely sure of.)
I did implement java.lang.Package so I could at least almost compile it with
Classpath. And there is also a diff for JarURLConnection attached to change
the getIdentities() method to a getCertificates() method which URLClassLoader
also needs.
I will now start on implementing java.util.jar so I can at least compile
everything with Classpath. And if that is finished and Loren has not yet
surfaced then I want to implement java.util.zip.
Cheers,
Mark
/*************************************************************************
/* URLClassLoader.java -- Class loader that loads classes from one or more URLs
/*
/* Copyright (c) 1999 Free Software Foundation, Inc.
/* Written by Mark Wielaard ([EMAIL PROTECTED])
/*
/* This library is free software; you can redistribute it and/or modify
/* it under the terms of the GNU Library General Public License as published
/* by the Free Software Foundation, either version 2 of the License, or
/* (at your option) any later verion.
/*
/* This library is distributed in the hope that it will be useful, but
/* WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU Library General Public License for more details.
/*
/* You should have received a copy of the GNU Library General Public License
/* along with this library; if not, write to the Free Software Foundation
/* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA
/*************************************************************************/
package java.net;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.FilePermission;
import java.security.CodeSource;
import java.security.SecureClassLoader;
import java.security.PermissionCollection;
import java.security.cert.Certificate;
import java.util.Enumeration;
import java.util.Vector;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
/**
* A secure class loader that can load classes and resources from multiple
* locations. Given an array of URLs this class loader will retrieve classes
* and resources by fetching them from possible remote locations. Each URL is
* searched in order in which it was added. If the file portion of the URL ends
* with a / character then it is interpreted as a base directory, otherwise it
* is interpreted as a jar file from which the classes/resources are resolved.
* <p>
* New instances can be created by two static newInstance() methods or by
* three public contructors. Both give the option to supply an initial array
* of URLs and (optionally) a parent classloader (that is different from the
* standard system class loader).
* Creating a <code>URLClassLoader</code> can always throw a
* <code>SecurityException</code> if the checkes performed by the constructor of
* <code>SecureClassLoader</code>, which are called when creating a new
* instance, fail.
* Note that only subclasses can add new URLs after the URLClassLoader had been
* created. But it is always possible to get an array of all URLs that the class
* loader uses to resolve classes and resources by way of the
* <code>getURLs()</code> method.
* <p>
* XXX - TODO - FIXME This implementation is not finished or tested!<br>
* Open issues:
* <ul>
* <li>Should the URLClassLoader actually add the locations found in the
* manifest or is this the responsibility of some other loader/(sub)class?
* (see <a href="http://java.sun.com/products/jdk/1.3/docs/guide/extensions/spec.html#bundled">Extension Machanism Architecture - Bundles Extensions</a>)
* <li> How does <code>definePackage()</code> and sealing work precisely?
* (Note that the method is currently not even implemented in
* <code>java.lang.ClassLoader</code>.)
* <li> Should the static <code>newInstance()</code> methods always succeed? Or
* are they also restricted by the normal security checks?
* <li> We might want to do some caching of URLs, Connections or JarFiles.
* For now we just assume that the (Jar)URLConnection takes care of this.
* <li> Is the way we create the "jar" URLs correct? What if it was already a
* "jar" URL?
* <li> XXX
* </ul>
*
* @author Mark Wielaard ([EMAIL PROTECTED])
*/
public class URLClassLoader extends SecureClassLoader {
// Variables
/** Locations to load classes from */
private Vector urls = new Vector();
/** Factory used to get the protocol handlers of the URLs */
private URLStreamHandlerFactory factory = null;
// Constructors
/**
* Creates a URLClassLoader that gets classes from the supplied URLs.
* To determine if this classloader may be created the constructor of
* the super class (<CODE>SecureClassLoader</CODE>) is called first, which
* can throw a SecurityException. Then the supplied URLs are added
* in the order given to the URLClassLoader which uses these URLs to
* load classes and resources (after using the default parent ClassLoader).
* @exception SecurityException if the SecurityManager disallows the
* creation of a ClassLoader.
* @param urls Locations that should be searched by this ClassLoader when
* resolving Classes or Resources.
* @see SecureClassLoader
*/
public URLClassLoader(URL[] urls) throws SecurityException {
super();
addURLs(urls);
}
/**
* Creates a URLClassLoader that gets classes from the supplied URLs.
* To determine if this classloader may be created the constructor of
* the super class (<CODE>SecureClassLoader</CODE>) is called first, which
* can throw a SecurityException. Then the supplied URLs are added
* in the order given to the URLClassLoader which uses these URLs to
* load classes and resources (after using the supplied parent ClassLoader).
* @exception SecurityException if the SecurityManager disallows the
* creation of a ClassLoader.
* @exception SecurityException
* @param urls Locations that should be searched by this ClassLoader when
* resolving Classes or Resources.
* @param parent The parent class loader used before trying this class
* loader.
* @see SecureClassLoader
*/
public URLClassLoader(URL[] urls, ClassLoader parent) throws
SecurityException {
super(parent);
addURLs(urls);
}
/**
* Creates a URLClassLoader that gets classes from the supplied URLs.
* To determine if this classloader may be created the constructor of
* the super class (<CODE>SecureClassLoader</CODE>) is called first, which
* can throw a SecurityException. Then the supplied URLs are added
* in the order given to the URLClassLoader which uses these URLs to
* load classes and resources (after using the supplied parent ClassLoader).
* It will use the supplied <CODE>URLStreamHandlerFactory</CODE> to get the
* protocol handlers of the supplied URLs.
* @exception SecurityException if the SecurityManager disallows the
* creation of a ClassLoader.
* @exception SecurityException
* @param urls Locations that should be searched by this ClassLoader when
* resolving Classes or Resources.
* @param parent The parent class loader used before trying this class
* loader.
* @param factory Used to get the protocol handler for the URLs.
* @see SecureClassLoader
*/
public URLClassLoader(URL[] urls,
ClassLoader parent,
URLStreamHandlerFactory factory) throws
SecurityException {
super(parent);
addURLs(urls);
this.factory = factory;
}
// Methods
/**
* Adds a new location to the end of the internal URL store.
* @param newUrl the location to add
*/
protected void addURL(URL newUrl) {
urls.add(newUrl);
}
/**
* Adds an array of new locations to the end of the internal URL store.
* @param newUrls the locations to add
*/
private void addURLs(URL[] newUrls) {
for (int i = 0; i < newUrls.length; i++) {
addURL(newUrls[i]);
}
}
/**
* Defines a Package based on the given name and the supplied manifest
* information. The manifest indicates the tile, version and
* vendor information of the specification and implementation and wheter the
* package is sealed. If the Manifest indicates that the package is sealed
* then the Package will be sealed with respect to the supplied URL.
*
* @exception IllegalArgumentException If this package name already exists
* in this class loader
* @param name The name of the package
* @param manifest The manifest describing the specification,
* implementation and sealing details of the package
* @param url the code source url to seal the package
* @return the defined Package
*/
protected Package definePackage(String name, Manifest manifest, URL url)
throws IllegalArgumentException {
Attributes attr = manifest.getMainAttributes();
String specTitle =
attr.getValue(Attributes.Name.SPECIFICATION_TITLE);
String specVersion =
attr.getValue(Attributes.Name.SPECIFICATION_VERSION);
String specVendor =
attr.getValue(Attributes.Name.SPECIFICATION_VENDOR);
String implTitle =
attr.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
String implVersion =
attr.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
String implVendor =
attr.getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
// Look if the Manifest indicates that this package is sealed
// XXX - most likely not completely correct!
// Shouldn't we also check the sealed attribute of the complete jar?
// http://java.sun.com/products/jdk/1.3/docs/guide/extensions/spec.html#bundled
// But how do we get that jar manifest here?
String sealed = attr.getValue(Attributes.Name.SEALED);
if ("false".equals(sealed)) {
// make sure that the URL is null so the package is not sealed
url = null;
}
// XXX - Since ClassLoader.definePackage is not implemented return null
return null;
// XXX - Replace it with this return when ClassLoader.definePackage()
// is actually implemented.
// return definePackage(name, specTitle, specVersion, specVendor,
// implTitle, implVersion, implVendor, url);
}
/**
* Finds (the first) class by name from one of the locations. The locations
* are searched in the order they were added to the URLClassLoader.
* @param className the classname to find
* @exception ClassNotFoundException when the class could not be found or
* loaded
* @return a Class object representing the found class
*/
protected Class findClass(String className) throws ClassNotFoundException {
// Just try to find the resource by the (almost) same name
String resourceName = className.replace('.', '/') + ".class";
URL url = findResource(resourceName);
if (url == null) {
throw new ClassNotFoundException(className);
}
// Try to read the class data, create the CodeSource and construct the
// class (and watch out for those nasty IOExceptions)
try {
byte [] classData;
URLConnection conn = url.openConnection();
InputStream in = conn.getInputStream();
int length = conn.getContentLength();
if (length != -1) {
// We know the length of the data, just read it all in at once
classData = new byte[length];
in.read(classData);
} else {
// We don't know the data length so we have to read it in chunks
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
byte b[] = new byte[1024];
int l = 0;
while (l != -1) {
l = in.read(b);
if (l != -1) {
out.write(b, 0, l);
}
}
classData = out.toByteArray();
}
// Now construct the CodeSource (if loaded from a jar file)
CodeSource source = null;
if (url.getProtocol().equals("jar")) {
Certificate[] certificates =
((JarURLConnection)conn).getCertificates();
source = new CodeSource(url, certificates);
}
// And finally construct the class!
return defineClass(className, classData,
0, classData.length,
source);
} catch (IOException ioe) {
throw new ClassNotFoundException(className + ": " + ioe);
}
}
/**
* Finds the first occurrence of a resource that can be found. The locations
* are searched in the order they were added to the URLClassLoader.
* @param resourceName the resource name to look for
* @return the URL if found, null otherwise
*/
public URL findResource(String resourceName) {
Enumeration e = urls.elements();
while (e.hasMoreElements()) {
URL url = findResource(resourceName, (URL)e.nextElement());
if (url != null) {
// Found the resource
return url;
}
}
// Resource not found
return null;
}
/**
* Find a resource relative to a base URL. If the base URL ends with a /
* character then the base URL is interpreted as a directory and the
* resource name is appended to it, otherwise the base URL is interpreted
* as a jar file and a "jar" URL is constructed from the base URL and the
* supplied resource name. This method tries to open a connection to the
* resulting URL to make sure the resource is actually there.
* <p>
* XXX - We might want to do some caching of URLs, Connections or JarFiles.
* For now we just assume that the (Jar)URLConnection takes care of this.
* XXX - Should we also disconnect/close the URLConnection again? And how
* would we do that? For now just assume the connection will actually be
* used.
* @param resourceName the resource name to look for
* @param url the base URL
* @return the URL if found, null otherwise
* @see JarURLConnection
*/
private URL findResource(String resourceName, URL url) {
URL resourceURL;
// Get the file portion of the base URL
String file = url.getFile();
// Construct the resourceURL
if (file.endsWith("/")) {
// Interpret it as a directory and just append the resource name
try {
resourceURL = new URL(url, resourceName,
createURLStreamHandler(url.getProtocol()));
} catch (MalformedURLException e) {
return null;
}
} else {
// Interpret it as a jar file and construct a "jar" URL
String external = url.toExternalForm();
try {
// XXX - Is this correct? Why is there no URL constructor that
// takes just a string and a URLStreamHandler?
resourceURL = new URL(new URL("jar:" + external + "!/"),
resourceName,
createURLStreamHandler("jar"));
} catch (MalformedURLException e) {
return null;
}
}
// Check if the resource is actually at that location
try {
resourceURL.openConnection();
} catch (IOException ioe) {
return null;
}
return resourceURL;
}
/**
* If the URLStreamHandlerFactory has been set this return the appropriate
* URLStreamHandler for the given protocol.
* @param protocol the protocol for which we need a URLStreamHandler
* @return the appropriate URLStreamHandler or null
*/
private URLStreamHandler createURLStreamHandler(String protocol) {
if (factory != null) {
return factory.createURLStreamHandler(protocol);
} else {
return null;
}
}
/**
* Finds all the resources with a particular name from all the locations.
* @exception IOException when an error occurs accessing one of the
* locations
* @param resourceName the name of the resource to lookup
* @return a (possible empty) enumeration of URLs where the resource can be
* found
*/
public Enumeration findResources(String resourceName) throws IOException {
Vector resources = new Vector();
Enumeration e = urls.elements();
while (e.hasMoreElements()) {
URL url = findResource(resourceName, (URL)e.nextElement());
if (url != null) {
// Found another resource
resources.add(url);
}
}
return resources.elements();
}
/**
* Returns the permissions needed to access a particular code source.
* These permissions includes those returned by
* <CODE>SecureClassLoader.getPermissions</CODE> and the actual permissions
* to access the objects referenced by the URL of the code source.
* The extra permissions added depend on the protocol and file portion of
* the URL in the code source. If the URL has the "file" protocol ends with
* a / character then it must be a directory and a file Permission to read
* everthing in that directory and all subdirectories is added. If the URL
* had the "file" protocol and doesn't end with a / character then it must
* be a normal file and a file permission to read that file is added. If the
* URL has any other protocol then a socket permission to connect and accept
* connections from the host portion of the URL is added.
* @param source The codesource that needs the permissions to be accessed
* @return the collection of permissions needed to access the code resource
* @see SecureClassLoader.getPermissions()
*/
protected PermissionCollection getPermissions(CodeSource source) {
// XXX - This implementation does exactly as the Javadoc describes.
// But maybe we should/could use URLConnection.getPermissions()?
// First get the permissions that would normally be granted
PermissionCollection permissions = super.getPermissions(source);
// Now add the any extra permissions depending on the URL location
URL url = source.getLocation();
String protocol = url.getProtocol();
if (protocol.equals("file")) {
String file = url.getFile();
// If the file end in / it must be an directory
if (file.endsWith("/")) {
// Grant permission to read everything in that directory and
// all subdirectories
permissions.add(new FilePermission(file + "-", "read"));
} else { // It is a 'normal' file
// Grant permission to access that file
permissions.add(new FilePermission(file, "read"));
}
} else {
// Grant permission to connect to and accept connections from host
String host = url.getHost();
permissions.add(new SocketPermission(host, "connect,accept"));
}
return permissions;
}
/**
* Returns all the locations that this class loader currently uses the
* resolve classes and resource. This includes both the initially supplied
* URLs as any URLs added later by the loader.
* @return All the currently used URLs
*/
public URL[] getURLs() {
return (URL[]) urls.toArray();
}
/**
* Creates a new instance of a URLClassLoader that gets classes from the
* supplied URLs. This class loader will have as parent the standard
* system class loader.
* @param urls the initial URLs used to resolve classes and resources
*/
public static URLClassLoader newInstance(URL urls[]) throws
SecurityException {
return new URLClassLoader(urls);
}
/**
* Creates a new instance of a URLClassLoader that gets classes from the
* supplied URLs and with the supplied loader as parent class loader.
* @param urls the initial URLs used to resolve classes and resources
* @param parent the parent class loader
*/
public static URLClassLoader newInstance(URL urls[],
ClassLoader parent) throws
SecurityException {
return new URLClassLoader(urls, parent);
}
}
/*
* java.lang.Package: part of the Java Class Libraries project.
* Copyright (C) 1999 Free Software Foundation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
package java.lang;
import java.net.URL;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
/**
* Everything you ever wanted to know about a package. This class makes it
* possible to attach specification and implementation information to a
* package as explained in the
* <a href="http://java.sun.com/products/jdk/1.3/docs/guide/versioning/spec/VersioningSpecification.html#PackageVersionSpecification">Package Versioning Specification</a>
* section of the
* <a href="http://java.sun.com/products/jdk/1.3/docs/guide/versioning/spec/VersioningSpecification.html">Product Versioning Specification</a>.
* It also allows packages to be sealed with respect to the originating URL.
* <p>
* The most usefull method is the <code>isCompatibleWith()</code> method that
* compares a desired version of a specification with the version of the
* specification as implemented by a package. A package is considered compatible
* with another version if the version of the specification is equal or higer
* then the requested version. Version numbers are represented as strings of
* positive numbers seperated by dots (e.g. "1.2.0"). The first number is called
* the major number, the second the minor, the third the micro, etc. A version
* is considered higher then another version if it has a bigger major number
* then the another version or when the major numbers of the versions are equal
* if it has a bigger minor number then the other version, etc. (If a version
* has no minor, micro, etc numbers then they are considered the be 0.)
*
* @since JDK1.2
* @author Mark Wielaard
* @version 1.0, 19 Dec 1999
*/
public class Package {
// Variables
/** The name of the Package
*/
final private String name;
/** The name if the implementation
*/
final private String implTitle;
/** The vendor that wrote this implementation
*/
final private String implVendor;
/** The version of this implementation
*/
final private String implVersion;
/** The name of the specification
*/
final private String specTitle;
/** The name of the specification designer
*/
final private String specVendor;
/** The version of this specification
*/
final private String specVersion;
/** If sealed the origin of the package classes
*/
final private URL sealed;
// Constructors
/**
* A package local constructor for the Package class.
* XXX - There are no public constructors defined for Package so I just
* invented a package local constructor that can be used by classes in
* java.lang.
*
* @param name The name of the Package
* @param implTitle The name of the implementation
* @param implVendor The vendor that wrote this implementation
* @param implVersion The version of this implementation
* @param specTitle The name of the specification
* @param specVendor The name of the specification designer
* @param specVersion The version of this specification
* @param sealed If sealed the origin of the package classes
*/
Package(String name,
String implTitle, String implVendor, String implVersion,
String specTitle, String specVendor, String specVersion,
URL sealed) {
this.name = name;
this.implTitle = implTitle;
this.implVendor = implVendor;
this.implVersion = implVersion;
this.specTitle = specTitle;
this.specVendor = specVendor;
this.specVersion = specVersion;
this.sealed = sealed;
}
// Methods
/**
* Returns the Package name.
*/
public String getName() {
return name;
}
/**
* Returns the name of the implementation or null if unknown.
*/
public String getImplementationTitle() {
return implTitle;
}
/**
* Returns the vendor that wrote this implementation or null if unknown.
*/
public String getImplementationVendor() {
return implVendor;
}
/**
* Returns the version of this implementation or null if unknown.
*/
public String getImplementationVersion() {
return implVersion;
}
/**
* Returns the name of the specification or null if unknown.
*/
public String getSpecificationTitle() {
return specTitle;
}
/**
* Returns the name of the specification designer.
*/
public String getSpecificationVendor() {
return specVendor;
}
/**
* Returns the version of the specification or null if unknown.
*/
public String getSpecificationVersion() {
return specVersion;
}
/**
* Returns true if this Package is sealed.
*/
public boolean isSealed() {
return (sealed != null);
}
/**
* Returns true if this Package is sealed and the origin of the classes is
* the given URL.
*
* @param url
*/
public boolean isSealed(URL url) {
return url.equals(sealed);
}
/**
* Checks if the version of the specification is higher or at least as high
* as the desired version.
* XXX - It may throw unexpected (NullPointer) exceptions when the supplied
* version or the specification version are null.
* @param version the (minimal) desired version of the specification
* @exception NumberFormatException when either version or the specification
* version is not a correctly formatted version number
*/
public boolean isCompatibleWith(String version) throws
NumberFormatException {
StringTokenizer versionTokens = new StringTokenizer(version, ".");
StringTokenizer specTokens = new StringTokenizer(specVersion, ".");
try {
while (versionTokens.hasMoreElements()) {
int vers = Integer.parseInt(versionTokens.nextToken());
int spec = Integer.parseInt(specTokens.nextToken());
if (spec < vers) {
return false;
} else if (spec > vers) {
return true;
}
// They must be equal, next Token please!
}
} catch (NoSuchElementException e) {
// this must have been thrown by spec.netToken() so return false
return false;
}
// They must have been exactly the same version.
// Or the specVersion has more subversions. That is also good.
return true;
}
/**
* Returns the named package if it is known by the callers class loader.
* It may return null if the package is unknown or when there is no
* information on that particular package available.
* XXX - Since ClassLoader.getPackage() is not yet implemented it just
* returns null.
* @param name the name of the desired package
*/
public static Package getPackage(String name) {
return null;
}
/**
* Returns all the packages that are known to the callers class loader.
* XXX - Since ClassLoader.getPackages() is not yet implemented it just
* returns null.
*/
public static Package[] getPackages() {
return null;
}
/**
* Returns the hashCode of the name of this package.
*/
public int hashCode() {
return name.hashCode();
}
/**
* Returns a string representation of this package name, specification,
* implementation and class origin if sealed.
*/
public String toString() {
return "package: " + name +
" spec: " + specTitle +
" version: " + specVersion +
" vendor: " + specVendor +
" implementation: " + implTitle +
" version: " + implVersion +
" vendor: " + implVendor +
" sealed: " + sealed;
}
}
--- JarURLConnection.java 1998/07/24 01:59:40 1.2
+++ JarURLConnection.java 1999/12/19 22:19:51
@@ -22,7 +22,7 @@
package java.net;
import java.io.IOException;
-import java.security.Identity;
+import java.security.cert.Certificate;
import java.util.jar.*;
/**
@@ -222,17 +222,17 @@
/*************************************************************************/
/**
- * Returns an array of Identity objects for the jar file entry specified
+ * Returns an array of Certificate objects for the jar file entry specified
* by this URL or null if there are none
*
- * @return An Identity array
+ * @return A Certificate array
*
* @exception IOException If an error occurs
*/
-public Identity[]
-getIdentities() throws IOException
+public Certificate[]
+getCertificates() throws IOException
{
- return(getJarEntry().getIdentities());
+ return(getJarEntry().getCertificates());
}
} // class JarURLConnection