1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 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 final 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;
}
public void start() {
// set as the current context classloader for hibernate, else it does not find the JDBC driver.
Thread.currentThread().setContextClassLoader(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();
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);
}
}
}
}
}
}
|