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 */
020package org.sonar.batch.bootstrap;
021
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024import org.sonar.api.utils.SonarException;
025
026import java.io.File;
027import java.io.IOException;
028import java.io.InputStream;
029import java.net.MalformedURLException;
030import java.net.URL;
031import java.net.URLClassLoader;
032import java.util.List;
033
034/**
035 * Contains and provides class loader extended with the JDBC Driver hosted on the server-side.
036 */
037public 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        List<String> driverNames = (List<String>) obj.getClass().getMethod("clearJdbcDriverRegistrations").invoke(obj);
113
114        for (String name : driverNames) {
115          LOG.debug("To prevent a memory leak, the JDBC Driver [{}] has been forcibly deregistered", name);
116        }
117      } catch (Exception e) {
118        LOG.warn("JDBC driver deregistration failed", e);
119      } finally {
120        if (is != null) {
121          try {
122            is.close();
123          } catch (IOException ioe) {
124            LOG.warn(ioe.getMessage(), ioe);
125          }
126        }
127      }
128    }
129  }
130
131}