[KEYCLOAK-19310] - MSSQL Support
This commit is contained in:
parent
1f000fe218
commit
3a7f5171ab
21 changed files with 292 additions and 93 deletions
|
@ -58,6 +58,10 @@
|
|||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-jdbc-mysql-deployment</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-jdbc-mssql-deployment</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-bootstrap-core</artifactId>
|
||||
|
|
|
@ -9,6 +9,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
|
||||
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
|
||||
import org.jboss.jandex.AnnotationInstance;
|
||||
import org.jboss.jandex.ClassInfo;
|
||||
|
@ -35,11 +36,12 @@ class LiquibaseProcessor {
|
|||
|
||||
@Record(ExecutionTime.STATIC_INIT)
|
||||
@BuildStep
|
||||
void configure(KeycloakRecorder recorder, CombinedIndexBuildItem indexBuildItem) {
|
||||
void configure(KeycloakRecorder recorder, List<JdbcDataSourceBuildItem> jdbcDataSources, CombinedIndexBuildItem indexBuildItem) {
|
||||
DotName liquibaseServiceName = DotName.createSimple(LiquibaseService.class.getName());
|
||||
Map<String, List<String>> services = new HashMap<>();
|
||||
|
||||
IndexView index = indexBuildItem.getIndex();
|
||||
JdbcDataSourceBuildItem dataSourceBuildItem = jdbcDataSources.get(0);
|
||||
String dbKind = dataSourceBuildItem.getDbKind();
|
||||
|
||||
for (Class<?> c : Arrays.asList(liquibase.diff.compare.DatabaseObjectComparator.class,
|
||||
liquibase.parser.NamespaceDetails.class,
|
||||
|
@ -61,7 +63,7 @@ class LiquibaseProcessor {
|
|||
} else {
|
||||
classes.addAll(index.getAllKnownSubclasses(DotName.createSimple(c.getName())));
|
||||
}
|
||||
filterImplementations(c, classes);
|
||||
filterImplementations(c, dbKind, classes);
|
||||
for (ClassInfo found : classes) {
|
||||
if (Modifier.isAbstract(found.flags()) ||
|
||||
Modifier.isInterface(found.flags()) ||
|
||||
|
@ -85,10 +87,10 @@ class LiquibaseProcessor {
|
|||
recorder.configureLiquibase(services);
|
||||
}
|
||||
|
||||
private void filterImplementations(Class<?> types, Set<ClassInfo> classes) {
|
||||
private void filterImplementations(Class<?> types, String dbKind, Set<ClassInfo> classes) {
|
||||
if (Database.class.equals(types)) {
|
||||
// removes unsupported databases
|
||||
classes.removeIf(classInfo -> !org.keycloak.quarkus.runtime.storage.database.Database.isSupported(classInfo.name().toString()));
|
||||
classes.removeIf(classInfo -> !org.keycloak.quarkus.runtime.storage.database.Database.isLiquibaseDatabaseSupported(classInfo.name().toString(), dbKind));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,10 @@
|
|||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-jdbc-mysql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-jdbc-mssql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-core</artifactId>
|
||||
|
|
|
@ -42,8 +42,6 @@ import liquibase.servicelocator.ServiceLocator;
|
|||
@Recorder
|
||||
public class KeycloakRecorder {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(KeycloakRecorder.class);
|
||||
|
||||
public void configureLiquibase(Map<String, List<String>> services) {
|
||||
LogFactory.setInstance(new LogFactory() {
|
||||
final KeycloakLogger logger = new KeycloakLogger();
|
||||
|
|
|
@ -52,7 +52,7 @@ public class OptionRenderer implements CommandLine.Help.IOptionRenderer {
|
|||
throw new CommandLine.PicocliException("Option[" + option + "] description should have a single line.");
|
||||
}
|
||||
|
||||
Text description = scheme.text(descriptions[0]);
|
||||
Text description = formatDescription(descriptions, option, scheme);
|
||||
|
||||
if (EMPTY_TEXT.equals(shortName)) {
|
||||
result[0] = new Text[] { longName, description };
|
||||
|
@ -63,6 +63,17 @@ public class OptionRenderer implements CommandLine.Help.IOptionRenderer {
|
|||
return result;
|
||||
}
|
||||
|
||||
private Text formatDescription(String[] descriptions, OptionSpec option, ColorScheme scheme) {
|
||||
String description = descriptions[0];
|
||||
String defaultValue = option.defaultValue();
|
||||
|
||||
if (defaultValue != null) {
|
||||
description = description + " Default: " + defaultValue + ".";
|
||||
}
|
||||
|
||||
return scheme.text(description);
|
||||
}
|
||||
|
||||
private Text createLongName(OptionSpec option, ColorScheme scheme) {
|
||||
Text name = scheme.optionText(option.longestName());
|
||||
String paramLabel = formatParamLabel(option);
|
||||
|
|
|
@ -67,6 +67,7 @@ public final class Picocli {
|
|||
|
||||
private static final String ARG_SEPARATOR = ";;";
|
||||
public static final String ARG_PREFIX = "--";
|
||||
public static final String ARG_SHORT_PREFIX = "-";
|
||||
public static final String ARG_PART_SEPARATOR = "-";
|
||||
public static final char ARG_KEY_VALUE_SEPARATOR = '=';
|
||||
public static final Pattern ARG_SPLIT = Pattern.compile(";;");
|
||||
|
@ -342,6 +343,7 @@ public final class Picocli {
|
|||
.description("Enables a group of features. Possible values are: " + String.join(",", featuresExpectedValues))
|
||||
.paramLabel("feature")
|
||||
.completionCandidates(featuresExpectedValues)
|
||||
.parameterConsumer(PropertyMapperParameterConsumer.INSTANCE)
|
||||
.type(String.class)
|
||||
.build());
|
||||
|
||||
|
@ -352,6 +354,7 @@ public final class Picocli {
|
|||
.description("Enables the " + feature.name() + " feature.")
|
||||
.paramLabel(String.join("|", expectedValues))
|
||||
.type(String.class)
|
||||
.parameterConsumer(PropertyMapperParameterConsumer.INSTANCE)
|
||||
.completionCandidates(expectedValues)
|
||||
.build());
|
||||
}
|
||||
|
@ -385,12 +388,14 @@ public final class Picocli {
|
|||
}
|
||||
|
||||
String defaultValue = mapper.getDefaultValue();
|
||||
Iterable<String> expectedValues = mapper.getExpectedValues();
|
||||
|
||||
argGroupBuilder.addArg(OptionSpec.builder(name)
|
||||
.defaultValue(defaultValue)
|
||||
.description(description + (defaultValue == null ? "" : " Default: ${DEFAULT-VALUE}."))
|
||||
.description(description)
|
||||
.paramLabel(mapper.getParamLabel())
|
||||
.completionCandidates(mapper.getExpectedValues())
|
||||
.completionCandidates(expectedValues)
|
||||
.parameterConsumer(PropertyMapperParameterConsumer.INSTANCE)
|
||||
.type(String.class)
|
||||
.build());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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.quarkus.runtime.cli;
|
||||
|
||||
import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_PREFIX;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Stack;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Model.ArgSpec;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
import picocli.CommandLine.Model.OptionSpec;
|
||||
import picocli.CommandLine.ParameterException;
|
||||
|
||||
public final class PropertyMapperParameterConsumer implements CommandLine.IParameterConsumer {
|
||||
|
||||
static final CommandLine.IParameterConsumer INSTANCE = new PropertyMapperParameterConsumer();
|
||||
|
||||
private PropertyMapperParameterConsumer() {
|
||||
// singleton
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consumeParameters(Stack<String> args, ArgSpec argSpec,
|
||||
CommandSpec commandSpec) {
|
||||
if (argSpec instanceof OptionSpec) {
|
||||
validateOption(args, argSpec, commandSpec);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateOption(Stack<String> args, ArgSpec argSpec, CommandSpec commandSpec) {
|
||||
OptionSpec option = (OptionSpec) argSpec;
|
||||
String name = String.join(", ", option.names());
|
||||
CommandLine commandLine = commandSpec.commandLine();
|
||||
|
||||
if (args.isEmpty() || !isOptionValue(args.peek())) {
|
||||
throw new ParameterException(
|
||||
commandLine, "Missing required value for option '" + name + "' (" + argSpec.paramLabel() + ")." + getExpectedValuesMessage(argSpec, option));
|
||||
}
|
||||
|
||||
// consumes the value
|
||||
String value = args.pop();
|
||||
|
||||
if (!args.isEmpty() && isOptionValue(args.peek())) {
|
||||
throw new ParameterException(
|
||||
commandLine, "Option '" + name + "' expects a single value (" + argSpec.paramLabel() + ")" + getExpectedValuesMessage(argSpec, option));
|
||||
}
|
||||
|
||||
if (isExpectedValue(option, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ParameterException(
|
||||
commandLine, "Invalid value for option '" + name + "': " + value + "." + getExpectedValuesMessage(argSpec, option));
|
||||
}
|
||||
|
||||
private boolean isOptionValue(String arg) {
|
||||
return !(arg.startsWith(ARG_PREFIX) || arg.startsWith(Picocli.ARG_SHORT_PREFIX));
|
||||
}
|
||||
|
||||
private String getExpectedValuesMessage(ArgSpec argSpec, OptionSpec option) {
|
||||
return option.completionCandidates().iterator().hasNext() ? " Expected values are: " + String.join(", ", argSpec.completionCandidates()) : "";
|
||||
}
|
||||
|
||||
private boolean isExpectedValue(OptionSpec option, String value) {
|
||||
Iterator<String> expectedValues = option.completionCandidates().iterator();
|
||||
|
||||
if (!expectedValues.hasNext()) {
|
||||
// accept any
|
||||
return true;
|
||||
}
|
||||
|
||||
while (expectedValues.hasNext()) {
|
||||
String expectedValue = expectedValues.next();
|
||||
|
||||
if (expectedValue.equals(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -19,8 +19,6 @@ package org.keycloak.quarkus.runtime.cli.command;
|
|||
|
||||
import static org.keycloak.quarkus.runtime.cli.Picocli.NO_PARAM_LABEL;
|
||||
|
||||
import org.keycloak.quarkus.runtime.cli.Picocli;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvi
|
|||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ final class ClusteringPropertyMappers {
|
|||
"Value 'default' enables clustering for infinispan caches.")
|
||||
.paramLabel("mode")
|
||||
.isBuildTimeProperty(true)
|
||||
.expectedValues(Arrays.asList("local", "default"))
|
||||
.build(),
|
||||
builder().from("cluster-stack")
|
||||
.to("kc.spi.connections-infinispan.quarkus.stack")
|
||||
|
|
|
@ -3,16 +3,15 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
|
|||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||
import org.keycloak.quarkus.runtime.storage.database.Database;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.keycloak.quarkus.runtime.configuration.mappers.Messages.invalidDatabaseVendor;
|
||||
import static org.keycloak.quarkus.runtime.integration.QuarkusPlatform.addInitializationException;
|
||||
|
||||
final class DatabasePropertyMappers {
|
||||
|
||||
private static final String[] supportedDatabaseVendors = {"h2-file", "h2-mem", "mariadb", "mysql", "postgres", "postgres-95", "postgres-10"};
|
||||
|
||||
private DatabasePropertyMappers(){}
|
||||
|
||||
public static PropertyMapper[] getDatabasePropertyMappers() {
|
||||
|
@ -29,10 +28,10 @@ final class DatabasePropertyMappers {
|
|||
builder().from("db").
|
||||
to("quarkus.datasource.db-kind")
|
||||
.isBuildTimeProperty(true)
|
||||
.transformer(getSupportedDbValue())
|
||||
.description("The database vendor. Possible values are: " + String.join(",", supportedDatabaseVendors))
|
||||
.transformer(toDatabaseKind())
|
||||
.description("The database vendor. Possible values are: " + String.join(", ", Database.getAliases()))
|
||||
.paramLabel("vendor")
|
||||
.expectedValues(Arrays.asList(supportedDatabaseVendors))
|
||||
.expectedValues(asList(Database.getAliases()))
|
||||
.build(),
|
||||
builder().from("db")
|
||||
.to("quarkus.datasource.jdbc.transactions")
|
||||
|
@ -83,13 +82,17 @@ final class DatabasePropertyMappers {
|
|||
};
|
||||
}
|
||||
|
||||
private static BiFunction<String, ConfigSourceInterceptorContext, String> getSupportedDbValue() {
|
||||
private static BiFunction<String, ConfigSourceInterceptorContext, String> toDatabaseKind() {
|
||||
return (db, context) -> {
|
||||
if (Database.isSupported(db)) {
|
||||
return Database.getDatabaseKind(db).orElse(db);
|
||||
Optional<String> databaseKind = Database.getDatabaseKind(db);
|
||||
|
||||
if (databaseKind.isPresent()) {
|
||||
return databaseKind.get();
|
||||
}
|
||||
addInitializationException(invalidDatabaseVendor(db, "h2-file", "h2-mem", "mariadb", "mysql", "postgres", "postgres-95", "postgres-10"));
|
||||
return "h2";
|
||||
|
||||
addInitializationException(invalidDatabaseVendor(db, Database.getAliases()));
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -32,8 +32,6 @@ import org.keycloak.services.resources.admin.permissions.AdminPermissions;
|
|||
|
||||
public final class QuarkusKeycloakSessionFactory extends DefaultKeycloakSessionFactory {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(QuarkusKeycloakSessionFactory.class);
|
||||
|
||||
public static QuarkusKeycloakSessionFactory getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new QuarkusKeycloakSessionFactory();
|
||||
|
|
|
@ -46,7 +46,7 @@ public class QuarkusRequestFilter extends AbstractRequestFilter implements Handl
|
|||
context.vertx().executeBlocking(promise -> {
|
||||
ClientConnection connection = createClientConnection(context.request());
|
||||
|
||||
filter(connection, (session) -> {
|
||||
filter(connection, session -> {
|
||||
try {
|
||||
configureContextualData(context, connection, session);
|
||||
|
||||
|
|
|
@ -17,34 +17,35 @@
|
|||
|
||||
package org.keycloak.quarkus.runtime.storage.database;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import liquibase.database.core.H2Database;
|
||||
import liquibase.database.core.PostgresDatabase;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.UpdatedMariaDBDatabase;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.UpdatedMySqlDatabase;
|
||||
|
||||
public final class Database {
|
||||
|
||||
private static Map<String, Vendor> DATABASES = new HashMap<>();
|
||||
private static final Map<String, Vendor> DATABASES = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (Vendor vendor : Vendor.values()) {
|
||||
DATABASES.put(vendor.name().toLowerCase(), vendor);
|
||||
|
||||
for (String alias : vendor.aliases) {
|
||||
DATABASES.put(alias, vendor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSupported(String alias) {
|
||||
return DATABASES.containsKey(alias);
|
||||
public static boolean isLiquibaseDatabaseSupported(String databaseType, String dbKind) {
|
||||
for (Vendor vendor : DATABASES.values()) {
|
||||
if (vendor.liquibaseTypes.contains(databaseType) && vendor.isOfKind(dbKind)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Optional<String> getDatabaseKind(String alias) {
|
||||
|
@ -76,74 +77,120 @@ public final class Database {
|
|||
|
||||
return Optional.of(vendor.driver);
|
||||
}
|
||||
|
||||
|
||||
public static Optional<String> getDialect(String alias) {
|
||||
Vendor vendor = DATABASES.get(alias);
|
||||
|
||||
|
||||
if (vendor == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
return Optional.of(vendor.dialect.apply(alias));
|
||||
}
|
||||
|
||||
public static String[] getAliases() {
|
||||
return DATABASES.keySet().stream().sorted().toArray(String[]::new);
|
||||
}
|
||||
|
||||
private enum Vendor {
|
||||
H2("h2", "org.h2.jdbcx.JdbcDataSource", "io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect",
|
||||
new Function<String, String>() {
|
||||
@Override
|
||||
public String apply(String alias) {
|
||||
if ("h2-file".equalsIgnoreCase(alias)) {
|
||||
return "jdbc:h2:file:${kc.home.dir:${kc.db.url.path:~}}" + File.separator + "${kc.data.dir:data}"
|
||||
+ File.separator + "h2" + File.separator + "keycloakdb${kc.db.url.properties:;;AUTO_SERVER=TRUE}";
|
||||
H2("h2",
|
||||
"org.h2.jdbcx.JdbcDataSource",
|
||||
"io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect",
|
||||
new Function<String, String>() {
|
||||
@Override
|
||||
public String apply(String alias) {
|
||||
if ("h2-file".equalsIgnoreCase(alias)) {
|
||||
return "jdbc:h2:file:${kc.home.dir:${kc.db.url.path:~}}" + File.separator + "${kc.data.dir:data}"
|
||||
+ File.separator + "h2" + File.separator
|
||||
+ "keycloakdb${kc.db.url.properties:;;AUTO_SERVER=TRUE}";
|
||||
}
|
||||
return "jdbc:h2:mem:keycloakdb${kc.db.url.properties:}";
|
||||
}
|
||||
return "jdbc:h2:mem:keycloakdb${kc.db.url.properties:}";
|
||||
}
|
||||
}, "h2-mem", "h2-file", H2Database.class
|
||||
.getName()),
|
||||
MYSQL("mysql", "com.mysql.cj.jdbc.MysqlXADataSource", "org.hibernate.dialect.MySQL8Dialect",
|
||||
"jdbc:mysql://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}",
|
||||
UpdatedMySqlDatabase.class
|
||||
.getName()),
|
||||
MARIADB("mariadb", "org.mariadb.jdbc.MySQLDataSource", "org.hibernate.dialect.MariaDBDialect",
|
||||
"jdbc:mariadb://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}",
|
||||
UpdatedMariaDBDatabase.class
|
||||
.getName()),
|
||||
POSTGRES("postgresql", "org.postgresql.xa.PGXADataSource", new Function<String, String>() {
|
||||
@Override
|
||||
public String apply(String alias) {
|
||||
if ("postgres-95".equalsIgnoreCase(alias)) {
|
||||
return "io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL95Dialect";
|
||||
}
|
||||
return "io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect";
|
||||
}
|
||||
}, "jdbc:postgresql://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}",
|
||||
"postgres-95", "postgres-10", PostgresDatabase.class.getName(), PostgresPlusDatabase.class.getName());
|
||||
},
|
||||
asList("liquibase.database.core.H2Database"),
|
||||
"h2-mem", "h2-file"
|
||||
),
|
||||
MYSQL("mysql",
|
||||
"com.mysql.cj.jdbc.MysqlXADataSource",
|
||||
"org.hibernate.dialect.MySQL8Dialect",
|
||||
|
||||
"jdbc:mysql://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}",
|
||||
asList("org.keycloak.connections.jpa.updater.liquibase.UpdatedMySqlDatabase")
|
||||
),
|
||||
MARIADB("mariadb",
|
||||
"org.mariadb.jdbc.MySQLDataSource",
|
||||
"org.hibernate.dialect.MariaDBDialect",
|
||||
"jdbc:mariadb://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}",
|
||||
asList("org.keycloak.connections.jpa.updater.liquibase.UpdatedMariaDBDatabase")
|
||||
),
|
||||
POSTGRES("postgresql",
|
||||
"org.postgresql.xa.PGXADataSource",
|
||||
new Function<String, String>() {
|
||||
@Override
|
||||
public String apply(String alias) {
|
||||
if ("postgres-95".equalsIgnoreCase(alias)) {
|
||||
return "io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL95Dialect";
|
||||
}
|
||||
return "io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect";
|
||||
}
|
||||
},
|
||||
"jdbc:postgresql://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}",
|
||||
asList("liquibase.database.core.PostgresDatabase",
|
||||
"org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase"),
|
||||
"postgres", "postgres-95"
|
||||
),
|
||||
MSSQL("mssql",
|
||||
"com.microsoft.sqlserver.jdbc.SQLServerXADataSource",
|
||||
new Function<String, String>() {
|
||||
@Override
|
||||
public String apply(String alias) {
|
||||
if ("mssql-12".equals(alias)) {
|
||||
return "org.hibernate.dialect.SQLServer2012Dialect";
|
||||
}
|
||||
// quarkus latest/default
|
||||
return "org.hibernate.dialect.SQLServer2016Dialect";
|
||||
}
|
||||
},
|
||||
"jdbc:sqlserver://${kc.db.url.host:localhost}:1433;databaseName=${kc.db.url.database:keycloak}${kc.db.url.properties:}",
|
||||
asList("org.keycloak.quarkus.runtime.storage.database.liquibase.database.CustomMSSQLDatabase"),
|
||||
"mssql", "mssql-2012"
|
||||
);
|
||||
|
||||
final String databaseKind;
|
||||
final String driver;
|
||||
final Function<String, String> dialect;
|
||||
final Function<String, String> defaultUrl;
|
||||
final List<String> liquibaseTypes;
|
||||
final String[] aliases;
|
||||
|
||||
Vendor(String databaseKind, String driver, String dialect, String defaultUrl, String... aliases) {
|
||||
this(databaseKind, driver, (alias) -> dialect, (alias) -> defaultUrl, aliases);
|
||||
Vendor(String databaseKind, String driver, String dialect, String defaultUrl, List<String> liquibaseTypes,
|
||||
String... aliases) {
|
||||
this(databaseKind, driver, alias -> dialect, alias -> defaultUrl, liquibaseTypes, aliases);
|
||||
}
|
||||
|
||||
Vendor(String databaseKind, String driver, String dialect, Function<String, String> defaultUrl, String... aliases) {
|
||||
this(databaseKind, driver, (alias) -> dialect, defaultUrl, aliases);
|
||||
Vendor(String databaseKind, String driver, String dialect, Function<String, String> defaultUrl,
|
||||
List<String> liquibaseTypes, String... aliases) {
|
||||
this(databaseKind, driver, alias -> dialect, defaultUrl, liquibaseTypes, aliases);
|
||||
}
|
||||
|
||||
Vendor(String databaseKind, String driver, Function<String, String> dialect, String defaultUrl, String... aliases) {
|
||||
this(databaseKind, driver, dialect, (alias) -> defaultUrl, aliases);
|
||||
Vendor(String databaseKind, String driver, Function<String, String> dialect, String defaultUrl,
|
||||
List<String> liquibaseTypes, String... aliases) {
|
||||
this(databaseKind, driver, dialect, alias -> defaultUrl, liquibaseTypes, aliases);
|
||||
}
|
||||
|
||||
Vendor(String databaseKind, String driver, Function<String, String> dialect, Function<String, String> defaultUrl,
|
||||
String... aliases) {
|
||||
List<String> liquibaseTypes,
|
||||
String... aliases) {
|
||||
this.databaseKind = databaseKind;
|
||||
this.driver = driver;
|
||||
this.dialect = dialect;
|
||||
this.defaultUrl = defaultUrl;
|
||||
this.aliases = aliases;
|
||||
this.liquibaseTypes = liquibaseTypes;
|
||||
this.aliases = aliases.length == 0 ? new String[] { databaseKind } : aliases;
|
||||
}
|
||||
|
||||
public boolean isOfKind(String dbKind) {
|
||||
return databaseKind.equals(dbKind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,6 @@ import javax.transaction.Transaction;
|
|||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import io.quarkus.runtime.Quarkus;
|
||||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.SessionFactoryImpl;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
|
@ -117,7 +116,6 @@ public final class QuarkusJpaConnectionProviderFactory implements JpaConnectionP
|
|||
}
|
||||
|
||||
private void addSpecificNamedQueries(KeycloakSession session, Connection connection) {
|
||||
SessionFactoryImplementor sfi = emf.unwrap(SessionFactoryImplementor.class);
|
||||
EntityManager em = createEntityManager(session);
|
||||
|
||||
try {
|
||||
|
|
|
@ -79,6 +79,15 @@ public class FastServiceLocator extends ServiceLocator {
|
|||
getPackages().remove("liquibase.parser.core.json");
|
||||
getPackages().remove("liquibase.serializer.core.json");
|
||||
|
||||
// register only the implementations related to the chosen db
|
||||
for (String databaseImpl : services.get(Database.class.getName())) {
|
||||
try {
|
||||
register((Database) getClass().getClassLoader().loadClass(databaseImpl).getDeclaredConstructor().newInstance());
|
||||
} catch (Exception cause) {
|
||||
throw new RuntimeException("Failed to load database implementation", cause);
|
||||
}
|
||||
}
|
||||
|
||||
this.services = services;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import liquibase.logging.Logger;
|
|||
|
||||
public class KeycloakLogger implements Logger {
|
||||
|
||||
private static final org.jboss.logging.Logger logger = org.jboss.logging.Logger.getLogger(QuarkusLiquibaseConnectionProvider.class);
|
||||
private static final org.jboss.logging.Logger logger = org.jboss.logging.Logger.getLogger(KeycloakLogger.class);
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
|
|
|
@ -29,9 +29,6 @@ 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.CustomChangeLogHistoryService;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProviderFactory;
|
||||
|
@ -79,11 +76,6 @@ public class QuarkusLiquibaseConnectionProvider implements LiquibaseConnectionPr
|
|||
JpaConnectionProviderFactory jpaConnectionProvider = (JpaConnectionProviderFactory) session
|
||||
.getKeycloakSessionFactory().getProviderFactory(JpaConnectionProvider.class);
|
||||
|
||||
// register our custom databases
|
||||
locator.register(new PostgresPlusDatabase());
|
||||
locator.register(new UpdatedMySqlDatabase());
|
||||
locator.register(new UpdatedMariaDBDatabase());
|
||||
|
||||
// registers only the database we are using
|
||||
try (Connection connection = jpaConnectionProvider.getConnection()) {
|
||||
Database database = DatabaseFactory.getInstance()
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.quarkus.runtime.storage.database.liquibase.database;
|
||||
|
||||
import liquibase.database.core.MSSQLDatabase;
|
||||
|
||||
public class CustomMSSQLDatabase extends MSSQLDatabase {
|
||||
|
||||
private static String ENGINE_EDITION;
|
||||
|
||||
@Override
|
||||
public String getEngineEdition() {
|
||||
// no need to query engine edition every time
|
||||
// it should be safe to update without any synchronization code as liquibase runs from a single thread
|
||||
if (ENGINE_EDITION == null) {
|
||||
return ENGINE_EDITION = super.getEngineEdition();
|
||||
}
|
||||
return ENGINE_EDITION;
|
||||
}
|
||||
}
|
|
@ -26,8 +26,6 @@ import org.keycloak.Config;
|
|||
|
||||
public class CacheInitializer {
|
||||
|
||||
private static final Logger log = Logger.getLogger(CacheInitializer.class);
|
||||
|
||||
private final String config;
|
||||
|
||||
public CacheInitializer(String config) {
|
||||
|
|
|
@ -235,7 +235,7 @@ public class ConfigurationTest {
|
|||
|
||||
@Test
|
||||
public void testDatabaseKindProperties() {
|
||||
System.setProperty(CLI_ARGS, "--db=postgres-10" + ARG_SEPARATOR + "--db-url=jdbc:postgresql://localhost/keycloak");
|
||||
System.setProperty(CLI_ARGS, "--db=postgres" + ARG_SEPARATOR + "--db-url=jdbc:postgresql://localhost/keycloak");
|
||||
SmallRyeConfig config = createConfig();
|
||||
assertEquals("io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect",
|
||||
config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
||||
|
|
Loading…
Reference in a new issue