001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2012 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020 package org.sonar.batch.bootstrap; 021 022 import org.slf4j.Logger; 023 import org.slf4j.LoggerFactory; 024 import org.sonar.api.utils.SonarException; 025 026 import java.io.File; 027 import java.io.IOException; 028 import java.io.InputStream; 029 import java.net.MalformedURLException; 030 import java.net.URL; 031 import java.net.URLClassLoader; 032 import java.util.List; 033 034 /** 035 * Contains and provides class loader extended with the JDBC Driver hosted on the server-side. 036 */ 037 public class JdbcDriverHolder { 038 039 private static final Logger LOG = LoggerFactory.getLogger(JdbcDriverHolder.class); 040 private JdbcDriverClassLoader classLoader; 041 042 public JdbcDriverHolder(ArtifactDownloader extensionDownloader) { 043 this(extensionDownloader.downloadJdbcDriver()); 044 } 045 046 JdbcDriverHolder(File jdbcDriver) { 047 try { 048 ClassLoader parentClassLoader = JdbcDriverHolder.class.getClassLoader(); 049 classLoader = new JdbcDriverClassLoader(jdbcDriver.toURI().toURL(), parentClassLoader); 050 051 } catch (MalformedURLException e) { 052 throw new SonarException("Fail to get URL of : " + jdbcDriver.getAbsolutePath(), e); 053 } 054 } 055 056 public URLClassLoader getClassLoader() { 057 return classLoader; 058 } 059 060 /** 061 * This method automatically invoked by PicoContainer and deregisters JDBC drivers, which were forgotten. 062 * <p> 063 * Dynamically loaded JDBC drivers can not be simply used and this is a well known problem of {@link java.sql.DriverManager}, 064 * so <a href="http://stackoverflow.com/questions/288828/how-to-use-a-jdbc-driver-from-an-arbitrary-location">workaround is to use proxy</a>. 065 * However DriverManager also contains memory leak, thus not only proxy, but also original driver must be deregistered, 066 * otherwise our class loader would be kept in memory. 067 * </p> 068 * <p> 069 * This operation contains unnecessary complexity because: 070 * <ul> 071 * <li>DriverManager checks the class loader of the calling class. Thus we can't simply ask it about deregistration.</li> 072 * <li>We can't use reflection against DriverManager, since it would create a dependency on DriverManager implementation, 073 * which can be changed (like it was done - compare Java 1.5 and 1.6).</li> 074 * <li>So we use companion - {@link JdbcLeakPrevention}. But we can't just create an instance, 075 * since it will be loaded by parent class loader and again will not pass DriverManager's check. 076 * So, we load the bytes via our parent class loader, but define the class with this class loader 077 * thus JdbcLeakPrevention looks like our class to the DriverManager.</li> 078 * </li> 079 * </p> 080 */ 081 public void stop() { 082 classLoader.clearReferencesJdbc(); 083 classLoader = null; 084 } 085 086 private static class JdbcDriverClassLoader extends URLClassLoader { 087 088 public JdbcDriverClassLoader(URL jdbcDriver, ClassLoader parent) { 089 super(new URL[] { jdbcDriver }, parent); 090 } 091 092 public void clearReferencesJdbc() { 093 InputStream is = getResourceAsStream("org/sonar/batch/bootstrap/JdbcLeakPrevention.class"); 094 byte[] classBytes = new byte[2048]; 095 int offset = 0; 096 try { 097 int read = is.read(classBytes, offset, classBytes.length - offset); 098 while (read > -1) { 099 offset += read; 100 if (offset == classBytes.length) { 101 // Buffer full - double size 102 byte[] tmp = new byte[classBytes.length * 2]; 103 System.arraycopy(classBytes, 0, tmp, 0, classBytes.length); 104 classBytes = tmp; 105 } 106 read = is.read(classBytes, offset, classBytes.length - offset); 107 } 108 109 Class<?> lpClass = defineClass("org.sonar.batch.bootstrap.JdbcLeakPrevention", classBytes, 0, offset, this.getClass().getProtectionDomain()); 110 Object obj = lpClass.newInstance(); 111 112 @SuppressWarnings("unchecked") 113 List<String> driverNames = (List<String>) obj.getClass().getMethod("clearJdbcDriverRegistrations").invoke(obj); 114 115 for (String name : driverNames) { 116 LOG.debug("To prevent a memory leak, the JDBC Driver [{}] has been forcibly deregistered", name); 117 } 118 } catch (Exception e) { 119 LOG.warn("JDBC driver deregistration failed", e); 120 } finally { 121 if (is != null) { 122 try { 123 is.close(); 124 } catch (IOException ioe) { 125 LOG.warn(ioe.getMessage(), ioe); 126 } 127 } 128 } 129 } 130 } 131 132 }