diff --git a/azkaban-common/src/main/java/azkaban/project/validator/ValidatorClassLoader.java b/azkaban-common/src/main/java/azkaban/project/validator/ValidatorClassLoader.java
new file mode 100644
index 0000000..36d43b9
--- /dev/null
+++ b/azkaban-common/src/main/java/azkaban/project/validator/ValidatorClassLoader.java
@@ -0,0 +1,270 @@
+package azkaban.project.validator;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+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.Vector;
+import java.util.jar.JarFile;
+
+/**
+ * Workaround for jdk 6 disgrace with open jar files & native libs,
+ * which is a reason of unrefreshable classloader.
+ */
+public class ValidatorClassLoader extends URLClassLoader {
+
+ protected HashSet<String> setJarFileNames2Close = new HashSet<String>();
+
+ public ValidatorClassLoader(URL[] urls, ClassLoader parent) {
+ super(urls, parent);
+ }
+
+ public ValidatorClassLoader(URL[] urls) {
+ super(urls);
+ }
+
+ public void close() {
+ setJarFileNames2Close.clear();
+ closeClassLoader(this);
+ finalizeNativeLibs(this);
+ cleanupJarFileFactory();
+ }
+
+ /**
+ * cleanup jar file factory cache
+ */
+ @SuppressWarnings({ "nls", "rawtypes" })
+ public boolean cleanupJarFileFactory() {
+ boolean res = false;
+ Class classJarURLConnection = null;
+ try {
+ classJarURLConnection = Class.forName("sun.net.www.protocol.jar.JarURLConnection");
+ } catch (ClassNotFoundException e) {
+ //ignore
+ }
+ if (classJarURLConnection == null) {
+ return res;
+ }
+ Field f = null;
+ try {
+ f = classJarURLConnection.getDeclaredField("factory");
+ } catch (NoSuchFieldException e) {
+ //ignore
+ }
+ if (f == null) {
+ return res;
+ }
+ f.setAccessible(true);
+ Object obj = null;
+ try {
+ obj = f.get(null);
+ } catch (IllegalAccessException e) {
+ //ignore
+ }
+ if (obj == null) {
+ return res;
+ }
+ Class classJarFileFactory = obj.getClass();
+ //
+ HashMap fileCache = null;
+ try {
+ f = classJarFileFactory.getDeclaredField("fileCache");
+ f.setAccessible(true);
+ obj = f.get(null);
+ if (obj instanceof HashMap) {
+ fileCache = (HashMap) obj;
+ }
+ } catch (NoSuchFieldException e) {
+ } catch (IllegalAccessException e) {
+ //ignore
+ }
+ HashMap urlCache = null;
+ try {
+ f = classJarFileFactory.getDeclaredField("urlCache");
+ f.setAccessible(true);
+ obj = f.get(null);
+ if (obj instanceof HashMap) {
+ urlCache = (HashMap) obj;
+ }
+ } catch (NoSuchFieldException e) {
+ } catch (IllegalAccessException e) {
+ //ignore
+ }
+ if (urlCache != null) {
+ HashMap urlCacheTmp = (HashMap) urlCache.clone();
+ Iterator it = urlCacheTmp.keySet().iterator();
+ while (it.hasNext()) {
+ obj = it.next();
+ if (!(obj instanceof JarFile)) {
+ continue;
+ }
+ JarFile jarFile = (JarFile) obj;
+ if (setJarFileNames2Close.contains(jarFile.getName())) {
+ try {
+ jarFile.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ if (fileCache != null) {
+ fileCache.remove(urlCache.get(jarFile));
+ }
+ urlCache.remove(jarFile);
+ }
+ }
+ res = true;
+ } else if (fileCache != null) {
+ // urlCache := null
+ HashMap fileCacheTmp = (HashMap) fileCache.clone();
+ Iterator it = fileCacheTmp.keySet().iterator();
+ while (it.hasNext()) {
+ Object key = it.next();
+ obj = fileCache.get(key);
+ if (!(obj instanceof JarFile)) {
+ continue;
+ }
+ JarFile jarFile = (JarFile) obj;
+ if (setJarFileNames2Close.contains(jarFile.getName())) {
+ try {
+ jarFile.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ fileCache.remove(key);
+ }
+ }
+ res = true;
+ }
+ setJarFileNames2Close.clear();
+ return res;
+ }
+
+ /**
+ * close jar files of cl
+ * @param cl
+ * @return
+ */
+ @SuppressWarnings({ "nls", "rawtypes" })
+ public boolean closeClassLoader(ClassLoader cl) {
+ boolean res = false;
+ if (cl == null) {
+ return res;
+ }
+ Class classURLClassLoader = URLClassLoader.class;
+ Field f = null;
+ try {
+ f = classURLClassLoader.getDeclaredField("ucp");
+ } catch (NoSuchFieldException e1) {
+ //ignore
+ }
+ if (f != null) {
+ f.setAccessible(true);
+ Object obj = null;
+ try {
+ obj = f.get(cl);
+ } catch (IllegalAccessException e1) {
+ //ignore
+ }
+ if (obj != null) {
+ final Object ucp = obj;
+ f = null;
+ try {
+ f = ucp.getClass().getDeclaredField("loaders");
+ } catch (NoSuchFieldException e1) {
+ //ignore
+ }
+ if (f != null) {
+ f.setAccessible(true);
+ ArrayList loaders = null;
+ try {
+ loaders = (ArrayList) f.get(ucp);
+ res = true;
+ } catch (IllegalAccessException e1) {
+ //ignore
+ }
+ for (int i = 0; loaders != null && i < loaders.size(); i++) {
+ obj = loaders.get(i);
+ f = null;
+ try {
+ f = obj.getClass().getDeclaredField("jar");
+ } catch (NoSuchFieldException e) {
+ //ignore
+ }
+ if (f != null) {
+ f.setAccessible(true);
+ try {
+ obj = f.get(obj);
+ } catch (IllegalAccessException e1) {
+ // ignore
+ }
+ if (obj instanceof JarFile) {
+ final JarFile jarFile = (JarFile) obj;
+ setJarFileNames2Close.add(jarFile.getName());
+ try {
+ jarFile.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ /**
+ * finalize native libraries
+ * @param cl
+ * @return
+ */
+ @SuppressWarnings({ "nls", "rawtypes" })
+ public boolean finalizeNativeLibs(ClassLoader cl) {
+ boolean res = false;
+ Class classClassLoader = ClassLoader.class;
+ java.lang.reflect.Field nativeLibraries = null;
+ try {
+ nativeLibraries = classClassLoader.getDeclaredField("nativeLibraries");
+ } catch (NoSuchFieldException e1) {
+ //ignore
+ }
+ if (nativeLibraries == null) {
+ return res;
+ }
+ nativeLibraries.setAccessible(true);
+ Object obj = null;
+ try {
+ obj = nativeLibraries.get(cl);
+ } catch (IllegalAccessException e1) {
+ //ignore
+ }
+ if (!(obj instanceof Vector)) {
+ return res;
+ }
+ res = true;
+ Vector java_lang_ClassLoader_NativeLibrary = (Vector) obj;
+ for (Object lib : java_lang_ClassLoader_NativeLibrary) {
+ java.lang.reflect.Method finalize = null;
+ try {
+ finalize = lib.getClass().getDeclaredMethod("finalize", new Class[0]);
+ } catch (NoSuchMethodException e) {
+ //ignore
+ }
+ if (finalize != null) {
+ finalize.setAccessible(true);
+ try {
+ finalize.invoke(lib, new Object[0]);
+ } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException e) {
+ //ignore
+ }
+ }
+ }
+ return res;
+ }
+}
diff --git a/azkaban-common/src/main/java/azkaban/project/validator/XmlValidatorManager.java b/azkaban-common/src/main/java/azkaban/project/validator/XmlValidatorManager.java
index 8e56834..0efcf43 100644
--- a/azkaban-common/src/main/java/azkaban/project/validator/XmlValidatorManager.java
+++ b/azkaban-common/src/main/java/azkaban/project/validator/XmlValidatorManager.java
@@ -5,7 +5,6 @@ import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
-import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -53,7 +52,7 @@ public class XmlValidatorManager implements ValidatorManager {
public static final String DEFAULT_VALIDATOR_KEY = "Directory Flow";
private static Map<String, Long> resourceTimestamps = new HashMap<String, Long>();
- private static URLClassLoader validatorLoader;
+ private static ValidatorClassLoader validatorLoader;
private Map<String, ProjectValidator> validators;
private String validatorDirPath;
@@ -108,16 +107,12 @@ public class XmlValidatorManager implements ValidatorManager {
}
if (reloadResources) {
- try {
- if (validatorLoader != null) {
- validatorLoader.close();
- }
- } catch (IOException e) {
- logger.error("Cannot reload validator classloader because failure "
- + "to close the validator classloader.");
- // We do not throw the ValidatorManagerException because we do not want to crash Azkaban at runtime.
+ if (validatorLoader != null) {
+ // Since we cannot use Java 7 feature inside Azkaban (....), we need a customized class loader
+ // that does the close for us.
+ validatorLoader.close();
}
- validatorLoader = new URLClassLoader(resources.toArray(new URL[resources.size()]));
+ validatorLoader = new ValidatorClassLoader(resources.toArray(new URL[resources.size()]));
}
}