Jersey non-annotated Provider - java

I'm using Jersey with Spring for a REST API and I wrote a provider to modify JSON serialization. The problem is that when I use the #Component annotation, the provider's callback method is called for other servlets. When I remove the #Component annotation, it doesn't get called at all.
Here is the provider:
#Component
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
public ObjectMapperProvider() {
}
#Override
public ObjectMapper getContext(Class<?> type) {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule("SimpleModule", new org.codehaus.jackson.Version(1, 0, 0, null));
module.addSerializer(BigInteger.class, new ToStringSerializer());
objectMapper.registerModule(module);
return objectMapper;
}
}
I tried to use the Jersey configuration in the web.xml, but that didn't help either.
Any ideas?

Apparently, that wasn't my problem. The provider was called for the right servlet. My application didn't work because I had an XmlAdapter for Map, and with my ObjectMapperProvider, the json response was different.
Here is my updated ObjectMapperProvider class:
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
#Context
UriInfo uriInfo;
public ObjectMapperProvider() {
}
#Override
public ObjectMapper getContext(Class<?> type) {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule("SimpleModule", new org.codehaus.jackson.Version(1, 0, 0, null));
module.addSerializer(BigInteger.class, new ToStringSerializer());
objectMapper = objectMapper.configure(Feature.WRAP_ROOT_VALUE, false).configure(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE, false)
.configure(Feature.WRAP_EXCEPTIONS, true).configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, true).configure(Feature.WRITE_EMPTY_JSON_ARRAYS, false);
final AnnotationIntrospector introspector = new JaxbAnnotationIntrospector();
objectMapper.getDeserializationConfig().setAnnotationIntrospector(introspector); // using a deprecated API that works. Non-deprecated API doesn't work...
objectMapper.getSerializationConfig().setAnnotationIntrospector(introspector);
objectMapper.registerModule(module);
return objectMapper;
}
}
Once I configured my Object wrapper to use JAXB annotations, everything worked as expected. I got the idea on how to do that from the following post

Related

Spring boot generic String trim Serialization

I am trying to implement generic Spring trim serializer across application however its doesn't seems to be working.
And if I manually put this serializer #JsonSerialize(using = StringTrimmerSerializer.class) on a particular field it does work not sure what i need to do to make it work throughout application without putting it for all fields individually
import java.io.IOException;
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.jackson.JsonComponent;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
#JsonComponent
public class StringTrimmerSerializer extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (!StringUtils.isEmpty(value)) {
value = value.trim();
}
gen.writeString(value);
}
}
Update:
Tried registering serializer as well but same issue
#Configuration
public class JacksonConfiguration {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper(); //
//mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
mapper.registerModule(new SimpleModule().addSerializer(String.class, new StringTrimmerSerializer()));
return mapper;
}
/*
* #Bean public Module customSerializer() { SimpleModule module = new
* SimpleModule(); module.addSerializer(String.class, new
* StringTrimmerSerializer()); return module; }
*/
}
Main Class package : com.demo
Serializer Package : com.demo.config
Spring boot Version - 2.2.5.RELEASE
Jackson-databind - 2.10.2
Add constructors in StringTrimmerSerializer
public StringTrimmerSerializer ()
public StringTrimmerSerializer (Class<String> s) {
super(s);
}
I was able to resolve by registering custom serializer to jaskcosn's default object mapper rather than creating a new reference of ObjectMapper.
#Configuration
public class JacksonConfiguration extends WebMvcConfigurationSupport {
#Autowired
private ObjectMapper objectMapper;
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
objectMapper.registerModule(new SimpleModule().addSerializer(String.class, new StringTrimmerSerializer()));
converter.setObjectMapper(objectMapper);
return converter;
}
#Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
}

Spring Boot JSON Serialization of Nulls

