/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2013 by Pentaho : http://www.pentaho.com * ******************************************************************************* * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ package org.pentaho.di.core.plugins; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.Vector; import java.util.jar.JarFile; import org.pentaho.di.i18n.BaseMessages; public class KettleURLClassLoader extends URLClassLoader { private static Class<?> PKG = KettleURLClassLoader.class; // for i18n purposes, needed by Translator2!! private String name; public KettleURLClassLoader( URL[] url, ClassLoader classLoader ) { super( url, classLoader ); } public KettleURLClassLoader( URL[] url, ClassLoader classLoader, String name ) { this( url, classLoader ); this.name = name; } @Override public String toString() { return super.toString() + " : " + name; } public void setName( String name ) { this.name = name; } public String getName() { return name; } protected Class<?> loadClassFromThisLoader( String arg0, boolean arg1 ) throws ClassNotFoundException { Class<?> clz = null; if ( ( clz = findLoadedClass( arg0 ) ) != null ) { if ( arg1 ) { resolveClass( clz ); } return clz; } if ( ( clz = findClass( arg0 ) ) != null ) { if ( arg1 ) { resolveClass( clz ); } return clz; } return clz; } protected Class<?> loadClassFromParent( String arg0, boolean arg1 ) throws ClassNotFoundException { Class<?> clz; if ( ( clz = getParent().loadClass( arg0 ) ) != null ) { if ( arg1 ) { resolveClass( clz ); } return clz; } throw new ClassNotFoundException( "Could not find :" + arg0 ); } @Override protected synchronized Class<?> loadClass( String arg0, boolean arg1 ) throws ClassNotFoundException { try { return loadClassFromThisLoader( arg0, arg1 ); } catch ( ClassNotFoundException e ) { // ignore } catch ( NoClassDefFoundError e ) { // ignore } return loadClassFromParent( arg0, arg1 ); } /* * Cglib doe's not creates custom class loader (to access package methotds and classes ) it uses reflection to invoke * "defineClass", but you can call protected method in subclass without problems: */ public Class<?> loadClass( String name, ProtectionDomain protectionDomain ) { Class<?> loaded = findLoadedClass( name ); if ( loaded == null ) { // Get the jar, load the bytes from the jar file, construct class from scratch as in snippet below... /* * * loaded = super.findClass(name); * * URL url = super.findResource(newName); * * InputStream clis = getResourceAsStream(newName); */ String newName = name.replace( '.', '/' ); InputStream is = super.getResourceAsStream( newName ); byte[] driverBytes = toBytes( is ); loaded = super.defineClass( name, driverBytes, 0, driverBytes.length, protectionDomain ); } return loaded; } private byte[] toBytes( InputStream is ) { byte[] retval = new byte[0]; try { int a = is.available(); while ( a > 0 ) { byte[] buffer = new byte[a]; is.read( buffer ); byte[] newretval = new byte[retval.length + a]; for ( int i = 0; i < retval.length; i++ ) { newretval[i] = retval[i]; // old part } for ( int i = 0; i < a; i++ ) { newretval[retval.length + i] = buffer[i]; // new part } retval = newretval; a = is.available(); // see what's left } return retval; } catch ( Exception e ) { System.out.println( BaseMessages.getString( PKG, "KettleURLClassLoader.Exception.UnableToReadClass" ) + e.toString() ); return null; } } private static Object getFieldObject( Class<?> clazz, String name, Object obj ) throws Exception { Field field = clazz.getDeclaredField( name ); field.setAccessible( true ); return field.get( obj ); } /** * This method is designed to clear out classloader file locks in windows. * * @param clazzLdr * class loader to clean up */ public void closeClassLoader() { HashSet<String> closedFiles = new HashSet<String>(); try { Object obj = getFieldObject( URLClassLoader.class, "ucp", this ); ArrayList<?> loaders = (ArrayList<?>) getFieldObject( obj.getClass(), "loaders", obj ); for ( Object ldr : loaders ) { try { JarFile file = (JarFile) getFieldObject( ldr.getClass(), "jar", ldr ); closedFiles.add( file.getName() ); file.close(); } catch ( Exception e ) { // skip } } } catch ( Exception e ) { // skip } try { Vector<?> nativeLibArr = (Vector<?>) getFieldObject( ClassLoader.class, "nativeLibraries", this ); for ( Object lib : nativeLibArr ) { try { Method fMethod = lib.getClass().getDeclaredMethod( "finalize", new Class<?>[0] ); fMethod.setAccessible( true ); fMethod.invoke( lib, new Object[0] ); } catch ( Exception e ) { // skip } } } catch ( Exception e ) { // skip } HashMap<?, ?> uCache = null; HashMap<?, ?> fCache = null; try { Class<?> jarUrlConnClass = null; try { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); jarUrlConnClass = contextClassLoader.loadClass( "sun.net.www.protocol.jar.JarURLConnection" ); } catch ( Throwable skip ) { // skip } if ( jarUrlConnClass == null ) { jarUrlConnClass = Class.forName( "sun.net.www.protocol.jar.JarURLConnection" ); } Class<?> factory = getFieldObject( jarUrlConnClass, "factory", null ).getClass(); try { fCache = (HashMap<?, ?>) getFieldObject( factory, "fileCache", null ); } catch ( Exception e ) { // skip } try { uCache = (HashMap<?, ?>) getFieldObject( factory, "urlCache", null ); } catch ( Exception e ) { // skip } if ( uCache != null ) { Set<?> set = null; while ( set == null ) { try { set = ( (HashMap<?, ?>) uCache.clone() ).keySet(); } catch ( ConcurrentModificationException e ) { //Fix for BACKLOG-2149 - Do nothing - while loop will try again. } } for ( Object file : set ) { if ( file instanceof JarFile ) { JarFile jar = (JarFile) file; if ( !closedFiles.contains( jar.getName() ) ) { continue; } try { jar.close(); } catch ( IOException e ) { // skip } if ( fCache != null ) { fCache.remove( uCache.get( jar ) ); } uCache.remove( jar ); } } } else if ( fCache != null ) { for ( Object key : ( (HashMap<?, ?>) fCache.clone() ).keySet() ) { Object file = fCache.get( key ); if ( file instanceof JarFile ) { JarFile jar = (JarFile) file; if ( !closedFiles.contains( jar.getName() ) ) { continue; } try { jar.close(); } catch ( IOException e ) { // ignore } fCache.remove( key ); } } } } catch ( Exception e ) { // skip e.printStackTrace(); } } @Override public URL getResource( String name ) { URL url; url = findResource( name ); if ( url == null && getParent() != null ) { url = getParent().getResource( name ); } return url; } }