Browse Source

Add configuration property to specify jOOQ settings file

The property is named 'spring.jooq.config' and is of type Resource,
so that it supports classpath: and all the other common prefixes.

The config is loaded through JAXB. If JAXB is not on the classpath,
an exception is thrown. Also adds a failure analyzer for this exception.

Closes gh-38778
pull/44012/head
Moritz Halbritter 1 year ago
parent
commit
cce509c7df
  1. 26
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JaxbNotAvailableException.java
  2. 36
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JaxbNotAvailableExceptionFailureAnalyzer.java
  3. 67
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java
  4. 17
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqProperties.java
  5. 4
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SpringTransaction.java
  6. 4
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SpringTransactionProvider.java
  7. 2
      spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/package-info.java
  8. 1
      spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
  9. 34
      spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java
  10. 4
      spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/jooq/settings.xml
  11. 1
      src/nohttp/allowlist.lines

26
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JaxbNotAvailableException.java

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* 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
*
* https://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.springframework.boot.autoconfigure.jooq;
/**
* Exception to be thrown if JAXB is not available.
*
* @author Moritz Halbritter
*/
class JaxbNotAvailableException extends RuntimeException {
}

36
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JaxbNotAvailableExceptionFailureAnalyzer.java

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* 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
*
* https://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.springframework.boot.autoconfigure.jooq;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.diagnostics.FailureAnalyzer;
/**
* {@link FailureAnalyzer} for {@link JaxbNotAvailableException}.
*
* @author Moritz Halbritter
*/
class JaxbNotAvailableExceptionFailureAnalyzer extends AbstractFailureAnalyzer<JaxbNotAvailableException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, JaxbNotAvailableException cause) {
return new FailureAnalysis("Unable to unmarshal jOOQ settings because JAXB is not available.",
"Add JAXB to the classpath or remove the spring.jooq.config property.", cause);
}
}

67
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,16 +16,30 @@ @@ -16,16 +16,30 @@
package org.springframework.boot.autoconfigure.jooq;
import java.io.IOException;
import java.io.InputStream;
import javax.sql.DataSource;
import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;
import org.jooq.ConnectionProvider;
import org.jooq.DSLContext;
import org.jooq.ExecuteListenerProvider;
import org.jooq.TransactionProvider;
import org.jooq.conf.Settings;
import org.jooq.impl.DataSourceConnectionProvider;
import org.jooq.impl.DefaultConfiguration;
import org.jooq.impl.DefaultDSLContext;
import org.jooq.impl.DefaultExecuteListenerProvider;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@ -33,20 +47,25 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -33,20 +47,25 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for JOOQ.
* {@link EnableAutoConfiguration Auto-configuration} for jOOQ.
*
* @author Andreas Ahlenstorf
* @author Michael Simons
* @author Dmytro Nosan
* @author Moritz Halbritter
* @since 1.3.0
*/
@AutoConfiguration(after = { DataSourceAutoConfiguration.class, TransactionAutoConfiguration.class })
@ -89,17 +108,57 @@ public class JooqAutoConfiguration { @@ -89,17 +108,57 @@ public class JooqAutoConfiguration {
@Bean
@ConditionalOnMissingBean(org.jooq.Configuration.class)
public DefaultConfiguration jooqConfiguration(JooqProperties properties, ConnectionProvider connectionProvider,
DefaultConfiguration jooqConfiguration(JooqProperties properties, ConnectionProvider connectionProvider,
DataSource dataSource, ObjectProvider<TransactionProvider> transactionProvider,
ObjectProvider<ExecuteListenerProvider> executeListenerProviders,
ObjectProvider<DefaultConfigurationCustomizer> configurationCustomizers) {
ObjectProvider<DefaultConfigurationCustomizer> configurationCustomizers,
ObjectProvider<Settings> settingsProvider) {
DefaultConfiguration configuration = new DefaultConfiguration();
configuration.set(properties.determineSqlDialect(dataSource));
configuration.set(connectionProvider);
transactionProvider.ifAvailable(configuration::set);
settingsProvider.ifAvailable(configuration::set);
configuration.set(executeListenerProviders.orderedStream().toArray(ExecuteListenerProvider[]::new));
configurationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configuration));
return configuration;
}
@Bean
@ConditionalOnProperty("spring.jooq.config")
@ConditionalOnMissingBean(Settings.class)
Settings settings(JooqProperties properties) throws IOException {
if (!ClassUtils.isPresent("jakarta.xml.bind.JAXBContext", null)) {
throw new JaxbNotAvailableException();
}
Resource resource = properties.getConfig();
Assert.state(resource.exists(),
() -> "Resource %s set in spring.jooq.config does not exist".formatted(resource));
try (InputStream stream = resource.getInputStream()) {
return new JaxbSettingsLoader().load(stream);
}
}
private static final class JaxbSettingsLoader {
private Settings load(InputStream inputStream) {
try {
// See
// https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#jaxb-unmarshaller
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
spf.setNamespaceAware(true);
spf.setXIncludeAware(false);
Source xmlSource = new SAXSource(spf.newSAXParser().getXMLReader(), new InputSource(inputStream));
JAXBContext jc = JAXBContext.newInstance(Settings.class);
Unmarshaller um = jc.createUnmarshaller();
return um.unmarshal(xmlSource, Settings.class).getValue();
}
catch (ParserConfigurationException | JAXBException | SAXException ex) {
throw new IllegalStateException("Failed to unmarshal settings", ex);
}
}
}
}

17
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqProperties.java

