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 }