[KEYCLOAK-11784] - Using Hibernate Extension
This commit is contained in:
parent
8c9b7b05ac
commit
bae802bcfa
21 changed files with 1148 additions and 252 deletions
0
model/jpa/src/main/resources/META-INF/beans.xml
Normal file
0
model/jpa/src/main/resources/META-INF/beans.xml
Normal file
|
@ -43,6 +43,21 @@
|
|||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-jpa</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.persistence</groupId>
|
||||
<artifactId>jakarta.persistence-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
<version>${hibernate.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
|
@ -75,7 +90,16 @@
|
|||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-agroal</artifactId>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-common</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
|
@ -91,6 +115,10 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
|
@ -100,16 +128,68 @@
|
|||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>extract-liquibase-for-indexing</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>unpack-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includeGroupIds>org.liquibase</includeGroupIds>
|
||||
<outputDirectory>${project.build.directory}/liquibase-extracted</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.jboss.jandex</groupId>
|
||||
<artifactId>jandex-maven-plugin</artifactId>
|
||||
<version>1.0.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-index</id>
|
||||
<goals>
|
||||
<goal>jandex</goal>
|
||||
</goals>
|
||||
<phase>process-sources</phase>
|
||||
<configuration>
|
||||
<indexName>liquibase.idx</indexName>
|
||||
<processDefaultFileSet>false</processDefaultFileSet>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}/liquibase-extracted</directory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-liquibase-index</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/classes/</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${project.build.directory}/liquibase-extracted</directory>
|
||||
<includes>
|
||||
<include>META-INF/liquibase.idx</include>
|
||||
</includes>
|
||||
<filtering>false</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 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;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
public class QuarkusJpaConnectionProvider implements JpaConnectionProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(QuarkusJpaConnectionProvider.class);
|
||||
private final EntityManager em;
|
||||
|
||||
public QuarkusJpaConnectionProvider(EntityManager em) {
|
||||
this.em = em;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityManager getEntityManager() {
|
||||
return em;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
logger.trace("QuarkusJpaConnectionProvider close()");
|
||||
em.close();
|
||||
}
|
||||
|
||||
}
|
|
@ -17,35 +17,34 @@
|
|||
|
||||
package org.keycloak.connections.jpa;
|
||||
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
|
||||
import static org.keycloak.connections.liquibase.QuarkusJpaUpdaterProvider.VERIFY_AND_RUN_MASTER_CHANGELOG;
|
||||
|
||||
import org.hibernate.internal.SessionFactoryImpl;
|
||||
import org.hibernate.internal.SessionImpl;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.ServerStartupError;
|
||||
import org.keycloak.common.Version;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||
import org.keycloak.migration.ModelVersion;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakSessionTask;
|
||||
import org.keycloak.models.KeycloakTransactionManager;
|
||||
import org.keycloak.models.dblock.DBLockManager;
|
||||
import org.keycloak.models.dblock.DBLockProvider;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||
import org.keycloak.timer.TimerProvider;
|
||||
import org.keycloak.transaction.JtaTransactionManagerLookup;
|
||||
|
||||
import javax.enterprise.inject.Instance;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.SynchronizationType;
|
||||
import javax.sql.DataSource;
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.sql.Statement;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import javax.enterprise.inject.spi.CDI;
|
||||
|
@ -57,11 +56,13 @@ public class QuarkusJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
|
||||
private static final Logger logger = Logger.getLogger(QuarkusJpaConnectionProviderFactory.class);
|
||||
|
||||
private static final String SQL_GET_LATEST_VERSION = "SELECT VERSION FROM %sMIGRATION_MODEL";
|
||||
|
||||
enum MigrationStrategy {
|
||||
UPDATE, VALIDATE, MANUAL
|
||||
}
|
||||
|
||||
private volatile EntityManagerFactory emf;
|
||||
private EntityManagerFactory emf;
|
||||
|
||||
private Config.Scope config;
|
||||
|
||||
|
@ -75,19 +76,22 @@ public class QuarkusJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
@Override
|
||||
public JpaConnectionProvider create(KeycloakSession session) {
|
||||
logger.trace("Create QuarkusJpaConnectionProvider");
|
||||
lazyInit(session);
|
||||
|
||||
EntityManager em;
|
||||
if (!jtaEnabled) {
|
||||
logger.trace("enlisting EntityManager in JpaKeycloakTransaction");
|
||||
em = emf.createEntityManager();
|
||||
try {
|
||||
SessionImpl.class.cast(em).connection().setAutoCommit(false);
|
||||
} catch (SQLException cause) {
|
||||
throw new RuntimeException(cause);
|
||||
}
|
||||
} else {
|
||||
|
||||
em = emf.createEntityManager(SynchronizationType.SYNCHRONIZED);
|
||||
}
|
||||
em = PersistenceExceptionConverter.create(em);
|
||||
if (!jtaEnabled) session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
|
||||
return new QuarkusJpaConnectionProvider(em);
|
||||
return new DefaultJpaConnectionProvider(em);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -111,11 +115,7 @@ public class QuarkusJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
public void postInit(KeycloakSessionFactory factory) {
|
||||
this.factory = factory;
|
||||
checkJtaEnabled(factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return 100;
|
||||
lazyInit();
|
||||
}
|
||||
|
||||
protected void checkJtaEnabled(KeycloakSessionFactory factory) {
|
||||
|
@ -127,78 +127,8 @@ public class QuarkusJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
}
|
||||
}
|
||||
|
||||
private void lazyInit(KeycloakSession session) {
|
||||
if (emf == null) {
|
||||
synchronized (this) {
|
||||
if (emf == null) {
|
||||
KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
|
||||
logger.debug("Initializing Quarkus JPA connections");
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
String unitName = "keycloak-default";
|
||||
|
||||
String schema = getSchema();
|
||||
if (schema != null) {
|
||||
properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
|
||||
}
|
||||
|
||||
MigrationStrategy migrationStrategy = getMigrationStrategy();
|
||||
boolean initializeEmpty = config.getBoolean("initializeEmpty", true);
|
||||
File databaseUpdateFile = getDatabaseUpdateFile();
|
||||
|
||||
properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
|
||||
properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
|
||||
properties.put(AvailableSettings.CONNECTION_HANDLING,
|
||||
PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT);
|
||||
|
||||
properties.put(AvailableSettings.DATASOURCE, getDataSource());
|
||||
|
||||
Connection connection = getConnection();
|
||||
try {
|
||||
prepareOperationalInfo(connection);
|
||||
|
||||
String driverDialect = detectDialect(connection);
|
||||
if (driverDialect != null) {
|
||||
properties.put("hibernate.dialect", driverDialect);
|
||||
}
|
||||
|
||||
migration(migrationStrategy, initializeEmpty, schema, databaseUpdateFile, connection, session);
|
||||
|
||||
int globalStatsInterval = config.getInt("globalStatsInterval", -1);
|
||||
if (globalStatsInterval != -1) {
|
||||
properties.put("hibernate.generate_statistics", true);
|
||||
}
|
||||
|
||||
logger.trace("Creating EntityManagerFactory");
|
||||
logger.tracev("***** create EMF jtaEnabled {0} ", jtaEnabled);
|
||||
|
||||
Collection<ClassLoader> classLoaders = new ArrayList<>();
|
||||
if (properties.containsKey(AvailableSettings.CLASSLOADERS)) {
|
||||
classLoaders.addAll((Collection<ClassLoader>) properties.get(AvailableSettings.CLASSLOADERS));
|
||||
}
|
||||
classLoaders.add(getClass().getClassLoader());
|
||||
properties.put(AvailableSettings.CLASSLOADERS, classLoaders);
|
||||
emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, jtaEnabled);
|
||||
logger.trace("EntityManagerFactory created");
|
||||
|
||||
if (globalStatsInterval != -1) {
|
||||
startGlobalStats(session, globalStatsInterval);
|
||||
}
|
||||
} finally {
|
||||
// Close after creating EntityManagerFactory to prevent in-mem databases from closing
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (SQLException e) {
|
||||
logger.warn("Can't close connection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
private String getSchema(String schema) {
|
||||
return schema == null ? "" : schema + ".";
|
||||
}
|
||||
|
||||
private File getDatabaseUpdateFile() {
|
||||
|
@ -221,56 +151,31 @@ public class QuarkusJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
}
|
||||
}
|
||||
|
||||
void migration(String schema, Connection connection, KeycloakSession session) {
|
||||
MigrationStrategy strategy = getMigrationStrategy();
|
||||
boolean initializeEmpty = config.getBoolean("initializeEmpty", true);
|
||||
File databaseUpdateFile = getDatabaseUpdateFile();
|
||||
|
||||
String version = null;
|
||||
|
||||
protected String detectDialect(Connection connection) {
|
||||
String driverDialect = config.get("driverDialect");
|
||||
if (driverDialect != null && driverDialect.length() > 0) {
|
||||
return driverDialect;
|
||||
} else {
|
||||
try {
|
||||
String dbProductName = connection.getMetaData().getDatabaseProductName();
|
||||
String dbProductVersion = connection.getMetaData().getDatabaseProductVersion();
|
||||
|
||||
// For MSSQL2014, we may need to fix the autodetected dialect by hibernate
|
||||
if (dbProductName.equals("Microsoft SQL Server")) {
|
||||
String topVersionStr = dbProductVersion.split("\\.")[0];
|
||||
boolean shouldSet2012Dialect = true;
|
||||
try {
|
||||
int topVersion = Integer.parseInt(topVersionStr);
|
||||
if (topVersion < 12) {
|
||||
shouldSet2012Dialect = false;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
}
|
||||
if (shouldSet2012Dialect) {
|
||||
String sql2012Dialect = "org.hibernate.dialect.SQLServer2012Dialect";
|
||||
logger.debugf("Manually override hibernate dialect to %s", sql2012Dialect);
|
||||
return sql2012Dialect;
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
try (ResultSet rs = statement.executeQuery(String.format(SQL_GET_LATEST_VERSION, getSchema(schema)))) {
|
||||
if (rs.next()) {
|
||||
version = rs.getString(1);
|
||||
}
|
||||
}
|
||||
// For Oracle19c, we may need to set dialect explicitly to workaround https://hibernate.atlassian.net/browse/HHH-13184
|
||||
if (dbProductName.equals("Oracle") && connection.getMetaData().getDatabaseMajorVersion() > 12) {
|
||||
logger.debugf("Manually specify dialect for Oracle to org.hibernate.dialect.Oracle12cDialect");
|
||||
return "org.hibernate.dialect.Oracle12cDialect";
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.warnf("Unable to detect hibernate dialect due database exception : %s", e.getMessage());
|
||||
} catch (SQLException ignore) {
|
||||
// migration model probably does not exist so we assume the database is empty
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void startGlobalStats(KeycloakSession session, int globalStatsIntervalSecs) {
|
||||
logger.debugf("Started Hibernate statistics with the interval %s seconds", globalStatsIntervalSecs);
|
||||
TimerProvider timer = session.getProvider(TimerProvider.class);
|
||||
timer.scheduleTask(new HibernateStatsReporter(emf), globalStatsIntervalSecs * 1000, "ReportHibernateGlobalStats");
|
||||
}
|
||||
|
||||
void migration(MigrationStrategy strategy, boolean initializeEmpty, String schema, File databaseUpdateFile, Connection connection, KeycloakSession session) {
|
||||
JpaUpdaterProvider updater = session.getProvider(JpaUpdaterProvider.class);
|
||||
|
||||
session.setAttribute(VERIFY_AND_RUN_MASTER_CHANGELOG, version == null || !version.equals(new ModelVersion(Version.VERSION_KEYCLOAK).toString()));
|
||||
|
||||
JpaUpdaterProvider.Status status = updater.validate(connection, schema);
|
||||
|
||||
if (status == JpaUpdaterProvider.Status.VALID) {
|
||||
logger.debug("Database is up-to-date");
|
||||
} else if (status == JpaUpdaterProvider.Status.EMPTY) {
|
||||
|
@ -303,8 +208,7 @@ public class QuarkusJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
}
|
||||
|
||||
protected void update(Connection connection, String schema, KeycloakSession session, JpaUpdaterProvider updater) {
|
||||
runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession lockSession) -> {
|
||||
DBLockManager dbLockManager = new DBLockManager(lockSession);
|
||||
DBLockManager dbLockManager = new DBLockManager(session);
|
||||
DBLockProvider dbLock2 = dbLockManager.getDBLock();
|
||||
dbLock2.waitForLock(DBLockProvider.Namespace.DATABASE);
|
||||
try {
|
||||
|
@ -312,12 +216,11 @@ public class QuarkusJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
} finally {
|
||||
dbLock2.releaseLock();
|
||||
}
|
||||
}, KeycloakTransactionManager.JTAPolicy.NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
protected void export(Connection connection, String schema, File databaseUpdateFile, KeycloakSession session, JpaUpdaterProvider updater) {
|
||||
runJobInTransaction(session.getKeycloakSessionFactory(), (KeycloakSession lockSession) -> {
|
||||
DBLockManager dbLockManager = new DBLockManager(lockSession);
|
||||
protected void export(Connection connection, String schema, File databaseUpdateFile, KeycloakSession session,
|
||||
JpaUpdaterProvider updater) {
|
||||
DBLockManager dbLockManager = new DBLockManager(session);
|
||||
DBLockProvider dbLock2 = dbLockManager.getDBLock();
|
||||
dbLock2.waitForLock(DBLockProvider.Namespace.DATABASE);
|
||||
try {
|
||||
|
@ -325,17 +228,16 @@ public class QuarkusJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
} finally {
|
||||
dbLock2.releaseLock();
|
||||
}
|
||||
}, KeycloakTransactionManager.JTAPolicy.NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection() {
|
||||
SessionFactoryImpl entityManagerFactory = SessionFactoryImpl.class.cast(emf);
|
||||
|
||||
try {
|
||||
DataSource dataSource = getDataSource();
|
||||
logger.tracev("CDI DataSource: {0}", dataSource);
|
||||
return dataSource.getConnection();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to connect to database", e);
|
||||
return entityManagerFactory.getJdbcServices().getBootstrapJdbcConnectionAccess().obtainConnection();
|
||||
} catch (SQLException cause) {
|
||||
throw new RuntimeException("Failed to obtain JDBC connection", cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -363,35 +265,38 @@ public class QuarkusJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
}
|
||||
}
|
||||
|
||||
private DataSource getDataSource() {
|
||||
return CDI.current().select(DataSource.class).get();
|
||||
private void lazyInit() {
|
||||
Instance<EntityManagerFactory> instance = CDI.current().select(EntityManagerFactory.class);
|
||||
|
||||
if (!instance.isResolvable()) {
|
||||
throw new RuntimeException("Failed to resolve " + EntityManagerFactory.class + " from Quarkus runtime");
|
||||
}
|
||||
|
||||
private static void runJobInTransaction(KeycloakSessionFactory factory, KeycloakSessionTask task, KeycloakTransactionManager.JTAPolicy jtaPolicy) {
|
||||
emf = instance.get();
|
||||
|
||||
try (Connection connection = getConnection()) {
|
||||
if (jtaEnabled) {
|
||||
KeycloakModelUtils.suspendJtaTransaction(factory, () -> {
|
||||
KeycloakSession session = factory.create();
|
||||
KeycloakTransactionManager tx = session.getTransactionManager();
|
||||
if (jtaPolicy != null)
|
||||
tx.setJTAPolicy(jtaPolicy);
|
||||
|
||||
try {
|
||||
tx.begin();
|
||||
task.run(session);
|
||||
|
||||
if (tx.isActive()) {
|
||||
if (tx.getRollbackOnly()) {
|
||||
tx.rollback();
|
||||
} else {
|
||||
tx.commit();
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException re) {
|
||||
if (tx.isActive()) {
|
||||
tx.rollback();
|
||||
}
|
||||
throw re;
|
||||
migration(getSchema(), connection, session);
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
KeycloakModelUtils.runJobInTransaction(factory, session -> {
|
||||
migration(getSchema(), connection, session);
|
||||
});
|
||||
}
|
||||
prepareOperationalInfo(connection);
|
||||
} catch (SQLException cause) {
|
||||
throw new RuntimeException("Failed to migrate model", cause);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
package org.keycloak.connections.liquibase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import liquibase.exception.ServiceNotFoundException;
|
||||
import liquibase.parser.core.xml.XMLChangeLogSAXParser;
|
||||
import org.jboss.jandex.AnnotationInstance;
|
||||
import org.jboss.jandex.ClassInfo;
|
||||
import org.jboss.jandex.DotName;
|
||||
import org.jboss.jandex.Index;
|
||||
import org.jboss.jandex.IndexReader;
|
||||
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 liquibase.database.Database;
|
||||
import liquibase.lockservice.LockService;
|
||||
import liquibase.logging.Logger;
|
||||
import liquibase.parser.ChangeLogParser;
|
||||
import liquibase.servicelocator.DefaultPackageScanClassResolver;
|
||||
import liquibase.servicelocator.LiquibaseService;
|
||||
import liquibase.servicelocator.ServiceLocator;
|
||||
import liquibase.sqlgenerator.SqlGenerator;
|
||||
|
||||
public class FastServiceLocator extends ServiceLocator {
|
||||
|
||||
private static Map<String, List<String>> CLASS_INDEX = new HashMap<>();
|
||||
|
||||
static {
|
||||
DotName liquibaseServiceName = DotName.createSimple(LiquibaseService.class.getName());
|
||||
|
||||
try (InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/liquibase.idx")) {
|
||||
IndexReader reader = new IndexReader(in);
|
||||
Index index = reader.read();
|
||||
for (Class<?> c : Arrays.asList(liquibase.diff.compare.DatabaseObjectComparator.class,
|
||||
liquibase.parser.NamespaceDetails.class,
|
||||
liquibase.precondition.Precondition.class,
|
||||
Database.class,
|
||||
ChangeLogParser.class,
|
||||
liquibase.change.Change.class,
|
||||
liquibase.snapshot.SnapshotGenerator.class,
|
||||
liquibase.changelog.ChangeLogHistoryService.class,
|
||||
liquibase.datatype.LiquibaseDataType.class,
|
||||
liquibase.executor.Executor.class,
|
||||
LockService.class,
|
||||
SqlGenerator.class)) {
|
||||
List<String> impls = new ArrayList<>();
|
||||
CLASS_INDEX.put(c.getName(), impls);
|
||||
Set<ClassInfo> classes = new HashSet<>();
|
||||
if (c.isInterface()) {
|
||||
classes.addAll(index.getAllKnownImplementors(DotName.createSimple(c.getName())));
|
||||
} else {
|
||||
classes.addAll(index.getAllKnownSubclasses(DotName.createSimple(c.getName())));
|
||||
}
|
||||
for (ClassInfo found : classes) {
|
||||
if (Modifier.isAbstract(found.flags()) ||
|
||||
Modifier.isInterface(found.flags()) ||
|
||||
!found.hasNoArgsConstructor() ||
|
||||
!Modifier.isPublic(found.flags())) {
|
||||
continue;
|
||||
}
|
||||
AnnotationInstance annotationInstance = found.classAnnotation(liquibaseServiceName);
|
||||
if (annotationInstance == null || !annotationInstance.value("skip").asBoolean()) {
|
||||
impls.add(found.name().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Failed to get liquibase jandex index", cause);
|
||||
}
|
||||
|
||||
CLASS_INDEX.put(Logger.class.getName(), Arrays.asList(KeycloakLogger.class.getName()));
|
||||
CLASS_INDEX.put(LockService.class.getName(), Arrays.asList(DummyLockService.class.getName()));
|
||||
CLASS_INDEX.put(ChangeLogParser.class.getName(), Arrays.asList(XMLChangeLogSAXParser.class.getName()));
|
||||
CLASS_INDEX.get(SqlGenerator.class.getName()).add(CustomInsertLockRecordGenerator.class.getName());
|
||||
CLASS_INDEX.get(SqlGenerator.class.getName()).add(CustomLockDatabaseChangeLogGenerator.class.getName());
|
||||
}
|
||||
|
||||
protected FastServiceLocator() {
|
||||
super(new DefaultPackageScanClassResolver() {
|
||||
@Override
|
||||
public Set<Class<?>> findImplementations(Class parent, String... packageNames) {
|
||||
List<String> found = CLASS_INDEX.get(parent.getName());
|
||||
|
||||
if (found == null) {
|
||||
return super.findImplementations(parent, packageNames);
|
||||
}
|
||||
|
||||
Set<Class<?>> ret = new HashSet<>();
|
||||
for (String i : found) {
|
||||
try {
|
||||
ret.add(Class.forName(i, false, Thread.currentThread().getContextClassLoader()));
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
});
|
||||
|
||||
if (!System.getProperties().containsKey("liquibase.scan.packages")) {
|
||||
if (getPackages().remove("liquibase.core")) {
|
||||
addPackageToScan("liquibase.core.xml");
|
||||
}
|
||||
|
||||
if (getPackages().remove("liquibase.parser")) {
|
||||
addPackageToScan("liquibase.parser.core.xml");
|
||||
}
|
||||
|
||||
if (getPackages().remove("liquibase.serializer")) {
|
||||
addPackageToScan("liquibase.serializer.core.xml");
|
||||
}
|
||||
|
||||
getPackages().remove("liquibase.ext");
|
||||
getPackages().remove("liquibase.sdk");
|
||||
}
|
||||
|
||||
// we only need XML parsers
|
||||
getPackages().remove("liquibase.parser.core.yaml");
|
||||
getPackages().remove("liquibase.serializer.core.yaml");
|
||||
getPackages().remove("liquibase.parser.core.json");
|
||||
getPackages().remove("liquibase.serializer.core.json");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object newInstance(Class requiredInterface) throws ServiceNotFoundException {
|
||||
if (Logger.class.equals(requiredInterface)) {
|
||||
return new KeycloakLogger();
|
||||
}
|
||||
return super.newInstance(requiredInterface);
|
||||
}
|
||||
|
||||
public void register(Class<? extends Database> type) {
|
||||
CLASS_INDEX.put(Database.class.getName(), Arrays.asList(type.getName()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package org.keycloak.connections.liquibase;
|
||||
|
||||
import liquibase.changelog.ChangeSet;
|
||||
import liquibase.changelog.DatabaseChangeLog;
|
||||
import liquibase.logging.LogLevel;
|
||||
import liquibase.logging.Logger;
|
||||
|
||||
public class KeycloakLogger implements Logger {
|
||||
|
||||
private static final org.jboss.logging.Logger logger = org.jboss.logging.Logger.getLogger(QuarkusLiquibaseConnectionProvider.class);
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogLevel(String logLevel, String logFile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String message) {
|
||||
logger.error(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String message, Throwable e) {
|
||||
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)) {
|
||||
logger.debug(message);
|
||||
} else {
|
||||
logger.warn(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String message, Throwable e) {
|
||||
logger.warn(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message) {
|
||||
logger.debug(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message, Throwable e) {
|
||||
logger.debug(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogLevel getLogLevel() {
|
||||
if (logger.isTraceEnabled()) {
|
||||
return LogLevel.DEBUG;
|
||||
} else if (logger.isDebugEnabled()) {
|
||||
return LogLevel.INFO;
|
||||
} else {
|
||||
return LogLevel.WARNING;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogLevel(String level) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogLevel(LogLevel level) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message, Throwable e) {
|
||||
logger.trace(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChangeLog(DatabaseChangeLog databaseChangeLog) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChangeSet(ChangeSet changeSet) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeLogFile() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* 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.liquibase;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Method;
|
||||
import java.sql.Connection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
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.ThreadLocalSessionContext;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
|
||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import liquibase.Contexts;
|
||||
import liquibase.LabelExpression;
|
||||
import liquibase.Liquibase;
|
||||
import liquibase.changelog.ChangeLogHistoryService;
|
||||
import liquibase.changelog.ChangeLogHistoryServiceFactory;
|
||||
import liquibase.changelog.ChangeSet;
|
||||
import liquibase.changelog.RanChangeSet;
|
||||
import liquibase.database.Database;
|
||||
import liquibase.exception.DatabaseException;
|
||||
import liquibase.exception.LiquibaseException;
|
||||
import liquibase.executor.Executor;
|
||||
import liquibase.executor.ExecutorService;
|
||||
import liquibase.executor.LoggingExecutor;
|
||||
import liquibase.snapshot.SnapshotControl;
|
||||
import liquibase.snapshot.SnapshotGeneratorFactory;
|
||||
import liquibase.statement.SqlStatement;
|
||||
import liquibase.statement.core.AddColumnStatement;
|
||||
import liquibase.statement.core.CreateDatabaseChangeLogTableStatement;
|
||||
import liquibase.statement.core.SetNullableStatement;
|
||||
import liquibase.statement.core.UpdateStatement;
|
||||
import liquibase.structure.core.Column;
|
||||
import liquibase.structure.core.Table;
|
||||
import liquibase.util.StreamUtil;
|
||||
|
||||
public class QuarkusJpaUpdaterProvider implements JpaUpdaterProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(QuarkusJpaUpdaterProvider.class);
|
||||
|
||||
public static final String CHANGELOG = "META-INF/jpa-changelog-master.xml";
|
||||
private static final String DEPLOYMENT_ID_COLUMN = "DEPLOYMENT_ID";
|
||||
public static final String VERIFY_AND_RUN_MASTER_CHANGELOG = "VERIFY_AND_RUN_MASTER_CHANGELOG";
|
||||
|
||||
private final KeycloakSession session;
|
||||
private Map<String, List<ChangeSet>> changeSets = new HashMap<>();
|
||||
|
||||
public QuarkusJpaUpdaterProvider(KeycloakSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Connection connection, String defaultSchema) {
|
||||
update(connection, null, defaultSchema);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void export(Connection connection, String defaultSchema, File file) {
|
||||
update(connection, file, defaultSchema);
|
||||
}
|
||||
|
||||
private void update(Connection connection, File file, String defaultSchema) {
|
||||
logger.debug("Starting database update");
|
||||
|
||||
// Need ThreadLocal as liquibase doesn't seem to have API to inject custom objects into tasks
|
||||
ThreadLocalSessionContext.setCurrentSession(session);
|
||||
|
||||
Writer exportWriter = null;
|
||||
try {
|
||||
if (needVerifyMasterChangelog()) {
|
||||
// Run update with keycloak master changelog first
|
||||
Liquibase liquibase = getLiquibaseForKeycloakUpdate(connection, defaultSchema);
|
||||
if (file != null) {
|
||||
exportWriter = new FileWriter(file);
|
||||
}
|
||||
updateChangeSet(liquibase, exportWriter);
|
||||
}
|
||||
|
||||
// Run update for each custom JpaEntityProvider
|
||||
Set<JpaEntityProvider> jpaProviders = session.getAllProviders(JpaEntityProvider.class);
|
||||
for (JpaEntityProvider jpaProvider : jpaProviders) {
|
||||
String customChangelog = jpaProvider.getChangelogLocation();
|
||||
if (customChangelog != null) {
|
||||
String factoryId = jpaProvider.getFactoryId();
|
||||
String changelogTableName = JpaUtils.getCustomChangelogTableName(factoryId);
|
||||
Liquibase liquibase = getLiquibaseForCustomProviderUpdate(connection, defaultSchema, customChangelog, jpaProvider.getClass().getClassLoader(), changelogTableName);
|
||||
updateChangeSet(liquibase, exportWriter);
|
||||
}
|
||||
}
|
||||
} catch (LiquibaseException | IOException e) {
|
||||
throw new RuntimeException("Failed to update database", e);
|
||||
} finally {
|
||||
ThreadLocalSessionContext.removeCurrentSession();
|
||||
if (exportWriter != null) {
|
||||
try {
|
||||
exportWriter.close();
|
||||
} catch (IOException ioe) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Boolean needVerifyMasterChangelog() {
|
||||
return session.getAttributeOrDefault(VERIFY_AND_RUN_MASTER_CHANGELOG, Boolean.TRUE);
|
||||
}
|
||||
|
||||
protected void updateChangeSet(Liquibase liquibase, Writer exportWriter) throws LiquibaseException {
|
||||
String changelog = liquibase.getChangeLogFile();
|
||||
Database database = liquibase.getDatabase();
|
||||
Table changelogTable = SnapshotGeneratorFactory.getInstance().getDatabaseChangeLogTable(new SnapshotControl(database, false, Table.class, Column.class), database);
|
||||
|
||||
if (changelogTable != null) {
|
||||
boolean hasDeploymentIdColumn = changelogTable.getColumn(DEPLOYMENT_ID_COLUMN) != null;
|
||||
|
||||
// create DEPLOYMENT_ID column if it doesn't exist
|
||||
if (!hasDeploymentIdColumn) {
|
||||
ChangeLogHistoryService changelogHistoryService = ChangeLogHistoryServiceFactory.getInstance().getChangeLogService(database);
|
||||
changelogHistoryService.generateDeploymentId();
|
||||
String deploymentId = changelogHistoryService.getDeploymentId();
|
||||
|
||||
logger.debugv("Adding missing column {0}={1} to {2} table", DEPLOYMENT_ID_COLUMN, deploymentId,changelogTable.getName());
|
||||
|
||||
List<SqlStatement> statementsToExecute = new ArrayList<>();
|
||||
statementsToExecute.add(new AddColumnStatement(database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(),
|
||||
changelogTable.getName(), DEPLOYMENT_ID_COLUMN, "VARCHAR(10)", null));
|
||||
statementsToExecute.add(new UpdateStatement(database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), changelogTable.getName())
|
||||
.addNewColumnValue(DEPLOYMENT_ID_COLUMN, deploymentId));
|
||||
statementsToExecute.add(new SetNullableStatement(database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(),
|
||||
changelogTable.getName(), DEPLOYMENT_ID_COLUMN, "VARCHAR(10)", false));
|
||||
|
||||
ExecutorService executorService = ExecutorService.getInstance();
|
||||
Executor executor = executorService.getExecutor(liquibase.getDatabase());
|
||||
|
||||
for (SqlStatement sql : statementsToExecute) {
|
||||
executor.execute(sql);
|
||||
database.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<ChangeSet> changeSets = getLiquibaseUnrunChangeSets(liquibase);
|
||||
if (!changeSets.isEmpty()) {
|
||||
List<RanChangeSet> ranChangeSets = liquibase.getDatabase().getRanChangeSetList();
|
||||
if (ranChangeSets.isEmpty()) {
|
||||
logger.infov("Initializing database schema. Using changelog {0}", changelog);
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debugv("Updating database from {0} to {1}. Using changelog {2}", ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId(), changelog);
|
||||
} else {
|
||||
logger.infov("Updating database. Using changelog {0}", changelog);
|
||||
}
|
||||
}
|
||||
|
||||
if (exportWriter != null) {
|
||||
if (ranChangeSets.isEmpty()) {
|
||||
outputChangeLogTableCreationScript(liquibase, exportWriter);
|
||||
}
|
||||
liquibase.update((Contexts) null, new LabelExpression(), exportWriter, false);
|
||||
} else {
|
||||
liquibase.update((Contexts) null);
|
||||
}
|
||||
|
||||
logger.debugv("Completed database update for changelog {0}", changelog);
|
||||
} else {
|
||||
logger.debugv("Database is up to date for changelog {0}", changelog);
|
||||
}
|
||||
|
||||
// Needs to restart liquibase services to clear ChangeLogHistoryServiceFactory.getInstance().
|
||||
// See https://issues.jboss.org/browse/KEYCLOAK-3769 for discussion relevant to why reset needs to be here
|
||||
resetLiquibaseServices(liquibase);
|
||||
}
|
||||
|
||||
private void outputChangeLogTableCreationScript(Liquibase liquibase, final Writer exportWriter) throws DatabaseException {
|
||||
Database database = liquibase.getDatabase();
|
||||
|
||||
Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
|
||||
LoggingExecutor executor = new LoggingExecutor(ExecutorService.getInstance().getExecutor(database), exportWriter, database);
|
||||
ExecutorService.getInstance().setExecutor(database, executor);
|
||||
|
||||
executor.comment("*********************************************************************");
|
||||
executor.comment("* Keycloak database creation script - apply this script to empty DB *");
|
||||
executor.comment("*********************************************************************" + StreamUtil.getLineSeparator());
|
||||
|
||||
executor.execute(new CreateDatabaseChangeLogTableStatement());
|
||||
// DatabaseChangeLogLockTable is created before this code is executed and recreated if it does not exist automatically
|
||||
// in org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService.init() called indirectly from
|
||||
// KeycloakApplication constructor (search for waitForLock() call). Hence it is not included in the creation script.
|
||||
|
||||
executor.comment("*********************************************************************" + StreamUtil.getLineSeparator());
|
||||
|
||||
ExecutorService.getInstance().setExecutor(database, oldTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Status validate(Connection connection, String defaultSchema) {
|
||||
logger.debug("Validating if database is updated");
|
||||
ThreadLocalSessionContext.setCurrentSession(session);
|
||||
|
||||
try {
|
||||
if (needVerifyMasterChangelog()) {
|
||||
// Validate with keycloak master changelog first
|
||||
Liquibase liquibase = getLiquibaseForKeycloakUpdate(connection, defaultSchema);
|
||||
|
||||
Status status = validateChangeSet(liquibase, liquibase.getChangeLogFile());
|
||||
if (status != Status.VALID) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate each custom JpaEntityProvider
|
||||
Set<JpaEntityProvider> jpaProviders = session.getAllProviders(JpaEntityProvider.class);
|
||||
for (JpaEntityProvider jpaProvider : jpaProviders) {
|
||||
String customChangelog = jpaProvider.getChangelogLocation();
|
||||
if (customChangelog != null) {
|
||||
String factoryId = jpaProvider.getFactoryId();
|
||||
String changelogTableName = JpaUtils.getCustomChangelogTableName(factoryId);
|
||||
Liquibase liquibase = getLiquibaseForCustomProviderUpdate(connection, defaultSchema, customChangelog, jpaProvider.getClass().getClassLoader(), changelogTableName);
|
||||
if (validateChangeSet(liquibase, liquibase.getChangeLogFile()) != Status.VALID) {
|
||||
return Status.OUTDATED;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (LiquibaseException e) {
|
||||
throw new RuntimeException("Failed to validate database", e);
|
||||
}
|
||||
|
||||
return Status.VALID;
|
||||
}
|
||||
|
||||
protected Status validateChangeSet(Liquibase liquibase, String changelog) throws LiquibaseException {
|
||||
final Status result;
|
||||
List<ChangeSet> changeSets = getLiquibaseUnrunChangeSets(liquibase);
|
||||
|
||||
if (!changeSets.isEmpty()) {
|
||||
if (changeSets.size() == liquibase.getDatabaseChangeLog().getChangeSets().size()) {
|
||||
result = Status.EMPTY;
|
||||
} else {
|
||||
logger.debugf("Validation failed. Database is not up-to-date for changelog %s", changelog);
|
||||
result = Status.OUTDATED;
|
||||
}
|
||||
} else {
|
||||
logger.debugf("Validation passed. Database is up-to-date for changelog %s", changelog);
|
||||
result = Status.VALID;
|
||||
}
|
||||
|
||||
// Needs to restart liquibase services to clear ChangeLogHistoryServiceFactory.getInstance().
|
||||
// See https://issues.jboss.org/browse/KEYCLOAK-3769 for discussion relevant to why reset needs to be here
|
||||
resetLiquibaseServices(liquibase);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void resetLiquibaseServices(Liquibase liquibase) {
|
||||
Method resetServices = Reflections.findDeclaredMethod(Liquibase.class, "resetServices");
|
||||
Reflections.invokeMethod(true, resetServices, liquibase);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<ChangeSet> getLiquibaseUnrunChangeSets(Liquibase liquibase) {
|
||||
// we don't need to fetch change sets if they were previously obtained
|
||||
return changeSets.computeIfAbsent(liquibase.getChangeLogFile(), new Function<String, List<ChangeSet>>() {
|
||||
@Override
|
||||
public List<ChangeSet> apply(String s) {
|
||||
// TODO tracked as: https://issues.jboss.org/browse/KEYCLOAK-3730
|
||||
// TODO: When https://liquibase.jira.com/browse/CORE-2919 is resolved, replace the following two lines with:
|
||||
// List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null, new LabelExpression(), false);
|
||||
Method listUnrunChangeSets = Reflections.findDeclaredMethod(Liquibase.class, "listUnrunChangeSets", Contexts.class, LabelExpression.class, boolean.class);
|
||||
|
||||
return Reflections
|
||||
.invokeMethod(true, listUnrunChangeSets, List.class, liquibase, (Contexts) null, new LabelExpression(), false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Liquibase getLiquibaseForKeycloakUpdate(Connection connection, String defaultSchema) throws LiquibaseException {
|
||||
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
|
||||
return liquibaseProvider.getLiquibase(connection, defaultSchema);
|
||||
}
|
||||
|
||||
private Liquibase getLiquibaseForCustomProviderUpdate(Connection connection, String defaultSchema, String changelogLocation, ClassLoader classloader, String changelogTableName) throws LiquibaseException {
|
||||
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
|
||||
return liquibaseProvider.getLiquibaseForCustomUpdate(connection, defaultSchema, changelogLocation, classloader, changelogTableName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
changeSets.clear();
|
||||
changeSets = null;
|
||||
}
|
||||
|
||||
public static String getTable(String table, String defaultSchema) {
|
||||
return defaultSchema != null ? defaultSchema + "." + table : table;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.liquibase;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProviderFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class QuarkusJpaUpdaterProviderFactory implements JpaUpdaterProviderFactory {
|
||||
|
||||
@Override
|
||||
public JpaUpdaterProvider create(KeycloakSession session) {
|
||||
return new QuarkusJpaUpdaterProvider(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "liquibase";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* 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.liquibase;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.sql.Connection;
|
||||
|
||||
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;
|
||||
import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
|
||||
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.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.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;
|
||||
import liquibase.resource.ClassLoaderResourceAccessor;
|
||||
import liquibase.resource.ResourceAccessor;
|
||||
import liquibase.servicelocator.ServiceLocator;
|
||||
|
||||
public class QuarkusLiquibaseConnectionProvider implements LiquibaseConnectionProviderFactory, LiquibaseConnectionProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(QuarkusLiquibaseConnectionProvider.class);
|
||||
|
||||
private volatile boolean initialized = false;
|
||||
private ClassLoaderResourceAccessor resourceAccessor;
|
||||
|
||||
@Override
|
||||
public LiquibaseConnectionProvider create(KeycloakSession session) {
|
||||
if (!initialized) {
|
||||
synchronized (this) {
|
||||
if (!initialized) {
|
||||
baseLiquibaseInitialization(session);
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
protected void baseLiquibaseInitialization(KeycloakSession session) {
|
||||
LogFactory.setInstance(new LogFactory() {
|
||||
KeycloakLogger logger = new KeycloakLogger();
|
||||
|
||||
@Override
|
||||
public liquibase.logging.Logger getLog(String name) {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public liquibase.logging.Logger getLog() {
|
||||
return logger;
|
||||
}
|
||||
});
|
||||
|
||||
resourceAccessor = new ClassLoaderResourceAccessor(getClass().getClassLoader());
|
||||
FastServiceLocator locator = new FastServiceLocator();
|
||||
ServiceLocator.setInstance(locator);
|
||||
|
||||
JpaConnectionProviderFactory jpaConnectionProvider = (JpaConnectionProviderFactory) session
|
||||
.getKeycloakSessionFactory().getProviderFactory(JpaConnectionProvider.class);
|
||||
|
||||
// registers only the database we are using
|
||||
try (Connection connection = jpaConnectionProvider.getConnection()) {
|
||||
Database database = DatabaseFactory.getInstance()
|
||||
.findCorrectDatabaseImplementation(new JdbcConnection(connection));
|
||||
DatabaseFactory.getInstance().clearRegistry();
|
||||
if (database.getDatabaseProductName().equals(PostgresDatabase.PRODUCT_NAME)) {
|
||||
// Adding PostgresPlus support to liquibase
|
||||
DatabaseFactory.getInstance().register(new PostgresPlusDatabase());
|
||||
} else if (database.getDatabaseProductName().equals(MySQLDatabase.PRODUCT_NAME)) {
|
||||
// Adding newer version of MySQL/MariaDB support to liquibase
|
||||
DatabaseFactory.getInstance().register(new UpdatedMySqlDatabase());
|
||||
// Adding CustomVarcharType for MySQL 8 and newer
|
||||
DataTypeFactory.getInstance().register(MySQL8VarcharType.class);
|
||||
} else if (database.getDatabaseProductName().equals(MariaDBDatabase.PRODUCT_NAME)) {
|
||||
DatabaseFactory.getInstance().register(new UpdatedMariaDBDatabase());
|
||||
// Adding CustomVarcharType for MySQL 8 and newer
|
||||
DataTypeFactory.getInstance().register(MySQL8VarcharType.class);
|
||||
} else {
|
||||
DatabaseFactory.getInstance().register(database);
|
||||
}
|
||||
|
||||
locator.register(database.getClass());
|
||||
|
||||
// disables XML validation
|
||||
for (ChangeLogParser parser : ChangeLogParserFactory.getInstance().getParsers()) {
|
||||
if (parser instanceof XMLChangeLogSAXParser) {
|
||||
Method getSaxParserFactory = null;
|
||||
try {
|
||||
getSaxParserFactory = XMLChangeLogSAXParser.class.getDeclaredMethod("getSaxParserFactory");
|
||||
getSaxParserFactory.setAccessible(true);
|
||||
SAXParserFactory saxParserFactory = (SAXParserFactory) getSaxParserFactory.invoke(parser);
|
||||
saxParserFactory.setValidating(false);
|
||||
} catch (Exception e) {
|
||||
logger.warnf("Failed to disable liquibase XML validations");
|
||||
} finally {
|
||||
if (getSaxParserFactory != null) {
|
||||
getSaxParserFactory.setAccessible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception cause) {
|
||||
throw new RuntimeException("Failed to configure Liquibase database", cause);
|
||||
}
|
||||
}
|
||||
|
||||
@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 = QuarkusJpaUpdaterProvider.CHANGELOG;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
0
quarkus/extensions/src/main/resources/META-INF/beans.xml
Normal file
0
quarkus/extensions/src/main/resources/META-INF/beans.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
org.keycloak.connections.liquibase.QuarkusJpaUpdaterProviderFactory
|
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
org.keycloak.connections.liquibase.QuarkusLiquibaseConnectionProvider
|
|
@ -31,10 +31,11 @@
|
|||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<quarkus.version>999-SNAPSHOT</quarkus.version>
|
||||
<quarkus.version>1.4.2.Final</quarkus.version>
|
||||
<resteasy.version>4.4.2.Final</resteasy.version>
|
||||
<jackson.version>2.10.2</jackson.version>
|
||||
<jackson.databind.version>${jackson.version}</jackson.databind.version>
|
||||
<hibernate.version>5.4.14.Final</hibernate.version>
|
||||
|
||||
<surefire-plugin.version>2.22.0</surefire-plugin.version>
|
||||
|
||||
|
|
|
@ -23,6 +23,18 @@
|
|||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-resteasy-jackson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-hibernate-orm</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-agroal</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.persistence</groupId>
|
||||
<artifactId>jakarta.persistence-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-jdbc-h2</artifactId>
|
||||
|
@ -35,6 +47,10 @@
|
|||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-jdbc-mariadb</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Keycloak -->
|
||||
<dependency>
|
||||
|
@ -261,17 +277,6 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!--TODO Should come from Hibernate extension -->
|
||||
<dependency>
|
||||
<groupId>jakarta.persistence</groupId>
|
||||
<artifactId>jakarta.persistence-api</artifactId>
|
||||
|
@ -279,6 +284,7 @@
|
|||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
<version>${hibernate.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.keycloak.services.resources;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
|
||||
|
@ -9,6 +11,9 @@ import javax.ws.rs.Path;
|
|||
@Path("/dummy")
|
||||
public class Dummy {
|
||||
|
||||
@Inject
|
||||
EntityManagerFactory entityManagerFactory;
|
||||
|
||||
// ...and doesn't load Resteasy providers unless there is at least one resource method
|
||||
@GET
|
||||
public String hello() {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
# Datasource
|
||||
datasource.url = jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
|
||||
datasource.driver = org.h2.Driver
|
||||
datasource.username = sa
|
||||
datasource.password = keycloak
|
||||
|
||||
hostname.default.frontendUrl = ${keycloak.frontendUrl:}
|
||||
|
||||
# Datasource
|
||||
datasource.driver=org.h2.jdbcx.JdbcDataSource
|
||||
datasource.url = jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
|
||||
#datasource.url = jdbc:h2:file:/tmp/keycloak-server-quarkus
|
||||
datasource.username = sa
|
||||
datasource.password = keycloak
|
||||
datasource.jdbc.transactions=xa
|
93
quarkus/server/src/main/resources/META-INF/persistence.xml
Normal file
93
quarkus/server/src/main/resources/META-INF/persistence.xml
Normal file
|
@ -0,0 +1,93 @@
|
|||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
|
||||
version="1.0">
|
||||
<persistence-unit name="keycloak-default" transaction-type="JTA">
|
||||
<class>org.keycloak.models.jpa.entities.ClientEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientAttributeEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.CredentialEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.RealmAttributeEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ComponentConfigEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ComponentEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserFederationProviderEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserFederationMapperEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.RoleEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.RoleAttributeEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.FederatedIdentityEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.MigrationModelEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserRequiredActionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserAttributeEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.IdentityProviderEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.IdentityProviderMapperEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ProtocolMapperEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserConsentEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserConsentClientScopeEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.AuthenticationFlowEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.AuthenticationExecutionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.AuthenticatorConfigEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.RequiredActionProviderEntity</class>
|
||||
<class>org.keycloak.models.jpa.session.PersistentUserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.session.PersistentClientSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.GroupEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.GroupAttributeEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.GroupRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserGroupMembershipEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientScopeEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientScopeAttributeEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientScopeRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientScopeClientMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.DefaultClientScopeRealmMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientInitialAccessEntity</class>
|
||||
|
||||
<!-- JpaAuditProviders -->
|
||||
<class>org.keycloak.events.jpa.EventEntity</class>
|
||||
<class>org.keycloak.events.jpa.AdminEventEntity</class>
|
||||
|
||||
<!-- Authorization -->
|
||||
<class>org.keycloak.authorization.jpa.entities.ResourceServerEntity</class>
|
||||
<class>org.keycloak.authorization.jpa.entities.ResourceEntity</class>
|
||||
<class>org.keycloak.authorization.jpa.entities.ScopeEntity</class>
|
||||
<class>org.keycloak.authorization.jpa.entities.PolicyEntity</class>
|
||||
<class>org.keycloak.authorization.jpa.entities.PermissionTicketEntity</class>
|
||||
<class>org.keycloak.authorization.jpa.entities.ResourceAttributeEntity</class>
|
||||
|
||||
<!-- User Federation Storage -->
|
||||
<class>org.keycloak.storage.jpa.entity.BrokerLinkEntity</class>
|
||||
<class>org.keycloak.storage.jpa.entity.FederatedUser</class>
|
||||
<class>org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity</class>
|
||||
<class>org.keycloak.storage.jpa.entity.FederatedUserConsentEntity</class>
|
||||
<class>org.keycloak.storage.jpa.entity.FederatedUserConsentClientScopeEntity</class>
|
||||
<class>org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity</class>
|
||||
<class>org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity</class>
|
||||
<class>org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity</class>
|
||||
<class>org.keycloak.storage.jpa.entity.FederatedUserRoleMappingEntity</class>
|
||||
|
||||
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
||||
|
||||
<properties>
|
||||
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
|
||||
<property name="hibernate.query.startup_check" value="false"/>
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
</persistence>
|
|
@ -1,6 +1,4 @@
|
|||
#quarkus.log.level = DEBUG
|
||||
|
||||
quarkus.application.name=Keycloak
|
||||
quarkus.datasource.driver=org.h2.Driver
|
||||
|
||||
resteasy.disable.html.sanitizer = true
|
|
@ -100,8 +100,6 @@ public class KeycloakApplication extends Application {
|
|||
|
||||
loadConfig();
|
||||
|
||||
this.sessionFactory = createSessionFactory();
|
||||
|
||||
Resteasy.pushDefaultContextObject(KeycloakApplication.class, this);
|
||||
Resteasy.pushContext(KeycloakApplication.class, this); // for injection
|
||||
|
||||
|
@ -128,6 +126,7 @@ public class KeycloakApplication extends Application {
|
|||
}
|
||||
|
||||
protected void startup() {
|
||||
this.sessionFactory = createSessionFactory();
|
||||
|
||||
ExportImportManager[] exportImportManager = new ExportImportManager[1];
|
||||
|
||||
|
|
|
@ -28,10 +28,10 @@ public class TestPlatform implements PlatformProvider {
|
|||
|
||||
@Override
|
||||
public void onStartup(Runnable startupHook) {
|
||||
startupHook.run();
|
||||
KeycloakApplication keycloakApplication = Resteasy.getContextData(KeycloakApplication.class);
|
||||
ServletContext context = Resteasy.getContextData(ServletContext.class);
|
||||
context.setAttribute(KeycloakSessionFactory.class.getName(), keycloakApplication.getSessionFactory());
|
||||
startupHook.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -31,10 +31,10 @@ public class WildflyPlatform implements PlatformProvider {
|
|||
|
||||
@Override
|
||||
public void onStartup(Runnable startupHook) {
|
||||
startupHook.run();
|
||||
KeycloakApplication keycloakApplication = Resteasy.getContextData(KeycloakApplication.class);
|
||||
ServletContext context = Resteasy.getContextData(ServletContext.class);
|
||||
context.setAttribute(KeycloakSessionFactory.class.getName(), keycloakApplication.getSessionFactory());
|
||||
startupHook.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in a new issue