Using customized ObjectWriter with Jersey - java

I am developing a REST webservice. Jersey as jax-rs provider and Jackson for serialization/deserialization. I also develop the client based on Retrofit2.
My class hierarchy is provided by a third-party library and all classes descend from a root base class BaseObject. Some of those classes have undesirable getters, e.g. isEmpty, that I want to ignore on serialization (Note that it is important that they do not get serialized at all and using FAIL_ON_UNKNOWN_PROPERTIES on deserialization is not enough in my case).
I have used Jackson #JsonFilter on BaseClass using Mixins. To apply a filter, as far as I know, one has to use the following:
new ObjectMapper().writer(filterProvider).writeValueAsString...
Everything is ok up to here: the undesired property is successfully filtered from the produced json.
Now I have to configure Jersey and Retrofit2 to use my customized json serializer/deserializer.
For Jersey, serialization/deserialization can be configured using a Provider class that implements ContextResolver<ObjectMapper> and returning customized ObjectMapper in getContext(Class<?> type) method.
Similarly in Retrofit2, by using
Retrofit.Builder().addConverterFactory(JacksonConverterFactory.create(objectMapper)), one can customize serialization/deserialization.
THE PROBLEM IS THAT new ObjectMapper().writer(filterProvider) is of type ObjectWriter and not of type ObjectMapper. How can I tell Jersey and Retrofit2 to use my customized ObjectWriter which uses my filters?