How do I force null objects to be serialized as an empty string in my default JSON responses in Spring Boot?
I would like it to show as:
{
myProperty: "",
}
But what comes out by default is:
{
myProperty: null,
}
I don't want myProperty to be excluded from this list, so I'm not interested in changing the JsonInclude.Include.NON_NULL
Here is what I've tried:
In my main WebMvcConfigurerAdapter class:
#Autowired
private NullAsEmptyStringSerializer nullSerializer;
#Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomSerialization() {
return jacksonObjectMapperBuilder -> {
DefaultSerializerProvider serializerProvider = new DefaultSerializerProvider.Impl();
serializerProvider.setNullValueSerializer(nullSerializer);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializerProvider(serializerProvider);
jacksonObjectMapperBuilder.configure(objectMapper);
};
And then my nullSerializer object class:
#Component
public class NullAsEmptyStringSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString("\"\"");
}
}
The problem is that the serialize() method is never called on my custom class.
The problem with this configuration through Jackson2ObjectMapperBuilderCustomizer lies in these lines:
ObjectMapper objectMapper = new ObjectMapper();
jacksonObjectMapperBuilder.configure(objectMapper);
In fact, this has no effect, because the builder always uses its own instance of ObjectMapper, created internally. If we check in the source code of the builder:
public <T extends ObjectMapper> T build() {
ObjectMapper mapper;
// conditional instantiation of mapper
configure(mapper);
return (T) mapper;
}
Our previous instance of ObjectMapper is just ignored.
Unfortunately, there is no way to set SerializerProviders through Jackson2ObjectMapperBuilder. Probably, this feature is not yet implemented in the API.
But there are at least two possible solutions for the problem.
First, according to the official Spring docs, you can always create your own ObjectMapper bean marked with #Primary and configured fully as you wish, for example:
#Bean
#Primary
public ObjectMapper objectMapper() {
DefaultSerializerProvider serializerProvider = new DefaultSerializerProvider.Impl();
serializerProvider.setNullValueSerializer(nullSerializer);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializerProvider(serializerProvider);
return objectMapper;
}
This will enable the nullSerializer properly, but the drawback is that Spring's auto-configuration of the ObjectMapper instance will be lost and that is probably not what you always want.
The second solution goes through bootstrapping the default instance of ObjectMapper and setting the desired property on it. This can be achieved by implementing the InitializingBean interface, in quite an easy way:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
#Configuration
public class ObjectMapperConfig implements InitializingBean {
#Autowired
private NullAsEmptyStringSerializer nullSerializer;
#Autowired
private ObjectMapper objectMapper;
// will be called by Spring after all the beans are created
// and the proper `objectMapper` instance is available here.
#Override
public void afterPropertiesSet() throws Exception {
DefaultSerializerProvider serializerProvider = new DefaultSerializerProvider.Impl();
serializerProvider.setNullValueSerializer(nullSerializer);
objectMapper.setSerializerProvider(serializerProvider);
}
}
Last but not least, there is a small problem in the NullAsEmptyStringSerializer above.
jsonGenerator.writeString("\"\"");
will output "myProperty":"\"\"". This should be changed to
jsonGenerator.writeString("");

How can I define a custom ObjectMapper bean without overriding the one used by Spring Boot

