[Arakhnę-Dev] [178] * Revert removal of DynamicURLClassLoader.

[ Thread Index | Date Index | More arakhne.org/dev Archives ]


Revision: 178
Author:   galland
Date:     2010-09-06 23:11:04 +0200 (Mon, 06 Sep 2010)
Log Message:
-----------
* Revert removal of DynamicURLClassLoader.

Added Paths:
-----------
    trunk/arakhneVmutils/java/src/main/java/org/arakhne/vmutil/DynamicURLClassLoader.java

Added: trunk/arakhneVmutils/java/src/main/java/org/arakhne/vmutil/DynamicURLClassLoader.java
===================================================================
--- trunk/arakhneVmutils/java/src/main/java/org/arakhne/vmutil/DynamicURLClassLoader.java	                        (rev 0)
+++ trunk/arakhneVmutils/java/src/main/java/org/arakhne/vmutil/DynamicURLClassLoader.java	2010-09-06 21:11:04 UTC (rev 178)
@@ -0,0 +1,588 @@
+/* 
+ * $Id$
+ * 
+ * Copyright (C) 2004-2008 Stéphane GALLAND
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser 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
+ * This program is free software; you can redistribute it and/or modify
+ */
+
+package org.arakhne.vmutil;
+
+import java.io.File;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.SocketPermission;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.CodeSigner;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
+import java.security.SecureClassLoader;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.NoSuchElementException;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.jar.Attributes.Name;
+import java.util.regex.Pattern;
+
+import sun.misc.Resource;
+import sun.misc.URLClassPath;
+import sun.net.www.ParseUtil;
+import sun.security.util.SecurityConstants;
+
+/** This class loader permits to load classes from
+ * a set of classpaths.
+ * 
+ * @author Stéphane GALLAND <galland@xxxxxxxxxxx>
+ * @version $Name$ $Revision$ $Date$
+ * @mavengroupid $GroupId$
+ * @mavenartifactid $ArtifactId$
+ */
+@SuppressWarnings("restriction")
+public class DynamicURLClassLoader extends SecureClassLoader {
+
+	/**
+	 * The search path for classes and resources.
+	 */
+	protected URLClassPath _ucp;
+	
+	/**
+	 * The context to be used when loading classes and resources
+	 */
+	protected AccessControlContext _acc;
+	
+	/**
+	 * Constructs a new ClassPathClassLoader for the given URLs. The URLs will be
+	 * searched in the order specified for classes and resources after first
+	 * searching in the specified parent class loader. Any URL that ends with
+	 * a '/' is assumed to refer to a directory. Otherwise, the URL is assumed
+	 * to refer to a JAR file which will be downloaded and opened as needed.
+	 *
+	 * <p>If there is a security manager, this method first
+	 * calls the security manager's <code>checkCreateClassLoader</code> method
+	 * to ensure creation of a class loader is allowed.
+	 * 
+	 * @param parent the parent class loader for delegation
+	 * @param acc is the current access context
+	 * @param urls the URLs from which to load classes and resources
+	 * @exception  SecurityException  if a security manager exists and its  
+	 *             <code>checkCreateClassLoader</code> method doesn't allow 
+	 *             creation of a class loader.
+	 * @see SecurityManager#checkCreateClassLoader
+	 */
+	protected DynamicURLClassLoader(ClassLoader parent, AccessControlContext acc, URL... urls) {
+		super(parent);
+		// this is to make the stack depth consistent with 1.1
+		SecurityManager security = System.getSecurityManager();
+		if (security != null) {
+			security.checkCreateClassLoader();
+		}
+		this._ucp = new URLClassPath(mergeClassPath(urls));
+		this._acc = acc;
+	}
+	
+	/**
+	 * Appends the specified URL to the list of URLs to search for
+	 * classes and resources.
+	 *
+	 * @param url the URL to be added to the search path of URLs
+	 */
+	public void addURL(final URL url) {
+		AccessController.doPrivileged(new PrivilegedAction<Object>() {
+			public Object run() {
+				DynamicURLClassLoader.this._ucp.addURL(url);
+				return null;
+			}
+		}, this._acc);
+	}
+	
+	/**
+	 * Appends the specified URL to the list of URLs to search for
+	 * classes and resources.
+	 *
+	 * @param urls the URLs to be added to the search path of URLs
+	 */
+	public void addURLs(URL... urls) {
+		for (URL url : urls) {
+			addURL(url);
+		}
+	}
+
+	/**
+	 * Appends the specified URL to the list of URLs to search for
+	 * classes and resources.
+	 *
+	 * @param urls the URL to be added to the search path of URLs
+	 */
+	public void removeURLs(URL... urls) {
+		HashSet<URL> set = new HashSet<URL>();
+		set.addAll(Arrays.asList(this._ucp.getURLs()));
+		set.removeAll(Arrays.asList(urls));
+		URL[] tab = new URL[set.size()];
+		set.toArray(tab);
+		this._ucp = new URLClassPath(tab);
+		tab = null;
+	}
+
+	/**
+	 * Appends the specified URL to the list of URLs to search for
+	 * classes and resources.
+	 *
+	 * @param url the URL to be added to the search path of URLs
+	 */
+	public void removeURL(URL url) {
+		removeURLs(url);
+	}
+
+	/**
+	 * Returns the search path of URLs for loading classes and resources.
+	 * This includes the original list of URLs specified to the constructor,
+	 * along with any URLs subsequently appended by the addURL() method.
+	 * @return the search path of URLs for loading classes and resources.
+	 */
+	public URL[] getURLs() {
+		return this._ucp.getURLs();
+	}
+	
+	/**
+	 * Finds and loads the class with the specified name from the URL search
+	 * path. Any URLs referring to JAR files are loaded and opened as needed
+	 * until the class is found.
+	 *
+	 * @param name the name of the class
+	 * @return the resulting class
+	 * @exception ClassNotFoundException if the class could not be found
+	 */
+	@Override
+	protected Class<?> findClass(final String name) throws ClassNotFoundException {
+		try {
+			return AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {
+				public Class<?> run() throws ClassNotFoundException {
+					String path = name.replace('.', '/').concat(".class"); //$NON-NLS-1$
+					Resource res = DynamicURLClassLoader.this._ucp.getResource(path, false);
+					if (res != null) {
+						try {
+							return defineClass(name, res);
+						}
+						catch (IOException e) {
+							throw new ClassNotFoundException(name, e);
+						}
+					}
+					throw new ClassNotFoundException(name);
+				}
+			}, this._acc);
+		}
+		catch (java.security.PrivilegedActionException pae) {
+			throw (ClassNotFoundException) pae.getException();
+		}
+	}
+	
+	/**
+	 * Defines a Class using the class bytes obtained from the specified
+	 * Resource. The resulting Class must be resolved before it can be
+	 * used.
+	 * 
+	 * @param name is the name of the class to define
+	 * @param res is the resource from which the class byte-code could be obtained
+	 * @return the loaded class.
+	 * @throws IOException in case the byte-code was unavailable.
+	 */
+	protected Class<?> defineClass(String name, Resource res) throws IOException {
+		int i = name.lastIndexOf('.');
+		URL url = res.getCodeSourceURL();
+		if (i != -1) {
+			String pkgname = name.substring(0, i);
+			// Check if package already loaded.
+			Package pkg = getPackage(pkgname);
+			Manifest man = res.getManifest();
+			if (pkg != null) {
+				// Package found, so check package sealing.
+				if (pkg.isSealed()) {
+					// Verify that code source URL is the same.
+					if (!pkg.isSealed(url)) {
+						throw new SecurityException(
+								"sealing violation: package " + pkgname + " is sealed"); //$NON-NLS-1$ //$NON-NLS-2$
+					}
+					
+				} else {
+					// Make sure we are not attempting to seal the package
+					// at this code source URL.
+					if ((man != null) && isSealed(pkgname, man)) {
+						throw new SecurityException(
+								"sealing violation: can't seal package " + pkgname +  //$NON-NLS-1$
+						": already loaded"); //$NON-NLS-1$
+					}
+				}
+			} else {
+				if (man != null) {
+					definePackage(pkgname, man, url);
+				} else {
+					definePackage(pkgname, null, null, null, null, null, null, null);
+				}
+			}
+		}
+		// Now read the class bytes and define the class
+		java.nio.ByteBuffer bb = res.getByteBuffer();
+		if (bb != null) {
+			// Use (direct) ByteBuffer:
+			CodeSigner[] signers = res.getCodeSigners();
+			CodeSource cs = new CodeSource(url, signers);
+			return defineClass(name, bb, cs);
+		}
+
+		byte[] b = res.getBytes();
+		// must read certificates AFTER reading bytes.
+		CodeSigner[] signers = res.getCodeSigners();
+		CodeSource cs = new CodeSource(url, signers);
+		return defineClass(name, b, 0, b.length, cs);
+
+	}
+	
+	/**
+	 * Defines a new package by name in this ClassLoader. The attributes
+	 * contained in the specified Manifest will be used to obtain package
+	 * version and sealing information. For sealed packages, the additional
+	 * URL specifies the code source URL from which the package was loaded.
+	 *
+	 * @param name  the package name
+	 * @param man   the Manifest containing package version and sealing
+	 *              information
+	 * @param url   the code source url for the package, or null if none
+	 * @exception   IllegalArgumentException if the package name duplicates
+	 *              an existing package either in this class loader or one
+	 *              of its ancestors
+	 * @return the newly defined Package object
+	 */
+	protected Package definePackage(String name, Manifest man, URL url)
+	throws IllegalArgumentException
+	{
+		String path = name.replace('.', '/').concat("/"); //$NON-NLS-1$
+		String specTitle = null, specVersion = null, specVendor = null;
+		String implTitle = null, implVersion = null, implVendor = null;
+		String sealed = null;
+		URL sealBase = null;
+		
+		Attributes attr = man.getAttributes(path);
+		if (attr != null) {
+			specTitle   = attr.getValue(Name.SPECIFICATION_TITLE);
+			specVersion = attr.getValue(Name.SPECIFICATION_VERSION);
+			specVendor  = attr.getValue(Name.SPECIFICATION_VENDOR);
+			implTitle   = attr.getValue(Name.IMPLEMENTATION_TITLE);
+			implVersion = attr.getValue(Name.IMPLEMENTATION_VERSION);
+			implVendor  = attr.getValue(Name.IMPLEMENTATION_VENDOR);
+			sealed      = attr.getValue(Name.SEALED);
+		}
+		attr = man.getMainAttributes();
+		if (attr != null) {
+			if (specTitle == null) {
+				specTitle = attr.getValue(Name.SPECIFICATION_TITLE);
+			}
+			if (specVersion == null) {
+				specVersion = attr.getValue(Name.SPECIFICATION_VERSION);
+			}
+			if (specVendor == null) {
+				specVendor = attr.getValue(Name.SPECIFICATION_VENDOR);
+			}
+			if (implTitle == null) {
+				implTitle = attr.getValue(Name.IMPLEMENTATION_TITLE);
+			}
+			if (implVersion == null) {
+				implVersion = attr.getValue(Name.IMPLEMENTATION_VERSION);
+			}
+			if (implVendor == null) {
+				implVendor = attr.getValue(Name.IMPLEMENTATION_VENDOR);
+			}
+			if (sealed == null) {
+				sealed = attr.getValue(Name.SEALED);
+			}
+		}
+		if ("true".equalsIgnoreCase(sealed)) { //$NON-NLS-1$
+			sealBase = url;
+		}
+		return definePackage(name, specTitle, specVersion, specVendor,
+				implTitle, implVersion, implVendor, sealBase);
+	}
+	
+	/*
+	 * Returns true if the specified package name is sealed according to the
+	 * given manifest.
+	 */
+	private boolean isSealed(String name, Manifest man) {
+		String path = name.replace('.', '/').concat("/"); //$NON-NLS-1$
+		Attributes attr = man.getAttributes(path);
+		String sealed = null;
+		if (attr != null) {
+			sealed = attr.getValue(Name.SEALED);
+		}
+		if (sealed == null) {
+			if ((attr = man.getMainAttributes()) != null) {
+				sealed = attr.getValue(Name.SEALED);
+			}
+		}
+		return "true".equalsIgnoreCase(sealed); //$NON-NLS-1$
+	}
+	
+	/**
+	 * Finds the resource with the specified name on the URL search path.
+	 *
+	 * @param name the name of the resource
+	 * @return a <code>URL</code> for the resource, or <code>null</code> 
+	 * if the resource could not be found.
+	 */
+	@Override
+	public URL findResource(final String name) {
+		/*
+		 * The same restriction to finding classes applies to resources
+		 */
+		URL url = 
+			AccessController.doPrivileged(new PrivilegedAction<URL>() {
+				public URL run() {
+					return DynamicURLClassLoader.this._ucp.findResource(name, true);
+				}
+			}, this._acc);
+		
+		return url != null ? this._ucp.checkURL(url) : null;
+	}
+	
+	/**
+	 * Returns an Enumeration of URLs representing all of the resources
+	 * on the URL search path having the specified name.
+	 *
+	 * @param name the resource name
+	 * @exception IOException if an I/O exception occurs
+	 * @return an <code>Enumeration</code> of <code>URL</code>s
+	 */
+	@Override
+	public Enumeration<URL> findResources(final String name) throws IOException {
+		final Enumeration<?> e = this._ucp.findResources(name, true);
+		
+		return new Enumeration<URL>() {
+			private URL url = null;
+			
+			private boolean next() {
+				if (this.url != null) {
+					return true;
+				}
+				do {
+					URL u = AccessController.doPrivileged(new PrivilegedAction<URL>() {
+						public URL run() {
+							if (!e.hasMoreElements())
+								return null;
+							return (URL)e.nextElement();
+						}
+					}, DynamicURLClassLoader.this._acc);
+					if (u == null) break;
+					this.url = DynamicURLClassLoader.this._ucp.checkURL(u);
+				}
+				while (this.url == null);
+				
+				return (this.url != null);
+			}
+			
+			public URL nextElement() {
+				if (!next()) {
+					throw new NoSuchElementException();
+				}
+				URL u = this.url;
+				this.url = null;
+				return u;
+			}
+			
+			public boolean hasMoreElements() {
+				return next();
+			}
+		};
+	}
+	
+	/**
+	 * Returns the permissions for the given codesource object.
+	 * The implementation of this method first calls super.getPermissions
+	 * and then adds permissions based on the URL of the codesource.
+	 * <p>
+	 * If the protocol is "file"
+	 * and the path specifies a file, then permission to read that
+	 * file is granted. If protocol is "file" and the path is
+	 * a directory, permission is granted to read all files
+	 * and (recursively) all files and subdirectories contained in
+	 * that directory.
+	 * <p>
+	 * If the protocol is not "file", then
+	 * to connect to and accept connections from the URL's host is granted.
+	 * @param codesource the codesource
+	 * @return the permissions granted to the codesource
+	 */
+	@Override
+	protected PermissionCollection getPermissions(CodeSource codesource) {
+		PermissionCollection perms = super.getPermissions(codesource);
+		
+		URL url = codesource.getLocation();
+		
+		Permission p;
+		URLConnection urlConnection;
+		
+		try {
+			urlConnection = url.openConnection();
+			p = urlConnection.getPermission();
+		}
+		catch (java.io.IOException ioe) {
+			p = null;
+			urlConnection = null;
+		}
+		
+		if ((p!=null)&&(p instanceof FilePermission)) {
+			// if the permission has a separator char on the end,
+			// it means the codebase is a directory, and we need
+			// to add an additional permission to read recursively
+			String path = p.getName();
+			if (path.endsWith(File.separator)) {
+				path += "-"; //$NON-NLS-1$
+				p = new FilePermission(path, SecurityConstants.FILE_READ_ACTION);
+			}
+		}
+		else if ((p == null) && (URISchemeType.FILE.isURL(url))) {
+			String path = url.getFile().replace('/', File.separatorChar);
+			path = ParseUtil.decode(path);
+			if (path.endsWith(File.separator))
+				path += "-"; //$NON-NLS-1$
+			p =  new FilePermission(path, SecurityConstants.FILE_READ_ACTION);
+		}
+		else {
+			URL locUrl = url;
+			if (urlConnection instanceof JarURLConnection) {
+				locUrl = ((JarURLConnection)urlConnection).getJarFileURL();
+			}
+			String host = locUrl.getHost();
+			if (host == null)
+				host = "localhost"; //$NON-NLS-1$
+			p = new SocketPermission(host,
+					SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION);
+		}
+		
+		// make sure the person that created this class loader
+		// would have this permission
+		
+		final SecurityManager sm = System.getSecurityManager();
+		if (sm != null) {
+			final Permission fp = p;
+			AccessController.doPrivileged(new PrivilegedAction<Object>() {
+				public Object run() throws SecurityException {
+					sm.checkPermission(fp);
+					return null;
+				}
+			}, this._acc);
+		}
+		perms.add(p);
+
+		return perms;
+	}
+	
+	/**
+	 * Creates a new instance of DynamicURLClassLoader for the specified
+	 * URLs and parent class loader. If a security manager is
+	 * installed, the <code>loadClass</code> method of the URLClassLoader
+	 * returned by this method will invoke the
+	 * <code>SecurityManager.checkPackageAccess</code> method before
+	 * loading the class.
+	 *
+	 * @param parent the parent class loader for delegation
+	 * @param urls the URLs to search for classes and resources
+	 * @return the resulting class loader
+	 */
+	public static DynamicURLClassLoader newInstance(final ClassLoader parent, final URL... urls) {
+		// Save the caller's context
+		final AccessControlContext acc = AccessController.getContext();
+		// Need a privileged block to create the class loader
+		DynamicURLClassLoader ucl =
+			AccessController.doPrivileged(new PrivilegedAction<DynamicURLClassLoader>() {
+				public DynamicURLClassLoader run() {
+					// Now set the context on the loader using the one we saved,
+					// not the one inside the privileged block...
+					return new FactoryDynamicURLClassLoader(parent, acc, urls);
+				}
+			});
+		return ucl;
+	}
+	
+    /**
+     * Merge the specified URLs to the current classpath.
+     */
+    private static URL[] mergeClassPath(URL... urls) {
+    	String path = System.getProperty("java.class.path"); //$NON-NLS-1$
+    	String separator = System.getProperty("path.separator"); //$NON-NLS-1$
+    	String[] parts = path.split(Pattern.quote(separator));
+    	URL[] u = new URL[parts.length+urls.length];
+    	for(int i=0; i<parts.length; i++) {
+    		try {
+				u[i] = new File(parts[i]).toURI().toURL();
+			}
+    		catch (MalformedURLException _) {
+				// ignore exception
+			}
+    	}
+    	System.arraycopy(urls,0,u,parts.length,urls.length);
+    	return u;
+    }
+
+    /** 
+     * @author St&eacute;phane GALLAND &lt;galland@xxxxxxxxxxx&gt;
+     * @version $Name$ $Revision$ $Date$
+     * @mavengroupid $GroupId$
+     * @mavenartifactid $ArtifactId$
+     */
+    protected final static class FactoryDynamicURLClassLoader extends DynamicURLClassLoader {
+
+    	/**
+    	 * @param parent is the parent class loader.
+    	 * @param acc is the accessible context.
+    	 * @param urls is the list of urls to insert inside the class loading path.
+    	 */
+    	protected FactoryDynamicURLClassLoader(ClassLoader parent, AccessControlContext acc, URL... urls) {
+    		super(parent,acc,urls);
+    	}
+    	
+    	/** {@inheritDoc}
+    	 * 
+    	 * @param name {@inheritDoc}
+    	 * @param resolve {@inheritDoc}
+    	 * @return {@inheritDoc}
+    	 * @throws ClassNotFoundException {@inheritDoc}
+    	 */
+    	@Override
+    	public final synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+    		// First check if we have permission to access the package. This
+    		// should go away once we've added support for exported packages.
+    		SecurityManager sm = System.getSecurityManager();
+    		if (sm != null) {
+    			int i = name.lastIndexOf('.');
+    			if (i != -1) {
+    				sm.checkPackageAccess(name.substring(0, i));
+    			}
+    		}
+    		return super.loadClass(name, resolve);
+    	}
+    	
+    }
+
+}


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/