diff --git a/docs/documentation/upgrading/topics/changes/changes-26_0_0.adoc b/docs/documentation/upgrading/topics/changes/changes-26_0_0.adoc index 11127e2597..f3f1e22cf1 100644 --- a/docs/documentation/upgrading/topics/changes/changes-26_0_0.adoc +++ b/docs/documentation/upgrading/topics/changes/changes-26_0_0.adoc @@ -190,6 +190,15 @@ Additionally, the following resources have been removed from the `common` theme: If you previously used any of the removed resources in your theme, make sure to add them to your own theme resources instead. += Additional datasources now require using XA + +{project_name} by default does not use XA datasources. However, this is considered unsafe if more than one datasource is used. +Starting with this release, you need to use XA datasources if you are adding additional datasources to {project_name}. +If the default datasource supports XA, you can do this by setting the `--transaction-xa-enabled=true` option. For additional datasources, you need to use +the `quarkus.datasource..jdbc.transactions=xa` option in your `quarkus.properties` file. +At most one datasource can be non-XA. +Recovery isn't supported when you don't have persistent storage for the transaction store. + = Hostname v1 feature removed The deprecated hostname v1 feature was removed. This feature was deprecated in {project_name} 25 and replaced by hostname v2. If you are still using this feature, you must migrate to hostname v2. For more details, see the https://www.keycloak.org/server/hostname[Configuring the hostname (v2)] and https://www.keycloak.org/docs/latest/upgrading/#new-hostname-options[the initial migration guide]. diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/CheckMultipleDatasourcesBuildStep.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/CheckMultipleDatasourcesBuildStep.java new file mode 100644 index 0000000000..9e3de599e8 --- /dev/null +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/CheckMultipleDatasourcesBuildStep.java @@ -0,0 +1,25 @@ +/* + * Copyright 2024 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.deployment; + +import io.quarkus.builder.item.EmptyBuildItem; + +/** + * @author Vaclav Muzikar + */ +public class CheckMultipleDatasourcesBuildStep extends EmptyBuildItem {} diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java index d8958d79bc..0969ea30a2 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java @@ -17,13 +17,17 @@ package org.keycloak.quarkus.deployment; +import io.quarkus.agroal.runtime.DataSourcesJdbcBuildTimeConfig; +import io.quarkus.agroal.runtime.TransactionIntegration; import io.quarkus.agroal.runtime.health.DataSourceHealthCheck; import io.quarkus.agroal.spi.JdbcDataSourceBuildItem; import io.quarkus.agroal.spi.JdbcDriverBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; import io.quarkus.arc.deployment.BuildTimeConditionBuildItem; import io.quarkus.bootstrap.logging.InitialConfigurator; +import io.quarkus.builder.item.EmptyBuildItem; import io.quarkus.datasource.deployment.spi.DevServicesDatasourceResultBuildItem; +import io.quarkus.datasource.runtime.DataSourcesBuildTimeConfig; import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -267,13 +271,33 @@ class KeycloakProcessor { // We do not want to initialize the JDBC driver class Class.forName(dbDriver.get(), false, Thread.currentThread().getContextClassLoader()); } catch (ClassNotFoundException e) { - // Ignore queued TRACE and DEBUG messages for not initialized log handlers - InitialConfigurator.DELAYED_HANDLER.setBuildTimeHandlers(new Handler[]{}); - throw new ConfigurationException(String.format("Unable to find the JDBC driver (%s). You need to install it.", dbDriver.get())); + throwConfigError(String.format("Unable to find the JDBC driver (%s). You need to install it.", dbDriver.get())); } } } + // Inspired by AgroalProcessor + @BuildStep + @Produce(CheckMultipleDatasourcesBuildStep.class) + void checkMultipleDatasourcesUseXA(DataSourcesBuildTimeConfig dataSourcesConfig, DataSourcesJdbcBuildTimeConfig jdbcConfig) { + long nonXADatasourcesCount = dataSourcesConfig.dataSources().keySet().stream() + .map(ds -> jdbcConfig.dataSources().get(ds).jdbc()) + .filter(jdbc -> jdbc.enabled() && jdbc.transactions() != TransactionIntegration.XA) + .count(); + if (nonXADatasourcesCount > 1) { + throwConfigError("Multiple datasources are configured but more than 1 is using non-XA transactions. " + + "All the datasources except one must must be XA to be able to use Last Resource Commit Optimization (LRCO). " + + "Please update your configuration by setting --transaction-xa-enabled=true " + + "and/or quarkus.datasource..jdbc.transactions=xa."); + } + } + + private void throwConfigError(String msg) { + // Ignore queued TRACE and DEBUG messages for not initialized log handlers + InitialConfigurator.DELAYED_HANDLER.setBuildTimeHandlers(new Handler[]{}); + throw new ConfigurationException(msg); + } + /** * Parse the default configuration for the User Profile provider */ @@ -344,6 +368,7 @@ class KeycloakProcessor { @BuildStep @Consume(CheckJdbcBuildStep.class) + @Consume(CheckMultipleDatasourcesBuildStep.class) void produceDefaultPersistenceUnit(BuildProducer producer) { ParsedPersistenceXmlDescriptor descriptor = PersistenceXmlParser.locateIndividualPersistenceUnit( Thread.currentThread().getContextClassLoader().getResource("default-persistence.xml")); diff --git a/quarkus/tests/integration/src/test-providers/resources/com/acme/provider/legacy/jpa/entity/quarkus.properties b/quarkus/tests/integration/src/test-providers/resources/com/acme/provider/legacy/jpa/entity/quarkus.properties index 186d221059..609b28c71d 100644 --- a/quarkus/tests/integration/src/test-providers/resources/com/acme/provider/legacy/jpa/entity/quarkus.properties +++ b/quarkus/tests/integration/src/test-providers/resources/com/acme/provider/legacy/jpa/entity/quarkus.properties @@ -17,4 +17,5 @@ quarkus.datasource.user-store.db-kind=h2 quarkus.datasource.user-store.username=sa -quarkus.datasource.user-store.jdbc.url=jdbc:h2:mem:user-store;DB_CLOSE_DELAY=-1 \ No newline at end of file +quarkus.datasource.user-store.jdbc.url=jdbc:h2:mem:user-store;DB_CLOSE_DELAY=-1 +quarkus.datasource.user-store.jdbc.transactions=xa \ No newline at end of file diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesAutoBuildDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesAutoBuildDistTest.java index 6e83bfbb24..e737498955 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesAutoBuildDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesAutoBuildDistTest.java @@ -17,7 +17,6 @@ package org.keycloak.it.cli.dist; -import static io.restassured.RestAssured.when; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -114,6 +113,15 @@ public class QuarkusPropertiesAutoBuildDistTest { cliResult.assertStarted(); } + @Test + @BeforeStartDistribution(AddNonXADatasource.class) + @Launch({ "start" }) + @Order(9) + void nonXADatasourceFailsToStart(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + cliResult.assertError("Multiple datasources are configured but more than 1 is using non-XA transactions."); + } + public static class UpdateConsoleLogLevelToWarn implements Consumer { @Override public void accept(KeycloakDistribution distribution) { @@ -135,6 +143,7 @@ public class QuarkusPropertiesAutoBuildDistTest { distribution.setQuarkusProperty("quarkus.datasource.user-store.db-kind", "h2"); distribution.setQuarkusProperty("quarkus.datasource.user-store.username","sa"); distribution.setQuarkusProperty("quarkus.datasource.user-store.jdbc.url","jdbc:h2:mem:user-store;DB_CLOSE_DELAY=-1"); + distribution.setQuarkusProperty("quarkus.datasource.user-store.jdbc.transactions", "xa"); } } @@ -145,6 +154,17 @@ public class QuarkusPropertiesAutoBuildDistTest { distribution.setQuarkusProperty("quarkus.datasource.user-store2.db-transactions", "enabled"); distribution.setQuarkusProperty("quarkus.datasource.user-store2.username","sa"); distribution.setQuarkusProperty("quarkus.datasource.user-store2.jdbc.url","jdbc:h2:mem:user-store2;DB_CLOSE_DELAY=-1"); + distribution.setQuarkusProperty("quarkus.datasource.user-store2.jdbc.transactions", "xa"); + } + } + + public static class AddNonXADatasource implements Consumer { + @Override + public void accept(KeycloakDistribution distribution) { + distribution.setQuarkusProperty("quarkus.datasource.user-store3.db-kind", "h2"); + distribution.setQuarkusProperty("quarkus.datasource.user-store3.db-transactions", "enabled"); + distribution.setQuarkusProperty("quarkus.datasource.user-store3.username","sa"); + distribution.setQuarkusProperty("quarkus.datasource.user-store3.jdbc.url","jdbc:h2:mem:user-store2;DB_CLOSE_DELAY=-1"); } } @@ -169,4 +189,4 @@ public class QuarkusPropertiesAutoBuildDistTest { distribution.setQuarkusProperty("quarkus.datasource.db-kind", "postgres"); } } -} \ No newline at end of file +}