I have a Spring Boot web app with several #RestController classes.
I like the default json format returned by my REST controllers.
For use in my DAO beans (which do json serialization and deserialization ), I have created a custom ObjectMapper:
#Configuration
public class Config{
#Bean
public ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
And in each of my DAO classes I autowire my custom ObjectMapper:
#Repository
#Transactional
public class MyDaoImpl implements MyDao {
#Autowired
ObjectMapper objectMapper
//Dao implementation...
}
This all works fine. The problem is that my custom ObjectMapper gets automatically picked up by Spring and is used for serializing REST responses.
This is undesirable. For REST controllers I want to keep the ObjectMapper that Spring creates by default.
How can I tell Spring Boot to not detect and not use my custom ObjectMapper bean for its own internal workings?
The Simone Pontiggia answer is in the correct direction. You should create one #Primary bean, which Spring will use in its internals, and then to create your own ObjectMapper beans and autowired them using #Qualifier.
The problem here is that, creating default bean like:
#Bean
#Primary
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
Won't actually work as expected, because the Spring default ObjectMapper has additional configurations.
The correct way to create default ObjectMapper that will be used by spring, is:
#Bean
#Primary
public ObjectMapper objectMapper() {
return Jackson2ObjectMapperBuilder.json().build();
}
You can find more information about the Spring default ObjectMapper here: https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html under 79.3 Customize the Jackson ObjectMapper
Since I didn't want to touch Spring's default ObjectMapper, creating a #Primary ObjectMapper to shadow Spring's default ObjectMapper was out of the question.
Instead, what I ended up doing is creating a BeanFactoryPostProcessor which registers in Spring's context a custom, non primary ObjectMapper:
#Component
public class ObjectMapperPostProcessor implements BeanFactoryPostProcessor {
public static final String OBJECT_MAPPER_BEAN_NAME = "persistenceObjectMapper";
#Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) {
final AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(ObjectMapper.class, this::getCustomObjectMapper)
.getBeanDefinition();
// Leave Spring's default ObjectMapper (configured by JacksonAutoConfiguration)
// as primary
beanDefinition.setPrimary(false);
final AutowireCandidateQualifier mapperQualifier = new AutowireCandidateQualifier(PersistenceObjectMapper.class);
beanDefinition.addQualifier(mapperQualifier);
((DefaultListableBeanFactory) beanFactory).registerBeanDefinition(OBJECT_MAPPER_BEAN_NAME, beanDefinition);
}
private ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
As can be seen in the code above, I also assigned a qualifier to my custom ObjectMapper bean.
My qualifier is an annotation which is annotated with #Qualifier:
#Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface PersistenceObjectMapper {
}
I can then autowire my custom ObjectMapper using my custom annotation, like this:
#Repository
public class MyDao {
#Autowired
public MyDao(DataSource dataSource, #PersistenceObjectMapper ObjectMapper objectMapper) {
// constructor code
}
You can provide a standard ObjectMapper and your customized object mapper, and set the standard as #Primary.
Then gives your custom ObjectMapper a name and use it with #Qualifier annotation.
#Configuration
public class Config{
//This bean will be selected for rest
#Bean
#Primary
public ObjectMapper stdMapper(){
return new ObjectMapper();
}
//You can explicitly refer this bean later
#Bean("customObjectMapper")
public ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
Now you can reference your custom mapper
#Repository
#Transactional
public class MyDaoImpl implements MyDao {
#Autowired
#Qualifier("customObjectMapper")
ObjectMapper objectMapper
//Dao implementation...
}
#Resource("custonmObjectMapper") will do the same of #Autowired and #Qualifier together
You can create:
public class MapperUtils {
private static final ObjectMapper mapper = new ObjectMapper();
public static <T> T parseResponse(byte[] byteArrray, Class<T> parseType) throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(byteArrray, parseType);
}
}
ObjectMapper is thread-safe. However, some people discourage having single instance because of performance issues (Should I declare Jackson's ObjectMapper as a static field? ).

How do I get a reference to the Jackson Object Mapper in a jersey2 / hk2 application

I have a jersey2 application configured for JSON support via Jackson, adding
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
</dependency>
in the POM file and
public MyApplication() {
...
register(JacksonFeature.class)
...
}
in my application. Everything works, my resources get deserialized POJOs as arguments
#POST #Consumes(MediaType.APPLICATION_JSON)
public void blah(MyPojo p) {
...
}
Now one of thoese resources needs a reference to Jackson's ObjectMapper to do some deserialization on its own. I've tried doing something like
#Inject
public MyResource(#Context ObjectMapper mapper) {
...
}
or
#GET
public String foo(#Context ObjectMapper mapper) {
...
}
but in both cases the reference to mapper is null. How can I inject a reference to the ObjectMapper in my resources?
First there is no default ObjectMapper used by the Jackson provider. It doesn't use an ObjectMapper at all actually. It makes use of other Jackson APIs to handle the (de)serialization.
If you want to use/inject a single ObjectMapper instance, then you should just create a Factory for it
public class ObjectMapperFactory implements Factory<ObjectMapper> {
final ObjectMapper mapper = new ObjectMapper();
#Override
public ObjectMapper provide() {
return mapper;
}
#Override
public void dispose(ObjectMapper t) {}
}
Then bind it
register(new AbstractBinder(){
#Override
public void configure() {
bindFactory(ObjectMapperFactory.class)
.to(ObjectMapper.class).in(Singleton.class);
}
});
One thing should be noted is that any configuration of the ObjectMapper is not thread safe. So say you tried to configure it from your resource method, those operations are not thread safe.
Another thing to note with the Jackson provider, is that if we provide a ContextResolver, like mentioned by #Laurentiu L, then the Jackson provider will switch to using our ObjectMapper. In which case, if you want to use that same ObjectMapper, then you can look it up in the Factory. For example
public class ObjectMapperFactory implements Factory<ObjectMapper> {
private final Providers providers;
final ObjectMapper mapper = new ObjectMapper();
public ObjectMapperFactory(#Context Providers providers) {
this.providers = providers;
}
#Override
public ObjectMapper provide() {
ContextResolver<ObjectMapper> resolver = providers.getContextResolver(
ObjectMapper.class, MediaType.APPLICATION_JSON);
if (resolver == null) { return mapper; }
return resolver.getContext(null);
}
#Override
public void dispose(ObjectMapper t) {}
}
For the above to work (use a single ObjectMapper), you need to make sure to implement the ContextResolver<ObjectMapper>, and make sure to annotation the ContextResolver with the corresponding #Produces and #Consumes media types.
Aside from the JacksonFeature you need to register a ContextResolver for ObjectMapper.
Simple example from the Documentation at 9.1.4.2. Configure and register
#Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public MyObjectMapperProvider() {
defaultObjectMapper = createDefaultMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
private static ObjectMapper createDefaultMapper() {
final ObjectMapper result = new ObjectMapper();
result.configure(Feature.INDENT_OUTPUT, true);
return result;
}
// ...
}
Complete code example
available on Github
You will also need to register it
.register(MyObjectMapperProvider.class)

Jackson ObjectMapper using custom Serializers and Deserializers

I've got a class that configures a Jackson ObjectMapper. It adds in some custom serializers and deserializers for my object types as follows:
public class JsonMapperFactory {
public static ObjectMapper createObjectMapper() {
final SimpleModule module = new SimpleModule("customerSerializationModule", new Version(1, 0, 0, "static version"));
addCustomDeserializersTo(module);
addCustomSerializersTo(module);
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
return objectMapper;
}
private static void addCustomSerializersTo(final SimpleModule module) {
module.addSerializer(DateTime.class, new DateTimeSerializer());
}
private static void addCustomDeserializersTo(final SimpleModule objectMapper) {
objectMapper.addDeserializer(DateTime.class, new DateTimeDeserializer());
}
}
I've tested my customer serializers within their own test classes, so in my test of this JsonMapperFactory class, I'm trying to simply check that the ObjectMapper created has the expected serializers (or deserializers) This could be achieve by introspecting the ObjectMapper, but it doesn't seem to have any mechanisms to do this.
Does anyone know of a nice way to test that?
For deserializers, I have the following:
private void assertThatObjectMapperUsesCorrectDeserializer(final Class<?> typeClazz, final Class<?> deserializerClazz) throws JsonMappingException {
final DeserializationConfig deserializationConfig = this.objectMapper.getDeserializationConfig();
final JsonDeserializer<Object> deserializer = this.objectMapper.getDeserializerProvider().findTypedValueDeserializer(deserializationConfig, javaTypeFor(typeClazz), null);
assertThat(deserializer, is(instanceOf(deserializerClazz)));
}
private JavaType javaTypeFor(final Class<?> clazz) {
return TypeFactory.type(clazz); //deprecated method :(
}
Which is quite verbose and uses deprecated methods.
I'm yet to find a way to do a similar test for the serializers. So I've currently resorted to serializing an object and check it serializes correctly (essentially duplicating the serializer test)
Any ideas are very welcome.
From the answers & comments provided here, I recently redesigned the class to use builders for both the Module and the ObjectMapper. This allowed me to provide mocks and check that the correct (de)serializers were added to the module and then the module is registered to the object mapper as expected.
Object Mapper Builder:
public class ObjectMapperBuilder {
ObjectMapper mapper;
public ObjectMapperBuilder configure(final ObjectMapper mapper) {
this.mapper = mapper;
return this;
}
public ObjectMapperBuilder withModule(final Module module) {
this.mapper.registerModule(module);
return this;
}
public ObjectMapper build() {
return this.mapper;
}
}
Module Builder:
public class SimpleModuleBuilder {
SimpleModule module;
public SimpleModuleBuilder configure(final SimpleModule module) {
this.module = module;
return this;
}
public <X> SimpleModuleBuilder withSerializer(final Class<X> clazz, final JsonSerializer<X> serializer) {
this.module.addSerializer(clazz, serializer);
return this;
}
public <X> SimpleModuleBuilder withDeserializer(final Class<X> clazz, final JsonDeserializer<X> deserializer) {
this.module.addDeserializer(clazz, deserializer);
return this;
}
public SimpleModule build() {
return this.module;
}
}
And finally, the new JsonMapperFactory:
public class JsonMapperFactory {
public static ObjectMapper configureObjectMapper(final ObjectMapper mapper, final SimpleModule module) {
final SimpleModuleBuilder modulebuilder = new SimpleModuleBuilder();
final SimpleModule configuredModule = modulebuilder.configure(module)
.withSerializer(DateTime.class, new DateTimeSerializer())
.withDeserializer(DateTime.class, new DateTimeDeserializer())
.build();
final ObjectMapperBuilder objectMapperBuilder = new ObjectMapperBuilder();
return objectMapperBuilder.configure(mapper).withModule(configuredModule).build();
}
}
The factory method is still used within Spring configuration, but the configuration now instantiates the blank Module and ObjectMapper before providing them to the factory methods that then configure them.
If JsonDeserializer (and DateTimeDeserializer too) was an interface, you could easily "JMock" it, pass mocked instance to JsonMapperFactory#createObjectMapper and then expect exactly 1 invocation of your custom "serialize" method; e.g.
DateTimeSerializer serializer = context.mock(DateTimeSerializer.class);
DateTimeDeserializer serializer = context.mock(DateTimeDeserializer.class);
ObjectMapper mapper = JacksonMapperFactory.createObjectMapper(deserializer, serializer);
exactly(1).of(jsonDeserializer).serialize(myDateTime,
with(any(JsonGenerator.class),
with(any(SerializerProvider.class)))
Being a concrete class, you can instead define a new (test-scoped) De/Serializer that extends your custom DateTime(De)serializer, and simply count invocation on that:
private static class DateTimeDeserializerWithCounter extends DateTimeDeserializer {
public int counter = 0;
#Override
public DateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
counter++;
return super.deserialize(jsonParser, deserializationContext);
}
}
#Test
public void usageTest(){
//init mapper with the above DateTimeDeserializerWithCounter - see below
mapper.readValue("...", DateTime.class);
Assert.assertEquals(1, deserializer.counter);
}
Below a snapshot of a more "test-oriented" Factory:
//package visibility, to allow passing different De/Serializers while testing
static ObjectMapper createObjectMapper(JsonDeserializer deserializer, JsonSerializer serializer) {
final SimpleModule module = new SimpleModule("customerSerializationModule", new Version(1, 0, 0, "static version"));
module.addDeserializer(DateTime.class, deserializer);
module.addSerializer(DateTime.class, serializer);
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
return objectMapper;
}
//production method: no-args, as in the original version
public static ObjectMapper createObjectMapper() {
return createObjectMapper(new DateTimeDeserializer(), new DateTimeSerializer());
}
Hope that helps.

Categories

Resources