Add Jsonb support

Spring Framework 5 will support Jsonb as a HttpMessageConverter, this
commit adds auto-configuration support. Also, support for Jsonb in the
@JsonTest has been added.

This implementation is running against Apache Johnzon

See gh-9648
This commit is contained in:
Eddú Meléndez
2017-04-10 21:01:13 -05:00
committed by Stephane Nicoll
parent 676dec8cf7
commit 97aeaa4a32
22 changed files with 674 additions and 16 deletions

View File

@@ -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(PreferGsonOrMissingJacksonAndJsonbCondition.class)
protected static class GsonHttpMessageConverterConfiguration {
@Bean
@@ -54,9 +56,9 @@ class GsonHttpMessageConvertersConfiguration {
}
private static class PreferGsonOrMissingJacksonCondition extends AnyNestedCondition {
private static class PreferGsonOrMissingJacksonAndJsonbCondition extends AnyNestedCondition {
PreferGsonOrMissingJacksonCondition() {
PreferGsonOrMissingJacksonAndJsonbCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@@ -65,11 +67,29 @@ class GsonHttpMessageConvertersConfiguration {
}
@ConditionalOnMissingBean(MappingJackson2HttpMessageConverter.class)
@Conditional(JacksonAndJsonbMissing.class)
static class JacksonJsonbMissing {
}
}
private static class JacksonAndJsonbMissing extends NoneNestedConditions {
JacksonAndJsonbMissing() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(MappingJackson2HttpMessageConverter.class)
static class JacksonMissing {
}
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "jsonb")
static class JsonbMissing {
}
}
}

View File

@@ -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";

View File

@@ -0,0 +1,94 @@
/*
* 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.boot.autoconfigure.condition.NoneNestedConditions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
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", matchIfMissing = false)
static class JsonbPreferred {
}
@Conditional(JacksonAndGsonMissing.class)
static class JacksonGsonMissing {
}
}
private static class JacksonAndGsonMissing extends NoneNestedConditions {
JacksonAndGsonMissing() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(MappingJackson2HttpMessageConverter.class)
static class JacksonMissing {
}
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "gson")
static class GsonMissing {
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -1362,6 +1362,9 @@
},
{
"value": "jackson"
},
{
"value": "jsonb"
}
],
"providers": [

View File

@@ -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,\

View File

@@ -19,6 +19,8 @@ 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;
@@ -28,6 +30,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
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.autoconfigure.jsonb.JsonbAutoConfiguration;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -38,6 +41,7 @@ import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessage
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,6 +55,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author David Liu
* @author Andy Wilkinson
* @author Sebastien Deleuze
* @author Eddú Meléndez
*/
public class HttpMessageConvertersAutoConfigurationTests {
@@ -140,19 +145,20 @@ public class HttpMessageConvertersAutoConfigurationTests {
@Test
public void jacksonIsPreferredByDefaultWhenBothGsonAndJacksonAreAvailable() {
this.context.register(GsonAutoConfiguration.class, JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class);
JsonbAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class);
this.context.refresh();
assertConverterBeanExists(MappingJackson2HttpMessageConverter.class,
"mappingJackson2HttpMessageConverter");
assertConverterBeanRegisteredWithHttpMessageConverters(
MappingJackson2HttpMessageConverter.class);
assertThat(this.context.getBeansOfType(GsonHttpMessageConverter.class)).isEmpty();
assertThat(this.context.getBeansOfType(JsonbHttpMessageConverter.class)).isEmpty();
}
@Test
public void gsonCanBePreferredWhenBothGsonAndJacksonAreAvailable() {
this.context.register(GsonAutoConfiguration.class, JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class);
JsonbAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class);
TestPropertyValues.of("spring.http.converters.preferred-json-mapper:gson")
.applyTo(this.context);
this.context.refresh();
@@ -160,6 +166,8 @@ public class HttpMessageConvertersAutoConfigurationTests {
"gsonHttpMessageConverter");
assertConverterBeanRegisteredWithHttpMessageConverters(
GsonHttpMessageConverter.class);
assertThat(this.context.getBeansOfType(JsonbHttpMessageConverter.class))
.isEmpty();
assertThat(this.context.getBeansOfType(MappingJackson2HttpMessageConverter.class))
.isEmpty();
}
@@ -176,6 +184,55 @@ public class HttpMessageConvertersAutoConfigurationTests {
GsonHttpMessageConverter.class);
}
@Test
public void noJsonb() throws Exception {
this.context.register(HttpMessageConvertersAutoConfiguration.class);
this.context.refresh();
assertThat(this.context.getBeansOfType(Jsonb.class).isEmpty()).isTrue();
assertThat(this.context.getBeansOfType(JsonbHttpMessageConverter.class).isEmpty())
.isTrue();
}
@Test
public void defaultJsonbConverter() throws Exception {
this.context.register(JsonbAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class);
this.context.refresh();
assertConverterBeanExists(JsonbHttpMessageConverter.class,
"jsonbHttpMessageConverter");
assertConverterBeanRegisteredWithHttpMessageConverters(
JsonbHttpMessageConverter.class);
}
@Test
public void jsonbCanBePreferredWhenBothGsonAndJacksonAreAvailable() {
this.context.register(GsonAutoConfiguration.class, JacksonAutoConfiguration.class,
JsonbAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class);
TestPropertyValues.of("spring.http.converters.preferred-json-mapper:jsonb")
.applyTo(this.context);
this.context.refresh();
assertConverterBeanExists(JsonbHttpMessageConverter.class,
"jsonbHttpMessageConverter");
assertConverterBeanRegisteredWithHttpMessageConverters(
JsonbHttpMessageConverter.class);
assertThat(this.context.getBeansOfType(GsonHttpMessageConverter.class))
.isEmpty();
assertThat(this.context.getBeansOfType(MappingJackson2HttpMessageConverter.class))
.isEmpty();
}
@Test
public void customJsonbConverter() throws Exception {
this.context.register(JsonbAutoConfiguration.class, JsonbConverterConfig.class,
HttpMessageConvertersAutoConfiguration.class);
this.context.refresh();
assertConverterBeanExists(JsonbHttpMessageConverter.class,
"customJsonbMessageConverter");
assertConverterBeanRegisteredWithHttpMessageConverters(
JsonbHttpMessageConverter.class);
}
@Test
public void defaultStringConverter() throws Exception {
this.context.register(HttpMessageConvertersAutoConfiguration.class);
@@ -288,6 +345,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 {

View File

@@ -0,0 +1,72 @@
/*
* 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.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JsonbAutoConfiguration}.
*
* @author Eddú Meléndez
*/
public class JsonbAutoConfigurationTests {
AnnotationConfigApplicationContext context;
@Before
public void setUp() {
this.context = new AnnotationConfigApplicationContext();
}
@After
public void tearDown() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void jsonbRegistration() {
this.context.register(JsonbAutoConfiguration.class);
this.context.refresh();
Jsonb jsonb = this.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;
}
}
}