Spring Jersey REST: arrays with ONE element issue - json

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
}

Related

spring-rabbit JSON deserialization for ArrayList contents

I am using Spring-boot with rabbitmq with JSON message serialization. Replies using the Direct-Reply-to feature cannot deserialize my classes inside the java.util.List container.
Using my debugger in Jackson2JsonMessageConverter.fromMessage(), the MessageProperties states the __TypeID__ is correctly set to java.util.ArrayList. However the __ContentTypeId__ is java.lang.Object is incorrect as I would be expecting FooDto (I assume...).
The exception message is:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to FooDto
Please note, I am using spring-rabbit 1.7.3 and not v2.0 so cannot use the ParameterizedTypeReference for rabbitTemplate.convertSendAndReceiveAsType() method.
I have attempted to use the DefaultClassMapper and the DefaultJackson2JavaTypeMapper (with TypePrecedence tested under both TYPE_ID and INFERRED) without success:
private DefaultJackson2JavaTypeMapper classMapper() {
final DefaultJackson2JavaTypeMapper classMapper = new DefaultJackson2JavaTypeMapper();
final Map<String, Class<?>> idClassMapping = new HashMap<>();
idClassMapping.put(FooDto.class.getSimpleName(), FooDto.class);
classMapper.setIdClassMapping(idClassMapping);
return classMapper;
}
The exception is now:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to FooDto
My workaround so far has been to use arrays of the raw types i.e. FooDto[].
Library versions:
- spring-boot 1.5.6
- RabbitMQ: 3.7.4
- spring-rabbit 1.7.3
Maven pom.xml:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
</dependencies>
Effective Pom:
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-amqp</artifactId>
<version>1.7.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.7.3.RELEASE</version>
</dependency>
RabbitMQ configuration:
#Configuration
#EnableRabbit
public class MessagingConfiguration implements ShutdownListener {
// FIXME: List<FooDto> in the direct-reply response contains ArrayList<Object> due to __ContentTypeID__ == SimpleObject/Object . __TypeID__ is correctly ArrayList
#Bean
public List<Declarable> bindings() {
final List<Declarable> declarations = new ArrayList<>();
final FanoutExchange exchange = new FanoutExchange("fx", true, false);
final Queue queue = QueueBuilder.durable("orders").build();
declarations.add(exchange);
declarations.add(queue);
declarations.add(BindingBuilder.bind(queue).to(exchange));
return declarations;
}
// #Bean
// public DefaultClassMapper classMapper() {
// DefaultClassMapper classMapper = new DefaultClassMapper();
// Map<String, Class<?>> idClassMapping = new HashMap<>();
// idClassMapping.put("FooDto", FooDto.class);
// java.util.List<FooDto>
// classMapper.setIdClassMapping(idClassMapping);
// return classMapper;
// }
//
// #Bean
// public DefaultClassMapper classMapper() {
// final DefaultClassMapper typeMapper = new DefaultClassMapper();
// // typeMapper.setDefaultType(new ArrayList<FooDto>().getClass());
// typeMapper.setDefaultType(FooDto[].class);
// return typeMapper;
// }
#Bean
public Jackson2JsonMessageConverter jsonConverter() {
// https://stackoverflow.com/questions/40491628/jackson-configuration-to-consume-list-of-records-in-rabbitmq
// https://github.com/FasterXML/jackson-core/issues/295
final Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
converter.setTypePrecedence(TypePrecedence.TYPE_ID);
// converter.setClassMapper(classMapper());
return converter;
}
#ConditionalOnProperty(name = "consumer", havingValue = "true")
#Bean
public ConsumerListener listenerConsumer() {
return new ConsumerListener();
}
#ConditionalOnProperty(name = "producer", havingValue = "true")
#Bean
public ProducerListener listenerProducer() {
return new ProducerListener();
}
#Bean
public RabbitAdmin rabbitAdmin(final CachingConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
final ConnectionFactory connectionFactory) {
// Setting the annotation #RabbitListener to use Jackson2JsonMessageConverter
final SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(jsonConverter());
factory.setConcurrentConsumers(5);
factory.setMaxConcurrentConsumers(5);
return factory;
}
#Bean
public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(jsonConverter()); // convert all sent messages to JSON
rabbitTemplate.setReplyTimeout(TimeUnit.SECONDS.toMillis(3));
rabbitTemplate.setReceiveTimeout(TimeUnit.SECONDS.toMillis(3));
return rabbitTemplate;
}
#Override
public void shutdownCompleted(final ShutdownSignalException arg0) {
}
}
The Listener consuming messages containing MyQuery objects from queue "orders" on exchange "fx":
public class ConsumerListener {
private static final Logger log = LoggerFactory.getLogger(ConsumerListener.class);
#RabbitListener(queues = { "orders" })
public FooDto[] receiveMessage(final MyQuery query) {
log.info(query);
List<FooDto> response = new ArrayList<>();
response.add(new FooDto());
response.add(new FooDto());
response.add(new FooDto());
return response;
}
}
POJO used when sending a message to the Exchange:
class MyQuery {
private String content = "test";
public MyQuery();
public String toString() {
return content;
}
}
POJO used for the response:
class FooDto {
private String content = "foo";
public FooDto();
public String toString() {
return content;
}
}
There's something weird about your listener; it has a return type of void but you return a list.
That said, I think the problem is due to type erasure.
A custom ClassMapper won't help because that's just for a top-level class.
You should, however, be able to construct a custom Jackson2JavaTypeMapper to create a more complex type. The type mapper is consulted if there is not a class mapper. See here.
I am not at a computer right now, but I can take a look tomorrow if you can't figure it out.
EDIT
Here's an example of how to customize the converter...
#SpringBootApplication
public class So49566278Application {
public static void main(String[] args) {
SpringApplication.run(So49566278Application.class, args);
}
#Bean
public ApplicationRunner runner(RabbitTemplate template) {
template.setReplyTimeout(60_000);
return args -> {
#SuppressWarnings("unchecked")
List<Foo> reply = (List<Foo>) template.convertSendAndReceive("so49566278", "baz");
System.out.println(reply);
Foo foo = reply.get(0);
System.out.println(foo);
};
}
#RabbitListener(queues = "so49566278")
public List<Foo> handle(String in) {
return Collections.singletonList(new Foo(in));
}
#Bean
public Queue queue() {
return new Queue("so49566278", false, false, true);
}
#Bean
public MessageConverter converter() {
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
converter.setJavaTypeMapper(new DefaultJackson2JavaTypeMapper() {
#Override
public JavaType toJavaType(MessageProperties properties) {
JavaType javaType = super.toJavaType(properties);
if (javaType instanceof CollectionLikeType) {
return TypeFactory.defaultInstance()
.constructCollectionLikeType(List.class, Foo.class);
}
else {
return javaType;
}
}
});
return converter;
}
public static class Foo {
private String bar;
public Foo() {
super();
}
public Foo(String bar) {
this.bar = bar;
}
public String getBar() {
return this.bar;
}
public void setBar(String bar) {
this.bar = bar;
}
#Override
public String toString() {
return "Foo [bar=" + this.bar + "]";
}
}
}
This way works with Collections without problems. I think it is very simple: https://stackoverflow.com/a/67130566/10746857

