From ff4c0e4412425ae25698b0ec7bd4fc491fa4a2b6 Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Fri, 16 Apr 2021 19:48:57 +0200 Subject: [PATCH] KEYCLOAK-16935 Fix liquibase to work with MySQL 8.0.23+ --- .../LiquibaseJpaUpdaterProvider.java | 3 + .../conn/CustomChangeLogHistoryService.java | 120 ++++++++++++++++++ .../DefaultLiquibaseConnectionProvider.java | 3 + pom.xml | 2 +- .../liquibase/QuarkusJpaUpdaterProvider.java | 3 + .../QuarkusLiquibaseConnectionProvider.java | 6 +- 6 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/CustomChangeLogHistoryService.java diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java index 8486ee78df..0563515227 100755 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java @@ -44,6 +44,7 @@ import org.jboss.logging.Logger; import org.keycloak.common.util.reflections.Reflections; import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider; import org.keycloak.connections.jpa.updater.JpaUpdaterProvider; +import org.keycloak.connections.jpa.updater.liquibase.conn.CustomChangeLogHistoryService; import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider; import org.keycloak.connections.jpa.util.JpaUtils; import org.keycloak.models.KeycloakSession; @@ -274,6 +275,8 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider { private void resetLiquibaseServices(Liquibase liquibase) { Method resetServices = Reflections.findDeclaredMethod(Liquibase.class, "resetServices"); Reflections.invokeMethod(true, resetServices, liquibase); + + ChangeLogHistoryServiceFactory.getInstance().register(new CustomChangeLogHistoryService()); } @SuppressWarnings("unchecked") diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/CustomChangeLogHistoryService.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/CustomChangeLogHistoryService.java new file mode 100644 index 0000000000..f41b8f4922 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/CustomChangeLogHistoryService.java @@ -0,0 +1,120 @@ +/* + * Copyright 2021 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.connections.jpa.updater.liquibase.conn; + +import org.keycloak.common.util.reflections.Reflections; +import java.lang.reflect.Field; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import liquibase.ContextExpression; +import liquibase.Labels; +import liquibase.change.CheckSum; +import liquibase.changelog.ChangeSet; +import liquibase.changelog.RanChangeSet; +import liquibase.changelog.StandardChangeLogHistoryService; +import liquibase.database.Database; +import liquibase.database.core.MySQLDatabase; +import liquibase.exception.DatabaseException; +import liquibase.logging.LogFactory; + +/** + * + * @author hmlnarik + */ +public class CustomChangeLogHistoryService extends StandardChangeLogHistoryService { + + private List ranChangeSetList; + + @Override + public List getRanChangeSets() throws DatabaseException { + Database database = getDatabase(); + if (! (database instanceof MySQLDatabase) || database.getDatabaseMajorVersion() < 8) { + return super.getRanChangeSets(); + } + if (this.ranChangeSetList == null) { + String databaseChangeLogTableName = getDatabase().escapeTableName(getLiquibaseCatalogName(), getLiquibaseSchemaName(), getDatabaseChangeLogTableName()); + List ranChangeSetList = new ArrayList<>(); + if (hasDatabaseChangeLogTable()) { + LogFactory.getLogger().info("Reading from " + databaseChangeLogTableName); + List> results = queryDatabaseChangeLogTable(database); + for (Map rs : results) { + String fileName = rs.get("FILENAME").toString(); + String author = rs.get("AUTHOR").toString(); + String id = rs.get("ID").toString(); + String md5sum = rs.get("MD5SUM") == null || getDatabaseChecksumsCompatible() ? null : rs.get("MD5SUM").toString(); + String description = rs.get("DESCRIPTION") == null ? null : rs.get("DESCRIPTION").toString(); + String comments = rs.get("COMMENTS") == null ? null : rs.get("COMMENTS").toString(); + Object tmpDateExecuted = rs.get("DATEEXECUTED"); + Date dateExecuted = null; + if (tmpDateExecuted instanceof Date) { + dateExecuted = (Date) tmpDateExecuted; + } else if (tmpDateExecuted instanceof LocalDateTime) { + dateExecuted = Date.from(((LocalDateTime) tmpDateExecuted).atZone(ZoneId.systemDefault()).toInstant()); + } else { + DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + try { + dateExecuted = df.parse((String) tmpDateExecuted); + } catch (ParseException e) { + } + } + String tmpOrderExecuted = rs.get("ORDEREXECUTED").toString(); + Integer orderExecuted = (tmpOrderExecuted == null ? null : Integer.valueOf(tmpOrderExecuted)); + String tag = rs.get("TAG") == null ? null : rs.get("TAG").toString(); + String execType = rs.get("EXECTYPE") == null ? null : rs.get("EXECTYPE").toString(); + ContextExpression contexts = new ContextExpression((String) rs.get("CONTEXTS")); + Labels labels = new Labels((String) rs.get("LABELS")); + String deploymentId = (String) rs.get("DEPLOYMENT_ID"); + + try { + RanChangeSet ranChangeSet = new RanChangeSet(fileName, id, author, CheckSum.parse(md5sum), dateExecuted, tag, ChangeSet.ExecType.valueOf(execType), description, comments, contexts, labels, deploymentId); + ranChangeSet.setOrderExecuted(orderExecuted); + ranChangeSetList.add(ranChangeSet); + } catch (IllegalArgumentException e) { + LogFactory.getLogger().severe("Unknown EXECTYPE from database: " + execType); + throw e; + } + } + } + + this.ranChangeSetList = ranChangeSetList; + } + return Collections.unmodifiableList(ranChangeSetList); + } + + private boolean getDatabaseChecksumsCompatible() { + Field f = Reflections.findDeclaredField(StandardChangeLogHistoryService.class, "databaseChecksumsCompatible"); + if (f != null) { + f.setAccessible(true); + Boolean databaseChecksumsCompatible = Reflections.getFieldValue(f, this, Boolean.class); + return databaseChecksumsCompatible == null ? true : databaseChecksumsCompatible; + } + return true; + } + + @Override + public int getPriority() { + return super.getPriority() + 1; // Ensure bigger priority than StandardChangeLogHistoryService + } +} diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java index 6b5921b31a..dc8f30d51c 100644 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java @@ -48,6 +48,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import java.sql.Connection; +import liquibase.changelog.ChangeLogHistoryServiceFactory; /** * @author Marek Posolda @@ -117,6 +118,8 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr // Use "SELECT FOR UPDATE" for locking database SqlGeneratorFactory.getInstance().register(new CustomLockDatabaseChangeLogGenerator()); + ChangeLogHistoryServiceFactory.getInstance().register(new CustomChangeLogHistoryService()); + // Adding CustomCreateIndexChange for handling conditional indices creation ChangeFactory.getInstance().register(CustomCreateIndexChange.class); } diff --git a/pom.xml b/pom.xml index d97cee0424..b91d5142b5 100755 --- a/pom.xml +++ b/pom.xml @@ -128,7 +128,7 @@ ${jetty92.version} 3.5.5 - 8.0.18 + 8.0.23 4.2.0 7.1.0 42.2.8 diff --git a/quarkus/runtime/src/main/java/org/keycloak/connections/liquibase/QuarkusJpaUpdaterProvider.java b/quarkus/runtime/src/main/java/org/keycloak/connections/liquibase/QuarkusJpaUpdaterProvider.java index d3859a4670..7ac4d78553 100755 --- a/quarkus/runtime/src/main/java/org/keycloak/connections/liquibase/QuarkusJpaUpdaterProvider.java +++ b/quarkus/runtime/src/main/java/org/keycloak/connections/liquibase/QuarkusJpaUpdaterProvider.java @@ -35,6 +35,7 @@ import org.keycloak.common.util.reflections.Reflections; import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider; import org.keycloak.connections.jpa.updater.JpaUpdaterProvider; import org.keycloak.connections.jpa.updater.liquibase.ThreadLocalSessionContext; +import org.keycloak.connections.jpa.updater.liquibase.conn.CustomChangeLogHistoryService; import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider; import org.keycloak.connections.jpa.util.JpaUtils; import org.keycloak.models.KeycloakSession; @@ -283,6 +284,8 @@ public class QuarkusJpaUpdaterProvider implements JpaUpdaterProvider { private void resetLiquibaseServices(Liquibase liquibase) { Method resetServices = Reflections.findDeclaredMethod(Liquibase.class, "resetServices"); Reflections.invokeMethod(true, resetServices, liquibase); + + ChangeLogHistoryServiceFactory.getInstance().register(new CustomChangeLogHistoryService()); } @SuppressWarnings("unchecked") diff --git a/quarkus/runtime/src/main/java/org/keycloak/connections/liquibase/QuarkusLiquibaseConnectionProvider.java b/quarkus/runtime/src/main/java/org/keycloak/connections/liquibase/QuarkusLiquibaseConnectionProvider.java index 21e00fd6e1..94f03da52d 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/connections/liquibase/QuarkusLiquibaseConnectionProvider.java +++ b/quarkus/runtime/src/main/java/org/keycloak/connections/liquibase/QuarkusLiquibaseConnectionProvider.java @@ -24,7 +24,6 @@ import javax.xml.parsers.SAXParserFactory; import liquibase.database.core.MariaDBDatabase; import liquibase.database.core.MySQLDatabase; -import liquibase.database.core.PostgresDatabase; import org.jboss.logging.Logger; import org.keycloak.Config; import org.keycloak.connections.jpa.JpaConnectionProvider; @@ -33,18 +32,19 @@ import org.keycloak.connections.jpa.updater.liquibase.MySQL8VarcharType; import org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase; import org.keycloak.connections.jpa.updater.liquibase.UpdatedMariaDBDatabase; import org.keycloak.connections.jpa.updater.liquibase.UpdatedMySqlDatabase; +import org.keycloak.connections.jpa.updater.liquibase.conn.CustomChangeLogHistoryService; import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider; import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProviderFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import liquibase.Liquibase; +import liquibase.changelog.ChangeLogHistoryServiceFactory; import liquibase.database.Database; import liquibase.database.DatabaseFactory; import liquibase.database.jvm.JdbcConnection; import liquibase.datatype.DataTypeFactory; import liquibase.exception.LiquibaseException; -import liquibase.logging.LogFactory; import liquibase.parser.ChangeLogParser; import liquibase.parser.ChangeLogParserFactory; import liquibase.parser.core.xml.XMLChangeLogSAXParser; @@ -91,6 +91,8 @@ public class QuarkusLiquibaseConnectionProvider implements LiquibaseConnectionPr if (database.getDatabaseProductName().equals(MySQLDatabase.PRODUCT_NAME)) { // Adding CustomVarcharType for MySQL 8 and newer DataTypeFactory.getInstance().register(MySQL8VarcharType.class); + + ChangeLogHistoryServiceFactory.getInstance().register(new CustomChangeLogHistoryService()); } else if (database.getDatabaseProductName().equals(MariaDBDatabase.PRODUCT_NAME)) { // Adding CustomVarcharType for MySQL 8 and newer DataTypeFactory.getInstance().register(MySQL8VarcharType.class);