/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2014 ForgeRock AS.
*/
package org.forgerock.openidm.patch.utils;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.jar.JarFile;
/**
* URL ClassLoader which can be 'closed' thereby closing file descriptors
* associated with JAR files.
*
* Required on Windows, otherwise JAR files cannot be deleted if they have
* previously been opened by a URLClassLoader.
*/
public class CloseableURLClassLoader extends URLClassLoader {
/**
* ClosableURLCLassLoader constructor.
*
* @param urls Array of JAR URLs to load
* @param parent The parent ClassLoader
*/
public CloseableURLClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
/**
* Close the CLassLoader and any associated resources including open JAR Files.
*/
public void close() {
Set<String> jarNames = getOpenJarFiles(this);
cleanupJarFileFactory(jarNames);
}
private void cleanupJarFileFactory(Set<String> jarNames) {
Class classJarURLConnection = null;
try {
classJarURLConnection = Class.forName("sun.net.www.protocol.jar.JarURLConnection");
} catch (ClassNotFoundException e) {
//ignore
}
Object factory = getFieldObject(classJarURLConnection, "factory", null);
HashMap cache = new HashMap();
HashMap fileCache = (HashMap)getFieldObject(factory.getClass(), "fileCache", null);
if (fileCache != null) {
cache.putAll(fileCache);
}
HashMap urlCache = (HashMap)getFieldObject(factory.getClass(), "urlCache", null);
if (urlCache != null) {
cache.putAll(urlCache);
}
Iterator it = cache.keySet().iterator();
while (it.hasNext()) {
Object obj = it.next();
if (!(obj instanceof JarFile)) {
continue;
}
JarFile jarFile = (JarFile) obj;
if (jarNames.contains(jarFile.getName())) {
try {
jarFile.close();
} catch (IOException e) {
//ignore
}
if (fileCache != null) {
fileCache.remove(jarFile);
}
if (urlCache != null) {
urlCache.remove(jarFile);
}
}
}
jarNames.clear();
}
private Object getFieldObject(Class c, String field, Object instance) {
Object obj = null;
try {
Field f = c.getDeclaredField(field);
if (f != null) {
f.setAccessible(true);
obj = f.get(instance);
}
} catch (NoSuchFieldException e) {
//ignore
} catch (IllegalAccessException e) {
//ignore
}
return obj;
}
private Set<String> getOpenJarFiles(ClassLoader cl) {
Set<String> jarNames = new HashSet<String>();
Class classURLClassLoader = URLClassLoader.class;
Object ucp = getFieldObject(classURLClassLoader, "ucp", cl);
ArrayList loaders = (ArrayList)getFieldObject(ucp.getClass(), "loaders", ucp);
for (Object o : loaders) {
Object jar = getFieldObject(o.getClass(), "jar", o);
if (jar != null && jar instanceof JarFile) {
final JarFile jarFile = (JarFile) jar;
jarNames.add(jarFile.getName());
try {
jarFile.close();
} catch (IOException e) {
// ignore
}
}
}
return jarNames;
}
}