How to use specific custom objectMapper/JsonSerializer for specific requestMapping

I know thar Jackson allow us to use custom serializer for specific domain/entity/modle, like this:
#JsonSerialize(using = CustomSerializer.class)
public class SimpleDomain {
}
And, is there any idea to specify custom serializer for some requestMapping,
(only for the specific requestMapping(that method), not to set global objectMapper.) like this:
#RequestMapping(method = RequestMethod.GET, value = "hello")
#JsonSerialize(nullsUsing = NullToEmptyStrSerializer.class)
public #ResponseBody
Object get() {
return new HashMap<String, Object>() {{
put("aa", null);
put("bb", "");
}};
}
I execute the code above, it returns:
{
"aa": null,
"bb": ""
}
instead of:
{
"aa": "",
"bb": ""
}
which exactly i wanted.
NullToEmptyStrSerializer :
public class NullToEmptyStrSerializer extends JsonSerializer {
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeString("");
}
}
Or, can i get HttpServletRequest in NullToEmptyStrSerializer, if yes, i can filter in NullToEmptyStrSerializer.
It may be a known limitation (#JsonSerialize with nullUsing option not working for String properties) and reported in this github issue.
It is scheduled to be released in jackson 2.9.3 and 2.8.11.
Fix will be in 2.9.3 but I also back-ported it in 2.8 branch in case
2.8.11 might be released at some point.
Finally, I found it out by myself.
I realized this point:
Or, can i get HttpServletRequest in NullToEmptyStrSerializer, if yes, i can filter in NullToEmptyStrSerializer.
My purpose :
Custom convert null to "" for specific Url(RequestMapping)
My solution :
Define a bean to keep ApplicationContext storing in a static field, also, a static getter:
#Component
public class ContextHolder {
private static ApplicationContext applicationContext;
#Resource
public void setApplicationContext(ApplicationContext applicationContext) {
ContextHolder.applicationContext = applicationContext;
}
public static ApplicationContext get(){
return applicationContext;
}
}
Define a bean below, this bean will create for every request, thus, i store ServletRequest in this bean.
#Component("servletRequestHolder")
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ServletRequestHolder {
#Resource
private HttpServletRequest request;
public HttpServletRequest getRequest(){
return request;
}
}
Then, a serializer is needed.
public class NullToEmptyStringSerializer extends JsonSerializer.None {
public static List<String> convertUrls = new ArrayList<String>(){{
add("/hello");
}};
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
HttpServletRequest request = ContextHolder.get().getBean(ServletRequestHolder.class).getRequest();
if (request != null) {
String currentUrl = request.getRequestURI();
boolean match = convertUrls.contains(currentUrl);
if (match) {
gen.writeString("");
} else {
gen.writeObject(null);
}
} else {
gen.writeObject(null);
}
}
}
Add the Serializer to objectMapper:
DefaultSerializerProvider.Impl sp = new DefaultSerializerProvider.Impl();
sp.setNullValueSerializer(new NullToEmptyStringSerializer());
objectMapper.setSerializerProvider(sp);
Finally, test it :
#GetMapping({"hello", "hello1"})
public Object get() {
return new HashMap<String, Object>() {{
put("a", null);
put("b", "");
}};
}
In case of requesting localhost:20000/hello, the client receive:
{
"aa": "",
"bb": ""
}
And for requesting localhost:20000/hello1, the client receive:
{
"aa": null,
"bb": ""
}

Spring boot Jackson Json Serialization for List implementation class (PagedList)

Is there any way to add serialization for list implementing class having custom attributes?
I am working on Rest service using Spring-boot 1.3. I have to return JSON response as Paged-List or Normal-List, depend on request on Controller. So, I have to keep return type of controller method as generic public List<Employee> getEmployees(int departmentId)
I am implementing list as below (using generics to use for different object lists)
public class PagedList<E> implements List<E> {
private List<E> list;
private long totalRecords; //Getter-setters are added
public PagedList(List<E> list) {
super();
this.list = list;
}
public PagedList(List<E> list, long totalRecords) {
super();
this.list = list;
this.totalRecords = totalRecords;
}
#Override
public boolean add(E element) {
return this.list.add(element);
}
//All other List abstract methods implemented same as above using this.list
}
Added JsonSerializer for same: public class PagedListSerializer extends JsonSerializer<PagedList> with serialization logic in serialize() method. Which is registered using spring-boot jackson customization :
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializerByType(PagedList.class, new PagedListSerializer());
return builder;
}
When I try to return PagedList<Employee>(list, 1000), I am not able to get following response. Its returning same as of normal list. Not executing custom serialization. How to get following paged response?
{
list : [{employeeId: "1", name: "John" }, ... ],
totalRecords : 1000
}
You probably don't need custom deserializer to get this json. Just add #JsonFormat(shape = JsonFormat.Shape.OBJECT) annotation to your class:
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public static class PagedList<E> implements List<E> {
#JsonProperty
private List<E> list;
#JsonProperty // no need for this if you have getter-setters
private long totalRecords;
#JsonIgnore
#Override
public boolean isEmpty() {
return false;
}
...
Here is full demo: https://gist.github.com/varren/35c4ede769499b1290f98e39a2f85589
Update after comments:
I think Spring uses Jacksons return mapper.writerFor(List.class).writeValueAsString(new MyList()); Here is demo:
#RestController
#RequestMapping(value="/")
public static class MyRestController {
private static final ObjectMapper mapper = new ObjectMapper();
//returns [] for both 0 and 1
#RequestMapping(value="test", method = RequestMethod.GET)
public List test(#RequestParam int user) {
return user == 0 ? new ArrayList(): new MyList();
}
//returns [] for 0 and expected custom {"empty": true} for 1
#RequestMapping(value="testObj", method = RequestMethod.GET)
public Object testObj(#RequestParam int user) {
return user == 0 ? new ArrayList(): new MyList();
}
// returns expected custom {"empty": true}
#RequestMapping(value="testMyList", method = RequestMethod.GET)
public MyList testMyList() {
return new MyList();
}
// returns expected custom {"empty": true}
#RequestMapping(value="testMyListMapper", method = RequestMethod.GET)
public String testMyListMapper() throws JsonProcessingException {
return mapper.writeValueAsString(new MyList());
}
// returns []
#RequestMapping(value="testMyListMapperListWriter", method = RequestMethod.GET)
public String testMyListMapperListWriter() throws JsonProcessingException {
return mapper.writerFor(List.class).writeValueAsString(new MyList());
}
}
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public static class MyList extends ArrayList {}
So you have to Option 1) return Object instead of List or Option 2) register custom serialifer for List (and not for PageList) builder.serializerByType(List.class, new PagedListSerializer()); like this:
public class PagedListSerializer extends JsonSerializer<List> {
#Override
public void serialize(List valueObj, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
if (valueObj instanceof PagedList) {
PagedList value = (PagedList) valueObj;
gen.writeStartObject();
gen.writeNumberField("totalRecords", value.getTotalRecords());
gen.writeObjectField("list", value.getList());
gen.writeEndObject();
}else{
gen.writeStartArray();
for(Object obj : valueObj)
gen.writeObject(obj);
gen.writeEndArray();
}
}
}
You can Create your customObject Mapper and use your serializer there.
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="custom.CustomObjectMapper"/>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

