Skip to content

Commit

Permalink
Refactored classloader management to not change the Thread's context …
Browse files Browse the repository at this point in the history
…classloader

Auto-add jar files found in configured classpath directories

Improved on-screen help for jdbc url and classpath

DAT-4624
SECURITY-519
  • Loading branch information
nvoxland committed May 6, 2020
1 parent 382a1ea commit 7726ce4
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 74 deletions.
94 changes: 40 additions & 54 deletions src/main/java/org/jenkinsci/plugins/liquibase/common/Util.java
@@ -1,32 +1,28 @@
package org.jenkinsci.plugins.liquibase.common;

import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import hudson.FilePath;
import liquibase.changelog.ChangeSet;
import liquibase.changelog.DatabaseChangeLog;
import liquibase.util.StringUtils;
import org.jenkinsci.plugins.liquibase.exception.LiquibaseRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;

import javax.annotation.Nullable;

import org.jenkinsci.plugins.liquibase.exception.LiquibaseRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.List;

public class Util {
private static final Logger LOG = LoggerFactory.getLogger(Util.class);
Expand All @@ -47,62 +43,52 @@ public static String formatChangeset(ChangeSet changeSet) {
return filePath + "::" + changeSetName.replace(filePath + "::", "");
}

public static void registerDatabaseDriver(String driverName, String classpath)
throws InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException {
public static void registerDatabaseDriver(String driverName, ClassLoader liquibaseClassLoader) throws SQLException {
Driver driver;
if (!Strings.isNullOrEmpty(classpath)) {
Driver actualDriver =
(Driver) Class.forName(driverName, true, Thread.currentThread().getContextClassLoader())
.newInstance();
try {
Driver actualDriver = (Driver) Class.forName(driverName, true, liquibaseClassLoader).getDeclaredConstructor().newInstance();
driver = new DriverShim(actualDriver);
} else {
driver = (Driver) Class.forName(driverName).newInstance();
}

DriverManager.registerDriver(driver);
DriverManager.registerDriver(driver);
} catch (ReflectiveOperationException e) {
throw new SQLException("Cannot create driver " + driverName + ": " + e.getMessage(), e);
}
}

public static void addClassloader(boolean isUnix, final FilePath workspace, String classpath) {
public static ClassLoader createClassLoader(boolean isUnix, final FilePath workspace, String classpath) {
String separator;
if (isUnix) {
separator = ":";
} else {
separator = ";";
}

List<URL> classpathUrls = new ArrayList<>();
Iterable<String> classPathElements = Splitter.on(separator).trimResults().split(classpath);
final Iterable<URL> urlIterable = Iterables.transform(classPathElements, new Function<String, URL>() {
@Override
public URL apply(@Nullable String filePath) {
URL url = null;
if (filePath != null) {
try {
if (Paths.get(filePath).isAbsolute()) {
url = new File(filePath).toURI().toURL();
} else {
URI workspaceUri = workspace.toURI();
File workspace = new File(workspaceUri);
url = new File(workspace, filePath).toURI().toURL();
}
} catch (MalformedURLException e) {
LOG.warn("Unable to transform classpath element " + filePath, e);
} catch (InterruptedException e) {
throw new LiquibaseRuntimeException("Error during database driver resolution", e);
} catch (IOException e) {
throw new LiquibaseRuntimeException("Error during database driver resolution", e);
}
}
return url;

for (String filePath : classPathElements) {
filePath = StringUtils.trimToNull(filePath);
if (filePath == null) {
continue;
}
});

URLClassLoader urlClassLoader = AccessController.doPrivileged(new PrivilegedAction<URLClassLoader>() {
@Override
public URLClassLoader run() {
return new URLClassLoader(Iterables.toArray(urlIterable, URL.class),
Thread.currentThread().getContextClassLoader());
//jenkins FilePath prefers unix style, even on windows
filePath = filePath.replace("\\", "/");

FilePath file = workspace.child(filePath);
try {

if (file.isDirectory()) {
for (FilePath jarFile : file.list("*.jar")) {
classpathUrls.add(jarFile.toURI().toURL());
}
}
classpathUrls.add(file.toURI().toURL());
} catch (Exception e) {
LOG.warn("Error handling classpath " + file+": "+e.getMessage(), e);
}
});
Thread.currentThread().setContextClassLoader(urlClassLoader);
}

return new URLClassLoader(Iterables.toArray(classpathUrls, URL.class), Util.class.getClassLoader());
}
}
Expand Up @@ -27,7 +27,6 @@
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Properties;

Expand Down Expand Up @@ -149,12 +148,14 @@ public Liquibase createLiquibase(Run<?, ?> build,
EnvVars environment = build.getEnvironment(listener);

try {
ClassLoader liquibaseClassLoader = this.getClass().getClassLoader();

if (!Strings.isNullOrEmpty(resolvedClasspath)) {
Util.addClassloader(launcher.isUnix(), workspace, resolvedClasspath);
liquibaseClassLoader = Util.createClassLoader(launcher.isUnix(), workspace, resolvedClasspath);
}
JdbcConnection jdbcConnection = createJdbcConnection(configProperties);
JdbcConnection jdbcConnection = createJdbcConnection(configProperties, liquibaseClassLoader);
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(jdbcConnection);
ResourceAccessor resourceAccessor = createResourceAccessor(workspace, environment, resolveMacros);
ResourceAccessor resourceAccessor = createResourceAccessor(workspace, environment, resolveMacros, liquibaseClassLoader);

String changeLogFile = getProperty(configProperties, LiquibaseProperty.CHANGELOG_FILE);
liquibase = new Liquibase(changeLogFile, resourceAccessor, database);
Expand All @@ -172,7 +173,8 @@ public Liquibase createLiquibase(Run<?, ?> build,

private ResourceAccessor createResourceAccessor(FilePath workspace,
Map environment,
boolean resolveMacros) {
boolean resolveMacros,
ClassLoader liquibaseClassLoader) {
String resolvedBasePath;
if (resolveMacros) {
resolvedBasePath = hudson.Util.replaceMacro(basePath, environment);
Expand All @@ -188,8 +190,7 @@ private ResourceAccessor createResourceAccessor(FilePath workspace,

ResourceAccessor filePathAccessor = new FilePathAccessor(filePath);
return new CompositeResourceAccessor(filePathAccessor,
new ClassLoaderResourceAccessor(Thread.currentThread().getContextClassLoader()),
new ClassLoaderResourceAccessor(ClassLoader.getSystemClassLoader())
new ClassLoaderResourceAccessor(liquibaseClassLoader)
);
}

Expand All @@ -213,25 +214,18 @@ protected static void populateChangeLogParameters(Liquibase liquibase,
}
}

private static JdbcConnection createJdbcConnection(Properties configProperties) {
private static JdbcConnection createJdbcConnection(Properties configProperties, ClassLoader liquibaseClassLoader) {
Connection connection;
String dbUrl = getProperty(configProperties, LiquibaseProperty.URL);
final String driverName = DatabaseFactory.getInstance().findDefaultDriver(dbUrl);
try {
Util.registerDatabaseDriver(driverName,
configProperties.getProperty(LiquibaseProperty.CLASSPATH.propertyName()));
Util.registerDatabaseDriver(driverName, liquibaseClassLoader);
String userName = getProperty(configProperties, LiquibaseProperty.USERNAME);
String password = getProperty(configProperties, LiquibaseProperty.PASSWORD);
connection = DriverManager.getConnection(dbUrl, userName, password);
} catch (SQLException e) {
throw new RuntimeException(
"Error getting database connection using driver " + driverName + " using url '" + dbUrl + "'", e);
} catch (InstantiationException e) {
throw new RuntimeException("Error registering database driver " + driverName, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Error registering database driver " + driverName, e);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Error registering database driver " + driverName, e);
}
return new JdbcConnection(connection);
}
Expand Down
Expand Up @@ -5,7 +5,7 @@
xmlns:c="/lib/credentials">

<f:section title="Connection Details">
<f:entry title="Database URL" field="url" description="example: jdbc:mysql://localhost:3306/sampledb">
<f:entry title="Database URL" field="url" description="example: jdbc:mysql://localhost:3306/sampledb. The JDBC driver must be available in the classpath">
<f:textbox value="${instance.url}"/>
</f:entry>

Expand Down Expand Up @@ -35,7 +35,7 @@
<f:expandableTextbox name="labels" value="${instance.labels}"/>
</f:entry>
<f:entry title="Classpath" field="classpath"
description="Classpath containing migration files and JDBC Driver">
description="Classpath containing migration files and JDBC Driver. Can be relative to working directory or absolute. Jar files in listed directories are automatically added. Drivers for PostgreSQL, Mysql, H2, and Hsql come pre-bundled with this plugin.">
<f:textbox value="${instance.classpath}"/>
</f:entry>
<f:entry title="Changelog Parameters" field="changeLogParameters"
Expand Down
Expand Up @@ -5,7 +5,7 @@
xmlns:c="/lib/credentials">

<f:section title="Connection Details">
<f:entry title="Database URL" field="url" description="example: jdbc:mysql://localhost:3306/sampledb">
<f:entry title="Database URL" field="url" description="example: jdbc:mysql://localhost:3306/sampledb. The JDBC driver must be available in the classpath">
<f:textbox/>
</f:entry>
<f:entry field="credentialsId" title="Credentials">
Expand Down Expand Up @@ -35,7 +35,7 @@
<f:expandableTextbox name="labels"/>
</f:entry>
<f:entry title="Classpath" field="classpath"
description="Classpath containing migration files and JDBC Driver">
description="Classpath containing migration files and JDBC Driver. Can be relative to working directory or absolute. Jar files in listed directories are automatically added. Drivers for PostgreSQL, Mysql, H2, and Hsql come pre-bundled with this plugin.">
<f:textbox/>
</f:entry>
<f:entry title="Changelog Parameters" field="changeLogParameters"
Expand Down

0 comments on commit 7726ce4

Please sign in to comment.