All Downloads are FREE. Search and download functionalities are using the official Maven repository.

liquibase.integration.commandline.LiquibaseLauncher Maven / Gradle / Ivy

The newest version!
package liquibase.integration.commandline;

import liquibase.Scope;
import liquibase.util.StringUtil;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static liquibase.util.LiquibaseLauncherSettings.LiquibaseLauncherSetting.*;
import static liquibase.util.LiquibaseLauncherSettings.getSetting;

/**
 * Launcher which builds up the classpath needed to run Liquibase, then calls {@link LiquibaseCommandLine#main(String[])}.
 * 

* Supports the following configuration options that can be passed as JVM properties and/or environment variables, taking * the former precedence over the latter: *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Environment variableJVM property
Meaning
LIQUIBASE_HOMEliquibase.home
Liquibase home. This option is mandatory.
LIQUIBASE_LAUNCHER_DEBUGliquibase.launcher.debug
Determine if it should, when true, log what it is doing to stderr. * Defaults to false.
LIQUIBASE_LAUNCHER_PARENT_CLASSLOADERliquibase.launcher.parent_classloader
Classloader that will be used to run Liquibase, either system or thread. * Defaults to system.
*/ public class LiquibaseLauncher { private static final String LIQUIBASE_CORE_JAR_PATTERN = ".*?/liquibase-core([-0-9.])*.jar"; private static final String LIQUIBASE_COMMERCIAL_JAR_PATTERN = ".*?/liquibase-commercial([-0-9.])*.jar"; private static final String LIQUIBASE_CORE_MESSAGE = "Liquibase Core"; private static final String LIQUIBASE_COMMERCIAL_MESSAGE = "Liquibase Commercial"; private static final String DEPENDENCY_JAR_VERSION_PATTERN = "(.*?)-?[0-9.]*.jar"; private static boolean debug = false; public static void main(final String[] args) throws Exception { final String debugSetting = getSetting(LIQUIBASE_LAUNCHER_DEBUG); if ("true".equals(debugSetting)) { LiquibaseLauncher.debug = true; debug("Debug mode enabled because either the JVM property 'liquibase.launcher.debug' or the environment " + "variable 'LIQUIBASE_LAUNCHER_DEBUG' is set to " + debugSetting); } String parentLoaderSetting = getSetting(LIQUIBASE_LAUNCHER_PARENT_CLASSLOADER); if (parentLoaderSetting == null) { parentLoaderSetting = "system"; } debug("Liquibase launcher parent classloader is set to " + parentLoaderSetting); final String liquibaseHomeEnv = getSetting(LIQUIBASE_HOME); debug("Liquibase home: " + liquibaseHomeEnv); if (liquibaseHomeEnv == null || liquibaseHomeEnv.equals("")) { throw new IllegalArgumentException("Unable to find either 'liquibase.home' JVM property nor " + "'LIQUIBASE_HOME' environment variable"); } File liquibaseHome = new File(liquibaseHomeEnv); List libUrls = getLibUrls(liquibaseHome); checkForDuplicatedJars(libUrls); if (debug) { debug("Final Classpath:"); for (URL url : libUrls) { debug(" " + url.toString()); } } ClassLoader parentLoader = getClassLoader(parentLoaderSetting); final URLClassLoader classloader = new URLClassLoader(libUrls.toArray(new URL[0]), parentLoader); Thread.currentThread().setContextClassLoader(classloader); Class cli; try { cli = classloader.loadClass(LiquibaseCommandLine.class.getName()); } catch (ClassNotFoundException classNotFoundException) { throw new RuntimeException( String.format("Unable to find Liquibase classes in the configured home: '%s'.", liquibaseHome), classNotFoundException ); } cli.getMethod("main", String[].class).invoke(null, new Object[]{args}); } private static ClassLoader getClassLoader(String parentLoaderSetting) { if (parentLoaderSetting.equalsIgnoreCase("system")) { //loading with the regular system classloader includes liquibase.jar in the parent. //That causes the parent classloader to load LiquibaseCommandLine which makes it not able to access files in the child classloader //The system classloader's parent is the boot classloader, which keeps the only classloader with liquibase-core.jar the same as the rest of the classes it needs to access. return ClassLoader.getSystemClassLoader().getParent(); } else if (parentLoaderSetting.equalsIgnoreCase("thread")) { return Thread.currentThread().getContextClassLoader(); } else { throw new RuntimeException("Unknown liquibase launcher parent classloader value: "+ parentLoaderSetting); } } /** * Check for duplicate core and commercial JAR files */ private static void checkForDuplicatedJars(List urls) { List duplicateCore = urls .stream() .map(URL::getFile) .filter(file -> file.matches(LIQUIBASE_CORE_JAR_PATTERN)) .collect(Collectors.toList()); List duplicateCommercial = urls .stream() .map(URL::getFile) .filter(file -> file.matches(LIQUIBASE_COMMERCIAL_JAR_PATTERN)) .collect(Collectors.toList()); if (duplicateCore.size() > 1) { buildDupsMessage(duplicateCore, LIQUIBASE_CORE_MESSAGE); } if (duplicateCommercial.size() > 1) { buildDupsMessage(duplicateCommercial, LIQUIBASE_COMMERCIAL_MESSAGE); } Map> duplicates = new LinkedHashMap<>(); findVersionedDuplicates(urls, Pattern.compile(DEPENDENCY_JAR_VERSION_PATTERN), duplicates); findExactDuplicates(urls, duplicates); duplicates.forEach((key, value) -> { if (value.size() > 1) { buildDupsMessage(value, key); } }); } private static List getLibUrls(File liquibaseHome) throws MalformedURLException { List urls = new ArrayList<>(); urls.add(new File(liquibaseHome, "internal/lib/liquibase-core.jar").toURI().toURL()); //make sure liquibase-core.jar is first in the list File[] libDirs = new File[]{ new File("./liquibase_libs"), new File(liquibaseHome, "lib"), new File(liquibaseHome, "internal/lib"), new File(liquibaseHome, "internal/extensions"), }; // We released libraries containing the version in the file name, // and we want to ignore them in the classpath as the installer/zip/tgz is // not able to update them . List libsToIgnoreInClasspath = Arrays.asList( new File(liquibaseHome, "internal/extensions/liquibase-commercial-bigquery-4.29.0.jar"), new File(liquibaseHome, "internal/extensions/liquibase-commercial-bigquery-4.29.1.jar") ); for (File libDirFile : libDirs) { debug("Looking for libraries in " + libDirFile.getAbsolutePath()); if (!libDirFile.exists()) { debug("Skipping directory " + libDirFile.getAbsolutePath() + " because it does not exist"); continue; } final File[] files = libDirFile.listFiles(); if (files == null) { debug("Skipping directory " + libDirFile.getAbsolutePath() + " because it does not list files"); continue; } for (File lib : files) { if (lib.getName().toLowerCase(Locale.US).endsWith(".jar") && !lib.getName().toLowerCase(Locale.US).equals("liquibase-core.jar")) { try { if (libsToIgnoreInClasspath.stream().anyMatch(l -> l.getAbsoluteFile().equals(lib.getAbsoluteFile()))) { debug("Ignoring " + lib.getAbsolutePath() + " in classpath"); continue; // skip the file if it is in the ignore list } urls.add(lib.toURI().toURL()); debug("Added " + lib.getAbsolutePath() + " to classpath"); } catch (Exception e) { debug("Error adding " + lib.getAbsolutePath() + ":" + e.getMessage(), e); } } } //add the dir itself try { urls.add(libDirFile.toURI().toURL()); debug("Added " + libDirFile.getAbsolutePath() + " to classpath"); } catch (Exception e) { debug("Error adding " + libDirFile.getAbsolutePath() + ":" + e.getMessage(), e); } } return urls; } // // Find duplicates that match pattern -, like snakeyaml-1.33.0 and snakeyaml-2.0 // private static void findVersionedDuplicates(List urls, Pattern versionedJarPattern, Map> duplicates) { urls .forEach(url -> { Matcher m = versionedJarPattern.matcher(new File(url.getFile()).getName()); if (m.find()) { String key = m.group(1); List dupEntries = duplicates.get(key); if (dupEntries == null) { dupEntries = new ArrayList<>(); } dupEntries.add(url.getFile()); duplicates.put(key, dupEntries); } }); } // // Find exact duplicates in the list of URL // skip core and commercial JARs and any directories // private static void findExactDuplicates(List urls, Map> duplicates) { urls .forEach(url -> { String key = new File(url.getFile()).getName(); if (! (url.toString().endsWith("/") || url.toString().endsWith("\\") || key.contains("liquibase-core") || key.contains("liquibase-commercial"))) { List dupEntries = duplicates.get(key); if (dupEntries == null) { dupEntries = new ArrayList<>(); } dupEntries.add(url.getFile()); duplicates.put(key, dupEntries); } }); } private static void buildDupsMessage(List duplicates, String title) { String jarString = StringUtil.join(duplicates, System.lineSeparator()); String message = String.format("*** Duplicate %s JAR files ***%n%s", title, jarString); Scope.getCurrentScope().getUI().sendMessage(String.format("WARNING: %s", message)); } private static void debug(String message) { if (debug) { debug(message, null); } } private static void debug(String message, Throwable e) { if (debug) { System.err.println("[LIQUIBASE LAUNCHER DEBUG] " + message); if (e != null) { e.printStackTrace(System.err); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy