Enable json serialization of Multimap in Spring Boot Project - json

I want to serialize some google guava Multimap in a spring boot application.
public class SomeDTO {
#JsonProperty
Multimap<A, B> prop = HashMultimap.create();
}
Without using a customized jackson serializer, I get some result like
{
   "prop ":
   {
       "empty": false
   }
}
Which is definitley not what I seek to get. I thought of something like:
{
"nodes": {
"key0": [
{
"prop0": 2,
"prop1": 4
},
{
"prop0": 5,
"prop1": 6
}
],
"key1": [
{
"prop0": 23,
"prop1": 0
}
]
}
}
Adding
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-guava</artifactId>
<version>${jackson.version}</version>
</dependency>
to the pom.xml seems not sufficient... However, I'm just starting with this whole spring // pivotal universe, so I guess I miss something obvious.

The solution i came up with is simply adding a #Bean to my main #Configuration:
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
// More-obvious imports missing
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class Application extends SpringBootServletInitializer {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
protected final SpringApplicationBuilder configure(final SpringApplicationBuilder application) {
return application.sources(Application.class);
}
#Bean
ObjectMapper customizeJacksonConfiguration() {
ObjectMapper om = new ObjectMapper();
om.registerModule(new GuavaModule());
return om;
}
}
Afaik, the ObjectMapper Bean approach has one drawback: Everytime an ObjectMapper is created this way, all previous configuration gets thrown away.
If you want to add a module to jackson - instead of overriding previous configuration, this approach is better:
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
// More-obvious imports missing
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class Application extends SpringBootServletInitializer {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
protected final SpringApplicationBuilder configure(final SpringApplicationBuilder application) {
return application.sources(Application.class);
}
#Bean
public Module guavaModule() {
return new GuavaModule();
}
}

Related

How to rollback Rabbit-mq message after Junit test?

When you test your code, you send a message to rabbit-mq. How do you get the message back when the test is over?
public interface RabbitProducer {
String OUTPUT = "rabbitmq_producer_channel";
#Output(OUTPUT)
MessageChannel output();
}
public class SysGroupServiceImpl {
#Autowired
private RabbitProducer rabbitProducer;
#Override
public Result remove(Collection<? extends Serializable> idList) {
rabbitProducer.output().send(MessageBuilder.withPayload(idList)
.setHeader("x-delay", 5000)
.setHeader("MessageType", "GroupBatchDelete").build());
return Result.booleanResult(true);
}
}
#SpringBootTest
#Transactional
#Rollback
public class SysGroupServiceTest {
#Autowired
private SysGroupService sysGroupService;
#Test
void removeTest(){
sysGroupService.remove(Stream.of("1").collect(Collectors.toList()));
}
}
I use Spring Cloud Stream to be compatible with RabbitMQ, and all the relevant code is there.Is there a way to mock this out?I tried the following scheme, but due to X-dealy, I got this error:No exchange type x-delayed-message
<dependency>
<groupId>com.github.fridujo</groupId>
<artifactId>rabbitmq-mock</artifactId>
</dependency>
#Component
public class RabbitMqMock {
#Bean
public ConnectionFactory connectionFactory() {
return new CachingConnectionFactory(MockConnectionFactoryFactory.build());
}
}
I know little about mocks. Can mocks create an X-delay exchange?

A callback method to execute post JSON to object conversion in Jersey JAX RS

