From 1b0f077f4e0f68b71d6dd5d2e383e04c9fd6fdaf Mon Sep 17 00:00:00 2001 From: Nicolas Humblot Date: Fri, 3 Sep 2021 14:49:43 +0200 Subject: [PATCH 01/11] #1054 Add test for varchar default value --- .../AddNotNullConstraintChangeTest.groovy | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy b/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy index 69289e5573a..b071a60648d 100644 --- a/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy +++ b/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy @@ -1,6 +1,9 @@ package liquibase.change.core import liquibase.change.StandardChangeTest +import liquibase.database.core.MySQLDatabase +import liquibase.statement.core.SetNullableStatement +import liquibase.statement.core.UpdateStatement public class AddNotNullConstraintChangeTest extends StandardChangeTest { @@ -28,4 +31,30 @@ public class AddNotNullConstraintChangeTest extends StandardChangeTest { reverses[0].getTableName() == "TABLE_NAME" reverses[0].getColumnName() == "COL_HERE" } -} \ No newline at end of file + + def should_generateStatements_add_update_statement_before_not_null_constraint() { + given: + def change = new AddNotNullConstraintChange() + change.setTableName("table_name") + change.setColumnName("column_name") + change.setColumnDataType("varchar(20)") + change.setDefaultNullValue("Hello World!") + + def database = new MySQLDatabase() + + when: + def output = change.generateStatements(database) + + then: + output.length == 2 + output[0] instanceof UpdateStatement + def update = (UpdateStatement) output[0] + update.getTableName() == "table_name" + update.getNewColumnValues().size() == 1 + update.getNewColumnValues().get("column_name") == "Hello World!" + update.getWhereClause() == "column_name IS NULL" + + output[1] instanceof SetNullableStatement + } + +} From 36546b2fdad0ed5ce856ac35eebd0962f819639f Mon Sep 17 00:00:00 2001 From: Nicolas Humblot Date: Fri, 3 Sep 2021 15:00:57 +0200 Subject: [PATCH 02/11] #1054 Parse to boolean when columnDataType is BOOLEAN --- .../core/AddNotNullConstraintChange.java | 11 +++++++- .../AddNotNullConstraintChangeTest.groovy | 25 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java b/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java index c236ab86c12..42bf78ea165 100755 --- a/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java +++ b/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java @@ -136,8 +136,9 @@ public SqlStatement[] generateStatements(Database database) { List statements = new ArrayList<>(); if (defaultNullValue != null) { + Object parsedDefaultNullValue = parseDefaultNullValue(); statements.add(new UpdateStatement(getCatalogName(), getSchemaName(), getTableName()) - .addNewColumnValue(getColumnName(), defaultNullValue) + .addNewColumnValue(getColumnName(), parsedDefaultNullValue) .setWhereClause(database.escapeObjectName(getColumnName(), Column.class) + " IS NULL")); } @@ -152,6 +153,14 @@ public SqlStatement[] generateStatements(Database database) { return statements.toArray(new SqlStatement[statements.size()]); } + private Object parseDefaultNullValue() { + if ("BOOLEAN".equalsIgnoreCase(columnDataType)) { + return Boolean.parseBoolean(defaultNullValue); + } else { + return defaultNullValue; + } + } + @Override protected Change[] createInverses() { DropNotNullConstraintChange inverse = new DropNotNullConstraintChange(); diff --git a/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy b/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy index b071a60648d..8c3468233fe 100644 --- a/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy +++ b/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy @@ -57,4 +57,29 @@ public class AddNotNullConstraintChangeTest extends StandardChangeTest { output[1] instanceof SetNullableStatement } + def should_generateStatements_update_statement_handle_boolean_type() { + given: + def change = new AddNotNullConstraintChange() + change.setTableName("FOO") + change.setColumnName("BAR") + change.setColumnDataType("BOOLEAN") + change.setDefaultNullValue("false") + + def database = new MySQLDatabase() + + when: + def output = change.generateStatements(database) + + then: + output.length == 2 + output[0] instanceof UpdateStatement + def update = (UpdateStatement) output[0] + update.getTableName() == "FOO" + update.getNewColumnValues().size() == 1 + update.getNewColumnValues().get("BAR") == false + update.getWhereClause() == "BAR IS NULL" + + output[1] instanceof SetNullableStatement + } + } From 71d94955397005324a42d3dcccffcc41dce32668 Mon Sep 17 00:00:00 2001 From: Nicolas Humblot Date: Fri, 3 Sep 2021 15:07:10 +0200 Subject: [PATCH 03/11] #1054 Parse to boolean when columnDataType is BIT(1) --- .../core/AddNotNullConstraintChange.java | 4 +-- .../AddNotNullConstraintChangeTest.groovy | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java b/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java index 42bf78ea165..fb2a990255f 100755 --- a/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java +++ b/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java @@ -154,8 +154,8 @@ public SqlStatement[] generateStatements(Database database) { } private Object parseDefaultNullValue() { - if ("BOOLEAN".equalsIgnoreCase(columnDataType)) { - return Boolean.parseBoolean(defaultNullValue); + if ("BOOLEAN".equalsIgnoreCase(columnDataType) || "BIT(1)".equalsIgnoreCase(columnDataType)) { + return Boolean.parseBoolean(defaultNullValue) || "1".equals(defaultNullValue); } else { return defaultNullValue; } diff --git a/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy b/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy index 8c3468233fe..7a91e42b181 100644 --- a/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy +++ b/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy @@ -82,4 +82,29 @@ public class AddNotNullConstraintChangeTest extends StandardChangeTest { output[1] instanceof SetNullableStatement } + def should_generateStatements_update_statement_handle_bit_1_type() { + given: + def change = new AddNotNullConstraintChange() + change.setTableName("xxx") + change.setColumnName("col_name") + change.setColumnDataType("BIT(1)") + change.setDefaultNullValue("1") + + def database = new MySQLDatabase() + + when: + def output = change.generateStatements(database) + + then: + output.length == 2 + output[0] instanceof UpdateStatement + def update = (UpdateStatement) output[0] + update.getTableName() == "xxx" + update.getNewColumnValues().size() == 1 + update.getNewColumnValues().get("col_name") == true + update.getWhereClause() == "col_name IS NULL" + + output[1] instanceof SetNullableStatement + } + } From 49e65de8d82eef74d15736fb82f22bb39c7f87d6 Mon Sep 17 00:00:00 2001 From: Nicolas Humblot Date: Fri, 3 Sep 2021 15:15:40 +0200 Subject: [PATCH 04/11] #1054 Refactor --- .../core/AddNotNullConstraintChange.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java b/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java index fb2a990255f..6f4b69a893a 100755 --- a/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java +++ b/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java @@ -3,9 +3,7 @@ import liquibase.change.*; import liquibase.database.Database; import liquibase.database.core.DB2Database; -import liquibase.database.core.SQLiteDatabase; import liquibase.database.core.SQLiteDatabase.AlterTableVisitor; -import liquibase.exception.DatabaseException; import liquibase.statement.SqlStatement; import liquibase.statement.core.ReorganizeTableStatement; import liquibase.statement.core.SetNullableStatement; @@ -154,11 +152,20 @@ public SqlStatement[] generateStatements(Database database) { } private Object parseDefaultNullValue() { - if ("BOOLEAN".equalsIgnoreCase(columnDataType) || "BIT(1)".equalsIgnoreCase(columnDataType)) { - return Boolean.parseBoolean(defaultNullValue) || "1".equals(defaultNullValue); - } else { - return defaultNullValue; + if (isABooleanColumnDataType()) { + return parseDefaultNullValueToBoolean(); } + + return defaultNullValue; + } + + private boolean isABooleanColumnDataType() { + return + "BOOLEAN".equalsIgnoreCase(columnDataType) || "BIT(1)".equalsIgnoreCase(columnDataType); + } + + private boolean parseDefaultNullValueToBoolean() { + return Boolean.parseBoolean(defaultNullValue) || "1".equals(defaultNullValue); } @Override From f15dc19eacc6bb689a6614fb2bdba6d42918d129 Mon Sep 17 00:00:00 2001 From: Jakub Herkel Date: Thu, 9 Sep 2021 08:12:30 +0200 Subject: [PATCH 05/11] [2054] fix OSGI support (manifest, class loading) --- liquibase-core/pom.xml | 69 ++++++++++- .../src/main/java/liquibase/Activator.java | 108 ++++++++++++++++++ .../src/main/java/liquibase/Scope.java | 4 +- .../change/custom/CustomChangeWrapper.java | 29 +++-- .../java/liquibase/util/LiquibaseUtil.java | 21 ++++ .../main/java/liquibase/util/OsgiUtil.java | 65 +++++++++++ 6 files changed, 281 insertions(+), 15 deletions(-) create mode 100644 liquibase-core/src/main/java/liquibase/Activator.java create mode 100644 liquibase-core/src/main/java/liquibase/util/OsgiUtil.java diff --git a/liquibase-core/pom.xml b/liquibase-core/pom.xml index 6523495e845..dbd3ee13fcb 100644 --- a/liquibase-core/pom.xml +++ b/liquibase-core/pom.xml @@ -58,13 +58,13 @@ 1.3 test - + org.osgi org.osgi.core provided - 4.3.1 - + 5.0.0 + org.springframework @@ -173,10 +173,11 @@ org.apache.felix maven-bundle-plugin - 3.3.0 + 5.1.2 org.liquibase.core + liquibase.Activator javax.activation*;resolution:=optional, javax.servlet.*;version="[2.6,4)";resolution:=optional, @@ -185,6 +186,66 @@ org.yaml.snakeyaml.*, *;resolution:=optional + + osgi.serviceloader; osgi.serviceloader=liquibase.serializer.ChangeLogSerializer, + osgi.serviceloader; osgi.serviceloader=liquibase.parser.NamespaceDetails, + osgi.serviceloader; osgi.serviceloader=liquibase.database.Database, + osgi.serviceloader; osgi.serviceloader=liquibase.change.Change, + osgi.serviceloader; osgi.serviceloader=liquibase.database.DatabaseConnection, + osgi.serviceloader; osgi.serviceloader=liquibase.precondition.Precondition, + osgi.serviceloader; osgi.serviceloader=liquibase.serializer.SnapshotSerializer, + osgi.serviceloader; osgi.serviceloader=liquibase.configuration.AutoloadedConfigurations, + osgi.serviceloader; osgi.serviceloader=liquibase.diff.DiffGenerator, + osgi.serviceloader; osgi.serviceloader=liquibase.lockservice.LockService, + osgi.serviceloader; osgi.serviceloader=liquibase.changelog.ChangeLogHistoryService, + osgi.serviceloader; osgi.serviceloader=liquibase.datatype.LiquibaseDataType, + osgi.serviceloader; osgi.serviceloader=liquibase.configuration.ConfigurationValueProvider, + osgi.serviceloader; osgi.serviceloader=liquibase.logging.LogService, + osgi.serviceloader; osgi.serviceloader=liquibase.snapshot.SnapshotGenerator, + osgi.serviceloader; osgi.serviceloader=liquibase.parser.ChangeLogParser, + osgi.serviceloader; osgi.serviceloader=liquibase.servicelocator.ServiceLocator, + osgi.serviceloader; osgi.serviceloader=liquibase.diff.compare.DatabaseObjectComparator, + osgi.serviceloader; osgi.serviceloader=liquibase.command.LiquibaseCommand, + osgi.serviceloader; osgi.serviceloader=liquibase.license.LicenseService, + osgi.serviceloader; osgi.serviceloader=liquibase.diff.output.changelog.ChangeGenerator, + osgi.serviceloader; osgi.serviceloader=liquibase.executor.Executor, + osgi.serviceloader; osgi.serviceloader=liquibase.structure.DatabaseObject, + osgi.serviceloader; osgi.serviceloader=liquibase.parser.SnapshotParser, + osgi.serviceloader; osgi.serviceloader=liquibase.hub.HubService, + osgi.serviceloader; osgi.serviceloader=liquibase.command.CommandStep, + osgi.serviceloader; osgi.serviceloader=liquibase.sqlgenerator.SqlGenerator + + + osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)", + osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)", + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.serializer.ChangeLogSerializer)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.parser.NamespaceDetails)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.database.Database)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.change.Change)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.database.DatabaseConnection)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.precondition.Precondition)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.serializer.SnapshotSerializer)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.configuration.AutoloadedConfigurations)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.diff.DiffGenerator)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.lockservice.LockService)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.changelog.ChangeLogHistoryService)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.datatype.LiquibaseDataType)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.configuration.ConfigurationValueProvider)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.logging.LogService)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.snapshot.SnapshotGenerator)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.parser.ChangeLogParser)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.servicelocator.ServiceLocator)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.diff.compare.DatabaseObjectComparator)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.command.LiquibaseCommand)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.license.LicenseService)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.diff.output.changelog.ChangeGenerator)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.executor.Executor)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.structure.DatabaseObject)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.parser.SnapshotParser)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.hub.HubService)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.command.CommandStep)"; cardinality:=multiple, + osgi.serviceloader; filter:="(osgi.serviceloader=liquibase.sqlgenerator.SqlGenerator)"; cardinality:=multiple + diff --git a/liquibase-core/src/main/java/liquibase/Activator.java b/liquibase-core/src/main/java/liquibase/Activator.java new file mode 100644 index 00000000000..6da1f6d200f --- /dev/null +++ b/liquibase-core/src/main/java/liquibase/Activator.java @@ -0,0 +1,108 @@ +package liquibase; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import liquibase.Activator.LiquibaseBundle; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.util.tracker.BundleTracker; +import org.osgi.util.tracker.BundleTrackerCustomizer; + +public class Activator implements BundleActivator, BundleTrackerCustomizer { + + private static final String LIQUIBASE_CUSTOM_CHANGE_WRAPPER_PACKAGES = "Liquibase-Custom-Change-Packages"; + private BundleTracker bundleTracker; + private static final List liquibaseBundles = new CopyOnWriteArrayList<>(); + + @Override + public void start(final BundleContext bc) throws Exception { + OSGIContainerChecker.osgiPlatform(); + bundleTracker = new BundleTracker<>(bc, Bundle.ACTIVE, this); + bundleTracker.open(); + } + + @Override + public void stop(BundleContext context) throws Exception { + bundleTracker.close(); + liquibaseBundles.clear(); + } + + public static List getLiquibaseBundles() { + return Collections.unmodifiableList(liquibaseBundles); + } + + @Override + public LiquibaseBundle addingBundle(Bundle bundle, BundleEvent event) { + if (bundle.getBundleId() == 0) { + return null; + } + String customWrapperPackages = (String) bundle.getHeaders().get(LIQUIBASE_CUSTOM_CHANGE_WRAPPER_PACKAGES); + if (customWrapperPackages != null) { + LiquibaseBundle lb = new LiquibaseBundle(bundle, customWrapperPackages); + liquibaseBundles.add(lb); + return lb; + } + return null; + } + + @Override + public void modifiedBundle(Bundle bundle, BundleEvent event, LiquibaseBundle liquibaseBundle) { + // nothing to do + } + + @Override + public void removedBundle(Bundle bundle, BundleEvent event, LiquibaseBundle liquibaseBundle) { + if (liquibaseBundle != null) { + liquibaseBundles.remove(liquibaseBundle); + } + } + + public static class LiquibaseBundle { + + private final Bundle bundle; + private final List allowedPackages; + + public LiquibaseBundle(Bundle bundle, String allowedPackages) { + if (bundle == null) { + throw new IllegalArgumentException("bundle cannot be empty"); + } + if (allowedPackages == null || allowedPackages.isEmpty()) { + throw new IllegalArgumentException("packages cannot be empty"); + } + this.bundle = bundle; + this.allowedPackages = Collections.unmodifiableList(Arrays.asList(allowedPackages.split(","))); + } + + public Bundle getBundle() { + return bundle; + } + + public boolean allowedAllPackages() { + return allowedPackages.size() == 1 + && "*".equals(allowedPackages.get(0)); + } + + public List getAllowedPackages() { + return allowedPackages; + } + + } + + public static class OSGIContainerChecker { + + private static volatile boolean osgiPlatform = false; + + public static boolean isOsgiPlatform() { + return osgiPlatform; + } + + static void osgiPlatform() { + osgiPlatform = true; + } + } + +} diff --git a/liquibase-core/src/main/java/liquibase/Scope.java b/liquibase-core/src/main/java/liquibase/Scope.java index b62ca0b45d4..0086fcba918 100644 --- a/liquibase-core/src/main/java/liquibase/Scope.java +++ b/liquibase-core/src/main/java/liquibase/Scope.java @@ -20,8 +20,6 @@ import liquibase.util.SmartMap; import liquibase.util.StringUtil; -import java.io.InputStream; -import java.io.OutputStream; import java.lang.reflect.Constructor; import java.nio.charset.Charset; import java.util.*; @@ -53,6 +51,7 @@ public enum Attr { fileEncoding, databaseChangeLog, changeSet, + osgiPlatform } private static ScopeManager scopeManager; @@ -93,6 +92,7 @@ public static Scope getCurrentScope() { } rootScope.values.put(Attr.serviceLocator.name(), serviceLocator); + rootScope.values.put(Attr.osgiPlatform.name(),Activator.OSGIContainerChecker.isOsgiPlatform()); } return scopeManager.getCurrentScope(); } diff --git a/liquibase-core/src/main/java/liquibase/change/custom/CustomChangeWrapper.java b/liquibase-core/src/main/java/liquibase/change/custom/CustomChangeWrapper.java index 1f4e4f94d9a..f86c0b68025 100644 --- a/liquibase-core/src/main/java/liquibase/change/custom/CustomChangeWrapper.java +++ b/liquibase-core/src/main/java/liquibase/change/custom/CustomChangeWrapper.java @@ -14,6 +14,7 @@ import liquibase.util.ObjectUtil; import java.util.*; +import liquibase.util.OsgiUtil; /** * Adapts CustomChange implementations to the standard change system used by Liquibase. @@ -69,16 +70,21 @@ public CustomChangeWrapper setClass(String className) throws CustomChangeExcepti return this; } this.className = className; - try { + try { + Boolean osgiPlatform = Scope.getCurrentScope().get(Scope.Attr.osgiPlatform, Boolean.class); + if (Boolean.TRUE.equals(osgiPlatform)) { + customChange = (CustomChange)OsgiUtil.loadClass(className).getConstructor().newInstance(); + } else { + try { + customChange = (CustomChange) Class.forName(className, true, Scope.getCurrentScope().getClassLoader()).getConstructor().newInstance(); + } catch (ClassCastException e) { //fails in Ant in particular try { - customChange = (CustomChange) Class.forName(className, true, Scope.getCurrentScope().getClassLoader()).getConstructor().newInstance(); - } catch (ClassCastException e) { //fails in Ant in particular - try { - customChange = (CustomChange) Thread.currentThread().getContextClassLoader().loadClass(className).getConstructor().newInstance(); - } catch (ClassNotFoundException e1) { - customChange = (CustomChange) Class.forName(className).getConstructor().newInstance(); - } + customChange = (CustomChange) Thread.currentThread().getContextClassLoader().loadClass(className).getConstructor().newInstance(); + } catch (ClassNotFoundException e1) { + customChange = (CustomChange) Class.forName(className).getConstructor().newInstance(); } + } + } } catch (Exception e) { throw new CustomChangeException(e); } @@ -316,7 +322,12 @@ public void customLoadLogic(ParsedNode parsedNode, ResourceAccessor resourceAcce CustomChange customChange = null; try { - customChange = (CustomChange) Class.forName(className, false, Scope.getCurrentScope().getClassLoader()).getConstructor().newInstance(); + Boolean osgiPlatform = Scope.getCurrentScope().get(Scope.Attr.osgiPlatform, Boolean.class); + if (Boolean.TRUE.equals(osgiPlatform)) { + customChange = (CustomChange)OsgiUtil.loadClass(className).getConstructor().newInstance(); + } else { + customChange = (CustomChange) Class.forName(className, false, Scope.getCurrentScope().getClassLoader()).getConstructor().newInstance(); + } } catch (Exception e) { throw new UnexpectedLiquibaseException(e); } diff --git a/liquibase-core/src/main/java/liquibase/util/LiquibaseUtil.java b/liquibase-core/src/main/java/liquibase/util/LiquibaseUtil.java index f32ccaa6362..c7dc7db751d 100644 --- a/liquibase-core/src/main/java/liquibase/util/LiquibaseUtil.java +++ b/liquibase-core/src/main/java/liquibase/util/LiquibaseUtil.java @@ -4,7 +4,10 @@ import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.util.Properties; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; public class LiquibaseUtil { @@ -27,6 +30,23 @@ public static String getBuildNumber() { private static String getBuildInfo(String propertyId) { String value = "UNKNOWN"; if (liquibaseBuildProperties == null) { + Boolean osgiPlatform = Scope.getCurrentScope().get(Scope.Attr.osgiPlatform, Boolean.class); + if (Boolean.TRUE.equals(osgiPlatform)) { + Bundle bundle = FrameworkUtil.getBundle(LiquibaseUtil.class); + URL propURL = bundle.getEntry("liquibase.build.properties"); + if (propURL == null) { + Scope.getCurrentScope().getLog(LiquibaseUtil.class).severe("Cannot read liquibase.build.properties"); + } else { + try (InputStream buildProperties = propURL.openStream()) { + liquibaseBuildProperties = new Properties(); + if (buildProperties != null) { + liquibaseBuildProperties.load(buildProperties); + } + } catch (IOException e) { + Scope.getCurrentScope().getLog(LiquibaseUtil.class).severe("Cannot read liquibase.build.properties", e); + } + } + } else { try (InputStream buildProperties = Scope.getCurrentScope().getClassLoader().getResourceAsStream("liquibase.build.properties")) { liquibaseBuildProperties = new Properties(); if (buildProperties != null) { @@ -35,6 +55,7 @@ private static String getBuildInfo(String propertyId) { } catch (IOException e) { Scope.getCurrentScope().getLog(LiquibaseUtil.class).severe("Cannot read liquibase.build.properties", e); } + } } if (liquibaseBuildProperties != null) { diff --git a/liquibase-core/src/main/java/liquibase/util/OsgiUtil.java b/liquibase-core/src/main/java/liquibase/util/OsgiUtil.java new file mode 100644 index 00000000000..4ede3e81868 --- /dev/null +++ b/liquibase-core/src/main/java/liquibase/util/OsgiUtil.java @@ -0,0 +1,65 @@ +package liquibase.util; + +import java.util.List; +import java.util.stream.Collectors; +import liquibase.Activator; +import liquibase.Activator.LiquibaseBundle; + +public final class OsgiUtil { + + private OsgiUtil() { + } + + /** + * try to load a class under OSGI environment. It will try to load the class + * from all liquibase bundles registered via + * {@link liquibase.Activator Activator} + * + * @param + * @param className name of class + * @return + * @throws ClassNotFoundException + */ + public static Class loadClass(String className) throws ClassNotFoundException { + List liquibaseBundles = Activator.getLiquibaseBundles(); + for (LiquibaseBundle lb : liquibaseBundles) { + try { + Class clazz = (Class) lb.getBundle().loadClass(className); + if (!isClassAllowed(lb, clazz)) { + throw new ClassNotFoundException("Class is not allowed to load, class:" + className + " bundles:" + + liquibaseBundles.stream().map(i -> i.getBundle().getSymbolicName()) + .collect(Collectors.joining(","))); + } + return clazz; + } catch (ClassNotFoundException ex) { + // nothing to do + } + } + throw new ClassNotFoundException("Cannot find class:" + className + " bundles:" + + liquibaseBundles.stream().map(i -> i.getBundle().getSymbolicName()) + .collect(Collectors.joining(","))); + } + + /** + * + * @param className + * @return true is a class is allowed + * @throws java.lang.ClassNotFoundException + */ + private static boolean isClassAllowed(LiquibaseBundle liquibaseBundle, Class clazz) { + if (liquibaseBundle.allowedAllPackages()) { + return true; + } + for (String allowedPackage : liquibaseBundle.getAllowedPackages()) { + Package pkg = clazz.getPackage(); + if (pkg != null) { + String pkgName = pkg.getName(); + if (allowedPackage.equals(pkgName) || allowedPackage.startsWith(pkgName + ".")) { + return true; + } + } + } + return false; + } + +} From f4dbf0f918fc8c0b32b04654417448b694dd8fd0 Mon Sep 17 00:00:00 2001 From: Nathan Voxland Date: Wed, 8 Dec 2021 12:52:22 -0600 Subject: [PATCH 06/11] Moved liquibase.Activator to liquibase.osgi.Activator --- liquibase-core/pom.xml | 2 +- liquibase-core/src/main/java/liquibase/Scope.java | 3 ++- .../src/main/java/liquibase/{ => osgi}/Activator.java | 4 ++-- liquibase-core/src/main/java/liquibase/util/OsgiUtil.java | 8 ++++---- 4 files changed, 9 insertions(+), 8 deletions(-) rename liquibase-core/src/main/java/liquibase/{ => osgi}/Activator.java (97%) diff --git a/liquibase-core/pom.xml b/liquibase-core/pom.xml index 248442962e8..0fdcaadc938 100644 --- a/liquibase-core/pom.xml +++ b/liquibase-core/pom.xml @@ -159,7 +159,7 @@ org.liquibase.core - liquibase.Activator + liquibase.osgi.Activator javax.activation*;resolution:=optional, javax.servlet.*;version="[2.6,4)";resolution:=optional, diff --git a/liquibase-core/src/main/java/liquibase/Scope.java b/liquibase-core/src/main/java/liquibase/Scope.java index 7e079cb0c80..0910d57134c 100644 --- a/liquibase-core/src/main/java/liquibase/Scope.java +++ b/liquibase-core/src/main/java/liquibase/Scope.java @@ -11,6 +11,7 @@ import liquibase.logging.Logger; import liquibase.logging.core.JavaLogService; import liquibase.logging.core.LogServiceFactory; +import liquibase.osgi.Activator; import liquibase.resource.ClassLoaderResourceAccessor; import liquibase.resource.ResourceAccessor; import liquibase.servicelocator.ServiceLocator; @@ -96,7 +97,7 @@ public static Scope getCurrentScope() { } rootScope.values.put(Attr.serviceLocator.name(), serviceLocator); - rootScope.values.put(Attr.osgiPlatform.name(),Activator.OSGIContainerChecker.isOsgiPlatform()); + rootScope.values.put(Attr.osgiPlatform.name(), Activator.OSGIContainerChecker.isOsgiPlatform()); } return scopeManager.getCurrentScope(); } diff --git a/liquibase-core/src/main/java/liquibase/Activator.java b/liquibase-core/src/main/java/liquibase/osgi/Activator.java similarity index 97% rename from liquibase-core/src/main/java/liquibase/Activator.java rename to liquibase-core/src/main/java/liquibase/osgi/Activator.java index 6da1f6d200f..673a53cebe1 100644 --- a/liquibase-core/src/main/java/liquibase/Activator.java +++ b/liquibase-core/src/main/java/liquibase/osgi/Activator.java @@ -1,10 +1,10 @@ -package liquibase; +package liquibase.osgi; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import liquibase.Activator.LiquibaseBundle; +import liquibase.osgi.Activator.LiquibaseBundle; import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; diff --git a/liquibase-core/src/main/java/liquibase/util/OsgiUtil.java b/liquibase-core/src/main/java/liquibase/util/OsgiUtil.java index 4ede3e81868..71a39077f77 100644 --- a/liquibase-core/src/main/java/liquibase/util/OsgiUtil.java +++ b/liquibase-core/src/main/java/liquibase/util/OsgiUtil.java @@ -2,8 +2,8 @@ import java.util.List; import java.util.stream.Collectors; -import liquibase.Activator; -import liquibase.Activator.LiquibaseBundle; +import liquibase.osgi.Activator; +import liquibase.osgi.Activator.LiquibaseBundle; public final class OsgiUtil { @@ -13,7 +13,7 @@ private OsgiUtil() { /** * try to load a class under OSGI environment. It will try to load the class * from all liquibase bundles registered via - * {@link liquibase.Activator Activator} + * {@link Activator Activator} * * @param * @param className name of class @@ -42,7 +42,7 @@ public static Class loadClass(String className) throws ClassNotFoundExcep /** * - * @param className + * @param clazz * @return true is a class is allowed * @throws java.lang.ClassNotFoundException */ From fb629e5b125f163c4509586fb6505f4f05cf385f Mon Sep 17 00:00:00 2001 From: Nathan Voxland Date: Fri, 10 Dec 2021 14:33:54 -0600 Subject: [PATCH 07/11] Replaced custom boolean parsing with existing function calls --- .../core/AddNotNullConstraintChange.java | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java b/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java index 6f4b69a893a..8547cbc2a7e 100755 --- a/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java +++ b/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java @@ -4,12 +4,16 @@ import liquibase.database.Database; import liquibase.database.core.DB2Database; import liquibase.database.core.SQLiteDatabase.AlterTableVisitor; +import liquibase.datatype.DataTypeFactory; +import liquibase.datatype.LiquibaseDataType; +import liquibase.datatype.core.BooleanType; import liquibase.statement.SqlStatement; import liquibase.statement.core.ReorganizeTableStatement; import liquibase.statement.core.SetNullableStatement; import liquibase.statement.core.UpdateStatement; import liquibase.structure.core.Column; import liquibase.structure.core.Index; +import liquibase.util.BooleanUtil; import liquibase.util.StringUtil; import java.util.ArrayList; @@ -133,10 +137,19 @@ public void setConstraintName(String constraintName) { public SqlStatement[] generateStatements(Database database) { List statements = new ArrayList<>(); - if (defaultNullValue != null) { - Object parsedDefaultNullValue = parseDefaultNullValue(); + if (defaultNullValue != null && !defaultNullValue.equalsIgnoreCase("null")) { + final String columnDataType = this.getColumnDataType(); + + Object finalDefaultNullValue = defaultNullValue; + if (columnDataType != null) { + final LiquibaseDataType datatype = DataTypeFactory.getInstance().fromDescription(columnDataType, database); + if (datatype instanceof BooleanType) { + finalDefaultNullValue = BooleanUtil.parseBoolean(defaultNullValue); + } + } + statements.add(new UpdateStatement(getCatalogName(), getSchemaName(), getTableName()) - .addNewColumnValue(getColumnName(), parsedDefaultNullValue) + .addNewColumnValue(getColumnName(), finalDefaultNullValue) .setWhereClause(database.escapeObjectName(getColumnName(), Column.class) + " IS NULL")); } @@ -151,23 +164,6 @@ public SqlStatement[] generateStatements(Database database) { return statements.toArray(new SqlStatement[statements.size()]); } - private Object parseDefaultNullValue() { - if (isABooleanColumnDataType()) { - return parseDefaultNullValueToBoolean(); - } - - return defaultNullValue; - } - - private boolean isABooleanColumnDataType() { - return - "BOOLEAN".equalsIgnoreCase(columnDataType) || "BIT(1)".equalsIgnoreCase(columnDataType); - } - - private boolean parseDefaultNullValueToBoolean() { - return Boolean.parseBoolean(defaultNullValue) || "1".equals(defaultNullValue); - } - @Override protected Change[] createInverses() { DropNotNullConstraintChange inverse = new DropNotNullConstraintChange(); From b4bdea7b491be1a327795dc21910a70b6db4953d Mon Sep 17 00:00:00 2001 From: Nathan Voxland Date: Fri, 17 Dec 2021 16:22:18 -0600 Subject: [PATCH 08/11] Correctly handle "bit" values on postgresql and other databases that also have booleans --- .../liquibase/change/core/AddNotNullConstraintChange.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java b/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java index 8547cbc2a7e..8dc380bb78c 100755 --- a/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java +++ b/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java @@ -144,7 +144,13 @@ public SqlStatement[] generateStatements(Database database) { if (columnDataType != null) { final LiquibaseDataType datatype = DataTypeFactory.getInstance().fromDescription(columnDataType, database); if (datatype instanceof BooleanType) { - finalDefaultNullValue = BooleanUtil.parseBoolean(defaultNullValue); + //need to detect a boolean type and handle it correctly sometimes or it is not converted to the correct datatype + finalDefaultNullValue = datatype.objectToSql(finalDefaultNullValue, database); + if (finalDefaultNullValue.equals("0")) { + finalDefaultNullValue = 0; + } else if (finalDefaultNullValue.equals("1")) { + finalDefaultNullValue = 1; + } } } From 82a5826c58ee4786e3aa97700d1cb7f390d9b110 Mon Sep 17 00:00:00 2001 From: Nathan Voxland Date: Mon, 20 Dec 2021 10:57:24 -0600 Subject: [PATCH 09/11] Fixed failing test --- .../change/core/AddNotNullConstraintChangeTest.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy b/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy index 7a91e42b181..411389c361d 100644 --- a/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy +++ b/liquibase-core/src/test/groovy/liquibase/change/core/AddNotNullConstraintChangeTest.groovy @@ -76,7 +76,7 @@ public class AddNotNullConstraintChangeTest extends StandardChangeTest { def update = (UpdateStatement) output[0] update.getTableName() == "FOO" update.getNewColumnValues().size() == 1 - update.getNewColumnValues().get("BAR") == false + update.getNewColumnValues().get("BAR") == 0 update.getWhereClause() == "BAR IS NULL" output[1] instanceof SetNullableStatement @@ -101,7 +101,7 @@ public class AddNotNullConstraintChangeTest extends StandardChangeTest { def update = (UpdateStatement) output[0] update.getTableName() == "xxx" update.getNewColumnValues().size() == 1 - update.getNewColumnValues().get("col_name") == true + update.getNewColumnValues().get("col_name") == 1 update.getWhereClause() == "col_name IS NULL" output[1] instanceof SetNullableStatement From 938665f268b3bc7066016ff54871a0d9a81f7661 Mon Sep 17 00:00:00 2001 From: Nathan Voxland Date: Tue, 21 Dec 2021 17:07:36 -0600 Subject: [PATCH 10/11] Correctly handle "bit" values on postgresql and other databases that also have booleans --- .../core/AddNotNullConstraintChange.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java b/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java index 8dc380bb78c..04bd19a2891 100755 --- a/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java +++ b/liquibase-core/src/main/java/liquibase/change/core/AddNotNullConstraintChange.java @@ -3,10 +3,12 @@ import liquibase.change.*; import liquibase.database.Database; import liquibase.database.core.DB2Database; +import liquibase.database.core.PostgresDatabase; import liquibase.database.core.SQLiteDatabase.AlterTableVisitor; import liquibase.datatype.DataTypeFactory; import liquibase.datatype.LiquibaseDataType; import liquibase.datatype.core.BooleanType; +import liquibase.statement.DatabaseFunction; import liquibase.statement.SqlStatement; import liquibase.statement.core.ReorganizeTableStatement; import liquibase.statement.core.SetNullableStatement; @@ -144,13 +146,29 @@ public SqlStatement[] generateStatements(Database database) { if (columnDataType != null) { final LiquibaseDataType datatype = DataTypeFactory.getInstance().fromDescription(columnDataType, database); if (datatype instanceof BooleanType) { - //need to detect a boolean type and handle it correctly sometimes or it is not converted to the correct datatype + //need to detect a boolean or bit type and handle it correctly sometimes or it is not converted to the correct datatype finalDefaultNullValue = datatype.objectToSql(finalDefaultNullValue, database); if (finalDefaultNullValue.equals("0")) { finalDefaultNullValue = 0; } else if (finalDefaultNullValue.equals("1")) { finalDefaultNullValue = 1; } + + if (columnDataType.toLowerCase().contains("bit")) { + if (BooleanUtil.parseBoolean(finalDefaultNullValue.toString())) { + finalDefaultNullValue = 1; + } else { + finalDefaultNullValue = 0; + } + } + + if (database instanceof PostgresDatabase) { + if (finalDefaultNullValue.equals(0)) { + finalDefaultNullValue = new DatabaseFunction( "B'0'"); + } else if (finalDefaultNullValue.equals(1)) { + finalDefaultNullValue = new DatabaseFunction( "B'1'"); + } + } } } From ab7b2bdea39f7dd98b1c3a6c4ffd2533d9912797 Mon Sep 17 00:00:00 2001 From: Nathan Voxland Date: Wed, 22 Dec 2021 16:20:34 -0600 Subject: [PATCH 11/11] Updated snyk reporting webhook --- .github/workflows/snyk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index 726a1696c46..5f2274ea738 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -60,7 +60,7 @@ jobs: SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff' SLACK_MESSAGE: "${{ github.job }}: ${{ job.status }} @here" SLACK_USERNAME: "liquibot" - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + SLACK_WEBHOOK: ${{ secrets.SNYK_LIQUIBASE_SLACK_WEBHOOK }} MSG_MINIMAL: actions url SLACK_ICON_EMOJI: ':liquibase:' SLACK_LINK_NAMES: true