Jax-rs ContextResolver for custom ObjectMapper - java

I am not sure how to register my custom objectMapper that I created below as a bean and inject it as dependency into other objects via constructor, or Autowire
#SpringBootApplication
public class DemoApplication {
#Bean
//how to register it as a bean here and inject wherever I need to via #Inject or #Autowire
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
private final ObjectMapper objectMapper = new ObjectMapper();
public ObjectMapperProvider() {
this.objectMapper.disable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
}
#Override
public ObjectMapper getContext(final Class<?> type) {
return objectMapper;
}
}

Be careful with that. You are mixing Jax-RS and Spring, but you have to know something: Spring does not implement fully the Jax-RS specification... The reason ? Spring MVC was developed about the same time as JAX-RS, and after JAX-RS was released, they never migrate to implement this (who would have anyway) ?
The best way to declare your own ObjectMapper with Spring would be the following:
#SpringBootApplication
public class DemoApplication {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// DO what you want;
return objectMapper;
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
}
Then, you can use #Autowired to inject your ObjectMapper in the class that needs it. (check this link if you want: Configuring ObjectMapper in Spring)
Hope it helps.

Related

Could not inject different beans with named qualifier in Micronaut

I have defined an ObjectMapper factory class like this:
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.micronaut.context.annotation.Factory;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
#Factory
public class MyObjectMapper {
#Singleton
#Named("jsonObjectMapper")
public ObjectMapper getJsonObjectMapper() {
return new ObjectMapper(new JsonFactory());
}
#Singleton
#Named("yamlObjectMapper")
public ObjectMapper getYamlObjectMapper() {
return new ObjectMapper(new YAMLFactory());
}
}
Then, on client class, I tried to inject them like this:
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
#Singleton
public class MyServiceImpl implements MyService {
private ObjectMapper jsonMapper;
private ObjectMapper yamlMapper;
#Inject
#Named("jsonObjectMapper")
public void setJsonMapper(ObjectMapper jsonMapper) {
this.jsonMapper = jsonMapper;
}
#Inject
#Named("yamlObjectMapper")
public void setYamlMapper(ObjectMapper yamlMapper) {
this.yamlMapper = yamlMapper;
}
...
My goal is to have jsonMapper to be injected by the bean with #Named("jsonObjectMapper") on MyObjectMapper class, and yamlMapper with #Named("yamlObjectMapper"). But, when I tried to debug, jsonMapper and yamlMapper had the same reference, which means they are injected by the same ObjectMapper. My question is how to inject 2 different beans for json and yaml mapper on Micronaut?
Thank you!
The injection qualified by name can be done with the #Named annotation used on the method argument, and not the method itself. It means that in your case you would have to move the #Named annotation to the setJsonMapper and setYamlMapper methods arguments.
#Singleton
public class MyServiceImpl {
private ObjectMapper jsonMapper;
private ObjectMapper yamlMapper;
#Inject
public void setJsonMapper(#Named("jsonObjectMapper") ObjectMapper jsonMapper) {
this.jsonMapper = jsonMapper;
}
#Inject
public void setYamlMapper(#Named("yamlObjectMapper") ObjectMapper yamlMapper) {
this.yamlMapper = yamlMapper;
}
// ...
}
Alternatively, you could use construction injection combined with the #Named annotation for each parameter. It allows you to mark both fields as private, just to make sure these objects are not re-assigned at the runtime.
#Singleton
public class MyServiceImpl {
private final ObjectMapper jsonMapper;
private final ObjectMapper yamlMapper;
public MyServiceImpl(
#Named("jsonObjectMapper") ObjectMapper jsonMapper,
#Named("yamlObjectMapper") ObjectMapper yamlMapper) {
this.jsonMapper = jsonMapper;
this.yamlMapper = yamlMapper;
}
// ...
}

Use Jackson Objectmapper configured by Spring boot in Hibernate

I want to configure Hibernate to use Jackson's Objectmapper created by Spring to map between json and entities. In the project I'm working on I already configured Jooq to use the Spring's ObjectMapper but I'm having trouble how to configure Hibernate to use it. The goal in the end is that both Jooq and Hibernate would use the same ObjectMapper.
I checked this article by Vlad. Unfortunately all the tips given in the article don't work for the project I'm working on.
Here's an example configuration I tried
#Configuration
public class HibernateConfiguration implements HibernatePropertiesCustomizer {
//Autowire Objectmapper created by Spring
#Autowired
ObjectMapper objectMapper;
#Override
public void customize(Map<String, Object> hibernateProperties) {
ObjectMapperSupplier objectMapperSupplier = () -> objectMapper;
// Below config doesn't work since Hibernate types creates it's own mapper
hibernateProperties.put("hibernate.types.jackson.object.mapper", objectMapperSupplier);
}
Also tried the same approach by adding the Objectmapper to hibernate-types.properties.
#Used by Hibernate but cannot get reference of Spring managed ObjectMapper since this is class is called outside of Spring's context.
hibernate.types.jackson.object.mapper=path.to.ObjectMapperSupplier
Another approach I used but it fails with a NullpointerException when converting from JSON to an entity in JsonTypeDescriptor class.
#Configuration
public class HibernateConfiguration implements HibernatePropertiesCustomizer{
#Autowired
ObjectMapper objectMapper;
#Override
public void customize(Map<String, Object> hibernateProperties) {
// Underlying implementation needs some JavaType or propertyClass, otherwise when converting
// from JSON we get a nullpointer.
var jsonBinaryType = new JsonBinaryType(objectMapper);
hibernateProperties.put("hibernate.type_contributors", (TypeContributorList) () ->
Collections.singletonList((typeContributions, serviceRegistry) ->
typeContributions.contributeType(jsonBinaryType)));
}
Below is the type declaration for entity super class.
// This makes Hibernate types create it's own mapper.
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
#MappedSuperclass
public abstract class Entity{
}
So, are there any possible solutions how I can hook up Spring managed ObjectMapper to Hibernate?
I finally figured this out, but it is kind of an creative solution...
TLDR: I have a bean that stores the Spring-configured objectMapper in a static field. A BeanFactoryPostProcessor ensures that this bean is initialized before Hibernate (types) tries to load / get the ObjectMapper.
hibernate.properties
hibernate.types.jackson.object.mapper=com.github.lion7.example.HibernateObjectMapperSupplier
HibernateObjectMapperSupplier.kt
package com.github.lion7.example
import com.fasterxml.jackson.databind.ObjectMapper
import com.vladmihalcea.hibernate.type.util.ObjectMapperSupplier
import org.springframework.beans.factory.config.BeanFactoryPostProcessor
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
import org.springframework.stereotype.Component
class HibernateObjectMapperSupplier : ObjectMapperSupplier {
override fun get(): ObjectMapper =
ObjectMapperHolder.objectMapper
}
#Component
class ObjectMapperHolder(objectMapper: ObjectMapper) {
companion object {
lateinit var objectMapper: ObjectMapper
}
init {
Companion.objectMapper = objectMapper
}
}
#Component
class ObjectMapperDependencyFixer : BeanFactoryPostProcessor {
override fun postProcessBeanFactory(beanFactory: ConfigurableListableBeanFactory) {
val beanDefinition = beanFactory.getBeanDefinition("entityManagerFactory")
val oldDependsOn = beanDefinition.dependsOn ?: emptyArray()
val newDependsOn = oldDependsOn + "objectMapperHolder"
beanDefinition.setDependsOn(*newDependsOn)
}
}
Same code as gist: https://gist.github.com/lion7/c8006b69a309e38183deb69124b888b5
A Java implementation.
#Component
public class HibernateObjectMapper implements Supplier<ObjectMapper> {
private static ObjectMapper objectMapper;
#Autowired
public void setObjectMapper(ObjectMapper objectMapper) {
HibernateObjectMapper.objectMapper = objectMapper;
}
#Override
public ObjectMapper get() {
return objectMapper;
}
}
If you define your own JPA beans, simply add #DependsOn("hibernateObjectMapper") to their config. Otherwise you need a BeanPostProcessor to add the dependency to the autoconfigured bean:
#Component
class HibernateBeanDependencyProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) {
BeanDefinition beanDefinition = factory.getBeanDefinition("entityManagerFactory");
String[] dependsOn = beanDefinition.getDependsOn();
dependsOn = dependsOn == null ? new String[]{} : dependsOn;
String[] newDependsOn = new String[dependsOn.length + 1];
System.arraycopy(dependsOn, 0, newDependsOn, 1, dependsOn.length);
newDependsOn[0] = "hibernateObjectMapper";
beanDefinition.setDependsOn(newDependsOn);
}
}
As for the property, hibernate.types.* don't work when set programmatically. The library looks directly in the hibernate.properties, hibernate-types.properties, and application.properties files.
I think that I've found solution to do it programmatically (without magic with fixing dependency graph).
HibernateConfiguration.kt
#Configuration(proxyBeanMethods = false)
class HibernateConfiguration {
#Bean
fun hibernatePropertiesCustomizer(
objectMapper: ObjectMapper // Thanks to that Spring can create correct dependency graph
): HibernatePropertiesCustomizer =
HibernatePropertiesCustomizer { hibernateProperties ->
HibernateObjectMapperSupplier.objectMapper = objectMapper
hibernateProperties["hibernate.types.jackson.object.mapper"] = HibernateObjectMapperSupplier::class.qualifiedName
}
}
HibernateObjectMapperSupplier.kt
class HibernateObjectMapperSupplier : Supplier<ObjectMapper> {
override fun get(): ObjectMapper {
return objectMapper
}
companion object {
lateinit var objectMapper: ObjectMapper
}
}
System.getProperties().put(
Configuration.PropertyKey.JACKSON_OBJECT_MAPPER.getKey(),
MyObjectMapperSupplier.class.getName()
);

Apache CXF (JAX-RS) with Spring Javaconfig and Jackson

How to setup Apache CXF with JAX-RS Spring Javaconfig and Jackson?
I have read
http://cxf.apache.org/docs/springboot.html#SpringBoot-SpringBootCXFJAX-RSStarter and
http://cxf.apache.org/docs/jax-rs-data-bindings.html#JAX-RSDataBindings-Jackson
It advises to use
<jaxrs:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider"/>
</jaxrs:providers>
but I want to stay away from XML and use plain javaconfig.
My current configuration is
#SpringBootApplication
public class Application {
#Autowired
private Bus bus;
public static void main(String[] args) {
SpringApplication.run(Application .class, args);
}
#Bean
public Server rsServer() {
JAXRSServerFactoryBean endpoint = new JAXRSServerFactoryBean();
endpoint.setBus(bus);
endpoint.setAddress("/");
endpoint.setServiceBeans(Arrays.<Object>asList(new MyService()));
return endpoint.create();
}
}
Andpoint defined in my class MyService works, but when he is invoked I am getting message
org.apache.cxf.jaxrs.utils.JAXRSUtils : No message body writer has
been found for class MyServiceResponse,
ContentType: application/json;charset=UTF-8
You need to add a json Provider bean. This will act as messageProvider. Your Application class should look like this.
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider
#SpringBootApplication
public class Application {
#Autowired
private Bus bus;
public static void main(String[] args) {
SpringApplication.run(Application .class, args);
}
#Bean
public Server rsServer() {
List<? extends Object> providers = new ArrayList<>();
providers.add(getJsonProvider());
JAXRSServerFactoryBean endpoint = new JAXRSServerFactoryBean();
endpoint.setProviders(providers);
endpoint.setBus(bus);
endpoint.setAddress("/");
endpoint.setServiceBeans(Arrays.asList(new MyService()));
return endpoint.create();
}
#Bean
public JacksonJsonProvider getJsonProvider() {
new JacksonJsonProvider();
}
}

JAX-RS, Jackson and JodaTime: not working automatic configuration

I'm trying to create a simple REST service with JAX-RS (Jersey), without using Spring. And I'm using Joda as date fields in my entity.
To configure automatic json mapping, I create a JsonMapperProvider, where I add JodaModule:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JsonMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper objectMapper;
public JsonMapperProvider() {
objectMapper = new ObjectMapper();
objectMapper.registerModule(new JodaModule());
}
#Override
public ObjectMapper getContext(Class<?> arg0) {
return objectMapper;
}
}
This is my Resource class:
#Path("users")
public class UserController {
#Inject
private UserService userService;
#GET
#Path("/{id}")
#Produces(MediaType.APPLICATION_JSON)
public User getUserById(#PathParam("id") Long id) {
return userService.findById(id);
}
}
And I'm using a "no web.xml" configuration, with this class:
#ApplicationPath("api")
public class RestApplication extends ResourceConfig {
}
But it doesn't work... the LocalDate field in User entity is always returned empty.
The only workaround I found is to register all the components (including JacksonFeature class from jersey-media-json-jackson) in the ResourceConfig class, like this:
#ApplicationPath("api")
public class RestApplication extends ResourceConfig {
public RestApplication() {
super(
UserController.class,
JsonMapperProvider.class,
JacksonFeature.class
);
}
}
Is there another solution to this problem? I'd rather not to register all my services and other stuff in this class manually...
No, this is how it's supposed to work. You can also override the methods in javax.ws.rs.core.Application instead of extending ResourceConfig