Since version 2.6 of Jackson it has the 'setFilterProvider' method for an ObjectMapper.
I didn't try it but the documentation has the description for this: https://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/ObjectMapper.html#setFilterProvider-com.fasterxml.jackson.databind.ser.FilterProvider-. You can try i think because the description fits for your case.
I built a test service with Jersey 2.7 and Jackson 2.9.5. it works fine but you have to know some tricks to run it.
In pom.xml add Jersey and Jackson:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
<properties>
<jersey.version>2.7</jersey.version>
<jackson.version>2.9.5</jackson.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
You have to define this dependence:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
it's mandatory.
In web.xml you have to make the ref to configuration of your service:
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>org.glassfish.jersey.server.ResourceConfig</param-name>
<param-value>com.home.MyApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
MyApplication.java:
package com.home;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import javax.ws.rs.ApplicationPath;
#ApplicationPath("/webapi")
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(ObjectMapperProvider.class);
register(JacksonFeature.class);
register(MyResource.class);
}
}
With a custom ObjectMapperProvider you have to register a JacksonFeature.class because without it Jersey doesn't use the custom ObjectMapperProvider.
ObjectMapperProvider.java:
package com.home;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper>{
final ObjectMapper defaultObjectMapper;
public ObjectMapperProvider() {
defaultObjectMapper = createDefaultMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {return defaultObjectMapper;}
public static ObjectMapper createDefaultMapper() {
final ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.setFilters(new SimpleFilterProvider().addFilter("dataFilter", SimpleBeanPropertyFilter.serializeAllExcept("region", "city")));
return mapper;
}
}
To define a filter use the 'setFilters' methods. This method is deprecated but the Jersey's library which called 'jersey-hk2' doesn't know the new method 'setFilterProvider' and throws an exception. With the old method everything works fine.
A business object with #JsonFilter:
#JsonFilter("dataFilter")
public class SimpleData {
#JsonProperty("name")
String firstName;
#JsonProperty("secondName")
String lastName;
#JsonProperty("country")
String country;
#JsonProperty("region")
String region;
#JsonProperty("city")
String city;
#JsonProperty("genre")
String genre;
public SimpleData() {
this.firstName = "Bryan";
this.lastName = "Adams";
this.country = "Canada";
this.region = "Ontario";
this.city = "Kingston";
this.genre = "Rock";
}
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getCountry() { return country; }
public void setCountry(String country) { this.country = country; }
public String getRegion() { return region; }
public void setRegion(String region) { this.region = region; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getGenre() { return genre; }
public void setGenre(String genre) { this.genre = genre; }
}
MyResource.java:
#Path("myresource")
public class MyResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public SimpleData getIt() {
return new SimpleData();
}
}
A filtered result:

Related

#Valid #ModelAttribute not working properly

I have this controller
#Controller
public class EmpleadoController {
#Autowired
private EmpleadoService servicio;
#PostMapping("/empleado/new/submit")
public String nuevoEmpleadoSubmit(#Valid #ModelAttribute("empleadoForm") Empleado empleadoForm, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "formulario";
}else {
servicio.add(empleadoForm);
return "redirect:/empleado/list";
}
}
}
With this model
package com.alexotero.spring.model;
import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
public class Empleado {
#Min(0)
private long id;
#NotEmpty
private String nombre;
#Email
private String email;
private String telefono;
//Constructors getters setters and stuff
I've also added this dependency to the pom.xml
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
It doesn't matter what i introduce in the form, the controller never detects any error. Always adds the object to the List(in the service) and redirects to the list.html. I cannot find where is the problem.
For my case, I removed this dependency
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
and added
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
I think they both conflict with each other.

JAX-RS gives status code 500 for GET operation for XML type response

I have just begun to learn RESTful services and this is the issue I have been facing recently.
When I run the application to GET an XML type response, I get status code 500 and I don't see any stack trace for this as well. Am I missing anything in the dependency list ? Please help me figure this.
Here is the entity class :
import java.util.Date;
import jakarta.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Message {
private int id;
private String message;
private String author;
private Date date;
public Message() {}
public Message(int id, String message, String author) {
super();
this.id = id;
this.message = message;
this.author = author;
this.date = new Date();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
#Override
public String toString() {
return "Message [id=" + id + ", message=" + message + ", author=" + author + ", date=" + date + "]";
}
}
And then I have my service class : MessageService.java :
import java.util.ArrayList;
import java.util.List;
import org.aravind.restful.messenger.model.Message;
public class MessageService {
public List<Message> getMessages()
{
Message m1 = new Message(1,"Hello World!","aravind");
Message m2 = new Message(2,"First step towards the goal!","aravind");
List <Message> messageList = new ArrayList<Message>();
messageList.add(m1);
messageList.add(m2);
return messageList;
}
}
And then I have the resource class : MessengerResouce.java
package org.aravind.restful.messenger.resources;
import java.util.List;
import org.aravind.restful.messenger.model.Message;
import org.aravind.restful.messenger.service.MessageService;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
#Path("/messages")
public class MessengerResource {
#GET
#Produces(MediaType.APPLICATION_XML)
public List<Message> getMessages()
{
MessageService msgService = new MessageService();
return msgService.getMessages();
}
}
And here is how my pom.xml looks :
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.aravind.restful</groupId>
<artifactId>messenger</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>messenger</name>
<build>
<finalName>messenger</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<inherited>true</inherited>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<!-- use the following artifactId if you don't need servlet 2.x compatibility -->
<!-- artifactId>jersey-container-servlet</artifactId -->
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
</dependency>
<!-- uncomment this to get JSON support
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-binding</artifactId>
</dependency>
-->
</dependencies>
<properties>
<jersey.version>3.0.0-M1</jersey.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
Please help me figure this out. Thanks.
The link Sam suggested answers your question. You have to wrap your response as a GenericEntity. Here is the documentation from the Response class:
"It is the callers responsibility to wrap the actual entity with GenericEntity if preservation of its generic type is required."
So your code should look something like this:
#GET
#Produces(MediaType.APPLICATION_XML)
public Response getMessages()
{
MessageService msgService = new MessageService();
return Response.status(Status.OK)
.entity(new GenericEntity<>(msgService.getMessages(), List.class))
.build(); ;
}

Springboot doesn't work except when hello is typed

I created a very simply SpringBoot project using my groupId and artifactId. Looks like it doesn't want to kick off and some mappings are missing. But when I use the same package names and classnames as the SPring Boot tutorial which is on spring.io site, it works.
My POM is the following:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.my.spring</groupId>
<artifactId>firstspringboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<url>http://maven.apache.org</url>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-parent -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
My Resource 'Greeting'
package com.my.spring.firstspringboot.entities;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class Greeting {
private String id;
private String firstName;
private String lastName;
public Greeting() {
}
public Greeting(String firstName, String lastName) throws NoSuchAlgorithmException {
this.setFirstName(firstName);
this.setLastName(lastName);
this.setId(new StringBuilder().append(SecureRandom.getInstance("SHA1PRNG").nextInt()).append("_").append(System.currentTimeMillis()).toString());
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
#Override
public String toString() {
return new StringBuilder().append("{\n\"name\":\"").append(this.firstName).append(" ").append(this.lastName).append("\"\n").append(",").append("{\n\"id\":\"").append(this.id).append("\"").toString();
}
}
And its Controller:
package com.my.spring.firstspringboot.controllers;
import java.security.NoSuchAlgorithmException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.my.spring.firstspringboot.entities.Greeting;
#RestController
public class GreetingController {
#RequestMapping("/")
public String home() {
return "Welcome!";
}
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(name="name",defaultValue="John") String name) throws NoSuchAlgorithmException {
return new Greeting(name,name);
}
}
And main App
package com.my.spring.firstspringboot.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class App
{
public static void main(String[] args)
{
SpringApplication.run(App.class, args);
}
}
Is there something more that I need to do here? I thought it's simply a boot and autoconfigure by default.
Try to add http method in your requestMapping
#RequestMapping(path = "/hello", method=RequestMethod.GET)
or try removing first endpoint which you have declared with path "/"
According to the documentation (Thanks to #JBNizet for pointing that out), I needed to keep my main application under the package name com.my.spring.firstspringboot
The reason is that Spring looks immediately under the firstspringboot subpackage to find the execution class since it's the artifact ID.
The correct approach should probably be the following:
1) Keep the app runner class in higher subpackage than resource/controller classes.
e.g com.my.firstapplication.App, com.my.firstapplication.resources.Resource etc.
2) If I rely on manual settings, use #EnableAutoConfiguration and #ComponentScan
3) If I am relying on Spring to do it - it's #SpringBootApplication

