diff options
author | simonbrandhof <simon.brandhof@gmail.com> | 2011-06-10 15:24:57 +0200 |
---|---|---|
committer | simonbrandhof <simon.brandhof@gmail.com> | 2011-06-10 15:24:57 +0200 |
commit | 78e9b96a51e2ef677a4eb8b801e18248d7e32283 (patch) | |
tree | e0bae2cb0f36df7ac10edddcb929683437abc2c1 /sonar-batch | |
parent | 3203b81516eb93052a05b74c6e0a5ddc7f1977ca (diff) | |
parent | 231d8aeceb89848a8099f1d516f85d76106f3fef (diff) | |
download | sonarqube-78e9b96a51e2ef677a4eb8b801e18248d7e32283.tar.gz sonarqube-78e9b96a51e2ef677a4eb8b801e18248d7e32283.zip |
Merge remote branch 'upstream/master'
Diffstat (limited to 'sonar-batch')
6 files changed, 201 insertions, 58 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapClassLoader.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapClassLoader.java deleted file mode 100644 index ca00f9a0691..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapClassLoader.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2008-2011 SonarSource - * mailto:contact AT sonarsource DOT com - * - * Sonar 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; either - * version 3 of the License, or (at your option) any later version. - * - * Sonar 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 Sonar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ -package org.sonar.batch.bootstrap; - -import org.sonar.api.utils.SonarException; - -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; - -/** - * ClassLoader extended with the JDBC Driver hosted on the server-side. - */ -public class BootstrapClassLoader { - - private URLClassLoader classLoader; - - public BootstrapClassLoader(ArtifactDownloader extensionDownloader) { - this(extensionDownloader.downloadJdbcDriver()); - } - - BootstrapClassLoader(File jdbcDriver) { - try { - ClassLoader parentClassLoader = BootstrapClassLoader.class.getClassLoader(); - classLoader = URLClassLoader.newInstance(new URL[]{jdbcDriver.toURI().toURL()}, parentClassLoader); - - } catch (MalformedURLException e) { - throw new SonarException("Fail to get URL of : " + jdbcDriver.getAbsolutePath(), e); - } - } - - public URLClassLoader getClassLoader() { - return classLoader; - } -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java index 0eaf338f8fa..fc09ef25dd0 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java @@ -55,9 +55,9 @@ public class BootstrapModule extends Module { addComponent(TempDirectories.class);// registered here because used by BootstrapClassLoader addComponent(HttpDownloader.class);// registered here because used by BootstrapClassLoader addComponent(ArtifactDownloader.class);// registered here because used by BootstrapClassLoader - addComponent(BootstrapClassLoader.class); + addComponent(JdbcDriverHolder.class); - URLClassLoader bootstrapClassLoader = getComponent(BootstrapClassLoader.class).getClassLoader(); + URLClassLoader bootstrapClassLoader = getComponent(JdbcDriverHolder.class).getClassLoader(); // set as the current context classloader for hibernate, else it does not find the JDBC driver. Thread.currentThread().setContextClassLoader(bootstrapClassLoader); diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java new file mode 100644 index 00000000000..427ac20e3ab --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java @@ -0,0 +1,132 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch.bootstrap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.utils.SonarException; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.List; + +/** + * Contains and provides class loader extended with the JDBC Driver hosted on the server-side. + */ +public class JdbcDriverHolder { + + private static Logger LOG = LoggerFactory.getLogger(JdbcDriverHolder.class); + private JdbcDriverClassLoader classLoader; + + public JdbcDriverHolder(ArtifactDownloader extensionDownloader) { + this(extensionDownloader.downloadJdbcDriver()); + } + + JdbcDriverHolder(File jdbcDriver) { + try { + ClassLoader parentClassLoader = JdbcDriverHolder.class.getClassLoader(); + classLoader = new JdbcDriverClassLoader(jdbcDriver.toURI().toURL(), parentClassLoader); + + } catch (MalformedURLException e) { + throw new SonarException("Fail to get URL of : " + jdbcDriver.getAbsolutePath(), e); + } + } + + public URLClassLoader getClassLoader() { + return classLoader; + } + + /** + * This method automatically invoked by PicoContainer and deregisters JDBC drivers, which were forgotten. + * <p> + * Dynamically loaded JDBC drivers can not be simply used and this is a well known problem of {@link java.sql.DriverManager}, + * so <a href="http://stackoverflow.com/questions/288828/how-to-use-a-jdbc-driver-from-an-arbitrary-location">workaround is to use proxy</a>. + * However DriverManager also contains memory leak, thus not only proxy, but also original driver must be deregistered, + * otherwise our class loader would be kept in memory. + * </p> + * <p> + * This operation contains unnecessary complexity because: + * <ul> + * <li>DriverManager checks the class loader of the calling class. Thus we can't simply ask it about deregistration.</li> + * <li>We can't use reflection against DriverManager, since it would create a dependency on DriverManager implementation, + * which can be changed (like it was done - compare Java 1.5 and 1.6).</li> + * <li>So we use companion - {@link JdbcLeakPrevention}. But we can't just create an instance, + * since it will be loaded by parent class loader and again will not pass DriverManager's check. + * So, we load the bytes via our parent class loader, but define the class with this class loader + * thus JdbcLeakPrevention looks like our class to the DriverManager.</li> + * </li> + * </p> + */ + public void stop() { + classLoader.clearReferencesJdbc(); + classLoader = null; + } + + private static class JdbcDriverClassLoader extends URLClassLoader { + + public JdbcDriverClassLoader(URL jdbcDriver, ClassLoader parent) { + super(new URL[] { jdbcDriver }, parent); + } + + public void clearReferencesJdbc() { + InputStream is = getResourceAsStream("org/sonar/batch/bootstrap/JdbcLeakPrevention.class"); + byte[] classBytes = new byte[2048]; + int offset = 0; + try { + int read = is.read(classBytes, offset, classBytes.length - offset); + while (read > -1) { + offset += read; + if (offset == classBytes.length) { + // Buffer full - double size + byte[] tmp = new byte[classBytes.length * 2]; + System.arraycopy(classBytes, 0, tmp, 0, classBytes.length); + classBytes = tmp; + } + read = is.read(classBytes, offset, classBytes.length - offset); + } + + Class<?> lpClass = defineClass("org.sonar.batch.bootstrap.JdbcLeakPrevention", classBytes, 0, offset, this.getClass().getProtectionDomain()); + Object obj = lpClass.newInstance(); + + @SuppressWarnings("unchecked") + List<String> driverNames = (List<String>) obj.getClass().getMethod("clearJdbcDriverRegistrations").invoke(obj); + + for (String name : driverNames) { + LOG.debug("To prevent a memory leak, the JDBC Driver [{}] has been forcibly deregistered", name); + } + } catch (Exception e) { + LOG.warn("JDBC driver deregistration failed", e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException ioe) { + LOG.warn(ioe.getMessage(), ioe); + } + } + } + } + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcLeakPrevention.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcLeakPrevention.java new file mode 100644 index 00000000000..6da6514d45e --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcLeakPrevention.java @@ -0,0 +1,64 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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; either + * version 3 of the License, or (at your option) any later version. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch.bootstrap; + +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; + +/** + * Companion of {@link JdbcDriverHolder} and allows it to deregister JDBC drivers. + * <p> + * Some hacks are involved in the loading of the class - see {@link JdbcDriverHolder#stop()}, + * so this class can refer to classes only from java.* package and must not be referred from other classes. + * Placement and naming of this class and methods are very important, since it loaded and invoked via reflection. + * </p> + */ +public class JdbcLeakPrevention { + + /** + * @return list of names of deregistered drivers + */ + public List<String> clearJdbcDriverRegistrations() throws SQLException { + List<String> driverNames = new ArrayList<String>(); + HashSet<Driver> originalDrivers = new HashSet<Driver>(); + Enumeration<Driver> drivers = DriverManager.getDrivers(); + while (drivers.hasMoreElements()) { + originalDrivers.add(drivers.nextElement()); + } + drivers = DriverManager.getDrivers(); + while (drivers.hasMoreElements()) { + Driver driver = drivers.nextElement(); + if (driver.getClass().getClassLoader() != this.getClass().getClassLoader()) { + continue; + } + if (originalDrivers.contains(driver)) { + driverNames.add(driver.getClass().getCanonicalName()); + } + DriverManager.deregisterDriver(driver); + } + return driverNames; + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java index 4962be2b8e6..dd59bb2009d 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java @@ -63,8 +63,8 @@ public class PastSnapshotFinder implements BatchExtension { case 1: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_1; break; case 2: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2; break; case 3: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_3; break; - case 4: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4; break; - case 5: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5; break;// NOSONAR false-positive: constant 5 is the same than 4 (empty string) + case 4: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4; break; // NOSONAR false-positive: constant 4 is the same than 5 (empty string) + case 5: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5; break; // NOSONAR false-positive: constant 5 is the same than 4 (empty string) } return conf.getString(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + index, defaultValue); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BootstrapClassLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BootstrapClassLoaderTest.java index b28a4c33431..dfee29db761 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BootstrapClassLoaderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BootstrapClassLoaderTest.java @@ -37,7 +37,7 @@ public class BootstrapClassLoaderTest { assertNull(getClass().getClassLoader().getResource("foo/foo.txt")); URL url = getClass().getResource("/org/sonar/batch/bootstrap/BootstrapClassLoaderTest/foo.jar"); - BootstrapClassLoader classloader = new BootstrapClassLoader(new File(url.toURI())); + JdbcDriverHolder classloader = new JdbcDriverHolder(new File(url.toURI())); assertNotNull(classloader.getClassLoader()); assertNotNull(classloader.getClassLoader().getResource("foo/foo.txt")); |