How to hook Jackson ObjectMapper with Guice / Jersey

I can't seem to get my Jackson ObjectMapper Module registered correctly.
I'm using a Guice + Jersey + Jackson (FasterXML) stack.
I've followed how to customise the ObjectMapper based on various question here. In particular, I have a ContextResolver declared, marked as an #javax.ws.rs.ext.Provider and a #javax.inject.Singleton.
I have a GuiceServletContextListener along the lines of:
#Override
protected Injector getInjector() {
Injector injector = Guice.createInjector(new DBModule(dataSource),
new ServletModule()
{
#Override
protected void configureServlets() {
// Mapper
bind(JacksonOMP.class).asEagerSingleton();
// ...
Map<String, String> initParams = new HashMap<String, String>();
initParams.put("com.sun.jersey.config.feature.Trace",
"true");
initParams.put("com.sun.jersey.api.json.POJOMappingFeature", "true");
serve("/services/*").with(
GuiceContainer.class,
initParams);
}
});
return injector;
}
Mapper defined
import javax.inject.Singleton;
import javax.ws.rs.Produces;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
#Provider
#Singleton
#Produces
public class JacksonOMP implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> aClass) {
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());
return mapper;
}
}
However - with this configuration alone, getContext() is never called, so the mapper is never registered. I'm stuck in a typical guice--annotations-mystery, where it's practically untraceable to what I'm actually supposed to be doing. Spring users just report registering the component, and the container just picks it up.
This answer talks about overriding my own javax.ws.rs.core.Application implementation. However, this looks hard-wired in the jersey-guice impementation of GuiceContainer to be DefaultResourceConfig():
#Override
protected ResourceConfig getDefaultResourceConfig(Map<String, Object> props,
WebConfig webConfig) throws ServletException {
return new DefaultResourceConfig();
}
Am I supposed subclass GuiceContainer here? Or is there some other magic annotation that I'm missing?
This seems a fairly common thing to want to do - I'm surprised at how hard it's proving to do with this guice combination.
I'm stuck in a typical guice--annotations-mystery, where it's
practically untraceable to what I'm actually supposed to be doing.
Spring users just report registering the component, and the container
just picks it up.
You really should read excellent Guice documentation. Guice is very easy to use, it has very small number of basic concepts. Your problem is in that you mixed Jersey JAX-RS dependency injection and Guice dependency injection. If you are using GuiceContainer then you declare that you will be using Guice for all of your DI, so you have to add bindings with Guice and not with JAX-RS.
For instance, you do not need ContextResolver, you should use plain Guice Provider instead:
import com.google.inject.Provider;
public class ObjectMapperProvider implements Provider<ObjectMapper> {
#Override
public ObjectMapper get() {
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());
return mapper;
}
}
Then you should add corresponding binding to your module:
bind(ObjectMapper.class).toProvider(ObjectMapperProvider.class).in(Singleton.class);
This will bind ObjectMapper, but it is not enough to use Jersey with Jackson. You will need some kind of MessageBodyReader/MessageBodyWriter, e.g. JacksonJsonProvider. You will need another provider for it:
public class JacksonJsonProviderProvider implements Provider<JacksonJsonProvider> {
private final ObjectMapper mapper;
#Inject
JacksonJsonProviderProvider(ObjectMapper mapper) {
this.mapper = mapper;
}
#Override
public JacksonJsonProvider get() {
return new JacksonJsonProvider(mapper);
}
}
Then bind it:
bind(JacksonJsonProvider.class).toProvider(JacksonJsonProviderProvider.class).in(Singleton.class);
This is all you need to do - no subclassing is needed.
There is a room for code size optimization though. If I were you I would use #Provides-methods:
#Provides #Singleton
ObjectMapper objectMapper() {
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());
return mapper;
}
#Provides #Singleton
JacksonJsonProvider jacksonJsonProvider(ObjectMapper mapper) {
return new JacksonJsonProvider(mapper);
}
These methods should be added to one of your modules, e.g. to anonymous ServletModule. Then you won't need separate provider classes.
BTW, you should use JerseyServletModule instead of plain ServletModule, it provides a lot of useful bindings for you.

Categories