I am using Jersey rest services with Jackson API for conversion of JSON String to POJOs. All the member variables of the POJO class need to be validated. I already have a framework in place for validation.
What I want to know is if there is any callback method or mechanism which can call my validation API post the JSON to POJO conversion itself. Doing this would make my job easier as I will not have to call the API at all the places in my Rest service class.
public class MyPojoClass{
private int interestRateCode;
private String name;
//just edited
private List<TestDTO> testObjs;
//Psuedo code
//#PostDeserialization
public String callbackMethod(Object obj){
if(!ValidationAPI.validate(obj))
return "false";
}
}
The TestDTO:
public class TestDTO {
private int var1;
private String stringVar;
public TestDTO() {
System.out.println("This constructor does get called every time");
}
}
Is there any annotation like PostDeserialization to achieve this. This will help me to make every POJO class having only one callback method for validation.
The JSON I am passing is
{"interestRateCode": 101,"name": "T",
"testObjs": [
{"var1" :10, "stringVar": "Arunabh"},
{"var1" :15, "stringVar": "Hejib"}
]}
Anyone who can help me on this problem? Thanks for any help.
One thing you can do is use a Request Filter. In the filter you would:
Get the resource Method using the injected ResourceInfo
Get the entity class by traversing the Method Parameters and checking for the method parameter without any annotations. Unless you're using bean validation where #Valid is used next to the parameter, then the entity parameter always is the parameter with no annotations. This is how we determine the entity parameter. From the parameter, we get the class.
Get the entity objects from the request.
From the entity class, using from reflection, find the Method with the #PostDeserialization annotation.
Call the method using reflection.
Below is a complete test. The ValidationFilter is the class with previous mentioned steps.
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import static org.junit.Assert.assertEquals;
/**
* Run like any other JUnit test. A couple required dependencies.
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
* <version>${jersey2.version}</version>
* <scope>test</scope>
* </dependency>
* <dependency>
* <groupId>org.glassfish.jersey.media</groupId>
* <artifactId>jersey-media-json-jackson</artifactId>
* <version>${jersey2.version}</version>
* </dependency>
*/
public class PostDeserializationTest extends JerseyTest {
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface PostDeserialization {}
public static class ValidationError extends RuntimeException {
public ValidationError(String message) {
super(message);
}
}
public static class ValidationErrorMapper
implements ExceptionMapper<ValidationError> {
#Override
public Response toResponse(ValidationError ex) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(ex.getMessage())
.build();
}
}
public static class Bean {
public String value;
#PostDeserialization
public void validate() {
if (!"expected".equals(value)) {
throw new ValidationError("value must be 'expected'");
}
}
}
public static class ValidationFilter implements ContainerRequestFilter {
#Context
private ResourceInfo info;
#Override
public void filter(ContainerRequestContext request) throws IOException {
Class<?> entityClass = getEntityClass();
if (entityClass != null) {
final ContainerRequest cr = (ContainerRequest) request;
cr.bufferEntity();
final Object entity = cr.readEntity(entityClass);
findMethodAndValidate(entity);
}
}
private Class<?> getEntityClass() {
final Method rm = info.getResourceMethod();
final Annotation[][] paramAnnotations = rm.getParameterAnnotations();
for (int i = 0; i < paramAnnotations.length; i++) {
// entity parameters have no annotations.
if (paramAnnotations[i].length == 0) {
return rm.getParameterTypes()[i];
}
}
return null;
}
private void findMethodAndValidate(Object entity) {
final Method[] methods = entity.getClass().getMethods();
for (Method method: methods) {
if (method.isAnnotationPresent(PostDeserialization.class)) {
// validation method should take no parameters.
if (method.getParameterCount() != 0) {
throw new RuntimeException(
"Validation method must not have parameters.");
}
try {
method.invoke(entity);
} catch (InvocationTargetException ex) {
// if an exception happens during invocation,
// an InvocationException is thrown. We want the cause,
// expecting it to be a ValidationError.
Throwable cause = ex.getCause();
if (cause instanceof ValidationError) {
throw (ValidationError) cause;
} else {
throw new RuntimeException(ex);
}
} catch (Exception ex) {
throw new RuntimeException(
"Error calling validation method.", ex);
}
}
}
}
}
#Path("test")
public static class TestResource {
#POST
#Consumes("application/json")
public String post(Bean bean) {
return bean.value;
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig()
.register(TestResource.class)
.register(ValidationErrorMapper.class)
.register(ValidationFilter.class)
.register(new ExceptionMapper<Throwable>() {
#Override
public Response toResponse(Throwable t) {
t.printStackTrace();
return Response.serverError()
.entity(t.getMessage()).build();
}
});
}
#Test
public void testValidationError() {
final Bean bean = new Bean();
bean.value = "not expected";
final Response response = target("test")
.request()
.post(Entity.json(bean));
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
assertEquals("value must be 'expected'", response.readEntity(String.class));
}
#Test
public void testNoValidationError() {
final Bean bean = new Bean();
bean.value = "expected";
final Response response = target("test")
.request()
.post(Entity.json(bean));
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
assertEquals("expected", response.readEntity(String.class));
}
}

