Java tutorial
/** * Copyright (C) 2009 BonitaSoft S.A. * BonitaSoft, 31 rue Gustave Eiffel - 38000 Grenoble * 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 * version 2.1 of the License. * 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 * program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth * Floor, Boston, MA 02110-1301, USA. **/ package org.ow2.bonita.runtime; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.JarFile; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.logging.LogFactory; import org.ow2.bonita.services.LargeDataRepository; import org.ow2.bonita.util.EnvTool; import org.ow2.bonita.util.Misc; import org.ow2.bonita.util.ServerConstants; /** * * @author Charles Souillard, Matthieu Chaffotte, Elias Ricken de Medeiros * */ abstract class AbstractClassLoader extends URLClassLoader { private static final Logger LOG = Logger.getLogger(AbstractClassLoader.class.getName()); protected Map<String, byte[]> otherResources; protected Set<URL> urls; private boolean isReleasing = false; static { // Setting useCaches to false avoids a memory leak of URLJarFile instances // It's a workaround for a Sun bug (see bug id 4167874 - fixed in jdk 1.7). Otherwise, // URLJarFiles will never be garbage collected. o.a.g.deployment.util.DeploymentUtil.readAll() // causes URLJarFiles to be created try { // Protocol/file shouldn't matter. // As long as we don't get an input/output stream, no operations should occur... if (Misc.isOnWindows()) { if (LOG.isLoggable(Level.WARNING)) { LOG.warning("Running on Windows. Deactivating cache"); } new java.net.URL("http://a").openConnection().setDefaultUseCaches(false); } } catch (IOException ioe) { // Can't Log this. Should we send to STDOUT/STDERR? } } AbstractClassLoader(final List<String> categories, final ClassLoader parent) { super(new URL[] {}, parent); final LargeDataRepository ldr = EnvTool.getLargeDataRepository(); final Map<String, byte[]> resources = ldr.getData(byte[].class, categories); if (LOG.isLoggable(Level.FINE)) { LOG.fine("Creating a new AbstratctClassLoader..."); } if (resources != null) { this.otherResources = new HashMap<String, byte[]>(); urls = new HashSet<URL>(); for (Map.Entry<String, byte[]> resource : resources.entrySet()) { if (resource.getKey().matches((".*\\.jar"))) { final byte[] data = ldr.getData(byte[].class, categories, resource.getKey()); try { final File tmpDir = Misc .createDirectories(ServerConstants.getTenantTemporaryFolder(EnvTool.getDomain())); final File file = Misc.createTempFile(resource.getKey(), null, tmpDir); Misc.write(file, data); final String path = file.getAbsolutePath(); final URL url = new File(path).toURL(); urls.add(url); addURL(url); file.deleteOnExit(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } else { this.otherResources.put(resource.getKey(), resource.getValue()); } } if (this.otherResources.isEmpty()) { this.otherResources = null; } } } @Override public InputStream getResourceAsStream(String name) { InputStream is = getInternalInputstream(name); if (is == null && name.startsWith("/")) { is = getInternalInputstream(name.substring(1)); } return is; } private InputStream getInternalInputstream(final String name) { byte[] classData = loadProcessResource(name); if (classData != null) { return new ByteArrayInputStream(classData); } InputStream is = super.getResourceAsStream(name); if (is != null) { return is; } return null; } private byte[] loadProcessResource(String resourceName) { if (this.otherResources == null) { return null; } return otherResources.get(resourceName); } @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> c = findLoadedClass(name); if (c == null && !isReleasing) { try { c = findClass(name); } catch (ClassNotFoundException e) { } catch (IllegalStateException e) { } } if (c == null) { c = getParent().loadClass(name); } if (resolve) { resolveClass(c); } if (LOG.isLoggable(Level.FINE)) { LOG.fine("loadClass: " + name + ", result: " + c); } return c; } public void release() { isReleasing = true; LogFactory.release(this); for (URL url : urls) { if (Misc.isOnWindows()) { releaseConnection(url); } File removeFile = new File(url.getFile()); removeFile.delete(); } } private void releaseConnection(URL url) { if (LOG.isLoggable(Level.INFO)) { LOG.info("Releasing class loader: " + this); } try { final URLConnection conn = url.openConnection(); if (LOG.isLoggable(Level.INFO)) { LOG.info("Getting connection of url: " + url + ", conn=" + conn); } final String fileURLConnectionClassName = "sun.net.www.protocol.file.FileURLConnection"; if (conn instanceof JarURLConnection) { JarFile jarfile = ((JarURLConnection) conn).getJarFile(); if (LOG.isLoggable(Level.INFO)) { LOG.info("Closing jar file: " + jarfile.getName()); } jarfile.close(); } else if (conn.getClass().getName().equals(fileURLConnectionClassName)) { if (LOG.isLoggable(Level.INFO)) { LOG.info("Closing connection (" + fileURLConnectionClassName + ": " + conn); } Method close = conn.getClass().getMethod("close", (Class[]) null); close.invoke(conn, (Object[]) null); } } catch (Exception e) { if (LOG.isLoggable(Level.WARNING)) { LOG.warning("Error while releasing classloader: " + this + ": " + Misc.getStackTraceFrom(e)); } e.printStackTrace(); } close(); } private void close() { try { Class<?> clazz = URLClassLoader.class; Field ucp = clazz.getDeclaredField("ucp"); ucp.setAccessible(true); Object sun_misc_URLClassPath = ucp.get(this); Field loaders = sun_misc_URLClassPath.getClass().getDeclaredField("loaders"); loaders.setAccessible(true); Object java_util_Collection = loaders.get(sun_misc_URLClassPath); for (Object sun_misc_URLClassPath_JarLoader : ((Collection<?>) java_util_Collection).toArray()) { try { java.lang.reflect.Field loader = sun_misc_URLClassPath_JarLoader.getClass() .getDeclaredField("jar"); loader.setAccessible(true); Object java_util_jar_JarFile = loader.get(sun_misc_URLClassPath_JarLoader); ((java.util.jar.JarFile) java_util_jar_JarFile).close(); } catch (Throwable t) { // if we got this far, this is probably not a JAR loader so skip it } } } catch (Throwable t) { // probably not a SUN VM } return; } }