JSON format of result isn't correct

I have developed RESTful web service which is running fine I have used one POJO and one service class that is shown below but the issue is that in the output it is showing extra $ please advise how to correct that right now the output is coming as
{
"student":{
"#id":"10",
"age":{
"$":"25"
},
"collegeName":{
"$":"UP College"
},
"name":{
"$":"Ram"
}
}
}
and I want that output should be
{
"student":{
"#id":"10",
"age":25,
"collegeName":"UP College",
"name":"Ram"
}
}
so below is my POJO
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "student")
public class Student {
private int id;
private String name;
private String collegeName;
private int age;
#XmlAttribute
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#XmlElement
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#XmlElement
public String getCollegeName() {
return collegeName;
}
public void setCollegeName(String collegeName) {
this.collegeName = collegeName;
}
public int getAge() {
return age;
}
#XmlElement
public void setAge(int age) {
this.age = age;
}
}
below is my service class
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import org.jboss.resteasy.annotations.providers.jaxb.json.BadgerFish;
#Path("/restwb")
public class StudentService {
#BadgerFish
#GET
//#Path("/{id}")
#Produces("application/json")
public Student getStudentDetails(){
Student student = new Student();
student.setId(10);
student.setName("Ram");
student.setCollegeName("UP College");
student.setAge(25);
return student;
}
}
and here is my pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.concretepage</groupId>
<artifactId>RestWB</artifactId>
<version>1</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>3.0.4.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
<version>3.0.4.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jettison-provider</artifactId>
<version>3.0.4.Final</version>
</dependency>
</dependencies>
</project>
Yes, this is the documented behaviour of Badgerfish:
Text content of elements goes in the $ property of an object.
From the documentation, there's no obvious way of transforming to object properties that don't have an #. (If you're happy with #age, you could use an XmlAttribute for age instead of XmlElement.)
But as you don't seem to have any need for an XML representation anyway, I'd suggest moving away from Badgerfish for your JSON representation, as Badgerfish is explicitly meant to be a transform from XML documents to JSON documents.
The format you get is what Badgerfish you gives. There is no way to change it. Either remove Badgerfish at all and get convenient JSON, or consume the result of this library's work.

Jersey cannot deserialize java.util.UUID out of String in request object

I have an endpoint which accepts a DriverJson class instance as http request payload. The DriverJson class has a property userId which is of type java.util.UUID.
The problem is that Jersey cannot deserialize UUID string into UUID object. When I debug and inspected driverJson, userId1 property is null (luckily it does not throw exception). I have read this article (article) that explains that classes that have fromString(String string) method (java.util.UUID has it) or have constructor with one (String) argument are deserialized by Jersey automatically (without writing any additional deserializer classes). If I change UUID type to String and I convert it my self (UUID.fromString(userId)) it's working perfectly.
In addition I use a #PathParam UUID arguments in some of my endpoints and they are working perfectly (as expected).
Below is my code:
My resource endpoint:
#POST
public Response add(DriverJson driverJson) {
DriverJson driver = service.add(driverJson);
return Response.status(Response.Status.CREATED).entity(driver).build();
}
My Driver class:
#XmlRootElement
public class DriverJson {
private UUID id;
private String name;
private UUID userId;
public DriverJson() {}
public DriverJson(UUID id, String name, UUID userId){
this.id = id;
this.name = name;
this.userId = userId;
}
/// getters & setters /////////
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
}
My JSON request object:
{
"name" : "John",
"userId" : "ff06c5a4-135c-40b7-83f3-3648ec035efc"
}
I am using the standard Jersey archetype (version 2.23.2) with the following dependencies:
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<!-- use the following artifactId if you don't need servlet 2.x compatibility -->
<!-- artifactId>jersey-container-servlet</artifactId -->
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.50</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>16.0.1</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.7</version>
</dependency>
</dependencies>
AFAIK you can fix it by using custom XmlAdapter:
public class UUIDAdapter extends XmlAdapter<String, UUID> {
#Override
public UUID unmarshal(String v) throws Exception {
return UUID.fromString(v);
}
#Override
public String marshal(UUID v) throws Exception {
return v.toString();
}
}
And then mark your fields with #XmlJavaTypeAdapter annotation
#XmlRootElement
public class DriverJson {
#XmlJavaTypeAdapter(UUIDAdapter.class)
private UUID id;
#XmlJavaTypeAdapter(UUIDAdapter.class)
private UUID userId;
/* ... */
}

Categories

Resources