Spring Boot 1.2.5 & Jersey 2 ignore null fields

In Spring boot 1.2.5 with a Jersey 2 interface, how can I set the JSON marshaller to not include fields that have null values?
For example:
[
{
"created": 1433987509174,
"lastModified": 1433876475580,
"id": 1,
"example": "example1b"
},
{
"created": 1434502031212,
"lastModified": 1434502031212,
"id": 10000,
"example": "example1c"
},
{
"created": 1439151444176,
"lastModified": 1439151444176,
"id": 10011,
"example": null
}
]
The field "example": null should not be included in the json output at all, but here it is specifying it is null.
In my #SpringBootApplication class, I've tried adding:
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
converter.setObjectMapper(objectMapper);
return converter;
}
or
#Bean
#Primary
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
return builder;
}
or
#Primary
#Bean
public ObjectMapper mapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return objectMapper;
}
and/or adding #JsonSerialize(include = Inclusion.NON_NULL) to the Object itself
But it still produces the same response above with the "example": null included.
It was working on Spring 3.0.7 with #JsonSerialize(include=Inclusion.NON_NULL) but that no longer works now that I've ported to Spring Boot 1.2.5.
I believe I've followed the documentation http://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html#howto-customize-the-jackson-objectmapper and it's not working so I'm hoping someone might see something I'm missing? Thanks in advance!
Edit: Also just tried adding the class:
#Configuration
public class WebConfiguration extends WebMvcAutoConfiguration {
#Primary
#Bean
public ObjectMapper mapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return objectMapper;
}
}
Solution:
package com.my.spring;
import javax.ws.rs.ext.ContextResolver;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletProperties;
import org.springframework.context.annotation.Configuration;
import com.my.spring.service.rs.MyRestServiceImpl;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
#Configuration
public class JerseyConfiguration extends ResourceConfig {
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperContextResolver() {
mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
public JerseyConfiguration() {
register(new ObjectMapperContextResolver());
register(MyRestServiceImpl.class); // My jax-rs implementation class
property(ServletProperties.FILTER_FORWARD_ON_404, true); // Not needed for this non_null issue
}
}
I don't know about mixing the Spring way (of configuring the mapper) and how Jersey handles this. But the Jersey way to configure the ObjectMapper is through a ContextResolver, as seen in this answer.
Then register the ObjectMapperContextResolver with your Jersey configuration.
public JerseyConfig extends ResourceConfig {
public JerseyConfig() {
...
register(ObjectMapperContextResolver.class);
}
}
Or if you are package scanning, the #Provider annotation will pick up the class.

Ignoring incoming json elements in jax-rs

I'm wondering where to put the #JsonIgnoreProperties(ignoreUnknown = true) in a Java REST API. I have the following class:
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.map.ObjectMapper;
#JsonIgnoreProperties(ignoreUnknown = true)
public class QueuePayload {
private String message;
private String id;
public String getId() {
return this.id;
}
public String getMessage() {
return this.message;
}
public void setId(String id) {
this.id = id;
}
public void setMessage(String message) {
this.message = message;
}
public String serialize() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(this);
}
}
Which I use in a JAX-RS servlet like this:
import javax.jms.JMSException;
import javax.naming.NamingException;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
#Path("/v1")
#Produces(MediaType.APPLICATION_JSON)
public class ServiceApiV1 {
#GET
public Response getApiRoot() {
String result = "{\"notice\" : \"It is alive!\"}";
return Response.status(Status.OK).entity(result).build();
}
#POST
#Path("/update")
#Consumes(MediaType.APPLICATION_JSON)
public Response update(UpdatePayload message) {
return Response.status(Status.OK).entity(message).build();
}
}
When I post { "message" : "Test", "id" : "one" } it works like a charm, but when I post { "message" : "Test", "id" : "one", "sneaked" : "in" } I get:
SRVE0315E: An exception occurred:
com.ibm.ws.webcontainer.webapp.WebAppErrorReport:
javax.servlet.ServletException:
org.codehaus.jackson.map.exc.UnrecognizedPropertyException:
Unrecognized field "sneaked";
(Class test.QueuePayload), not marked as ignorable
at [Source: com.ibm.ws.webcontainer.srt.SRTInputStream#b7328421; line: 1, column: 7] (through reference chain: test.QueuePayload["sneaked"])
I though #JsonIgnoreProperties(ignoreUnknown = true) was designed exactly for this use case. I also tried without the property in the servlet and the various permutations. I checked this question and made sure to have imported the same API version in both classes.
What do I miss?
Update
Change the imported classed to codehouse (see above) and ran this test):
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
QueuePayload qp = new QueuePayload();
qp.setPayload("PayLoad");
qp.setQueueId("ID-of-Q");
qp.setStatus("Some Status");
System.out.println(qp.serialize());
ObjectMapper om = new ObjectMapper();
QueuePayload qp2 = om.readValue("{\"payload\":\"PayLoad\",\"queueId\":\"ID-of-Q\",\"newstatus\":\"New Status\"}",
QueuePayload.class);
System.out.println(qp2.serialize());
}
That worked. The servlet doesn't
Problem seems to be the server uses Jackson 1.x (codehaus). You are trying to use Jackson 2.x annotations (fasterxml). They don't interact. You can tell by the exception that it's a codehaus exception, meaning the older Jackson is actual used.
You can look in the server an try and find the exact version, or you can just try and use an random 1.x version (of course in a provided scope, to there's no conflict), like this one
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.13</version>
<scope>provided</scope>
</dependency>
Then use org.codehaus.jackson.annotate.JsonIgnoreProperties on the model class (not the resource class).

Spring Jersey REST: arrays with ONE element issue

I am using spring and jersey. Problem with this combo is when trying to create a json where an objects has List and there is only one element. If there are many, json is fine. Jersey "forgot" to add brackets [] on single elems.
As you can see, even tried to force it to use jackson (as I read in many tutorials), but ... same result:
<servlet>
<servlet-name>jersey-serlvet</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>org.codehaus.jackson.jaxrs;au.com.bikesquare.core.rest</param-value>
</init-param>
</servlet>
Then found this, and still nothing.
#Provider
#Component
#Produces ("application/json")
public class JsonJaxbResolver implements ContextResolver<JAXBContext> {
#Override
public JAXBContext getContext(Class<?> type) {
JSONConfiguration.MappedBuilder b = JSONConfiguration.mapped();
b.arrays("to");
try {
return new JSONJAXBContext(b.build(), EmailMessageTransferObject.class);
} catch (JAXBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
Server where I am trying to send my object (that contains list) captures this:
....
"to": {
"email": "aaa#bbb.ccc",
"name": "aaa"
}
....
Supposed to be:
"to": [
{
"email": "aaa#bbb.ccc",
"name": "aaa"
}
],
My class (parts of it):
#XmlRootElement
public class EmailMessageTransferObject {
...
#XmlElement
private List<EmailRecipientTransferObject> to;
...
}
Any ideas? Tnx
Found the answer later:
#Provider
#Produces ("application/json")
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class<?>[] types = {SendEmailMessageTransferObject.class};
public JAXBContextResolver() throws Exception {
this.context = new JSONJAXBContext(JSONConfiguration.mapped().arrays("to").build(), types);
}
#Override
public JAXBContext getContext(Class objectType) {
for (Class type : types) {
if (type == objectType) {
return context;
}
}
return null;
}
}
Client:
public MandrillRestMailImpl() {
ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getClasses().add(JAXBContextResolver.class);
client = Client.create(clientConfig);
client.addFilter(new LoggingFilter(System.out)); //todo: remove this for prod
}