Spring MVC: Complex object as parameter - java

I started to learn spring boot and I'm faced with problems. I have following code:
#RestController
public class UserController {
#RequestMapping("/")
public String getMessageInfo(Message message) {
return "Id is " + message.getId() + ", message is " + message.getMessage() + ", parameter good is " + message.isGood();
}
}
Class Message:
public class Message {
private String message;
private int id;
private boolean good;
public Message() {}
public Message(int id) {this.id = id;}
public Message(String message) {this.message = message;}
public Message(boolean good) {this.good = good;}
public Message(String message, boolean good, int id) {
this.message = message;
this.good = good;
this.id = id;
}
public String getMessage() {
return message;
}
public int getId() {
return id;
}
public boolean isGood() {
return good;
}
}
And when I try to do something like this:
RestTemplate request = new RestTemplate();
String info = request.getForObject("http://localhost:8080/?id=4", String.class);
value of id is ignored. Same problem appears when I send request with boolean good parameter (for example localhost:8080/?good=true). It is called the default constructor instead of Message(boolean)/Message(int). But when I do something like localhost:8080/?message=1234 it isn't ignored. Can you explain me what is the problem?
And one more question: can I send instance of class Message to getMessageInfo in different way than localhost:8080/?message=1234&good=true&id=145? If I have more than 3 parameters? For example if class Message has 100 parameters?

since you are trying to deal with a complex object accept your object from a post request.
#RequestMapping("/",method=RequestMethod.POST)
public String getMessageInfo(#RequestBody Message message) {
return message;
}
in the above code i'm setting method attribute to POST then it will be called when you are making a POST request, and i am using #RequestBody Message message inside the method parameter. which will convert and form an Message object from the incoming request, if you dont put #requestBody annotation then a Bean will be injected to the method by spring instead of forming a one from the request.
you can try this code to make the request
final String uri = "http://localhost:8080/";
Message message = new Message(1, "Adam",true);
RestTemplate restTemplate = new RestTemplate();
Message result = restTemplate.postForObject( uri, message, Message.class);
when making an request create an Message object setting each and every field in it, otherwise you will end up in having Bad request error.

I solved the problem, if add smth like this:
#ModelAttribute("message")
public Message getMessage(String message, boolean good, int id){
return new Message(message, good, id);
}
#RequestMapping("/")
public String getUserInfo(#ModelAttribute("message") Message message) {
return "Id is " + message.getId() + ", message is " + message.getMessage() + ", parameter good is " + message.isGood();
}
all parameters aren't ignored.

You can use like this,
MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
params.add("id", 1);
params.add("good", true);
params.add("message", 1234);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(params, requestHeaders);
RestTemplate restTemplate = new RestTemplate();
Message message= restTemplate.postForObject("http://localhost:8080/", requestEntity, Message.class);

Related

ResponseEntity is returning null when imposing class object

