Empty JSON response when testing SpringBoot API - java

I am currently building a SpringBoot API and I have the following problem :
When I try to test an GET request in a test class, the JSON returned is empty with the following log :
MockHttpServletRequest:
HTTP Method = GET
Request URI = /employee/1
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = com.openclassrooms.api.controller.EmployeeController
Method = com.openclassrooms.api.controller.EmployeeController#getEmployee(Long)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
MockHttpServletRequest:
HTTP Method = GET
Request URI = /employee/1
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = com.openclassrooms.api.controller.EmployeeController
Method = com.openclassrooms.api.controller.EmployeeController#getEmployee(Long)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
I also get the following error : java.lang.AssertionError: No value at JSON path "$.firstName"
However, when I do the exact same GET request with Postman, I get the answer correctly.
For additionnal information, my test class :
package com.openclassrooms.api;
import static org.hamcrest.CoreMatchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import java.io.PrintStream;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
//import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.ResultHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.openclassrooms.api.controller.EmployeeController;
import com.openclassrooms.api.models.Employee;
import com.openclassrooms.api.service.EmployeeService;
//#SpringBootTest
//#AutoConfigureWebMvc
#WebMvcTest(controllers = EmployeeController.class)
public class EmployeeControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private EmployeeService employeeService;
#Test
public void testGetEmployees() throws Exception {
Employee response = new Employee();
mockMvc.perform(get("/employee/1"))
.andExpect(status().isOk())
.andDo(print(System.out))
.andExpect(jsonPath("$.firstName").value("Laurent"));
// .andExpect(jsonPath("$[0].firstName", is("Laurent")));
}
}
The controller class :
package com.openclassrooms.api.controller;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.openclassrooms.api.models.Employee;
import com.openclassrooms.api.service.EmployeeService;
#RestController
public class EmployeeController {
#Autowired
private EmployeeService employeeService;
// Read - Get all employees
// #return - An Iterable object of Employee full filled
#GetMapping("/employees")
public Iterable<Employee> getEmployees() {
Iterable<Employee> list = employeeService.getEmployees();
System.out.println(list);
return list;
}
#GetMapping("/employee/{id}")
public Optional<Employee> getEmployee(#PathVariable("id") final Long id) {
Optional<Employee> emp = employeeService.getEmployee(id);
if (emp.isPresent()) {
System.out.println(emp.get().getFirstName());
return emp;
} else {
System.out.println("ABSENT");
return null;
}
}
#GetMapping("/employee")
public Optional<Employee> getEmployee() {
Optional<Employee> emp = employeeService.getEmployee(1L);
if (emp.isPresent()) {
System.out.println(emp.get().getFirstName());
return emp;
} else {
System.out.println("ABSENT");
return emp;
}
}
}
The service class :
package com.openclassrooms.api.service;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.openclassrooms.api.models.Employee;
import com.openclassrooms.api.repository.EmployeeRepository;
#Service
public class EmployeeService {
#Autowired
private EmployeeRepository employeeRepository;
public Optional<Employee> getEmployee(final Long id) {
System.out.println("getEmployee ok");
return employeeRepository.findById(id);
}
public Iterable<Employee> getEmployees() {
System.out.println("getEmployees ok");
return employeeRepository.findAll();
}
public void deleteEmployee(final Long id) {
employeeRepository.deleteById(id);
}
public Employee saveEmployee(Employee employee) {
Employee savedEmployee = employeeRepository.save(employee);
return savedEmployee;
}
}
And the model class :
package com.openclassrooms.api.models;
import jakarta.persistence.*;
import lombok.Data;
#Data
#Entity
#Table(name = "employees")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String mail;
private String password;
}

The way that your response is shown in the test case, shows that it is probably returning null (as you do inside the else statement). My approach would be to debug the test, set up breakpoints after the optional retrieval inside the controller, and check whether the optional is empty or not. My guess is, that in the test enviroment, you might be using a different datasource (like h2) instead of your regular one that is filled with data.

