diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 75e92bd158d..070a4ad44ba 100755 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -120,6 +120,11 @@ cache-api true + + javax.json.bind + javax.json.bind-api + true + io.searchbox jest @@ -724,11 +729,21 @@ json-path test + + javax.json + javax.json-api + test + mysql mysql-connector-java test + + org.apache.johnzon + johnzon-jsonb + test + org.hsqldb hsqldb diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/GsonHttpMessageConvertersConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/GsonHttpMessageConvertersConfiguration.java index b5d454834b7..dc754cd6941 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/GsonHttpMessageConvertersConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/GsonHttpMessageConvertersConfiguration.java @@ -23,6 +23,7 @@ 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.condition.NoneNestedConditions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -33,6 +34,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert * Configuration for HTTP Message converters that use Gson. * * @author Andy Wilkinson + * @author Eddú Meléndez * @since 1.2.2 */ @Configuration @@ -41,7 +43,7 @@ class GsonHttpMessageConvertersConfiguration { @Configuration @ConditionalOnBean(Gson.class) - @Conditional(PreferGsonOrMissingJacksonCondition.class) + @Conditional(PreferGsonOrJacksonAndJsonbUnavailableCondition.class) protected static class GsonHttpMessageConverterConfiguration { @Bean @@ -54,22 +56,41 @@ class GsonHttpMessageConvertersConfiguration { } - private static class PreferGsonOrMissingJacksonCondition extends AnyNestedCondition { + private static class PreferGsonOrJacksonAndJsonbUnavailableCondition + extends AnyNestedCondition { - PreferGsonOrMissingJacksonCondition() { + PreferGsonOrJacksonAndJsonbUnavailableCondition() { super(ConfigurationPhase.REGISTER_BEAN); } - @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "gson", matchIfMissing = false) + @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "gson") static class GsonPreferred { } - @ConditionalOnMissingBean(MappingJackson2HttpMessageConverter.class) + @Conditional(JacksonAndJsonbUnavailable.class) + static class JacksonJsonbUnavailable { + + } + + } + + private static class JacksonAndJsonbUnavailable extends NoneNestedConditions { + + JacksonAndJsonbUnavailable() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnBean(MappingJackson2HttpMessageConverter.class) static class JacksonMissing { } + @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "jsonb") + static class JsonbPreferred { + + } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfiguration.java index f962e7673df..7cb9ae218f5 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfiguration.java @@ -26,6 +26,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -44,12 +45,15 @@ import org.springframework.http.converter.StringHttpMessageConverter; * @author Andy Wilkinson * @author Sebastien Deleuze * @author Stephane Nicoll + * @author Eddú Meléndez */ @Configuration @ConditionalOnClass(HttpMessageConverter.class) -@AutoConfigureAfter({ GsonAutoConfiguration.class, JacksonAutoConfiguration.class }) +@AutoConfigureAfter({ GsonAutoConfiguration.class, JacksonAutoConfiguration.class, + JsonbAutoConfiguration.class }) @Import({ JacksonHttpMessageConvertersConfiguration.class, - GsonHttpMessageConvertersConfiguration.class }) + GsonHttpMessageConvertersConfiguration.class, + JsonbHttpMessageConvertersConfiguration.class }) public class HttpMessageConvertersAutoConfiguration { static final String PREFERRED_MAPPER_PROPERTY = "spring.http.converters.preferred-json-mapper"; diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/JsonbHttpMessageConvertersConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/JsonbHttpMessageConvertersConfiguration.java new file mode 100644 index 00000000000..ef88290de2a --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/JsonbHttpMessageConvertersConfiguration.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2017 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 + * + * 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.springframework.boot.autoconfigure.http; + +import javax.json.bind.Jsonb; + +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; +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.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.GsonHttpMessageConverter; +import org.springframework.http.converter.json.JsonbHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; + +/** + * Configuration for HTTP Message converters that use JSON-B. + * + * @author Eddú Meléndez + * @author 2.0.0 + */ +@Configuration +@ConditionalOnClass(Jsonb.class) +class JsonbHttpMessageConvertersConfiguration { + + @Configuration + @ConditionalOnBean(Jsonb.class) + @Conditional(PreferJsonbOrMissingJacksonAndGsonCondition.class) + protected static class JsonbHttpMessageConverterConfiguration { + + @Bean + @ConditionalOnMissingBean + public JsonbHttpMessageConverter jsonbHttpMessageConverter(Jsonb jsonb) { + JsonbHttpMessageConverter converter = new JsonbHttpMessageConverter(); + converter.setJsonb(jsonb); + return converter; + } + + } + + private static class PreferJsonbOrMissingJacksonAndGsonCondition + extends AnyNestedCondition { + + PreferJsonbOrMissingJacksonAndGsonCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "jsonb") + static class JsonbPreferred { + + } + + @ConditionalOnMissingBean({ MappingJackson2HttpMessageConverter.class, GsonHttpMessageConverter.class }) + static class JacksonAndGsonMissing { + + } + + } + + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jsonb/JsonbAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jsonb/JsonbAutoConfiguration.java new file mode 100644 index 00000000000..8b20f642552 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jsonb/JsonbAutoConfiguration.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2017 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 + * + * 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.springframework.boot.autoconfigure.jsonb; + +import javax.json.bind.Jsonb; +import javax.json.bind.JsonbBuilder; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for JSON-B. + * + * @author Eddú Meléndez + * @since 2.0.0 + */ +@Configuration +@ConditionalOnClass(Jsonb.class) +public class JsonbAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public Jsonb jsonb() { + return JsonbBuilder.create(); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jsonb/package-info.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jsonb/package-info.java new file mode 100644 index 00000000000..a812ace6ca7 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jsonb/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2017 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 + * + * 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. + */ + +/** + * Auto-configuration for JSON-B. + */ +package org.springframework.boot.autoconfigure.jsonb; diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 79de697bf99..cdc88b8e070 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -200,7 +200,7 @@ { "name": "spring.http.converters.preferred-json-mapper", "type": "java.lang.String", - "description": "Preferred JSON mapper to use for HTTP message conversion. Set to \"gson\" to force the use of Gson when both it and Jackson are on the classpath." + "description": "Preferred JSON mapper to use for HTTP message conversion, auto-detected according to the environment by default." }, { "name": "spring.jersey.type", @@ -1362,6 +1362,9 @@ }, { "value": "jackson" + }, + { + "value": "jsonb" } ], "providers": [ diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 67da10e4659..dc62226cd2c 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -80,6 +80,7 @@ org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\ org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\ +org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\ org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfigurationTests.java index 592eb342433..927dce7544a 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfigurationTests.java @@ -16,28 +16,33 @@ package org.springframework.boot.autoconfigure.http; -import java.util.Arrays; -import java.util.List; +import javax.json.bind.Jsonb; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; -import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration; import org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration.MappingJackson2HttpMessageConverterConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration; +import org.springframework.boot.test.context.HidePackagesClassLoader; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.http.converter.json.JsonbHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; @@ -51,191 +56,220 @@ import static org.assertj.core.api.Assertions.assertThat; * @author David Liu * @author Andy Wilkinson * @author Sebastien Deleuze + * @author Eddú Meléndez */ public class HttpMessageConvertersAutoConfigurationTests { - private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + private ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + HttpMessageConvertersAutoConfiguration.class)); - @After - public void close() { - if (this.context != null) { - this.context.close(); - } + @Test + public void jacksonNotAvailable() { + this.contextRunner.run((context) -> { + assertThat(context).doesNotHaveBean(ObjectMapper.class); + assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class); + assertThat(context).doesNotHaveBean(MappingJackson2XmlHttpMessageConverter.class); + }); } @Test - public void noObjectMapperMeansNoConverter() throws Exception { - this.context.register(HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBeansOfType(ObjectMapper.class)).isEmpty(); - assertThat(this.context.getBeansOfType(MappingJackson2HttpMessageConverter.class)) - .isEmpty(); - assertThat( - this.context.getBeansOfType(MappingJackson2XmlHttpMessageConverter.class)) - .isEmpty(); + public void jacksonDefaultConverter() { + this.contextRunner.withUserConfiguration(JacksonObjectMapperConfig.class) + .run(assertConverter(MappingJackson2HttpMessageConverter.class, + "mappingJackson2HttpMessageConverter")); } @Test - public void defaultJacksonConverter() throws Exception { - this.context.register(JacksonObjectMapperConfig.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - - assertConverterBeanExists(MappingJackson2HttpMessageConverter.class, - "mappingJackson2HttpMessageConverter"); - - assertConverterBeanRegisteredWithHttpMessageConverters( - MappingJackson2HttpMessageConverter.class); + public void jacksonConverterWithBuilder() { + this.contextRunner.withUserConfiguration(JacksonObjectMapperBuilderConfig.class) + .run(assertConverter(MappingJackson2HttpMessageConverter.class, + "mappingJackson2HttpMessageConverter")); } @Test - public void defaultJacksonConvertersWithBuilder() throws Exception { - this.context.register(JacksonObjectMapperBuilderConfig.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - - assertConverterBeanExists(MappingJackson2HttpMessageConverter.class, - "mappingJackson2HttpMessageConverter"); - assertConverterBeanExists(MappingJackson2XmlHttpMessageConverter.class, - "mappingJackson2XmlHttpMessageConverter"); - - assertConverterBeanRegisteredWithHttpMessageConverters( - MappingJackson2HttpMessageConverter.class); - assertConverterBeanRegisteredWithHttpMessageConverters( - MappingJackson2XmlHttpMessageConverter.class); + public void jacksonXmlConverterWithBuilder() { + this.contextRunner.withUserConfiguration(JacksonObjectMapperBuilderConfig.class) + .run(assertConverter(MappingJackson2XmlHttpMessageConverter.class, + "mappingJackson2XmlHttpMessageConverter")); } @Test - public void customJacksonConverter() throws Exception { - this.context.register(JacksonObjectMapperConfig.class, - JacksonConverterConfig.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - - assertConverterBeanExists(MappingJackson2HttpMessageConverter.class, - "customJacksonMessageConverter"); + public void jacksonCustomConverter() { + this.contextRunner.withUserConfiguration(JacksonObjectMapperConfig.class, + JacksonConverterConfig.class + ).run(assertConverter(MappingJackson2HttpMessageConverter.class, + "customJacksonMessageConverter")); } @Test - public void noGson() throws Exception { - this.context.register(HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBeansOfType(Gson.class).isEmpty()).isTrue(); - assertThat(this.context.getBeansOfType(GsonHttpMessageConverter.class).isEmpty()) - .isTrue(); + public void gsonNotAvailable() { + this.contextRunner.run((context) -> { + assertThat(context).doesNotHaveBean(Gson.class); + assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class); + }); } @Test - public void defaultGsonConverter() throws Exception { - this.context.register(GsonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertConverterBeanExists(GsonHttpMessageConverter.class, - "gsonHttpMessageConverter"); - - assertConverterBeanRegisteredWithHttpMessageConverters( - GsonHttpMessageConverter.class); + public void gsonDefaultConverter() { + this.contextRunner.withConfiguration(AutoConfigurations.of( + GsonAutoConfiguration.class) + ).run(assertConverter(GsonHttpMessageConverter.class, + "gsonHttpMessageConverter")); } @Test - public void jacksonIsPreferredByDefaultWhenBothGsonAndJacksonAreAvailable() { - this.context.register(GsonAutoConfiguration.class, JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertConverterBeanExists(MappingJackson2HttpMessageConverter.class, - "mappingJackson2HttpMessageConverter"); - assertConverterBeanRegisteredWithHttpMessageConverters( - MappingJackson2HttpMessageConverter.class); - assertThat(this.context.getBeansOfType(GsonHttpMessageConverter.class)).isEmpty(); + public void gsonCustomConverter() { + this.contextRunner.withUserConfiguration(GsonConverterConfig.class) + .withConfiguration(AutoConfigurations.of(GsonAutoConfiguration.class)) + .run(assertConverter(GsonHttpMessageConverter.class, + "customGsonMessageConverter")); } @Test - public void gsonCanBePreferredWhenBothGsonAndJacksonAreAvailable() { - this.context.register(GsonAutoConfiguration.class, JacksonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class); - TestPropertyValues.of("spring.http.converters.preferred-json-mapper:gson") - .applyTo(this.context); - this.context.refresh(); - assertConverterBeanExists(GsonHttpMessageConverter.class, - "gsonHttpMessageConverter"); - assertConverterBeanRegisteredWithHttpMessageConverters( - GsonHttpMessageConverter.class); - assertThat(this.context.getBeansOfType(MappingJackson2HttpMessageConverter.class)) - .isEmpty(); + public void gsonCanBePreferred() { + allOptionsRunner().withPropertyValues( + "spring.http.converters.preferred-json-mapper:gson").run((context) -> { + assertConverterBeanExists(context, GsonHttpMessageConverter.class, + "gsonHttpMessageConverter"); + assertConverterBeanRegisteredWithHttpMessageConverters(context, + GsonHttpMessageConverter.class); + assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class); + assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class); + }); } @Test - public void customGsonConverter() throws Exception { - this.context.register(GsonAutoConfiguration.class, GsonConverterConfig.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertConverterBeanExists(GsonHttpMessageConverter.class, - "customGsonMessageConverter"); - - assertConverterBeanRegisteredWithHttpMessageConverters( - GsonHttpMessageConverter.class); + public void jsonbNotAvailable() { + this.contextRunner.run((context) -> { + assertThat(context).doesNotHaveBean(Jsonb.class); + assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class); + }); } @Test - public void defaultStringConverter() throws Exception { - this.context.register(HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertConverterBeanExists(StringHttpMessageConverter.class, - "stringHttpMessageConverter"); - assertConverterBeanRegisteredWithHttpMessageConverters( - StringHttpMessageConverter.class); + public void jsonbDefaultConverter() { + this.contextRunner.withConfiguration(AutoConfigurations.of( + JsonbAutoConfiguration.class) + ).run(assertConverter(JsonbHttpMessageConverter.class, + "jsonbHttpMessageConverter")); } @Test - public void customStringConverter() throws Exception { - this.context.register(StringConverterConfig.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertConverterBeanExists(StringHttpMessageConverter.class, - "customStringMessageConverter"); - - assertConverterBeanRegisteredWithHttpMessageConverters( - StringHttpMessageConverter.class); + public void jsonbCustomConverter() { + this.contextRunner.withUserConfiguration(JsonbConverterConfig.class) + .withConfiguration(AutoConfigurations.of(JsonbAutoConfiguration.class)) + .run(assertConverter(JsonbHttpMessageConverter.class, + "customJsonbMessageConverter")); } @Test - public void typeConstrainedConverterDoesNotPreventAutoConfigurationOfJacksonConverter() - throws Exception { - this.context.register(JacksonObjectMapperBuilderConfig.class, - TypeConstrainedConverterConfiguration.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - - BeanDefinition beanDefinition = this.context - .getBeanDefinition("mappingJackson2HttpMessageConverter"); - assertThat(beanDefinition.getFactoryBeanName()).isEqualTo( - MappingJackson2HttpMessageConverterConfiguration.class.getName()); + public void jsonbCanBePreferred() { + allOptionsRunner().withPropertyValues( + "spring.http.converters.preferred-json-mapper:jsonb").run((context) -> { + assertConverterBeanExists(context, JsonbHttpMessageConverter.class, + "jsonbHttpMessageConverter"); + assertConverterBeanRegisteredWithHttpMessageConverters(context, + JsonbHttpMessageConverter.class); + assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class); + assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class); + }); } @Test - public void typeConstrainedConverterFromSpringDataDoesNotPreventAutoConfigurationOfJacksonConverter() - throws Exception { - this.context.register(JacksonObjectMapperBuilderConfig.class, - RepositoryRestMvcConfiguration.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - BeanDefinition beanDefinition = this.context - .getBeanDefinition("mappingJackson2HttpMessageConverter"); - assertThat(beanDefinition.getFactoryBeanName()).isEqualTo( - MappingJackson2HttpMessageConverterConfiguration.class.getName()); + public void stringDefaultConverter() { + this.contextRunner.run(assertConverter(StringHttpMessageConverter.class, + "stringHttpMessageConverter")); } - private void assertConverterBeanExists(Class type, String beanName) { - assertThat(this.context.getBeansOfType(type)).hasSize(1); - List beanNames = Arrays.asList(this.context.getBeanDefinitionNames()); - assertThat(beanNames).contains(beanName); + @Test + public void sringCustomConverter() { + this.contextRunner.withUserConfiguration(StringConverterConfig.class) + .run(assertConverter(StringHttpMessageConverter.class, + "customStringMessageConverter")); } - private void assertConverterBeanRegisteredWithHttpMessageConverters(Class type) { - Object converter = this.context.getBean(type); - HttpMessageConverters converters = this.context - .getBean(HttpMessageConverters.class); - assertThat(converters.getConverters().contains(converter)).isTrue(); + @Test + public void typeConstrainedConverterDoesNotPreventAutoConfigurationOfJacksonConverter() { + this.contextRunner.withUserConfiguration(JacksonObjectMapperBuilderConfig.class, + TypeConstrainedConverterConfiguration.class).run((context) -> { + BeanDefinition beanDefinition = ((GenericApplicationContext) context.getSourceApplicationContext()) + .getBeanDefinition("mappingJackson2HttpMessageConverter"); + assertThat(beanDefinition.getFactoryBeanName()).isEqualTo( + MappingJackson2HttpMessageConverterConfiguration.class.getName()); + }); + } + + @Test + public void typeConstrainedConverterFromSpringDataDoesNotPreventAutoConfigurationOfJacksonConverter() { + this.contextRunner.withUserConfiguration(JacksonObjectMapperBuilderConfig.class, + RepositoryRestMvcConfiguration.class).run((context) -> { + BeanDefinition beanDefinition = ((GenericApplicationContext) context.getSourceApplicationContext()) + .getBeanDefinition("mappingJackson2HttpMessageConverter"); + assertThat(beanDefinition.getFactoryBeanName()).isEqualTo( + MappingJackson2HttpMessageConverterConfiguration.class.getName()); + }); + } + + @Test + public void jacksonIsPreferredByDefault() { + allOptionsRunner().run((context) -> { + assertConverterBeanExists(context, MappingJackson2HttpMessageConverter.class, + "mappingJackson2HttpMessageConverter"); + assertConverterBeanRegisteredWithHttpMessageConverters(context, + MappingJackson2HttpMessageConverter.class); + assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class); + assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class); + }); + } + + @Test + public void gsonIsPreferredIfJacksonIsNotAvailable() { + allOptionsRunner() + .withClassLoader(new HidePackagesClassLoader( + ObjectMapper.class.getPackage().getName())).run((context) -> { + assertConverterBeanExists(context, GsonHttpMessageConverter.class, + "gsonHttpMessageConverter"); + assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class); + }); + } + + @Test + public void jsonbIsPreferredIfJacksonAndGsonAreNotAvailable() { + allOptionsRunner() + .withClassLoader(new HidePackagesClassLoader( + ObjectMapper.class.getPackage().getName(), + Gson.class.getPackage().getName())) + .run(assertConverter(JsonbHttpMessageConverter.class, + "jsonbHttpMessageConverter")); + } + + private ApplicationContextRunner allOptionsRunner() { + return this.contextRunner.withConfiguration(AutoConfigurations.of( + GsonAutoConfiguration.class, JacksonAutoConfiguration.class, + JsonbAutoConfiguration.class)); + } + + + private ContextConsumer assertConverter( + Class converterType, String beanName) { + return context -> { + assertConverterBeanExists(context, converterType, beanName); + assertConverterBeanRegisteredWithHttpMessageConverters(context, converterType); + }; + } + + private void assertConverterBeanExists(AssertableApplicationContext context, + Class type, String beanName) { + assertThat(context).hasSingleBean(type); + assertThat(context).hasBean(beanName); + } + + private void assertConverterBeanRegisteredWithHttpMessageConverters( + AssertableApplicationContext context, Class type) { + HttpMessageConverter converter = context.getBean(type); + HttpMessageConverters converters = context.getBean(HttpMessageConverters.class); + assertThat(converters.getConverters()).contains(converter); } @Configuration @@ -288,6 +322,18 @@ public class HttpMessageConvertersAutoConfigurationTests { } + @Configuration + protected static class JsonbConverterConfig { + + @Bean + public JsonbHttpMessageConverter customJsonbMessageConverter(Jsonb jsonb) { + JsonbHttpMessageConverter converter = new JsonbHttpMessageConverter(); + converter.setJsonb(jsonb); + return converter; + } + + } + @Configuration protected static class StringConverterConfig { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jsonb/JsonbAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jsonb/JsonbAutoConfigurationTests.java new file mode 100644 index 00000000000..3f6a55c4b41 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jsonb/JsonbAutoConfigurationTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2017 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 + * + * 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.springframework.boot.autoconfigure.jsonb; + +import javax.json.bind.Jsonb; + +import org.junit.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link JsonbAutoConfiguration}. + * + * @author Eddú Meléndez + */ +public class JsonbAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(JsonbAutoConfiguration.class)); + + @Test + public void jsonbRegistration() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(Jsonb.class); + Jsonb jsonb = context.getBean(Jsonb.class); + assertThat(jsonb.toJson(new DataObject())).isEqualTo("{\"data\":\"hello\"}"); + }); + } + + public class DataObject { + + @SuppressWarnings("unused") + private String data = "hello"; + + public String getData() { + return this.data; + } + + public void setData(String data) { + this.data = data; + } + } + +} diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index e694710fc78..37762f8bac5 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -118,8 +118,11 @@ 4.4.0 2.9.9 1.3.7 + 1.1.2 3.9.5 1.5.0 + 1.1 + 1.0 2.4.0 1.2 1.3.1 @@ -938,6 +941,16 @@ javax.jms-api ${javax-jms.version} + + javax.json + javax.json-api + ${javax-json.version} + + + javax.json.bind + javax.json.bind-api + ${javax-jsonb.version} + javax.mail javax.mail-api @@ -1305,6 +1318,11 @@ httpmime ${httpclient.version} + + org.apache.johnzon + johnzon-jsonb + ${johnzon-jsonb.version} + org.apache.logging.log4j log4j-bom diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index ee4bc501477..111f722999e 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -297,7 +297,7 @@ content into your application; rather pick only the properties that you need. spring.hateoas.use-hal-as-default-json-media-type=true # Specify if application/hal+json responses should be sent to requests that accept application/json. # HTTP message conversion - spring.http.converters.preferred-json-mapper=jackson # Preferred JSON mapper to use for HTTP message conversion. Set to "gson" to force the use of Gson when both it and Jackson are on the classpath. + spring.http.converters.preferred-json-mapper= # Preferred JSON mapper to use for HTTP message conversion, auto-detected according to the environment by default. # HTTP encoding ({sc-spring-boot-autoconfigure}/http/HttpEncodingProperties.{sc-ext}[HttpEncodingProperties]) spring.http.encoding.charset=UTF-8 # Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly. diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 82a3b61b7d6..76c84040c4a 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -5675,16 +5675,21 @@ TIP: It's also possible to use the `@AutoConfigure...` annotations with the stan [[boot-features-testing-spring-boot-applications-testing-autoconfigured-json-tests]] ==== Auto-configured JSON tests To test that Object JSON serialization and deserialization is working as expected you can -use the `@JsonTest` annotation. `@JsonTest` will auto-configure Jackson `ObjectMapper`, -any `@JsonComponent` beans and any Jackson `Modules`. It also configures `Gson` -if you happen to be using that instead of, or as well as, Jackson. If you need to -configure elements of the auto-configuration you can use the `@AutoConfigureJsonTesters` -annotation. +use the `@JsonTest` annotation. `@JsonTest` will auto-configure the available supported +json mapper: + +* Jackson `ObjectMapper`, any `@JsonComponent` beans and any Jackson `Modules` +* `Gson` +* `Jsonb` + +If you need to configure elements of the auto-configuration you can use the +`@AutoConfigureJsonTesters` annotation. Spring Boot includes AssertJ based helpers that work with the JSONassert and JsonPath -libraries to check that JSON is as expected. The `JacksonTester`, `GsonTester` and -`BasicJsonTester` classes can be used for Jackson, Gson and Strings respectively. Any -helper fields on the test class can be `@Autowired` when using `@JsonTest`. +libraries to check that JSON is as expected. The `JacksonTester`, `GsonTester`, +`JsonbTester` and `BasicJsonTester` classes can be used for Jackson, Gson, Jsonb and +Strings respectively. Any helper fields on the test class can be `@Autowired` when using +`@JsonTest`. [source,java,indent=0] ---- @@ -6237,9 +6242,9 @@ A list of the auto-configuration that is enabled by `@DataLdapTest` can be [[boot-features-testing-spring-boot-applications-testing-autoconfigured-rest-client]] ==== Auto-configured REST clients The `@RestClientTest` annotation can be used if you want to test REST clients. By default -it will auto-configure Jackson and GSON support, configure a `RestTemplateBuilder` and -add support for `MockRestServiceServer`. The specific beans that you want to test should -be specified using `value` or `components` attribute of `@RestClientTest`: +it will auto-configure Jackson, GSON and Jsonb support, configure a `RestTemplateBuilder` +and add support for `MockRestServiceServer`. The specific beans that you want to test +should be specified using `value` or `components` attribute of `@RestClientTest`: [source,java,indent=0] diff --git a/spring-boot-test-autoconfigure/pom.xml b/spring-boot-test-autoconfigure/pom.xml index 69bd946d7a0..cf109e9383b 100644 --- a/spring-boot-test-autoconfigure/pom.xml +++ b/spring-boot-test-autoconfigure/pom.xml @@ -29,6 +29,11 @@ spring-boot-autoconfigure + + javax.json.bind + javax.json.bind-api + true + javax.servlet javax.servlet-api @@ -183,6 +188,11 @@ reactor-core test + + javax.json + javax.json-api + test + org.apache.commons commons-pool2 @@ -208,6 +218,11 @@ jooq test + + org.apache.johnzon + johnzon-jsonb + test + org.mongodb mongodb-driver-async diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/AutoConfigureJsonTesters.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/AutoConfigureJsonTesters.java index ea48f3c07be..e68477b34d3 100644 --- a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/AutoConfigureJsonTesters.java +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/AutoConfigureJsonTesters.java @@ -28,6 +28,7 @@ import org.springframework.boot.test.autoconfigure.properties.PropertyMapping; import org.springframework.boot.test.json.BasicJsonTester; import org.springframework.boot.test.json.GsonTester; import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonbTester; /** * Annotation that can be applied to a test class to enable and configure @@ -45,8 +46,8 @@ import org.springframework.boot.test.json.JacksonTester; public @interface AutoConfigureJsonTesters { /** - * If {@link BasicJsonTester}, {@link JacksonTester} and {@link GsonTester} beans - * should be registered. Defaults to {@code true} + * If {@link BasicJsonTester}, {@link JacksonTester}, {@link JsonbTester} and + * {@link GsonTester} beans should be registered. Defaults to {@code true} * @return if tester support is enabled */ boolean enabled() default true; diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTest.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTest.java index 676459c5858..84aa698d6bb 100644 --- a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTest.java +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTest.java @@ -31,6 +31,7 @@ import org.springframework.boot.test.autoconfigure.filter.TypeExcludeFilters; import org.springframework.boot.test.context.SpringBootTestContextBootstrapper; import org.springframework.boot.test.json.GsonTester; import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonbTester; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.core.annotation.AliasFor; import org.springframework.test.context.BootstrapWith; @@ -45,8 +46,9 @@ import org.springframework.test.context.BootstrapWith; * {@code Module}) *

* By default, tests annotated with {@code JsonTest} will also initialize - * {@link JacksonTester} and {@link GsonTester} fields. More fine-grained control can be - * provided via the {@link AutoConfigureJsonTesters @AutoConfigureJsonTesters} annotation. + * {@link JacksonTester}, {@link JsonbTester} and {@link GsonTester} fields. More + * fine-grained control can be provided via the {@link AutoConfigureJsonTesters @AutoConfigureJsonTesters} + * annotation. * * @author Phillip Webb * @see AutoConfigureJson diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTestersAutoConfiguration.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTestersAutoConfiguration.java index 4110e311670..ffb7b10c318 100644 --- a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTestersAutoConfiguration.java +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTestersAutoConfiguration.java @@ -19,6 +19,8 @@ package org.springframework.boot.test.autoconfigure.json; import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import javax.json.bind.Jsonb; + import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; @@ -33,10 +35,12 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration; import org.springframework.boot.test.json.AbstractJsonMarshalTester; import org.springframework.boot.test.json.BasicJsonTester; import org.springframework.boot.test.json.GsonTester; import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonbTester; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; @@ -48,13 +52,15 @@ import org.springframework.util.ReflectionUtils; * Auto-configuration for Json testers. * * @author Phillip Webb + * @author Eddú Meléndez * @see AutoConfigureJsonTesters * @since 1.4.0 */ @Configuration @ConditionalOnClass(name = "org.assertj.core.api.Assert") @ConditionalOnProperty("spring.test.jsontesters.enabled") -@AutoConfigureAfter({ JacksonAutoConfiguration.class, GsonAutoConfiguration.class }) +@AutoConfigureAfter({ JacksonAutoConfiguration.class, GsonAutoConfiguration.class, + JsonbAutoConfiguration.class }) public class JsonTestersAutoConfiguration { @Bean @@ -94,6 +100,18 @@ public class JsonTestersAutoConfiguration { } + @ConditionalOnClass(Jsonb.class) + private static class JsonbJsonTesterConfiguration { + + @Bean + @Scope("prototype") + @ConditionalOnBean(Jsonb.class) + public FactoryBean> jsonbTesterFactoryBean(Jsonb jsonb) { + return new JsonTesterFactoryBean<>(JsonbTester.class, jsonb); + } + + } + /** * {@link FactoryBean} used to create JSON Tester instances. */ diff --git a/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories index 0d335481a55..783c0b60397 100644 --- a/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories @@ -67,13 +67,15 @@ org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration # AutoConfigureJson auto-configuration imports org.springframework.boot.test.autoconfigure.json.AutoConfigureJson=\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ -org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration +org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ +org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration # AutoConfigureJsonTesters auto-configuration imports org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters=\ org.springframework.boot.test.autoconfigure.json.JsonTestersAutoConfiguration,\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ -org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration +org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ +org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration # AutoConfigureWebClient auto-configuration imports org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient=\ @@ -111,6 +113,7 @@ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\ org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ +org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\ org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration @@ -123,6 +126,7 @@ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ +org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\ @@ -145,4 +149,4 @@ org.springframework.test.context.TestExecutionListener=\ org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener,\ org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener,\ org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener,\ -org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener \ No newline at end of file +org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestIntegrationTests.java index 64dbb108ba8..f4ce184ae78 100644 --- a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestIntegrationTests.java +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestIntegrationTests.java @@ -28,6 +28,7 @@ import org.springframework.boot.test.json.BasicJsonTester; import org.springframework.boot.test.json.GsonTester; import org.springframework.boot.test.json.JacksonTester; import org.springframework.boot.test.json.JsonContent; +import org.springframework.boot.test.json.JsonbTester; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @@ -38,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Phillip Webb * @author Madhura Bhave + * @author Eddú Meléndez */ @RunWith(SpringRunner.class) @JsonTest @@ -59,6 +61,9 @@ public class JsonTestIntegrationTests { @Autowired private GsonTester gsonJson; + @Autowired + private JsonbTester jsonbJson; + @Test public void basicJson() throws Exception { assertThat(this.basicJson.from("{\"a\":\"b\"}")).hasJsonPathStringValue("@.a"); @@ -84,6 +89,13 @@ public class JsonTestIntegrationTests { assertThat(this.gsonJson.write(object)).isEqualToJson("example.json"); } + @Test + public void jsonb() throws Exception { + ExampleBasicObject object = new ExampleBasicObject(); + object.setValue("spring"); + assertThat(this.jsonbJson.write(object)).isEqualToJson("example.json"); + } + @Test public void customView() throws Exception { ExampleJsonObjectWithView object = new ExampleJsonObjectWithView(); diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestWithAutoConfigureJsonTestersTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestWithAutoConfigureJsonTestersTests.java index 1c34491b132..62b44ac09f8 100644 --- a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestWithAutoConfigureJsonTestersTests.java +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/JsonTestWithAutoConfigureJsonTestersTests.java @@ -25,6 +25,7 @@ import org.springframework.boot.test.autoconfigure.json.app.ExampleJsonApplicati import org.springframework.boot.test.json.BasicJsonTester; import org.springframework.boot.test.json.GsonTester; import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonbTester; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @@ -50,6 +51,9 @@ public class JsonTestWithAutoConfigureJsonTestersTests { @Autowired(required = false) private GsonTester gsonTester; + @Autowired(required = false) + private JsonbTester jsonbTester; + @Test public void basicJson() throws Exception { assertThat(this.basicJson).isNull(); @@ -65,4 +69,9 @@ public class JsonTestWithAutoConfigureJsonTestersTests { assertThat(this.gsonTester).isNull(); } + @Test + public void jsonb() throws Exception { + assertThat(this.jsonbTester).isNull(); + } + } diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/SpringBootTestWithAutoConfigureJsonTestersTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/SpringBootTestWithAutoConfigureJsonTestersTests.java index 8c997427bae..330dc41881e 100644 --- a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/SpringBootTestWithAutoConfigureJsonTestersTests.java +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/SpringBootTestWithAutoConfigureJsonTestersTests.java @@ -26,6 +26,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.json.BasicJsonTester; import org.springframework.boot.test.json.GsonTester; import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.json.JsonbTester; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @@ -51,10 +52,14 @@ public class SpringBootTestWithAutoConfigureJsonTestersTests { @Autowired private GsonTester gsonTester; + @Autowired + private JsonbTester jsonbTester; + @Test public void contextLoads() { assertThat(this.basicJson).isNotNull(); assertThat(this.jacksonTester).isNotNull(); + assertThat(this.jsonbTester).isNotNull(); assertThat(this.gsonTester).isNotNull(); } diff --git a/spring-boot-test/pom.xml b/spring-boot-test/pom.xml index 77934e5e235..30b1c504dcb 100644 --- a/spring-boot-test/pom.xml +++ b/spring-boot-test/pom.xml @@ -45,6 +45,11 @@ reactor-netty true + + javax.json.bind + javax.json.bind-api + true + javax.servlet javax.servlet-api @@ -116,6 +121,11 @@ true + + javax.json + javax.json-api + test + org.springframework.boot spring-boot-test-support @@ -142,6 +152,11 @@ true test + + org.apache.johnzon + johnzon-jsonb + test + org.jetbrains.kotlin kotlin-runtime diff --git a/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonbTester.java b/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonbTester.java new file mode 100644 index 00000000000..899f90eeaad --- /dev/null +++ b/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonbTester.java @@ -0,0 +1,128 @@ +/* + * Copyright 2012-2017 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 + * + * 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.springframework.boot.test.json; + +import java.io.IOException; +import java.io.Reader; + +import javax.json.bind.Jsonb; + +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.core.ResolvableType; +import org.springframework.util.Assert; + +/** + * AssertJ based JSON tester backed by Jsonb. Usually instantiated via + * {@link #initFields(Object, Jsonb)}, for example:

+ * public class ExampleObjectJsonTests {
+ *
+ * 	private JsonbTester<ExampleObject> json;
+ *
+ * 	@Before
+ * 	public void setup() {
+ * 		Jsonb jsonb = JsonbBuilder.create();
+ * 		JsonbTester.initFields(this, jsonb);
+ * 	}
+ *
+ * 	@Test
+ * 	public void testWriteJson() throws IOException {
+ * 		ExampleObject object = // ...
+ * 		assertThat(json.write(object)).isEqualToJson("expected.json");
+ * 	}
+ *
+ * }
+ * 
+ * + * See {@link AbstractJsonMarshalTester} for more details. + * + * @param the type under test + * @author Eddú Meléndez + * @since 2.0.0 + */ +public class JsonbTester extends AbstractJsonMarshalTester { + + private final Jsonb jsonb; + + /** + * Create a new uninitialized {@link JsonbTester} instance. + * @param jsonb the Jsonb instance + */ + protected JsonbTester(Jsonb jsonb) { + Assert.notNull(jsonb, "Jsonb must not be null"); + this.jsonb = jsonb; + } + + /** + * Create a new {@link JsonbTester} instance. + * @param resourceLoadClass the source class used to load resources + * @param type the type under test + * @param jsonb the Jsonb instance + * @see #initFields(Object, Jsonb) + */ + public JsonbTester(Class resourceLoadClass, ResolvableType type, Jsonb jsonb) { + super(resourceLoadClass, type); + Assert.notNull(jsonb, "Jsonb must not be null"); + this.jsonb = jsonb; + } + + @Override + protected String writeObject(T value, ResolvableType type) throws IOException { + return this.jsonb.toJson(value, type.getType()); + } + + @Override + protected T readObject(Reader reader, ResolvableType type) throws IOException { + return this.jsonb.fromJson(reader, type.getType()); + } + + /** + * Utility method to initialize {@link JsonbTester} fields. See {@link JsonbTester + * class-level documentation} for example usage. + * @param testInstance the test instance + * @param jsonb the Jsonb instance + */ + public static void initFields(Object testInstance, Jsonb jsonb) { + new JsonbFieldInitializer().initFields(testInstance, jsonb); + } + + /** + * Utility method to initialize {@link JsonbTester} fields. See {@link JsonbTester + * class-level documentation} for example usage. + * @param testInstance the test instance + * @param jsonb an object factory to create the Jsonb instance + */ + public static void initFields(Object testInstance, ObjectFactory jsonb) { + new JsonbTester.JsonbFieldInitializer().initFields(testInstance, jsonb); + } + + /** + * {@link FieldInitializer} for Jsonb. + */ + private static class JsonbFieldInitializer extends FieldInitializer { + + protected JsonbFieldInitializer() { + super(JsonbTester.class); + } + + @Override + protected AbstractJsonMarshalTester createTester( + Class resourceLoadClass, ResolvableType type, Jsonb marshaller) { + return new JsonbTester<>(resourceLoadClass, type, marshaller); + } + } + +} diff --git a/spring-boot-test/src/test/java/org/springframework/boot/test/json/JsonbTesterTests.java b/spring-boot-test/src/test/java/org/springframework/boot/test/json/JsonbTesterTests.java new file mode 100644 index 00000000000..91bd5987edf --- /dev/null +++ b/spring-boot-test/src/test/java/org/springframework/boot/test/json/JsonbTesterTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-2017 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 + * + * 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.springframework.boot.test.json; + +import java.util.List; + +import javax.json.bind.Jsonb; +import javax.json.bind.JsonbBuilder; + +import org.junit.Test; + +import org.springframework.core.ResolvableType; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link JsonbTester}. + * + * @author Eddú Meléndez + */ +public class JsonbTesterTests extends AbstractJsonMarshalTesterTests { + + @Test + public void initFieldsWhenTestIsNullShouldThrowException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("TestInstance must not be null"); + JsonbTester.initFields(null, JsonbBuilder.create()); + } + + @Test + public void initFieldsWhenMarshallerIsNullShouldThrowException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Marshaller must not be null"); + JsonbTester.initFields(new InitFieldsTestClass(), (Jsonb) null); + } + + @Test + public void initFieldsShouldSetNullFields() { + InitFieldsTestClass test = new InitFieldsTestClass(); + assertThat(test.test).isNull(); + assertThat(test.base).isNull(); + JsonbTester.initFields(test, JsonbBuilder.create()); + assertThat(test.test).isNotNull(); + assertThat(test.base).isNotNull(); + assertThat(test.test.getType().resolve()).isEqualTo(List.class); + assertThat(test.test.getType().resolveGeneric()).isEqualTo(ExampleObject.class); + } + + @Override + protected AbstractJsonMarshalTester createTester(Class resourceLoadClass, + ResolvableType type) { + return new JsonbTester<>(resourceLoadClass, type, JsonbBuilder.create()); + } + + static abstract class InitFieldsBaseClass { + + public JsonbTester base; + + public JsonbTester baseSet = new JsonbTester<>( + InitFieldsBaseClass.class, ResolvableType.forClass(ExampleObject.class), + JsonbBuilder.create()); + + } + + static class InitFieldsTestClass extends InitFieldsBaseClass { + + public JsonbTester> test; + + public JsonbTester testSet = new JsonbTester<>( + InitFieldsBaseClass.class, ResolvableType.forClass(ExampleObject.class), + JsonbBuilder.create()); + + } + +}