diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JaxbNotAvailableException.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JaxbNotAvailableException.java new file mode 100644 index 00000000000..96326437316 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JaxbNotAvailableException.java @@ -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 { + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JaxbNotAvailableExceptionFailureAnalyzer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JaxbNotAvailableExceptionFailureAnalyzer.java new file mode 100644 index 00000000000..dfb9eb9892c --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JaxbNotAvailableExceptionFailureAnalyzer.java @@ -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 { + + @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); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java index 6e0afdfa09a..aa25c2a8826 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfiguration.java @@ -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 @@ 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; 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 { @Bean @ConditionalOnMissingBean(org.jooq.Configuration.class) - public DefaultConfiguration jooqConfiguration(JooqProperties properties, ConnectionProvider connectionProvider, + DefaultConfiguration jooqConfiguration(JooqProperties properties, ConnectionProvider connectionProvider, DataSource dataSource, ObjectProvider transactionProvider, ObjectProvider executeListenerProviders, - ObjectProvider configurationCustomizers) { + ObjectProvider configurationCustomizers, + ObjectProvider 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); + } + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqProperties.java index 4a8229e4aec..a8179ea21e9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/JooqProperties.java @@ -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 { */ 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 { 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}. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SpringTransaction.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SpringTransaction.java index 8ad4e14b4a3..d23fa93e4d2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SpringTransaction.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SpringTransaction.java @@ -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; import org.springframework.transaction.TransactionStatus; /** - * Adapts a Spring transaction for JOOQ. + * Adapts a Spring transaction for jOOQ. * * @author Lukas Eder * @author Andreas Ahlenstorf diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SpringTransactionProvider.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SpringTransactionProvider.java index f5c51e60c18..6c86f03d2b6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SpringTransactionProvider.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/SpringTransactionProvider.java @@ -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; 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 diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/package-info.java index 8f455ab5bbb..d83562f1b49 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/package-info.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/package-info.java @@ -15,6 +15,6 @@ */ /** - * Auto-configuration for JOOQ. + * Auto-configuration for jOOQ. */ package org.springframework.boot.autoconfigure.jooq; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 6c4f3a005be..9eb9419cdbe 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -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,\ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java index 8e8025f3e6f..0a3513644d1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/JooqAutoConfigurationTests.java @@ -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; 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; * @author Stephane Nicoll * @author Dmytro Nosan * @author Dennis Melzer + * @author Moritz Halbritter */ 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; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/jooq/settings.xml b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/jooq/settings.xml new file mode 100644 index 00000000000..ee57678ae40 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/jooq/settings.xml @@ -0,0 +1,4 @@ + + + 100 + diff --git a/src/nohttp/allowlist.lines b/src/nohttp/allowlist.lines index 351e601bb7c..99fd3098c78 100644 --- a/src/nohttp/allowlist.lines +++ b/src/nohttp/allowlist.lines @@ -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-.*