Enable json serialization of Multimap in Spring Boot Project

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

Jersey Custom Exception not caught by ExceptionMapper

I am building a REST API and created some custom parameter types that apply some validation on QueryParameters and not only. Problem is I am throwing a "IllegalQueryArgumentException" but it isn't caught by a custom ExceptionMapper.
If i throw the exception from inside a controller method the exception is caught.
SOLUTION
I made the custom exception extend WebApplicationException instead of RuntimeException
#Provider
public class CustomHandler implements ExceptionMapper<IllegalQueryArgumentException> {
Override
public Response toResponse(IllegalQueryArgumentException e) {
return Response.accepted("HELLO").build();
}
}
config
<servlet>
<servlet-name>rest</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.epaas.eshop.api.v1;com.epaas.eshop.api.v1.jersey;org.codehaus.jackson.jaxrs</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>com.epaas.eshop.api.v1.jersey.CustomHandler</param-value>
</init-param>
Abstract param
public abstract class AbstractQueryParameter<V> {
private final V value;
private final String originalParameter;
public AbstractQueryParameter(String param) throws InvalidQueryArgumentException {
this.originalParameter = param;
this.value = parse(param);
}
public V getValue() {
return value;
}
public String getOriginalParameter() {
return originalParameter;
}
#Override
public String toString() {
return value.toString();
}
protected abstract V parse(String param) throws InvalidQueryArgumentException;
}
Custom parameter class
public class IdParam extends AbstractQueryParameter<Long> {
public IdParam(String param) {
super(param);
}
#Override
protected Long parse(String param) {
try {
Long id = Long.parseLong(param);
if(id < 1) throw new Throwable("Id is under 1");
return Long.parseLong(param);
} catch (Throwable throwable) {
throw new InvalidQueryArgumentException("parameter should be an integer", throwable);
}
}
}
The parse method is automaticly called when Jersey tries to bind the String parameter to the IdParam type.
Route method..
#SuppressWarnings("unchecked")
#GET
public CollectionResource getAll(
#Context UriInfo info,
#QueryParam("expand") #DefaultValue("false") BooleanParam expand,
#QueryParam("city") IdParam city,
#QueryParam("account") IdParam account,
#QueryParam("zipCode") ZipCodeParam zipCode,
#QueryParam("limit") #DefaultValue(Pagination.DEFAULT_LIMIT) LimitParam limit,
#QueryParam("offset") #DefaultValue(Pagination.DEFAULT_OFFSET) OffsetParam offset) {