You have to persist a Entity first.
The Statement Employee response = new Employee(); does not create an Entity in the Database, it just creates an Object.
So in your Test you should persist the Entity first, something like:
#WebMvcTest(controllers = EmployeeController.class)
public class EmployeeControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private EmployeeService employeeService;
#Test
public void testGetEmployees() throws Exception {
// given
Employee existingEmployee = new Employee();
existingEmployee.setFirstName("Laurent"); // <-- set the name you want to test
employeeService.saveEmployee(response); // <-- persist the entity
// when
mockMvc.perform(get("/employee/1"))
// then
.andExpect(status().isOk())
.andDo(print(System.out))
.andExpect(jsonPath("$.firstName").value("Laurent"));
}
}

Related

org.springframework.web.client.RestClientException: Error while extracting response for type and content type [application/json;charset=utf-8]

I can't figure out what the problem is. I am using postgre DB. When I run a test for the GET method, an error occurs, for the second day I can not solve it.
Here is my Entity class
import lombok.*;
import lombok.experimental.FieldDefaults;
import javax.persistence.*;
#Entity
#Table(name = "cities_catalog")
#FieldDefaults(level = AccessLevel.PRIVATE)
#Data
#NoArgsConstructor
public class PostgreCity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id;
#Column(name = "name")
String name;
public PostgreCity(String name) {
this.name = name;
}
}
here is my Repository class
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface CityRepository extends JpaRepository<PostgreCity, Integer> {
}
here is my Controller class
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#RestController
#AllArgsConstructor
#FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class CityPostController {
#Autowired
CityRepository cityRepository;
#GetMapping(value = "/get")
public List<PostgreCity> get(){
List<PostgreCity> list = this.cityRepository.findAll();
return list;
}
}
here is my Junit test class
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import java.util.HashMap;
import static org.junit.jupiter.api.Assertions.*;
class CityPostControllerTest extends RequestService {
#Autowired
CityRepository cityRepositoryp;
Integer id;
#BeforeEach
void setUp() {
}
#AfterEach
void tearDown() {
}
#Test
void get() {
ResponseEntity<PostgreCity> responseEntity = this.get(PostgreCity.class);
assertNotNull(responseEntity);
assertEquals(HttpStatus.OK.value(), responseEntity.getStatusCodeValue());
}
#Override
public String getPath() {
return "/get";
}
}
here is my RequestService class
import io.egrow.eugene.insurance.InsuranceApplicationTests;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
public abstract class RequestService extends InsuranceApplicationTests {
#Autowired
TestRestTemplate testRestTemplate;
public <T> ResponseEntity<T> patchNoAuth(String payload, Class<T> tClass) {
HttpHeaders headers = getHeaderWithoutAuthentication();
HttpEntity<String> entity = new HttpEntity<>(payload, headers);
return testRestTemplate.exchange(this.getPath(), HttpMethod.PATCH, entity, tClass);
}
public <T> ResponseEntity<T> get(Class<T> tClass) {
return testRestTemplate.getForEntity(this.getPath(), tClass);
}
private HttpHeaders getHeaderWithoutAuthentication() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
public abstract String getPath();
}
here is error message when I run test.
org.springframework.web.client.RestClientException: Error while extracting response for type [class io.egrow.eugene.insurance.boundary.databases.postgre.models.cities.PostgreCity] and content type [application/json;charset=utf-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `io.egrow.eugene.insurance.boundary.databases.postgre.models.cities.PostgreCity` from Array value (token `JsonToken.START_ARRAY`); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `io.egrow.eugene.insurance.boundary.databases.postgre.models.cities.PostgreCity` from Array value (token `JsonToken.START_ARRAY`)
at [Source: (PushbackInputStream); line: 1, column: 1]
The problem is here:
ResponseEntity<PostgreCity> responseEntity = this.get(PostgreCity.class);
You are expecting a single entity but in the RestController you have a List:
#GetMapping(value = "/get")
public List<PostgreCity> get(){
List<PostgreCity> list = this.cityRepository.findAll();
return list;
}
For getting the list you can use the ParameterizedTypeReference, like so:
ResponseEntity<List<PostgreCity>> responseEntity =
restTemplate.exchange(
"/get",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<PostgreCity>>() {}
);
List<PostgreCity> postgreCities = responseEntity.getBody();
For more details and reference can have a look at this tutorial:
https://www.baeldung.com/spring-resttemplate-json-list

How to provide completely custom JSON for example in Swagger?

I have Java endpoint which receives json-deserializable object. Unfortunately, Swagger is unable to auto-generate good example for it. Is it possible to provide completely custom JSON for an example?
Example is below, regard class Body. It has two fields.
One field is a Set. I want to provide some example list of values for it. I can't use example parameter for this.
Another field is a Parent. It can contain one of two of subclessed, Child1 and Child2. Springfox generates me
{
"parent": {
"#child#": "string"
},
"tags": "[\"tag1\", \"tag2\"]"
}
and I can't send this value (it's incorrect serialization). While I want to have
{
"parent": {
"#child#": "1",
"field1": "value of field 1"
},
"tags": ["tag1", "tag2"]
}
The code:
package com.example.demo;
import java.io.IOException;
import java.util.Set;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DatabindContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
#RestController
#SpringBootApplication
#Configuration
#EnableOpenApi
public class DemoApplication {
#PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
public Body create(#RequestBody Body body) {
return body;
}
#Bean
public Docket docket() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.basePackage(DemoApplication.class.getPackageName()))
.paths(PathSelectors.any())
.build()
//.apiInfo(apiInfo())
//.securitySchemes(Collections.singletonList(apiKey()))
//.protocols(getProtocols(systemSettings))
;
}
public static class Body {
#ApiModelProperty(example = "[\"tag1\", \"tag2\"]")
public Set<String> tags;
public Parent parent;
}
#JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "#child#", include = JsonTypeInfo.As.EXISTING_PROPERTY, visible = true)
#JsonTypeIdResolver(MyTypeIdResolver.class)
#ApiModel(discriminator = "#child#")
public static class Parent {
final String childTypeNumber;
#JsonProperty("#child#")
public String childTypeNumber() {
return childTypeNumber;
}
public Parent(String childTypeNumber) {
this.childTypeNumber = childTypeNumber;
}
}
public static class MyTypeIdResolver extends TypeIdResolverBase {
private JavaType superType;
#Override
public void init(JavaType baseType) {
superType = baseType;
}
#Override
public String idFromValue(Object value) {
return null;
}
#Override
public String idFromValueAndType(Object value, Class<?> suggestedType) {
return null;
}
#Override
public JsonTypeInfo.Id getMechanism() {
return null;
}
#Override
public JavaType typeFromId(DatabindContext context, String id) throws IOException {
char c = id.charAt(0);
Class<?> subType = null;
switch (c) {
case '1':
subType = Child1.class;
break;
case '2':
subType = Child2.class;
break;
default:
throw new RuntimeException("Invalid Child type");
}
return context.constructSpecializedType(superType, subType);
}
}
public static class Child1 extends Parent {
public String field1;
public Child1() {
super("1");
}
}
public static class Child2 extends Parent {
public String field2;
public Child2() {
super("2");
}
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
From what I understand, you want swagger to display the resource returned by the endpoint.
If so, this is the solution:
#Operation(summary = "create new resource",
description = "create resourcey completely", responses = {
#ApiResponse(responseCode = "200",
description = "createresource",
content = {#Content(mediaType = "application/json",
schema = #Schema(implementation = Body.class))})
#PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
public Body create(#RequestBody Body body) {
return body;
}
So that the controller does not have so many things left, what is done is to create the controller interface with all the annotations on the method signature, then your controller will implement the interface that already has all the documentation annotations.

Injecting a SessionScope bean in Controller not working

I am having trouble setting an attribute value ie. shortname to a SessionScope bean in Spring Boot.
Here is my class:
import java.util.Map;
public class LdapUser {
private String shortname = "";
private Map<String,String> token = null;
private String id = "";
public LdapUser() {
}
public String getshortname() {
return shortname;
}
public void setshortname(String shortname) {
this.shortname = shortname;
}
... remaining geters and setters
My Bean definition is here:
import xxx.controllers.SwitchController;
import xxx.isim.LdapUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.WebApplicationContext;
#Configuration
public class RestTemplateClient {
Logger logger = LoggerFactory.getLogger(SwitchController.class);
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
#Bean
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public LdapUser sessionScopedLdapUser() {
logger.info("LdapUser bean instance created");
return new LdapUser();
}
}
I am using the Bean in a Controller:
import xxx.errors.IsimConnectionException;
import xxx.isim.IsimConnection;
import xxx.isim.LdapUser;
import xxx.services.IsimRestApiService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
import java.security.Principal;
#Controller
public class HomeController {
private static final Logger log = LoggerFactory.getLogger(HomeController.class);
#Autowired
IsimRestApiService isimConn;
#Resource(name = "sessionScopedLdapUser")
LdapUser sessionScopedLdapUser;
#RequestMapping("/")
public String index(Principal principal) throws IsimConnectionException {
Authentication authentication = (Authentication) principal;
/
if ((authentication.getPrincipal() != null) && (authentication.isAuthenticated())) {
// set the shortname for the session
String shortname = (String)authentication.getPrincipal();
sessionScopedLdapUser.setshortname(shortname); //<-----
My Bean's value for shortname remains null after the line with the arrow even though I correctly get the shortname String value and that one is not null. Can you please point me out what am I doing wrong when setting the bean attribute values. I followed the example here for SessionScope Beans
Update:
I also tried to use autowired instead of #Resource(name = "sessionScopedLdapUser") but the value still remains null after executing sessionScopedLdapUser.setshortname(shortname);
#Autowired
LdapUser sessionScopedLdapUser
Also in the log I can see the LdapUser bean instance is created three times. How is that possible?
2021-09-21 10:55:55,469 INFO [http-nio-8080-exec-4] xxx.config.RestTemplateClient: LdapUser bean instance created
2021-09-21 10:57:05,247 INFO [http-nio-8080-exec-4] xxx.config.RestTemplateClient: LdapUser bean instance created
2021-09-21 10:58:08,401 INFO [http-nio-8080-exec-4] xxx.config.RestTemplateClient: LdapUser bean instance created
The ideas is to have one bean per HTTP session. I am really confused and would appreciate some hints. I was reading this article and maybe that is because I am trying to inject a Session Scope bean to a Singletone bean.
My file structure is:
xxx
---auth
---config
--- RestRemplateClient
---controllers
--- HomeController
---errors
---isim
--- LdapUser
---services
Mainapp
Thanks to #M. Deinum I was able to figure it out. I was looking at the field value in the debugger and that one was always null since I was looking the proxy and not the real object.
Here is the code that injects a session scoped bean in the #Controller class. It also works correctly and in the same way in the #Service class.
public class LdapUser {
private String shortname = "";
private Map<String,String> token = new HashMap<>();
private String id = "";
public LdapUser() {
this.shortname = shortname;
this.token = token;
this.id = id;
}
public String getshortname() {
return shortname;
}
public void setshortname(String shortname) {
this.shortname = shortname;
}
... other getters and setters
My bean configuration class:
#Configuration
public class RestTemplateClient {
Logger logger = LoggerFactory.getLogger(SwitchController.class);
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
#Bean
#SessionScope
public LdapUser sessionScopedLdapUser() {
logger.info("LdapUser bean instance created at "+ LocalDateTime.now());
return new LdapUser();
}
}
My controller class:
#Controller
public class HomeController {
private static final Logger log = LoggerFactory.getLogger(HomeController.class);
#Autowired
IsimRestApiService isimConn;
#Autowired
LdapUser sessionScopedLdapUser;
#RequestMapping("/")
public String index(Principal principal) throws IsimConnectionException {
Authentication authentication = (Authentication) principal;
//System.out.println("******* USER IS " + authentication.getPrincipal());
if ((authentication.getPrincipal() != null) && (authentication.isAuthenticated())) {
// set the shortname for the session
String shortname = (String)authentication.getPrincipal();
sessionScopedLdapUser.setshortname(shortname);
You can specify your configurations as below:-
import org.springframework.context.annotation.Scope;
import java.time.LocalDateTime;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class LdapUser {
private String shortName = "LdapUser Session Scope";
// other properties
public LdapUser() {
System.out.println("LdapUser SessionScope Constructor Called at "+LocalDateTime.now());
}
public String getShortName() {
return shortName;
}
public void setShortName(String shortName) {
this.shortName = shortName;
}
}
In configuration:
#Configuration
public class RestTemplateClient {
Logger logger = LoggerFactory.getLogger(SwitchController.class);
#Autowired
private LdapUser ldapUser;
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public void setLdapUser(LdapUser ldapUser) {
this.ldapUser = ldapUser;
}
public LdapUser getLdapUser() {
return ldapUser;
}
}
and into your controller:-
#Controller
public class HomeController {
// Other Codes
#Autowired
private RestTemplateClient restTemplateClient;
private static final Logger log = LoggerFactory.getLogger(HomeController.class);
#Autowired
IsimRestApiService isimConn;
#RequestMapping("/")
public String index(Principal principal) throws IsimConnectionException {
Authentication authentication = (Authentication) principal;
/
if ((authentication.getPrincipal() != null) && (authentication.isAuthenticated())) {
// set the shortname for the session
String shortname = (String)authentication.getPrincipal();
restTemplateClient.getLdapUser().setShortName("LdapUser Session Scope Updated");
// .... Other codes
}
}
}

Spring Rest Controller

I can't make spring serialize the response when results is array/list .
So when I call clients from RestController it does return [{},{},{}], instead of real objects, all other methods works just fine.
package com.test.Domain.Client;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.UUID;
#Entity
#Table(name = "client")
public class Client {
#Column(name = "client_id")
#Id
private UUID clientId;
#Column(name = "name")
private String name;
private Client() {
}
private Client(UUID clientId, String name) {
this.clientId = clientId;
this.name = name;
}
public static Client create(String name)
{
return new Client(UUID.randomUUID(), name);
}
}
package com.test.Rest;
import com.test.Domain.Calendar.AppointmentRepository;
import com.test.Domain.Client.Client;
import com.test.Domain.Client.ClientRepository;
import com.test.Domain.Worker.WorkerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
#org.springframework.web.bind.annotation.RestController
public class RestController {
#Autowired
private ClientRepository clientRepository;
#Autowired
private WorkerRepository workerRepository;
#Autowired
private AppointmentRepository appointmentRepository;
#RequestMapping(path = "/client", method = RequestMethod.POST)
public void registerClient(#RequestParam(name = "name") String name) {
this.clientRepository.save(Client.create(name));
}
#RequestMapping(path = "/clientCount", method = RequestMethod.GET)
public Long countClient() {
return this.clientRepository.count();
}
#RequestMapping(path = "/clients", method = RequestMethod.GET)
#ResponseBody
public List<Client> clients() {
List<Client> list = new ArrayList<Client>();
for (Client client : this.clientRepository.findAll()) {
list.add(client);
}
return list;
}
}
Jackson needs Getter and Setter methods in order to serialize the Client object properly into JSON. Therefore a list of empty objects is returned and the values for the members are missing. Add them to Client and the response should look fine.
Spring applies first registered applicable by response mime-type HttpMessageConverter implementation when serializing the response to /clients call. In your case this is some JSON serializer. As you have no JSON configuration specified on Client class the default POJO serializing approach is used: reflection scanning of object properties. As mentioned earlier your Client class doesn't define any properties (at least getters), so serializer do not detect any.
Please refer to the following article for a more detailed explanation: https://www.javacodegeeks.com/2013/07/spring-mvc-requestbody-and-responsebody-demystified.html
P.S. Marking method with #ResponseBody in #RestController annotated class is not necessary as itself is a convenience annotation aggregating #Controller and #ResponseBody.

Return XML response Rest API with Spring

I am trying to response with XML to a given call to the API.
Right now it works with JSON, I can send JSON or XML and return JSON.
But I cannot do the same with XML.
From now what I have is this:
RestVoterController class:
#RequestMapping("/rest")
#RestController
public class RESTVoterController {
#Autowired
private VoterService voterService;
#RequestMapping(value = {"/user.json","/user"},
method = RequestMethod.POST,
consumes = {"application/json","application/xml"},
produces = {"application/json"})
#Transactional(readOnly = true)
public Voter getVoterInfoJSON(#RequestBody VoterRequestGet voterRequestGet) {
return this.voterService.findByEmailAndPassword(voterRequestGet.getLogin(), voterRequestGet.getPassword());
}
#RequestMapping(value = "/user.xml",
method = RequestMethod.POST,
consumes = {"application/xml","application/json"},
produces = "application/xml")
#Transactional(readOnly = true)
public Voter getVoterInfoXML(#RequestBody VoterRequestGet voterRequestGet) {
return this.voterService.findByEmailAndPassword(voterRequestGet.getLogin(), voterRequestGet.getPassword());
}
#RequestMapping(value = "/changepassword",
method = RequestMethod.POST,
headers = "Accept=application/json",
produces = "application/json")
#Transactional(readOnly = true)
public void changePassword(#RequestBody VoterRequestChangePassword voterRequestChangePassword) {
this.voterService.changePassword(voterRequestChangePassword.getLogin(), voterRequestChangePassword.getOldPassword(), voterRequestChangePassword.getNewPassword());
}
}
VoterRequestGet class:
#XmlRootElement(name = "user")
#XmlAccessorType(XmlAccessType.FIELD)
public class VoterRequestGet {
#XmlElement
private String login;
#XmlElement
private String password;
public VoterRequestGet()
{
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
I tested your code and it works for me. Please look:
First I create mock for voterService.
package com.example;
import org.springframework.stereotype.Service;
#Service
public class VoterService {
public Voter findByEmailAndPassword(String login, String password) {
Voter voter = new Voter();
voter.setLogin(login);
voter.setPassword(password);
return voter;
}
}
Then I have to slightly modify your controller (remove Transaction annotation because I have no data source in my service mock).
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RequestMapping("/rest")
#RestController
public class RESTVoterController {
#Autowired
private VoterService voterService;
#RequestMapping(value = {"/user.json","/user"},
method = RequestMethod.POST,
consumes = {"application/json","application/xml"},
produces = {"application/json"})
public Voter getVoterInfoJSON(#RequestBody VoterRequestGet voterRequestGet) {
return this.voterService.findByEmailAndPassword(voterRequestGet.getLogin(), voterRequestGet.getPassword());
}
#RequestMapping(value = "/user.xml",
method = RequestMethod.POST,
consumes = {"application/xml","application/json"},
produces = "application/xml")
public Voter getVoterInfoXML(#RequestBody VoterRequestGet voterRequestGet) {
return this.voterService.findByEmailAndPassword(voterRequestGet.getLogin(), voterRequestGet.getPassword());
}
}
Also I have to create mock for Voter because you don't share it.
package com.example;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "voter")
#XmlAccessorType(XmlAccessType.FIELD)
public class Voter {
#XmlElement
private String login;
#XmlElement
private String password;
public Voter() {
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Override
public String toString() {
return "Voter [login=" + login + ", password=" + password + "]";
}
}
And finally integration test.
package com.example;
import javax.annotation.Resource;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.client.RestTemplate;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestApplication.class)
#WebIntegrationTest("server.port:0")
public class RESTVoterControllerTest {
private ServerProperties serverProperties;
private RestTemplate restTemplate = new RestTemplate();
#Resource
public void setServerProperties(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}
#Value("${local.server.port}")
private int serverPort;
private String serverUri;
#Before
public void setUp() throws Exception {
String contextPath = serverProperties.getContextPath();
serverUri = "http://localhost:" + serverPort + (contextPath == null ? "/" : contextPath);
}
#After
public void tearDown() throws Exception {
}
#Test
public void testCreate() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
VoterRequestGet voterRequest = new VoterRequestGet();
voterRequest.setLogin("email");
voterRequest.setPassword("secret");
HttpEntity<VoterRequestGet> request = new HttpEntity<>(voterRequest, headers);
System.out.println(restTemplate.postForEntity(
serverUri + "/rest/user.json", request, String.class).getBody());
System.out.println(restTemplate.postForEntity(
serverUri + "/rest/user.xml", request, String.class).getBody());
}
}
The result could be find in a output of the test and should contain two lines.
{"login":"email","password":"secret"}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><voter><login>email</login><password>secret</password></voter>
You need an XML mapping library like jackson, so adding this to your pom.xml will fix your problem:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
produces = { MediaType.APPLICATION_JSON_VALUE}
is the solution to me

Categories