I am new to Spring boot and sorry in case it's very basic but I am posting as I have tried other ways and checked similar threads as well.
If I use below code it's returning correct response
ResponseEntity<String> responseEntityString = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
Output
[{"Id":"123aa","TenId":5198,"Name":"test","Description":"test11","Tags":[]}]
Now I have created workspace class like below (getter/setter/arg constructor and no-arg construcntor is also there)
public class Workspace {
private String Id;
private String TenId;
private String Name;
private String Description;
private List<String> Tags;
}
Now I execute the below code -
ResponseEntity<List<Workspace>> response = restTemplate.exchange(
url,
HttpMethod.GET,
requestEntity,
new ParameterizedTypeReference<List<Workspace>>(){});
List<Workspace> employees = response.getBody();
employees.stream().forEach(entry -> System.out.println(entry.getId() + ": " + entry.getName()));
It's returning
null: null
Below is returning true
System.out.println("Value "+ response.hasBody());
Below is returning - New Values [com.pratik.model.Workspace#3cbf1ba4]
New Values [com.pratik.model.Workspace#3cbf1ba4]
So please advise what needs to change to get the values
================================================================
Initialized resttemplate bean like below
public class app1 {
static RestTemplate restTemplate = new RestTemplate();
static String url = url;
public static void main(String[] args) {
SpringApplication.run(app1.class, args);
getCallSample();
}
===============================================================
Update on the latest code
ResponseEntity<Workspace[]> responseNew = restTemplate
.exchange(
url,
HttpMethod.GET,
requestEntity,
Workspace[].class);
Workspace [] employees1 = responseNew.getBody();
List<Workspace> list = Arrays.asList(employees1);
list.stream().forEach(entry -> System.out.println(entry.getId() + ": " + entry.getName()));
Still the response is
null: null
===============================================================
Another update
When tried with String.class it's returning
[{"Id":"abc","TenId":11,"Name":"tt1 Workspace","Description":"testtenant Workspace (System Generated)","Tags":[]}]
But when using workspace class - it's returning -
[Id=null, TenId=null, Name=null, Description=null, Tags=null, getId()=null, getTenId()=null, getName()=null, getDescription()=null, getTags()=null]
So is using Workspace[].class would be the right method ?
Replace your static RestTemplate restTemplate = new RestTemplate(); variable for a real bean:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
#Configuration
public class app1 {
//remove this variable
//static RestTemplate restTemplate = new RestTemplate();
static String url = "your_url";
public static void main(String[] args) {
SpringApplication.run(app1.class, args);
//getCallSample();
}
//create a proper RestTemplate bean
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
//add a converter so you can unmarshall the json content
MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();
//this is an example to set an ObjectMapper instance
//you can define a bean to configure the ObjectMapper
//with specific details like avoid unmarshalling unknown fields
converter.setObjectMapper(new ObjectMapper());
restTemplate.getMessageConverters().add(converter);
return restTemplate;
}
}
Now, in the method you're using the rest template. Get it from Spring's application context rather than using your own static bean. Example:
#Component
public class WorkspaceService {
private final RestTemplate restTemplate;
public WorkspaceService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public List<Workspace> getWorkspaces() {
ResponseEntity<List<Workspace>> response = restTemplate.exchange(
url,
HttpMethod.GET,
requestEntity,
new ParameterizedTypeReference<List<Workspace>>(){});
List<Workspace> employees = response.getBody();
employees.stream().forEach(entry -> System.out.println(entry.getId() + ": " + entry.getName()));
return employees;
}
}
Now you can use this bean in your components. For example, if you want to use it in main class:
#Configuration
public class app1 {
static String url = "your_url";
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(app1.class, args);
WorkspaceService ws = ctx.getBean(WorkspaceService.class);
ws.getWorkspaces();
}
//create a proper bean
#Bean
public RestTemplate restTemplate() {
/* code from above... */
}
}
The issue is resolved by changing the Pojo class (used this to get the class https://json2csharp.com/code-converters/json-to-pojo) to
public class Root{
#JsonProperty("Id")
public String id;
#JsonProperty("TenantId")
public int tenantId;
#JsonProperty("Name")
public String name;
#JsonProperty("Description")
public String description;
#JsonProperty("Tags")
public ArrayList<Object> tags;
}
and the code is used
ResponseEntity<Workspace[]> responseNew = restTemplate
.exchange(
url,
HttpMethod.GET,
requestEntity,
Workspace[].class);
Workspace [] employees1 = responseNew.getBody();
list.stream().forEach(entry -> System.out.println(entry.getDescription()+": "+ entry.getId() + ": " + entry.getName()));
Thanks for the responses , got to learn a lot from the answers

How to test getting parameters on the Rest service using the Post method

I'm trying to test getting parameters for processing a request using the Post method
#RestController
#RequestMapping("api")
public class InnerRestController {
…
#PostMapping("createList")
public ItemListId createList(#RequestParam String strListId,
#RequestParam String strDate) {
…
return null;
}
}
test method
variant 1
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class InnerRestControllerTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Test
void innerCreatePublishList() {
String url = "http://localhost:" + this.port;
String uri = "/api/createList";
String listStr = "kl";
String strDate = "10:21";
URI uriToEndpoint = UriComponentsBuilder
.fromHttpUrl(url)
.path(uri)
.queryParam("strListId", listStr)
.queryParam("strDate ", strDate)
.build()
.encode()
.toUri();
ResponseEntity< ItemListId > listIdResponseEntity =
restTemplate.postForEntity(uri, uriToEndpoint, ItemListId.class);
}
}
variant 2
#Test
void createList() {
String uri = "/api/createList";
String listStr = "kl";
String strDate = "10:21";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(uri)
.queryParam("strListId", listStr)
.queryParam("strDate ", strDate);
Map<String, String> map = new HashMap<>();
map.put("strListId", listStr);//request parameters
map.put("strDate", strDate);
ResponseEntity< ItemListId > listIdResponseEntity =
restTemplate.postForEntity(uri, map, ItemListId.class);
}
Update_1
In my project exceptions is handled thus:
dto
public final class ErrorResponseDto {
private String errorMsg;
private int status;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
LocalDateTime timestamp;
...
handler
#RestControllerAdvice
public class ExceptionAdviceHandler {
#ExceptionHandler(value = PublishListException.class)
public ResponseEntity<ErrorResponseDto> handleGenericPublishListDublicateException(PublishListException e) {
ErrorResponseDto error = new ErrorResponseDto(e.getMessage());
error.setTimestamp(LocalDateTime.now());
error.setStatus((HttpStatus.CONFLICT.value()));
return new ResponseEntity<>(error, HttpStatus.CONFLICT);
}
}
In methods, where necessary, I throw a specific exception...
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved
[org.springframework.web.bind.MissingServletRequestParameterException:
Required String parameter 'strListId' is not present]
Who knows what the error is. Please explain what you need to add here and why ?
Let's take a look on declarations of postEntity:
postForEntity(URI url, Object request, Class<T> responseType)
...
postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
As you can see, first argument is either URI or String with uriVariables, but second argument is always request entity.
In you first variant you put uri String as URI and then pass uriToEndpoint as request entity, pretending that it is request object. Correct solution will be:
ResponseEntity<ItemListId> listIdResponseEntity =
restTemplate.postForEntity(uriToEndpoint, null, ItemListId.class);
Addressing your comments.
If server responded with HTTP 409, RestTemplate will throw exception with content of your ErrorResponseDto. You can catch RestClientResponseException and deserialize server response stored in exception. Something like this:
try {
ResponseEntity<ItemListId> listIdResponseEntity =
restTemplate.postForEntity(uriToEndpoint, null,
ItemListId.class);
...
} catch(RestClientResponseException e) {
byte[] errorResponseDtoByteArray = e.getResponseBodyAsByteArray();
// Deserialize byte[] array using Jackson
}

Java / Jackson - 'Unrecognized token' passing JSON object parameter

Java JAX-RS web service with Jersey / Jackson, a service method expects a User parameter (POJO) as JSON. The client app (Angular 6) sends a POST request containing the User parameter (serialized as JSON). The service method call fails with error message: "Unrecognized token 'jsonUser': was expecting ('true', 'false' or 'null')".
Here is the User class (POJO) - you can see I tried annotating all the properties with #JsonProperty, but it's unnecessary, as I'm not "renaming" them:
import java.io.Serializable;
import javax.ws.rs.FormParam;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
//import org.glassfish.jersey.media.multipart.FormDataParam;
/**
* JavaBean for passing the User properties between the UI app (Angular)
* and TearsWs. Implementation requires this to be serializable (JSON).
*/
#JsonIgnoreProperties({ "DELIM" })
public class User implements Serializable {
private String userName;
private String employeeId;
private String employeeName;
private String homeUnitCode;
private boolean certifier;
private HomeUnit[] tkHomeUnits;
private boolean supervisor;
private Employee[] whoISupervise;
private boolean hrStaff;
private boolean collector;
private final static String DELIM = ", ";
public User() {
}
// getters / setters
//#JsonProperty("userName")
public void setUserName(String ldapUid) {
this.userName = ldapUid;
}
public String getUserName() {
return this.userName;
}
//#JsonProperty("employeeId")
public void setEmployeeId(String employeeId) {
this.employeeId = employeeId;
}
public String getEmployeeId() {
return this.employeeId;
}
//#JsonProperty("employeeName")
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public String getEmployeeName() {
return this.employeeName;
}
//#JsonProperty("homeUnitCode")
public void setHomeUnitCode(String homeUnitCode) {
this.homeUnitCode = homeUnitCode;
}
public String getHomeUnitCode() {
return this.homeUnitCode;
}
//#JsonProperty("certifier")
public void setCertifier(boolean certifier) {
this.certifier = certifier;
}
public boolean getCertifier() {
return this.certifier;
}
//#JsonProperty("tkHomeUnits")
public void setTkHomeUnits(HomeUnit[] tkHomeUnitCodes) {
this.tkHomeUnits = tkHomeUnitCodes;
}
public HomeUnit[] getTkHomeUnits() {
return this.tkHomeUnits;
}
//#JsonProperty("supervisor")
public void setSupervisor(boolean supervisor) {
this.supervisor = supervisor;
}
public boolean isSupervisor() {
return this.supervisor;
}
//#JsonProperty("whoISupervise")
public void setWhoISupervise(Employee[] whoISupervise) {
this.whoISupervise = whoISupervise;
}
public Employee[] getWhoISupervise() {
return this.whoISupervise;
}
//#JsonProperty("hrStaff")
public void setHrStaff(boolean hrStaff) {
this.hrStaff = hrStaff;
}
public boolean isHrStaff() {
return this.hrStaff;
}
//#JsonProperty("collector")
public void setCollector(boolean collector) {
this.collector = collector;
}
public boolean isCollector() {
return this.collector;
}
//methods
public boolean hasTauthority() {
return this.certifier || this.collector;
}
public String toString() {
int tkHUs = (tkHomeUnits == null) ? 0 : tkHomeUnits.length;
return "[User: "
+ "userName=" + this.userName + DELIM
+ "employeeId=" + this.employeeId + DELIM
+ "employeeName=" + this.employeeName + DELIM
+ "homeUnitCode=" + this.homeUnitCode + DELIM
+ "certifier=" + this.certifier + DELIM
+ "hrStaff=" + this.hrStaff + DELIM
+ "collector=" + this.collector + DELIM
+ "I can certify " + tkHUs + " homeUnits" + "]";
}
}
Here is the (Java) service method, which should accept and process the POST request:
/**
* Web service method.
*/
#POST
#Path("getTkHomeUnitEmployees")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response getTkHomeUnitEmployees(User user, #HeaderParam("X-Request-Param") String homeUnitCode) throws Exception {
String exceptionMessage;
if (user == null) {
exceptionMessage = "getTkHomeUnitEmployees() received a null User.";
log.error(exceptionMessage);
Response response = Response
.status(500)
.entity(exceptionMessage)
.build();
return response;
}
if (homeUnitCode == null || homeUnitCode.equals("")) {
exceptionMessage = "getTkHomeUnitEmployees() received a null HomeUnitCode.";
log.error(exceptionMessage);
Response response = Response
.status(500)
.entity(exceptionMessage)
.build();
return response;
}
if (!user.hasTauthority()) {
exceptionMessage = "getTkHomeUnitEmployees() received a request from a non-timekeeper and non-collector.";
log.error(exceptionMessage);
Response response = Response
.status(500)
.entity(exceptionMessage)
.build();
return response;
}
try {
Employee[] tkHomeUnitEmployees = new SecurityDao().getTkHomeUnitEmployees(user.getEmployeeId(), homeUnitCode);
Response response = Response
.ok(tkHomeUnitEmployees)
.header("Access-Control-Allow-Origin", "*")
.build();
return response;
} catch (Exception ex) {
exceptionMessage = "getTkHomeUnitEmployees(): " + ex;
Response response = Response
.status(500)
.entity(exceptionMessage)
.build();
return response;
}
}
The User object (client side, Javascript) is converted to JSON and encapsulated as a parameter in HttpParams; the POST passes it in the body of the request.
Here is the (Angular) client method, which sends the POST request to the web service:
getTkHomeUnitEmployees(user: User, homeUnitCode: string): Observable<Employee[]> {
const headers = new HttpHeaders()
.set('Content-Type', 'application/json')
.set('X-Request-Param', homeUnitCode); // homeUnitCode parameter in HttpHeaders
const httpOptions = {
headers: headers
};
let jsonUser: string = JSON.stringify(user);
const httpParams = new HttpParams()
.set('jsonUser', jsonUser);
let postUrl = this.wsUrl + 'getTkHomeUnitEmployees';
//postUrl += '?homeUnitCode=' + homeUnitCode; // homeUnitCode parameter as QueryParam
let obsArrayEmployees: Observable<Employee[]> = this.httpClient.post<Employee[]>(postUrl, httpParams, httpOptions);
return obsArrayEmployees;
}
...here I'm debugging the client (# browser Dev Tools), with a break in the getTkHomeUnitEmployees() method:
...I've displayed the value of jsonUser in the Console:
...here is the error in the Response:
...and here is the Request Params.
So, it appears the Jackson JsonParser is attempting to read and parse the parameter sent in the request, but the parameter includes "jsonUser=" at the beginning as part of it's value (json to be parsed). This is clearly wrong...
The service method blows up before actually entering / processing code; I can't set a breakpoint within the service method to examine the value of the parameter. It behaves as a "parameter invalid, return to caller" response.
I thought to manually hack the "jsonUser=" out of it (# client side), but it's not there. At the client, "jsonUser=" is not part of the parameter value; I believe it's just the key=value syntax of an http parameter (parameter-name=parameter-value), perhaps it's being prepended when the parameter is encapsulated into the HttpParams object.
Obviously I'm doing something wrong, but I haven't been able to figure it out; I thought this was the correct way to do this, but apparently not. Hope someone can help soon, I've been stuck on this for a couple days already.
You don't need to covert the 'user' object to string to pass to backend. Try passing the user object as it is.
this.httpClient.post<Employee[]>(postUrl, user, httpOptions);
And also please check if parameters passed really match the rest service exposed.

Post and Receive Result

Android space
void post(Food food)
{
Gson gson = new Gson();
String jsonFood = gson.toJson(food);
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.postForEntity(URL, jsonFood, String.class);
}
Back end space
#PostMapping("/food")
public void postFood(#RequestBody String foodJson)
{
Food food = new GsonBuilder().create().fromJson(foodJson, Food.class);
String id = createId(food);
// now how do I send back saying I got this and here is an id?
}
After I receive I want to reply back saying I got the information and send back an ID.
Spring boot will automatically convert the json to a model object under the covers using Jackson
#PostMapping("/food")
public YourResponse postFood(#RequestBody Food food)
{
String id = createId(food);
return new YourResponse(id,"hello World");
}
Response object
public class YourResponse{
private String id;
private String response;
//.. constructor, getter setter
}
You can create a response model
public class PostFoodResponse{
private String id;
private String response;
//.. constructor, getter setter
}
In your code create an object of PostFoodResponse set data and send the object back as a json response
#PostMapping("/food")
public String postFood(#RequestBody String foodJson)
{
Food food = new GsonBuilder().create().fromJson(foodJson, Food.class);
String id = createId(food);
// now how do I send back saying I got this and here is an id?
PostFoodResponse response = new PostFoodResponse(id, "I got this");
return new GsonBuilder().create().toJson(response);
}

405 Method Not Allowed with Spring

I have the following test for an HTTP endpoint:
public static final String DATA_PARAMETER = "data";
public static final String ID_PARAMETER = "id";
public static final String VIDEO_SVC_PATH = "/video";
public static final String VIDEO_DATA_PATH = VIDEO_SVC_PATH + "/{id}/data";
#Multipart
#POST(VIDEO_DATA_PATH)
public VideoStatus setVideoData(#Path(ID_PARAMETER) long id, #Part(DATA_PARAMETER) TypedFile videoData);
#Test
public void testAddVideoData() throws Exception {
Video received = videoSvc.addVideo(video);
VideoStatus status = videoSvc.setVideoData(received.getId(),
new TypedFile(received.getContentType(), testVideoData));
assertEquals(VideoState.READY, status.getState());
Response response = videoSvc.getData(received.getId());
assertEquals(200, response.getStatus());
InputStream videoData = response.getBody().in();
byte[] originalFile = IOUtils.toByteArray(new FileInputStream(testVideoData));
byte[] retrievedFile = IOUtils.toByteArray(videoData);
assertTrue(Arrays.equals(originalFile, retrievedFile));
}
I'm trying to implement the requirements defined by this test with the following endpoint defined in Swing:
#RequestMapping(method = RequestMethod.POST, value = "/video/{id}/data")
public void postVideoData(#PathVariable("id") long videoId,
#RequestParam("data") MultipartFile videoData) throws IOException {
if (videoId <= 0 || videoId > videos.size()) {
throw new ResourceNotFoundException("Invalid id: " + videoId);
}
Video video = videos.get((int)videoId - 1);
InputStream in = videoData.getInputStream();
manager.saveVideoData(video, in);
}
The problem is that I get a "405 Method Not Allowed" error. What am I doing wrong so that my POST method is not being recognized?
The problem is that the client interface expects a VideoStatus object returned from the server. I declared the method on the server side to return void.
I don't know if you already fix your problem, but I got the same error, because I am new with Retrofit too :).
The solution for me, was to put an Annotation to specify the response content type, in my case
#ResponseBody
Another change that I must did, was to change void for a custom status.
Hope this helps or at least gives you a light.
Rgds.
I had the same issue. RetroFit request calls must have either a return type or Callback as last argument.
So in the RetroFitted API:
#POST("/path")
public Void saveWhatever(#Body Whatever whatever);
Than in the controller it must be :
#RequestMapping(value = "/path", method = RequestMethod.POST)
public #ResponseBody Void saveWhatever(#RequestBody Whatever whatever) {
repository.save(whatever);
return null;
}

Categories