@ -21,12 +21,14 @@ import javax.sql.DataSource; @@ -21,12 +21,14 @@ import javax.sql.DataSource;
import org.jooq.SQLDialect;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.Resource;
/**
* Configuration properties for the JOOQ database library.
* Configuration properties for the jOOQ database library.
*
* @author Andreas Ahlenstorf
* @author Michael Simons
* @author Moritz Halbritter
* @since 1.3.0
*/
@ConfigurationProperties("spring.jooq")
@ -37,6 +39,11 @@ public class JooqProperties { @@ -37,6 +39,11 @@ public class JooqProperties {
*/
private SQLDialect sqlDialect;
/**
* Location of the jOOQ config file.
*/
private Resource config;
public SQLDialect getSqlDialect() {
return this.sqlDialect;
}
@ -45,6 +52,14 @@ public class JooqProperties { @@ -45,6 +52,14 @@ public class JooqProperties {
this.sqlDialect = sqlDialect;
}
public Resource getConfig() {
return this.config;
}
public void setConfig(Resource config) {
this.config = config;
}
/**
* Determine the {@link SQLDialect} to use based on this configuration and the primary
* {@link DataSource}.

4
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SpringTransaction.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,7 +21,7 @@ import org.jooq.Transaction; @@ -21,7 +21,7 @@ import org.jooq.Transaction;
import org.springframework.transaction.TransactionStatus;
/**
* Adapts a Spring transaction for JOOQ.
* Adapts a Spring transaction for jOOQ.
*
* @author Lukas Eder
* @author Andreas Ahlenstorf

4
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SpringTransactionProvider.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -25,7 +25,7 @@ import org.springframework.transaction.TransactionStatus; @@ -25,7 +25,7 @@ import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
/**
* Allows Spring Transaction to be used with JOOQ.
* Allows Spring Transaction to be used with jOOQ.
*
* @author Lukas Eder
* @author Andreas Ahlenstorf

2
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/package-info.java

@ -15,6 +15,6 @@ @@ -15,6 +15,6 @@
*/
/**
* Auto-configuration for JOOQ.
* Auto-configuration for jOOQ.
*/
package org.springframework.boot.autoconfigure.jooq;

1
spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

@ -27,6 +27,7 @@ org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer, @@ -27,6 +27,7 @@ org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer,
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jooq.JaxbNotAvailableExceptionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jooq.NoDslContextBeanFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.MissingR2dbcPoolDependencyFailureAnalyzer,\

34
spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java

@ -28,6 +28,7 @@ import org.jooq.SQLDialect; @@ -28,6 +28,7 @@ import org.jooq.SQLDialect;
import org.jooq.TransactionContext;
import org.jooq.TransactionProvider;
import org.jooq.TransactionalRunnable;
import org.jooq.conf.Settings;
import org.jooq.impl.DataSourceConnectionProvider;
import org.jooq.impl.DefaultDSLContext;
import org.jooq.impl.DefaultExecuteListenerProvider;
@ -35,6 +36,7 @@ import org.junit.jupiter.api.Test; @@ -35,6 +36,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -57,6 +59,7 @@ import static org.mockito.Mockito.mock; @@ -57,6 +59,7 @@ import static org.mockito.Mockito.mock;
* @author Stephane Nicoll
* @author Dmytro Nosan
* @author Dennis Melzer
* @author Moritz Halbritter
*/
class JooqAutoConfigurationTests {
@ -221,6 +224,37 @@ class JooqAutoConfigurationTests { @@ -221,6 +224,37 @@ class JooqAutoConfigurationTests {
.run((context) -> assertThat(context).hasSingleBean(DSLContext.class).hasBean("customDslContext"));
}
@Test
void shouldLoadSettingsFromConfigPropertyThroughJaxb() {
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class)
.withPropertyValues("spring.jooq.config=classpath:org/springframework/boot/autoconfigure/jooq/settings.xml")
.run((context) -> {
assertThat(context).hasSingleBean(Settings.class);
Settings settings = context.getBean(Settings.class);
assertThat(settings.getBatchSize()).isEqualTo(100);
});
}
@Test
void shouldNotProvideSettingsIfJaxbIsMissing() {
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class)
.withClassLoader(new FilteredClassLoader("jakarta.xml.bind"))
.withPropertyValues("spring.jooq.config=classpath:org/springframework/boot/autoconfigure/jooq/settings.xml")
.run((context) -> assertThat(context).hasFailed()
.getFailure()
.hasRootCauseInstanceOf(JaxbNotAvailableException.class));
}
@Test
void shouldFailWithSensibleErrorMessageIfConfigIsNotFound() {
this.contextRunner.withUserConfiguration(JooqDataSourceConfiguration.class)
.withPropertyValues("spring.jooq.config=classpath:does-not-exist.xml")
.run((context) -> assertThat(context).hasFailed()
.getFailure()
.hasMessageContaining("spring.jooq.config")
.hasMessageContaining("does-not-exist.xml"));
}
static class AssertFetch implements TransactionalRunnable {
private final DSLContext dsl;

4
spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/jooq/settings.xml

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<settings xmlns="http://www.jooq.org/xsd/jooq-runtime-3.19.0.xsd">
<batchSize>100</batchSize>
</settings>

1
src/nohttp/allowlist.lines

@ -4,3 +4,4 @@ @@ -4,3 +4,4 @@
^http://www.jdotsoft.com.*
^http://www.liquibase.org/xml/ns/dbchangelog/.*
^http://www.w3.org/2000/09/xmldsig.*
^http://www.jooq.org/xsd/jooq-runtime-.*

Loading…
Cancel
Save