I have a problem in my webservice controller, due to jackson's serialisation of a third party object.
java.lang.IllegalArgumentException: Conflicting setter definitions for
property "X": ThirdPartyClass#setX(1 params) vs ThirdPartyClass#setX(1
params)
I've read that you can solve it thanks to MixIn annotation.
In my controller i'm giving a list, i'd like to know if there is a way to automatically define somewhere the use of the MixInAnnotation ?
If i had to do return a String instead of objects, i'd do something like that:
ObjectMapper mapper = new ObjectMapper();
mapper.getSerializationConfig().addMixInAnnotations(xxx);
return mapper.writeValueAsString(myObject);
Nevertheless, my controller is giving List:
#RequestMapping(method = RequestMethod.GET)
public #ResponseBody List<MyObject> getMyObjects
and several times returning MyObject in other methods, and so i'd like to declare only one time the use of the MixInAnnotation for jackson serialisation ?
Thank you,
RoD
I suggest that you use the "Spring Way" of doing this by following the steps provided in the Spring Docs.
If you want to replace the default ObjectMapper completely, define a #Bean of that type and mark it as #Primary.
Defining a #Bean of type Jackson2ObjectMapperBuilder will allow you to customize both default ObjectMapper and XmlMapper (used in MappingJackson2HttpMessageConverter and MappingJackson2XmlHttpMessageConverter respectively).
Another way to customize Jackson is to add beans of type com.fasterxml.jackson.databind.Module to your context. They will be registered with every bean of type ObjectMapper, providing a global mechanism for contributing custom modules when you add new features to your application.
Basically this means that if you simply register a Module as a bean with the provided mixin-settings you should be all set and there will be no need to define your own ObjectMapper or to alter the HttpMessageConverters.
So, in order to do this, i customised the Jackson JSON mapper in Spring Web MVC.
Custom mapper:
#Component
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
this.addMixInAnnotations(Target.class, SourceMixIn.class);
}
}
Register the new mapper at start up of spring context:
#Component
public class JacksonInit {
#Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
#Autowired
private CustomObjectMapper objectMapper;
#PostConstruct
public void init() {
List<HttpMessageConverter<?>> messageConverters = requestMappingHandlerAdapter.getMessageConverters();
for (HttpMessageConverter<?> messageConverter : messageConverters) {
if (messageConverter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter m = (MappingJackson2HttpMessageConverter) messageConverter;
m.setObjectMapper(objectMapper);
}
}
}
}
Thanks to that, i didn't modify my WebService Controller.
Related
I'm having the following code:
#Data
#Validated
#ConfigurationProperties
public class Keys {
private final Key key = new Key();
#Data
#Validated
#ConfigurationProperties(prefix = "key")
public class Key {
private final Client client = new Client();
private final IntentToken intentToken = new IntentToken();
private final Intent intent = new Intent();
private final OAuth oauth = new OAuth();
private final ResourceToken resourceToken = new ResourceToken();
#Valid #NotNull private String authorization;
#Valid #NotNull private String bearer;
...
}
}
That is an instance representing a properties file such as:
key.authorization=Authorization
key.bearer=Bearer
..
As I can have different sources for the properties (properties file, MongoDB, etc), I have a client that inherit from Keys as follow:
Properties files source
#Component
#Configuration
#Primary
#PropertySource("classpath:${product}-keys.${env}.properties")
//#JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class CustomerKeysProperties extends Keys {
}
Mongo source
#Data
#EqualsAndHashCode(callSuper=true)
#Component
//#Primary
#Document(collection = "customerKeys")
public class CustomerKeysMongo extends Keys {
#Id
private String id;
}
I just select the source I want to use annotating the class with #Primary. In the example above, CustomerKeysProperties is the active source.
All this work fine.
The issue I have is when I try to convert an instance of CustomerKeysProperties into JSON, as in the code below:
#SpringBootApplication
public class ConverterUtil {
public static void main(String[] args) throws Exception {
SpringApplication.run(ConverterUtil.class, args);
}
#Component
class CustomerInitializer implements CommandLineRunner {
#Autowired
private Keys k;
private final ObjectMapper mapper = new ObjectMapper();
#Override
public void run(String... args) throws Exception {
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
//mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
String jsonInString = mapper.writeValueAsString(k);
System.out.println(jsonInString);
}
}
}
While k contains all the properties set, the conversion fails:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: x.client.customer.properties.CustomerKeysProperties$$EnhancerBySpringCGLIB$$eda308bd["CGLIB$CALLBACK_0"]->org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor["advised"]->org.springframework.aop.framework.ProxyFactory["targetSource"]->org.springframework.aop.target.SingletonTargetSource["target"]->x.client.customer.properties.CustomerKeysProperties$$EnhancerBySpringCGLIB$$4fd6c568["CGLIB$CALLBACK_0"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191)
And if I uncomment
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
as suggested in the logs, I have an infinite loop happening in Jackson causing a stackoverflow:
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
..
Questions
At the end, I just want to provide an Util class than can convert a properties file in a JSON format that will be stored in MongoDB.
How can I solve this problem ?
Without passing through the object above, how can I transform a properties file into JSON ?
Can I save an arbitrary Java bean in MongoDB, with the conversion to JSON automagically done ?
The answer to any of the 3 questions above would be helpful.
Notes
To be noted that I use lombok. Not sure if this is the problem.
Another guess is that I'm trying to serialize a Spring managed bean and the proxy it involve cause jackson to not be able to do the serialization ? If so, what can be the turn-around ?
Thanks!
So found the problem:
jackson can't process managed bean.
The turn around was
try (InputStream input = getClass().getClassLoader().getResourceAsStream("foo.properties")) {
JavaPropsMapper mapper = new JavaPropsMapper();
Keys keys = mapper.readValue(input, Keys.class);
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
String res = ow.writeValueAsString(keys);
System.out.println(res);
} catch (IOException e) {
e.printStackTrace();
}
where Keys was the Spring managed bean I was injecting.
And:
JavaPropsMapper come from:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-properties</artifactId>
</dependency>
I'm testing a service layer and not sure how to mock ObjectMapper().readValue in that class. I'm fairly new to mockito and could figure out how to do it.
The following is my code,
service.java
private configDetail fetchConfigDetail(String configId) throws IOException {
final String response = restTemplate.getForObject(config.getUrl(), String.class);
return new ObjectMapper().readValue(response, ConfigDetail.class);
}
ServiceTest.java
#Test
public void testgetConfigDetailReturnsNull() throws Exception {
restTemplate = Mockito.mock(restTemplate.class);
Service service = new Service();
Config config = Mockito.mock(Config.class);
ObjectMapper objMapper = Mockito.mock(ObjectMapper.class);
Mockito.doReturn("").when(restTemplate).getForObject(anyString(), eq(String.class));
Mockito.doReturn(configDetail).when(objMapper).readValue(anyString(),eq(ConfigDetail.class));
assertEquals(configDetail, service.getConfigDetail("1234"));
}
I get the following results when I run this test,
com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
at [Source: (String)""; line: 1, column: 0]
Posting ServiceTest.Java here
#RunWith(MockitoJUnitRunner.class)
public class ConfigServiceTest {
#Mock
private ConfigPersistenceService persistenceService;
#InjectMocks
private ConfigService configService;
#Mock
ConfigDetail configDetail;
#Mock
private RestTemplate restTemplate;
#Mock
private ObjectMapper objMapper;
#Mock
private Config config;
#Test
public void testgetConfigDetailReturnsNull() throws Exception {
ObjectMapper objMapper = Mockito.mock(ObjectMapper.class);
Mockito.doReturn(ucpConfig).when(persistenceService).findById("1234");
Mockito.doReturn("").when(restTemplate).getForObject(anyString(), eq(String.class));
Mockito.when((objMapper).readValue(“”,ConfigDetail.class)).thenReturn(configDetail);
assertEquals(ConfigDetail, ConfigService.getConfigDetail("1234"));
}
}
With your current Service class it would be difficult to mock ObjectMapper, ObjectMapper is tightly coupled to fetchConfigDetail method.
You have to change your service class as follows to mock ObjectMapper.
#Service
public class MyServiceImpl {
#Autowired
private ObjectMapper objectMapper;
private configDetail fetchConfigDetail(String configId) throws IOException {
final String response = restTemplate.getForObject(config.getUrl(), String.class);
return objectMapper.readValue(response, ConfigDetail.class);
}
}
Here what I did is instead of creating objectMapper inside the method I am injecting that from outside (objectMapper will be created by Spring in this case)
Once you change your service class, you can mock the objectMapper as follows.
ObjectMapper mockObjectMapper = Mockito.mock(ObjectMapper.class);
Mockito.when(mockObjectMapper.readValue(anyString(), any(ConfigDetail.class)).thenReturn(configDetail);
Problem is with the this line where you are mocking the call to objectmapper.
Mockito.when((objMapper).readValue(“”,ConfigDetail.class)).thenReturn(configDetail);
Correct syntax is
Mockito.when(objMapper.readValue(“”,ConfigDetail.class)).thenReturn(configDetail);
Notice the bracket position. When using Spy or Verify, the bracket position is diff. then when using when-then syntax.
Mocking objects created in a SUT is IMO the single biggest limitation of mockito. Use jmockit or powerMock or checkout the offical mockito way of handling this. https://github.com/mockito/mockito/wiki/Mocking-Object-Creation
I'm trying to develop a small Spring MVC application, where i'd like User object to initialize from the beginning of each session.
I have the User class
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyUser implements User {
// private fields
// getters and setters
public void fillByName(String username) {
userDao.select(username);
}
}
And i want to initialize MyUser object once Spring Security recognize the user, in Interceptor Class (Btw, is it a good practice?)
public class AppInterceptor extends HandlerInterceptorAdapter {
#Autowired
MyUser user;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (!(auth instanceof AnonymousAuthenticationToken)) {
user.fillByName(auth.getName());
}
return true;
}
}
So, when Controller handles the request, there is already initialized session scoped User class. But when i try to serialize MyUser object with Jackson, it just doesnt't work:
#RequestMapping("/")
public String launchApp(ModelMap model) {
ObjectMapper mapper = new ObjectMapper();
try {
System.out.println(user.getUsername()); // Works good!
model.addAttribute("user", mapper.writeValueAsString(user)); // Doesn't work
} catch (JsonProcessingException e) {
// #todo log an error
}
return "app/base";
}
As you can see, MyUser object getters work good from the Controller class, but Jackson - doesn't.
When i remove #Scope annotation from User object, Jackson serialization start working.
Obviously, scoped proxy bean and singleton Controller class are the problem
But how i can fix it?
--
UPDATE
Looks like i'm first who came across this :)
Maybe it is bad architecture? Should i create a new instance of MyUser class in the Controller? What is the common practice?
The reason for this is that your proxyMode is set to TARGET_CLASS, which instructs Spring to create a class-based proxy for your class (essentially a new class that implements the interface of the original class, but might not be compatible with the way ObjectMapper converts objects into string).
You can solve this by telling ObjectMapper which class signatures to use when writing the object by using writerFor as such:
mapper.writerFor(User.class).writeValueAsString(user)
Or if you already have a custom writer, use forClass, like this:
writer.forClass(User.class).writeValueAsString(user)
One way that I can think of is to add yet another wrapper:
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyScopedUser implements User {
private MyUser myUser;
// private fields
// getters and setters
public void fillByName(String username) {
userDao.select(username);
}
public MyUser getMyUser() {
return this.myUser;
}
}
So now your MyScopedUser is a scoped proxy, but the core user is a normal class. You can get the user out and marshal later on:
mapper.writeValueAsString(scopeUser.getMyUser())
I started with a service that consumes and produces output in JSON. I use the resteasy-jackson-provider for (de)marshalling which takes its information from the class description. After a while I was asked to add XML as MediaType. So I annotated my DTOs with JAXB annotations and added the resteasy-jaxb-provider. As a result, I observed that the produced JSON output derives from the JAXB annotations which differs from the original format.
I am on RestEasy Version 3.0.4. As described I use the following providers
resteasy-jackson-provider
resteasy-axb-provider.
resteasy-jettison-provider, because I integrated RestEasy into Spring and this provider is a transitive dependency.
I got aware of the problem when I
used XmlElementWrapper for lists and when
I wrote a custom XmlAdapter which serializes a complex data structure Map<String, List<String>>. Requests with XML MediaType are fine. Requests with JSON MediaType cause an exception. Jackson seems to exploit the XmlAdapter for further information. This was not the case before. Jackson was able to marshall the Map without the JAXB annotations.
org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "customer" (Class x.y.z.OptionalParametersMapType), not marked as ignorable
at [Source: org.apache.catalina.connector.CoyoteInputStream#77119553; line: 1, column: 131] (through reference chain: x.y.z.Request["optional"]->x.y.zOptionalParametersMapType["customer"]
)
at org.codehaus.jackson.map.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:53)
at org.codehaus.jackson.map.deser.StdDeserializationContext.unknownFieldException(StdDeserializationContext.java:267)
at org.codehaus.jackson.map.deser.std.StdDeserializer.reportUnknownProperty(StdDeserializer.java:673)
at org.codehaus.jackson.map.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:659)
at org.codehaus.jackson.map.deser.BeanDeserializer.handleUnknownProperty(BeanDeserializer.java:1365)
at org.codehaus.jackson.map.deser.BeanDeserializer._handleUnknown(BeanDeserializer.java:725)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:703)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
at org.codehaus.jackson.xc.XmlAdapterJsonDeserializer.deserialize(XmlAdapterJsonDeserializer.java:59)
at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:299)
at org.codehaus.jackson.map.deser.SettableBeanProperty$FieldProperty.deserializeAndSet(SettableBeanProperty.java:579)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:697)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
at org.codehaus.jackson.map.ObjectMapper._readValue(ObjectMapper.java:2704)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1315)
at org.codehaus.jackson.jaxrs.JacksonJsonProvider.readFrom(JacksonJsonProvider.java:419)
So, how can I prevent RestEasy from using the JAXB annotations for marshalling to and from JSON?
Here is the request class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "domainRecommendationRequest")
public class Request {
#XmlJavaTypeAdapter(OptionalParametersXmlAdapter.class)
private Map<String, List<String>> optional = new HashMap<>();
}
Here is the XmlAdapter:
#Override
public class OptionalParametersXmlAdapter extends XmlAdapter<OptionalParametersMapType, Map<String, List<String>>> {
public OptionalParametersMapType marshal(Map<String, List<String>> v) throws Exception {
OptionalParametersMapType result = new OptionalParametersMapType();
List<OptionalParameterItemType> optionalParameterItemTypes = new ArrayList<>();
Set<String> keySet = v.keySet();
for (String parameterName : keySet) {
OptionalParameterItemType item = new OptionalParameterItemType();
item.name = parameterName;
item.values = v.get(parameterName);
optionalParameterItemTypes.add(item);
}
result.parameter = optionalParameterItemTypes;
return result;
}
}
Here is the wrapper for the map:
public class OptionalParametersMapType {
public List<OptionalParameterItemType> parameter = new ArrayList<>();
}
Here is the actual map entry item:
public class OptionalParameterItemType {
#XmlAttribute
public String name;
#XmlElementWrapper(name = "values")
#XmlElement(name = "value")
public List<String> values = new ArrayList<>();
}
This is what I expect in the JSON request:
{"optional":{"customer":["Mike"]}}
As you can see, I do intend to have a different format in XML.
The problem is resteasy-jackson-provider depends on jackson-module-jaxb-annotations, which is used to map JAXB annotations/annotated classes to JSON. Now in a normal explicit use of ObjectMapper, in order to make use of this module, we would need to explicitly register this module like (See here)
ObjectMapper objectMapper = new ObjectMapper();
JaxbAnnotationModule module = new JaxbAnnotationModule();
objectMapper.registerModule(module);
-- OR --
AnnotationIntrospector introspector = new JaxbAnnotationIntrospector();
objectMapper.setAnnotationIntrospector(introspector);
That being said, it appears (not confirmed with any facts, but looks probable) that when the ObjectMapper is being created for your serialization, when the JAXB annotations are noticed, the module is automatically registered.
I don't know of any possible annotations we can use to stop this, but one way to solve this problem is to create a ContextResolver for the ObjectMapper, where we don't register the JAXB module.
#Provider
public class ObjectMapperContextResolver
implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> type) {
return new ObjectMapper();
}
}
Once we register that with our JAX-RS application, it will be the context resolver used to get the ObjectMapper. We could configure the ObjectMapper further, but this is just an example. Test it and it works as expected.
I have a really simple rest web service returning a list of questions. This code works as expected when the number of questions returned are greater than zero. But if the server returns an empty json array like [], JAXB creates a list with one question instance where all fields are set to null!
I'm new to both Jersey and JAXB so I don't know whether I haven't configured it correctly or whether this is a known problem. Any tips?
Client configuration:
DefaultApacheHttpClientConfig config = new DefaultApacheHttpClientConfig();
config.getProperties().put(DefaultApacheHttpClientConfig.PROPERTY_HANDLE_COOKIES, true);
config.getClasses().add(JAXBContextResolver.class);
//config.getClasses().add(JacksonJsonProvider.class); // <- Jackson causes other problems
client = ApacheHttpClient.create(config);
JAXBContextResolver:
#Provider
public final class JAXBContextResolver implements ContextResolver<JAXBContext> {
private final JAXBContext context;
private final Set<Class> types;
private final Class[] cTypes = { Question.class };
public JAXBContextResolver() throws Exception {
this.types = new HashSet(Arrays.asList(cTypes));
this.context = new JSONJAXBContext(JSONConfiguration.natural().build(), cTypes);
}
#Override
public JAXBContext getContext(Class<?> objectType) {
return (types.contains(objectType)) ? context : null;
}
}
Client code:
public List<Question> getQuestionsByGroupId(int id) {
return digiRest.path("/questions/byGroupId/" + id).get(new GenericType<List<Question>>() {});
}
The Question class is just a simple pojo.
I know this is not exactly an answer to your question, but I choosed to use GSON on top of jersey, for my current projects. (and I try to avoid JAXB as much as possible), and I found it very easy and resilient.
You just have to declare
#Consumes(MediaType.TEXT_PLAIN)
or
#Produces(MediaType.TEXT_PLAIN)
or both, and use the GSON marshaller/unmarshaller, and work with plain Strings. Very easy to debug, unittest too...
Using Jackson may help.
See org.codehaus.jackson.map.ObjectMapper and org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion.NON_EMPTY
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
public class SampleContextResolver implements ContextResolver<ObjectMapper>
{
#Override
public ObjectMapper getContext(Class<?> type)
{
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationConfig(mapper.getSerializationConfig()
.withSerializationInclusion(JsonSerialize.Inclusion.NON_EMPTY)
}
}