/*
* Copyright 2016 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 liquibase.Liquibase;
import liquibase.changelog.ChangeSet;
import liquibase.changelog.DatabaseChangeLog;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.LiquibaseException;
import liquibase.logging.LogFactory;
import liquibase.logging.LogLevel;
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.resource.ResourceAccessor;
import liquibase.servicelocator.ServiceLocator;
import liquibase.sqlgenerator.SqlGeneratorFactory;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider;
import org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase;
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomInsertLockRecordGenerator;
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockDatabaseChangeLogGenerator;
import org.keycloak.connections.jpa.updater.liquibase.lock.DummyLockService;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import java.sql.Connection;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionProviderFactory, LiquibaseConnectionProvider {
private static final Logger logger = Logger.getLogger(DefaultLiquibaseConnectionProvider.class);
private volatile boolean initialized = false;
@Override
public LiquibaseConnectionProvider create(KeycloakSession session) {
if (!initialized) {
synchronized (this) {
if (!initialized) {
baseLiquibaseInitialization();
initialized = true;
}
}
}
return this;
}
protected void baseLiquibaseInitialization() {
ServiceLocator sl = ServiceLocator.getInstance();
sl.setResourceAccessor(new ClassLoaderResourceAccessor(getClass().getClassLoader()));
if (!System.getProperties().containsKey("liquibase.scan.packages")) {
if (sl.getPackages().remove("liquibase.core")) {
sl.addPackageToScan("liquibase.core.xml");
}
if (sl.getPackages().remove("liquibase.parser")) {
sl.addPackageToScan("liquibase.parser.core.xml");
}
if (sl.getPackages().remove("liquibase.serializer")) {
sl.addPackageToScan("liquibase.serializer.core.xml");
}
sl.getPackages().remove("liquibase.ext");
sl.getPackages().remove("liquibase.sdk");
String lockPackageName = DummyLockService.class.getPackage().getName();
logger.debugf("Added package %s to liquibase", lockPackageName);
sl.addPackageToScan(lockPackageName);
}
LogFactory.setInstance(new LogWrapper());
// Adding PostgresPlus support to liquibase
DatabaseFactory.getInstance().register(new PostgresPlusDatabase());
// Change command for creating lock and drop DELETE lock record from it
SqlGeneratorFactory.getInstance().register(new CustomInsertLockRecordGenerator());
// Use "SELECT FOR UPDATE" for locking database
SqlGeneratorFactory.getInstance().register(new CustomLockDatabaseChangeLogGenerator());
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "default";
}
@Override
public Liquibase getLiquibase(Connection connection, String defaultSchema) throws LiquibaseException {
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
if (defaultSchema != null) {
database.setDefaultSchemaName(defaultSchema);
}
String changelog = LiquibaseJpaUpdaterProvider.CHANGELOG;
ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(getClass().getClassLoader());
logger.debugf("Using changelog file %s and changelogTableName %s", changelog, database.getDatabaseChangeLogTableName());
return new Liquibase(changelog, resourceAccessor, database);
}
@Override
public Liquibase getLiquibaseForCustomUpdate(Connection connection, String defaultSchema, String changelogLocation, ClassLoader classloader, String changelogTableName) throws LiquibaseException {
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
if (defaultSchema != null) {
database.setDefaultSchemaName(defaultSchema);
}
ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(classloader);
database.setDatabaseChangeLogTableName(changelogTableName);
logger.debugf("Using changelog file %s and changelogTableName %s", changelogLocation, database.getDatabaseChangeLogTableName());
return new Liquibase(changelogLocation, resourceAccessor, database);
}
private static class LogWrapper extends LogFactory {
private static final liquibase.logging.Logger logger = new liquibase.logging.Logger() {
@Override
public void setName(String name) {
}
@Override
public void setLogLevel(String level) {
}
@Override
public void setLogLevel(LogLevel level) {
}
@Override
public void setLogLevel(String logLevel, String logFile) {
}
@Override
public void severe(String message) {
DefaultLiquibaseConnectionProvider.logger.error(message);
}
@Override
public void severe(String message, Throwable e) {
DefaultLiquibaseConnectionProvider.logger.error(message, e);
}
@Override
public void warning(String message) {
// Ignore this warning as cascaded drops doesn't work anyway with all DBs, which we need to support
if ("Database does not support drop with cascade".equals(message)) {
DefaultLiquibaseConnectionProvider.logger.debug(message);
} else {
DefaultLiquibaseConnectionProvider.logger.warn(message);
}
}
@Override
public void warning(String message, Throwable e) {
DefaultLiquibaseConnectionProvider.logger.warn(message, e);
}
@Override
public void info(String message) {
DefaultLiquibaseConnectionProvider.logger.debug(message);
}
@Override
public void info(String message, Throwable e) {
DefaultLiquibaseConnectionProvider.logger.debug(message, e);
}
@Override
public void debug(String message) {
if (DefaultLiquibaseConnectionProvider.logger.isTraceEnabled()) {
DefaultLiquibaseConnectionProvider.logger.trace(message);
}
}
@Override
public LogLevel getLogLevel() {
if (DefaultLiquibaseConnectionProvider.logger.isTraceEnabled()) {
return LogLevel.DEBUG;
} else if (DefaultLiquibaseConnectionProvider.logger.isDebugEnabled()) {
return LogLevel.INFO;
} else {
return LogLevel.WARNING;
}
}
@Override
public void debug(String message, Throwable e) {
DefaultLiquibaseConnectionProvider.logger.trace(message, e);
}
@Override
public void setChangeLog(DatabaseChangeLog databaseChangeLog) {
}
@Override
public void setChangeSet(ChangeSet changeSet) {
}
@Override
public int getPriority() {
return 0;
}
};
@Override
public liquibase.logging.Logger getLog(String name) {
return logger;
}
@Override
public liquibase.logging.Logger getLog() {
return logger;
}
}
}