mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 23:12:06 -03:30
Improve handling of datasource name specified in persistence.xml files (#41194)
Closes #41192 Signed-off-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
parent
7ea7c2dcc4
commit
8d77dfaf72
@ -53,6 +53,8 @@ import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
|
||||
import io.quarkus.vertx.http.deployment.RouteBuildItem;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.PersistenceUnitTransactionType;
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
import org.hibernate.cfg.JdbcSettings;
|
||||
import org.eclipse.microprofile.health.Readiness;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
|
||||
@ -154,6 +156,7 @@ import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.jar.JarEntry;
|
||||
@ -366,6 +369,47 @@ class KeycloakProcessor {
|
||||
recorder.setDefaultUserProfileConfiguration(configuration.getDefaultConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get datasource name obtained from the persistence.xml file based on this order:
|
||||
* <ol>
|
||||
* <li> return {@link JdbcSettings#JAKARTA_JTA_DATASOURCE} if specified
|
||||
* <li> return {@link AvailableSettings#DATASOURCE} property if specified
|
||||
* <li> return persistence unit name
|
||||
* </ol>
|
||||
* Can be removed after removing support for persistence.xml files
|
||||
*/
|
||||
static String getDatasourceNameFromPersistenceXml(PersistenceUnitDescriptor descriptor) {
|
||||
if (descriptor == null) {
|
||||
throw new IllegalStateException("Descriptor cannot be null");
|
||||
}
|
||||
final BiConsumer<String, String> infoAboutUsedSourceForDsName = (source, name) -> logger.debugf(
|
||||
"Datasource name '%s' is obtained from the '%s' configuration property in persistence.xml file. " +
|
||||
"Use '%s' name for datasource options like 'db-kind-%s'.", name, source, name, name);
|
||||
|
||||
String persistenceUnitName = descriptor.getName();
|
||||
Properties properties = descriptor.getProperties();
|
||||
|
||||
// 1. return Jakarta properties
|
||||
var jakartaProperty = properties.getProperty(JdbcSettings.JAKARTA_JTA_DATASOURCE);
|
||||
if (jakartaProperty != null) {
|
||||
infoAboutUsedSourceForDsName.accept(JdbcSettings.JAKARTA_JTA_DATASOURCE, jakartaProperty);
|
||||
return jakartaProperty;
|
||||
}
|
||||
|
||||
// 2. return deprecated Hibernate property
|
||||
var deprecatedHibernateProperty = properties.getProperty(AvailableSettings.DATASOURCE);
|
||||
if (deprecatedHibernateProperty != null) {
|
||||
logger.warnf("Property '%s' is deprecated for some time and you should rather use '%s' property for datasource name in persistence.xml file.",
|
||||
AvailableSettings.DATASOURCE, JdbcSettings.JAKARTA_JTA_DATASOURCE);
|
||||
infoAboutUsedSourceForDsName.accept(AvailableSettings.DATASOURCE, deprecatedHibernateProperty);
|
||||
return deprecatedHibernateProperty;
|
||||
}
|
||||
|
||||
// 3. return persistence unit name
|
||||
infoAboutUsedSourceForDsName.accept("Persistence unit name", persistenceUnitName);
|
||||
return persistenceUnitName;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Configures the persistence unit for Quarkus.
|
||||
*
|
||||
@ -398,10 +442,11 @@ class KeycloakProcessor {
|
||||
runtimeConfigured.produce(new HibernateOrmIntegrationRuntimeConfiguredBuildItem("keycloak", defaultUnitDescriptor.getName())
|
||||
.setInitListener(recorder.createDefaultUnitListener()));
|
||||
} else {
|
||||
Properties properties = descriptor.getProperties();
|
||||
String datasourceName = getDatasourceNameFromPersistenceXml(descriptor);
|
||||
configurePersistenceUnitProperties(datasourceName, descriptor);
|
||||
// register a listener for customizing the unit configuration at runtime
|
||||
runtimeConfigured.produce(new HibernateOrmIntegrationRuntimeConfiguredBuildItem("keycloak", descriptor.getName())
|
||||
.setInitListener(recorder.createUserDefinedUnitListener(properties.getProperty(AvailableSettings.DATASOURCE))));
|
||||
.setInitListener(recorder.createUserDefinedUnitListener(datasourceName)));
|
||||
userManagedEntities.addAll(descriptor.getManagedClassNames());
|
||||
}
|
||||
}
|
||||
@ -427,6 +472,25 @@ class KeycloakProcessor {
|
||||
producer.produce(new PersistenceXmlDescriptorBuildItem(descriptor));
|
||||
}
|
||||
|
||||
static void configurePersistenceUnitProperties(String datasourceName, ParsedPersistenceXmlDescriptor descriptor) {
|
||||
Properties unitProperties = descriptor.getProperties();
|
||||
var isResourceLocalSpecified = PersistenceUnitTransactionType.RESOURCE_LOCAL.equals(descriptor.getPersistenceUnitTransactionType()) ||
|
||||
Optional.ofNullable(unitProperties.getProperty(AvailableSettings.JAKARTA_TRANSACTION_TYPE))
|
||||
.map(f -> f.equalsIgnoreCase(PersistenceUnitTransactionType.RESOURCE_LOCAL.name()))
|
||||
.orElse(false);
|
||||
if (isResourceLocalSpecified) {
|
||||
throw new IllegalArgumentException("You need to use '%s' transaction type in your persistence.xml file."
|
||||
.formatted(PersistenceUnitTransactionType.JTA.name()));
|
||||
}
|
||||
|
||||
unitProperties.setProperty(AvailableSettings.JAKARTA_TRANSACTION_TYPE, PersistenceUnitTransactionType.JTA.name());
|
||||
descriptor.setTransactionType(PersistenceUnitTransactionType.JTA);
|
||||
|
||||
// set datasource name
|
||||
unitProperties.setProperty(JdbcSettings.JAKARTA_JTA_DATASOURCE,datasourceName);
|
||||
unitProperties.setProperty(AvailableSettings.DATASOURCE, datasourceName); // for backward compatibility
|
||||
}
|
||||
|
||||
private void configureDefaultPersistenceUnitProperties(ParsedPersistenceXmlDescriptor descriptor, HibernateOrmConfig config,
|
||||
JdbcDataSourceBuildItem defaultDataSource) {
|
||||
if (defaultDataSource == null || !defaultDataSource.isDefault()) {
|
||||
@ -566,7 +630,7 @@ class KeycloakProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the custom {@link org.eclipse.microprofile.config.spi.ConfigSource} implementations.
|
||||
* Register the custom {@link ConfigSource} implementations.
|
||||
*
|
||||
* @param configSources
|
||||
*/
|
||||
|
||||
@ -0,0 +1,200 @@
|
||||
package org.keycloak.quarkus.deployment;
|
||||
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.cfg.JdbcSettings;
|
||||
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
|
||||
import org.hibernate.jpa.boot.spi.PersistenceXmlParser;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.keycloak.quarkus.deployment.KeycloakProcessor.configurePersistenceUnitProperties;
|
||||
import static org.keycloak.quarkus.deployment.KeycloakProcessor.getDatasourceNameFromPersistenceXml;
|
||||
import static org.wildfly.common.Assert.assertNotNull;
|
||||
|
||||
public class PersistenceXmlDatasourcesTest {
|
||||
private static final String PERSISTENCE_XML_BODY = """
|
||||
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
|
||||
version="3.0">
|
||||
|
||||
%s
|
||||
|
||||
</persistence>
|
||||
""";
|
||||
|
||||
private static PersistenceXmlParser parser;
|
||||
|
||||
@BeforeAll
|
||||
public static void setupParser() {
|
||||
parser = PersistenceXmlParser.create();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void datasourceNamesOrder() throws IOException {
|
||||
// use Jakarta property
|
||||
var content = """
|
||||
<persistence-unit name="user-store-pu" transaction-type="JTA">
|
||||
<properties>
|
||||
<property name="jakarta.persistence.jtaDataSource" value="user-store" />
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
""";
|
||||
assertUsedName(content, "user-store");
|
||||
|
||||
// use Hibernate property
|
||||
content = """
|
||||
<persistence-unit name="user-store-pu" transaction-type="JTA">
|
||||
<properties>
|
||||
<property name="hibernate.connection.datasource" value="my-store" />
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
""";
|
||||
assertUsedName(content, "my-store");
|
||||
|
||||
// use persistence unit name
|
||||
content = """
|
||||
<persistence-unit name="user-store-pu" transaction-type="JTA">
|
||||
</persistence-unit>
|
||||
""";
|
||||
assertUsedName(content, "user-store-pu");
|
||||
|
||||
// prefer Jakarta property
|
||||
content = """
|
||||
<persistence-unit name="user-store-pu" transaction-type="JTA">
|
||||
<properties>
|
||||
<property name="jakarta.persistence.jtaDataSource" value="user-store" />
|
||||
<property name="hibernate.connection.datasource" value="my-store" />
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
""";
|
||||
assertUsedName(content, "user-store");
|
||||
|
||||
// prefer Hibernate property as not accepting nonJta datasource
|
||||
content = """
|
||||
<persistence-unit name="user-store-pu" transaction-type="JTA">
|
||||
<properties>
|
||||
<property name="jakarta.persistence.nonJtaDataSource" value="user-store" />
|
||||
<property name="hibernate.connection.datasource" value="my-store" />
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
""";
|
||||
assertUsedName(content, "my-store");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transactionTypes() throws IOException {
|
||||
// not specified transaction-type -> error
|
||||
var content = """
|
||||
<persistence-unit name="user-store-pu">
|
||||
<properties>
|
||||
<property name="jakarta.persistence.jtaDataSource" value="user-store" />
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
""";
|
||||
assertPersistenceXmlSingleDS(content, descriptor -> {
|
||||
var exception = assertThrows(IllegalArgumentException.class, () -> configurePersistenceUnitProperties("user-store", descriptor));
|
||||
assertThat(exception.getMessage(), is("You need to use 'JTA' transaction type in your persistence.xml file."));
|
||||
});
|
||||
|
||||
// jta data source is specified, so the tx type is JTA by default -> ok
|
||||
content = """
|
||||
<persistence-unit name="user-store-pu">
|
||||
<jta-data-source>JDBC/something</jta-data-source>
|
||||
<properties>
|
||||
<property name="jakarta.persistence.jtaDataSource" value="user-store" />
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
""";
|
||||
assertPersistenceXmlSingleDS(content, descriptor -> {
|
||||
assertDoesNotThrow(() -> configurePersistenceUnitProperties("user-store", descriptor));
|
||||
});
|
||||
|
||||
// tx type is set to RESOURCE_LOCAL -> error
|
||||
content = """
|
||||
<persistence-unit name="user-store-pu" transaction-type="RESOURCE_LOCAL">
|
||||
<properties>
|
||||
<property name="jakarta.persistence.jtaDataSource" value="user-store" />
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
""";
|
||||
assertPersistenceXmlSingleDS(content, descriptor -> {
|
||||
var exception = assertThrows(IllegalArgumentException.class, () -> configurePersistenceUnitProperties("user-store", descriptor));
|
||||
assertThat(exception.getMessage(), is("You need to use 'JTA' transaction type in your persistence.xml file."));
|
||||
});
|
||||
|
||||
// Jakarta TX prop is set to RESOURCE_LOCAL -> error
|
||||
content = """
|
||||
<persistence-unit name="user-store-pu">
|
||||
<jta-data-source>JDBC/something</jta-data-source>
|
||||
<properties>
|
||||
<property name="jakarta.persistence.jtaDataSource" value="user-store" />
|
||||
<property name="jakarta.persistence.transactionType" value="RESOURCE_LOCAL" />
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
""";
|
||||
assertPersistenceXmlSingleDS(content, descriptor -> {
|
||||
var exception = assertThrows(IllegalArgumentException.class, () -> configurePersistenceUnitProperties("user-store", descriptor));
|
||||
assertThat(exception.getMessage(), is("You need to use 'JTA' transaction type in your persistence.xml file."));
|
||||
});
|
||||
|
||||
// Everything is correct, we can check if the Jakarta prop is automatically set -> ok
|
||||
content = """
|
||||
<persistence-unit name="user-store-pu" transaction-type="JTA">
|
||||
<properties>
|
||||
<property name="jakarta.persistence.jtaDataSource" value="user-store" />
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
""";
|
||||
assertPersistenceXmlSingleDS(content, descriptor -> {
|
||||
configurePersistenceUnitProperties("user-store", descriptor);
|
||||
assertThat(descriptor.getProperties().getProperty(AvailableSettings.JAKARTA_TRANSACTION_TYPE), is("JTA"));
|
||||
});
|
||||
}
|
||||
|
||||
private void assertUsedName(String content, String expectedName) throws IOException {
|
||||
assertPersistenceXmlSingleDS(content, descriptor -> {
|
||||
var name = getDatasourceNameFromPersistenceXml(descriptor);
|
||||
assertThat(name, is(expectedName));
|
||||
configurePersistenceUnitProperties(name, descriptor);
|
||||
var properties = descriptor.getProperties();
|
||||
assertNotNull(properties);
|
||||
assertThat(properties.getProperty(JdbcSettings.JAKARTA_JTA_DATASOURCE), is(expectedName));
|
||||
assertThat(properties.getProperty(AvailableSettings.DATASOURCE), is(expectedName));
|
||||
});
|
||||
}
|
||||
|
||||
private void assertPersistenceXmlSingleDS(String content, Consumer<ParsedPersistenceXmlDescriptor> asserts) throws IOException {
|
||||
assertPersistenceXml(content, descriptors -> {
|
||||
assertNotNull(descriptors);
|
||||
assertThat(descriptors.size(), is(1));
|
||||
var descriptor = descriptors.get(0);
|
||||
assertNotNull(descriptor);
|
||||
asserts.accept(descriptor);
|
||||
});
|
||||
}
|
||||
|
||||
private void assertPersistenceXml(String content, Consumer<List<ParsedPersistenceXmlDescriptor>> asserts) throws IOException {
|
||||
String finalPersistenceXmlFileContent = PERSISTENCE_XML_BODY.formatted(content);
|
||||
Path persistenceXmlFile = null;
|
||||
try {
|
||||
persistenceXmlFile = Files.createTempFile("persistence", ".xml");
|
||||
Files.writeString(persistenceXmlFile, finalPersistenceXmlFileContent);
|
||||
asserts.accept(parser.parse(List.of(persistenceXmlFile.toUri().toURL())).values().stream().map(f -> (ParsedPersistenceXmlDescriptor) f).toList());
|
||||
} finally {
|
||||
if (persistenceXmlFile != null) {
|
||||
Files.deleteIfExists(persistenceXmlFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -25,8 +25,7 @@
|
||||
<properties>
|
||||
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
|
||||
<!-- Sets the name of the datasource to be the same as the datasource name in quarkus.properties-->
|
||||
<property name="hibernate.connection.datasource" value="user-store" />
|
||||
<property name="jakarta.persistence.transactionType" value="JTA" />
|
||||
<property name="jakarta.persistence.jtaDataSource" value="user-store" />
|
||||
<property name="hibernate.hbm2ddl.auto" value="update" />
|
||||
<property name="hibernate.show_sql" value="false" />
|
||||
</properties>
|
||||
|
||||
@ -34,8 +34,10 @@ public class CustomJpaEntityProviderDistTest {
|
||||
|
||||
@Test
|
||||
@TestProvider(CustomJpaEntityProvider.class)
|
||||
@Launch({ "start-dev", "--log-level=org.hibernate.jpa.internal.util.LogHelper:debug" })
|
||||
@Launch({ "start-dev", "--log-level=org.hibernate.jpa.internal.util.LogHelper:debug,org.keycloak.quarkus.deployment.KeycloakProcessor:debug" })
|
||||
void testUserManagedEntityNotAddedToDefaultPU(CLIResult cliResult) {
|
||||
cliResult.assertMessage("Multiple datasources are specified: <default>, user-store");
|
||||
cliResult.assertMessage("Datasource name 'user-store' is obtained from the 'jakarta.persistence.jtaDataSource' configuration property in persistence.xml file. Use 'user-store' name for datasource options like 'db-kind-user-store'.");
|
||||
cliResult.assertStringCount("name: user-store", 1);
|
||||
cliResult.assertStringCount("com.acme.provider.legacy.jpa.entity.Realm", 1);
|
||||
cliResult.assertStartedDevMode();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user