package org.codehaus.mojo.runtime.execute; /* * Copyright (c) 2004, Codehaus.org * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.net.URL; import java.security.SecureClassLoader; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import org.codehaus.mojo.runtime.model.Runtime; /** * The RuntimeClassLoader is meant to be used inside of a jar file and takes in a * Runtime object (as defined by the runtime mojo, not the System Runtime) and * uses the dependencies specified in it to construct references to jar files * inside of that jar file. * <p/> * This classloader can be used with a RuntimeExecutor to generate a complete * execution environment. * <p/> * NOTE: the classes that the RuntimeExecutor are trying in instantiate need to be * in their own jars and referenced as dependencies as well, if they are in the same * loader as the RuntimeExecutor class then they are loaded by the parent classloader * regardless if they are loaded _through_ this classloader...meaning they are unable * to resolve the dependencies this classloader services. * * @author jesse * @version $Id$ */ public class RuntimeClassLoader extends SecureClassLoader { private Map classMap = new HashMap(); private List dependencyUrlList = new ArrayList(); /** * constructor for the classloader * * @param runtime * @param parentLoader */ public RuntimeClassLoader( Runtime runtime, ClassLoader parentLoader ) { super( parentLoader ); try { List dependencies = runtime.getJar().getDependencies(); for ( int i = 0; i < dependencies.size(); ++i ) { String dependency = (String) dependencies.get( i ); String[] data = dependency.split( ":" ); if ( data.length != 3 ) { throw new IllegalArgumentException( "invalid dependency string '" + dependency + "'" ); } StringBuffer sb = new StringBuffer(); sb.append( "lib" ).append( File.separator ).append( data[1] ).append( "-" ); sb.append( data[2] ).append( ".jar" ); if ( Thread.currentThread().getContextClassLoader().getResource( sb.toString() ) != null ) { dependencyUrlList.add( Thread.currentThread().getContextClassLoader().getResource( sb.toString() ) ); } } } catch ( Exception e ) { System.err.println( "FATAL: error constructing classpath: " + e.getMessage() ); e.printStackTrace(); } } /** * @see ClassLoader */ public synchronized Class findClass( String className ) throws ClassNotFoundException { String classPath = className.replace( '.', '/' ) + ".class"; //System.out.println("looking for: " + className ); if ( this.classMap.containsKey( classPath ) ) { //System.out.println("getting loaded class" + className); return (Class) this.classMap.get( classPath ); } try { for ( Iterator i = dependencyUrlList.iterator(); i.hasNext(); ) { URL jarUrl = (URL) i.next(); //System.out.println("checking " + jarUrl.toExternalForm()); JarInputStream in = new JarInputStream( jarUrl.openStream() ); try { JarEntry entry; while ( ( entry = in.getNextJarEntry() ) != null ) { if ( entry.getName().equals( classPath ) ) { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { byte[] buffer = new byte[2048]; int read; while ( in.available() > 0 ) { read = in.read( buffer, 0, buffer.length ); if ( read < 0 ) { break; } out.write( buffer, 0, read ); } buffer = out.toByteArray(); //System.out.println("defining class " + className); Class cls = defineClass( className, buffer, 0, buffer.length ); //System.out.println("linking class " + className); resolveClass( cls ); this.classMap.put( className, cls ); return cls; } finally { out.close(); } } } } finally { in.close(); } } } catch ( IOException e ) { e.printStackTrace(); throw new ClassNotFoundException( "io error reading stream for: " + className ); } return super.findClass( className ); // throw new ClassNotFoundException( className ); } protected synchronized Class loadClass( String className, boolean resolve ) throws ClassNotFoundException { //System.out.println(className + " runtime classloader bool" + resolve); Class c = super.loadClass( className, resolve ); if ( c != null ) { return c; } else { if ( resolve ) { c = findClass( className ); if ( c != null ) { resolveClass( c ); return c; } else { throw new ClassNotFoundException( className ); } } else { return findClass( className ); } } } public Class loadClass( String className ) throws ClassNotFoundException { //System.out.println(className + " runtime classloader"); Class c = super.loadClass( className ); if ( c == null ) { return findClass( className ); } else { return c; } } }