I have this piece of code:
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
protected Date statusDate;
But for somehow it accepts date formats like -
"statusDate": "2017-13-27"
or
"statusDate": "201823-12-12"
Is it possible to validate the format within the request (not manually)?
#JsonFormat is used to set the output format when you're returning the statusDate as response.
It is better you create a DTO object that will accept String statusDate and then convert it to Date format in your controller.
To validate the date in String format, you can use #Pattern
public class StatusDateDto {
#NotNull(message="Status date is a required field")
#Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}", message="Invalid status date")
private String statusDate;
//Getter and setter
}
public ResponseEntity<?> postStatusDate(#Valid #RequestBody StatusDateDto dateDto, BindingResult result) {
if (result.hasFieldErrors()) {
String errors = result.getFieldErrors().stream()
.map(p -> p.getDefaultMessage()).collect(Collectors.joining("\n"));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
}
// Convert the String to Date after validation
return ResponseEntity.ok().build();
}
Yes, you can. Let me show u the details.
First, pls create a base controller to filter all the requests like below:
package com.letv.address.controller;
import com.letv.address.utils.ConstantCode;
import com.letv.address.utils.ResponseWrapper;
import com.letv.xice.core.controller.GlobalController;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import java.util.ArrayList;
import java.util.List;
/**
* Created by shichaoyang on 2017/1/10.
*/
public class BaseController extends GlobalController {
public ResponseWrapper requestCheckAndPost(BindingResult result) {
if (result.hasErrors()) {
List<Object> errorList = new ArrayList<>();
StringBuilder sBuilder = new StringBuilder();
for (ObjectError error : result.getAllErrors()) {
String fieldName = ((FieldError) error).getField();
String fieldMessage = error.getDefaultMessage();
sBuilder.append(fieldName)
.append(" ")
.append(getMessage(fieldMessage))
.append(";");
errorList.add(fieldName);
}
return new ResponseWrapper(
ConstantCode.FAILING_WITH_ERROR_PARAM_CODE
, errorList.toArray()
, ""
, sBuilder.toString()
);
}
return null;
}
}
From above code, the BindingResult will check the #JsonFormat or other components header, like #NotBlank, #Pattern and so on. If they hit the rule, they will be caught by the BindingResult and we can get the error. Below is the DTO object I used, just show it to u so that you can get more details:
package com.letv.address.controller.dto;
import com.letv.address.utils.ConstantCode;
import org.hibernate.validator.constraints.NotBlank;
/**
* Created by shichaoyang on 2016/12/23.
*/
public class ChildrenAreaSelectRequest{
#NotBlank(message = ConstantCode.REQUEST_VALIDATE_NOT_EMPTY)
private String areaIds;
public String getAreaIds() {
return areaIds;
}
public void setAreaIds(String areaIds) {
this.areaIds = areaIds;
}
}
Then in our business logic controller, we need to extend the base controller and write the codes like below:
package com.letv.address.controller;
import com.letv.address.controller.dto.ChildrenAreaSelectRequest;
import com.letv.address.controller.dto.ParentAreaSelectReqeust;
import com.letv.address.domain.Area;
import com.letv.address.service.ChildAreaService;
import com.letv.address.utils.ConstantCode;
import com.letv.address.utils.ResponseWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
/**
* Created by shichaoyang on 2016/12/12.
*/
#RestController("areaController")
public class AreaController extends BaseController {
#Autowired
protected ChildAreaService childAreaService;
/**
* get area info by parent id
*
* #param areaReqeust
* #param result
*/
#ResponseBody
#RequestMapping(value = ConstantCode.CHILD_AREA_PATH, method = {RequestMethod.POST})
public ResponseWrapper childArea(#RequestBody #Valid ParentAreaSelectReqeust areaReqeust, BindingResult result) {
ResponseWrapper validationWrapper = requestCheckAndPost(result);
if (validationWrapper != null) {
return validationWrapper;
}
List<Area> areaList = childAreaService.selectByParentId(areaReqeust.getParentId());
if (areaList == null || areaList.size() == 0) {
return new ResponseWrapper(ConstantCode.SUCCESS_WITH_EMPTY_DATA_CODE, new ArrayList<>());
} else {
return new ResponseWrapper(ConstantCode.SUCCESS_WITH_FILL_DATA_CODE, areaList);
}
}
}
By using above method, you can validate the field easily within the request. It's a beautiful way to achive this.
Hope that helps.
EDIT:
Replace the images with real codes so that anyone needs to test it.
Well, you need to write a custom date serialisation/de-serialisation class and throw a custom exception if the date format you receive while intercepting the data is not what you'd expected.
This answer will point you in the right direction of how to do so.
Also you can have the validator in your code to validate the payload and leave Jackson to it's simple state.
Related
Check out these two controller methods. They both use the same object. The first one, called backtestStrategy correctly returns a JSON formatted response. The second one, called getAllStrategies, returns an array of values not in JSON format. All the values are there but none of the keys.
#RestController
#RequestMapping("/api/v1")
public class StrategyController {
#Autowired
private StrategyService strategyService;
#GetMapping("/backtest")
public Response backtestStrategy(#RequestParam String strategyName, #RequestParam String symbol) {
Response response = strategyService.backtestStrategy(strategyName,symbol);
return response;
}
#GetMapping("/strategies")
public List<Response> getAllStrategies() {
List<Response> strategies = strategyService.getAllStrategies();
return strategies;
}
}
Suggestions?
EDIT:
The first one apparently works because I create the Response object, populate it, save it to a db, and return the created object. The second one is reading from the db. The values are in the db correctly.
Here is the order of operations: controller calls service implementation which is defined by an interface. Service implementation calls repository, which makes the db query. Here is the query:
#Query(value="select * from strategy", nativeQuery = true)
List<Response> getAllStrategies();
You can use the following example to get the desired response:
Pojo.java
import lombok.AllArgsConstructor;
import lombok.Getter;
#Getter
#AllArgsConstructor
public class Pojo {
private String name;
}
DemoRestController.java
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
#RestController
#RequestMapping("/api/v1")
public class DemoRestController {
#GetMapping("/single")
public ResponseEntity<Pojo> single() {
return ResponseEntity.ok(new Pojo("one"));
}
#GetMapping("/multiple")
public ResponseEntity<List<Pojo>> multiple() {
return ResponseEntity.ok(Arrays.asList(new Pojo("one"), new Pojo("two")));
}
}
Output - Single
{
"name": "one"
}
Output - Multiple
[
{
"name": "one"
},
{
"name": "two"
}
]
I'm making simple REST service and the client for it. I try to make some kind of security, so I generate UUID just, when you go to /login site:
#RequestMapping("/login")
public uuid getUUID()
{
temp = new uuid();
return temp;
}
Then by the client side I get this UUID. Now I want to pass this UUID to my service "getPerson" which looks like this:
#RequestMapping("/{userId}/getperson")
public Person getPerson(#PathVariable("userId") int user, uuid uuid)
{
if(System.currentTimeMillis() - uuid.getDate().getTime() < 60000 &&
uuid.getHash().toString().equals(temp.toString()))
return personService.getPerson(user);
else
return null;
}
What I wanted to achieve is a simple validation of UUID by comparing its timestamps and Strings with the previously made temp object. And here is my problem - I don't know how to pass the object uuid from client.
My very sophisticated client looks like this:
RestTemplate restTemplate = new RestTemplate();
uuid myUUID = restTemplate.getForObject("http://localhost:8080/login", uuid.class);
HttpEntity<uuid> requestUUID = new HttpEntity<uuid>(myUUID);
//HttpEntity<Person> request = new HttpEntity<Person>(new Person("John", "Great", 2));
//restTemplate.postForObject("http://localhost:8080/addperson", request, Person.class);
Person person = restTemplate.postForObject("http://localhost:8080/2/getperson", requestUUID, Person.class);
I don't know if this type of validation is safe, but would be great if you could tell me how to pass my object.
I found simple solution below,
It is the concept which i shown below
import java.util.UUID;
public class Person {
private UUID uuid;
public Person() {
}
public Person(UUID uuid) {
super();
this.uuid = uuid;
}
public UUID getUuid() {
return uuid;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
#Override
public String toString() {
return "Person [uuid=" + uuid + "]";
}
}
Controller IWebExample Interface:
import java.util.UUID;
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.ResponseBody;
public interface IWebExample {
#RequestMapping(value = "/login", method = RequestMethod.GET)
#ResponseBody
UUID getUUID();
#RequestMapping(value = "/{userId}/getPerson", method = RequestMethod.POST)
#ResponseBody
Person getPerson(int userId, UUID uuid);
}
Controller Implementation
import java.util.UUID;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import com.finvendor.serverwebapi.resources.example.IWebExample;
import com.finvendor.serverwebapi.resources.example.Person;
#Controller
public class WebExampleImpl implements IWebExample {
#Override
public UUID getUUID() {
return UUID.randomUUID();
}
#Override
public Person getPerson(#PathVariable("userId") int userId, #RequestBody UUID uuid) {
return new Person(uuid);
}
}
Client code:
import java.util.UUID;
import org.springframework.web.client.RestTemplate;
public class ClientMainApp {
public static void main(String[] args) {
RestTemplate template=new RestTemplate();
UUID uuid = template.getForObject("http://localhost:8080/mylogin",UUID.class);
System.out.println("UUID="+uuid);
//here i am passing uuid object (as per your requirement)
Person person = template.postForObject("http://localhost:8080/1/getPerson", uuid, Person.class);
System.out.println(person);
}
}
Step to run
-Create war file
-Deploy in tomcat and
-Run the client code
Output would be:
UUID=e1e89e96-8118-446c-900d-d123b1b566ea
Person [uuid=e1e89e96-8118-446c-900d-d123b1b566ea]
In output you can observe we pass object uuid in postForObject(...) rest template method in client code.
The bottom line of solution of your problem is you need to use #RequestBody for uuid input param
Hope you got the idea and solution!!
I'm trying to use the validation annotations within a pojo object by sending json in the body of the request and converting the json to the pojo object. What I would like to do is have the service consuming this json to return a bad request if the object is not valid. Does anyone know how to go about doing this? I've seen plenty of tutorials that show how to do form validation using the annotations, but what about just a simple json request?
POJO Object:
import play.data.validation.Constraints.*;
public class TestObject {
#Required
#Min(0)
public Integer testInt;
public Integer getTestInt() {
return testInt;
}
public void setTestInt(Integer testInt) {
this.testInt = testInt;
}
}
I could probably look at every element by parsing the json and validate it that way, but that seems ridiculous...
import models.Domain;
import play.libs.Json;
import play.mvc.BodyParser;
import play.mvc.Controller;
import play.mvc.Result;
import com.fasterxml.jackson.databind.JsonNode;
public class TestController extends Controller {
#BodyParser.Of(value = BodyParser.Json.class, maxLength = 10 * 1024)
public static Result create() {
JsonNode json = request().body().asJson();
TestObject testObject = Json.fromJson(json, TestObject.class);
//would like to validate the object here, based on annotations
//in the bean object...
//only if the object is valid, return ok...
return ok(json);
}
}
Thanks Peace!!
Since Play 2.5 Form.form(...) is deprecated.
Here is how you should do it now:
private FormFactory formFactory;
#Inject
YourContructor(FormFactory formFactory){
this.formFactory
}
#BodyParser.Of(value = BodyParser.Json.class, maxLength = 10 * 1024)
public static Result create() {
JsonNode json = request().body().asJson();
Form<TestObject > TestObjectValidationForm = formFactory.form(TestObject .class)
.bind(json);
if(TestObjectValidationForm .hasErrors())
return badRequest("Invalid");
TestObject testObject = Json.fromJson(json, TestObject.class);
//only if the object is valid, return ok...
return ok(json);
}
Anyway, I don't really like to use form to do it... there is no better option?
this is what i'll do
#BodyParser.Of(value = BodyParser.Json.class, maxLength = 10 * 1024)
public static Result create() {
JsonNode json = request().body().asJson();
//this is not neccessary for validation except maybe to be sure the json can be
//mapped back to pojo before validating then you'll have to wrap
//this statement in a try catch so you can recover from any errors during mapping
//TestObject testObject = Json.fromJson(json, TestObject.class);
Form<TestObject> testObjectForm = Form.form(TestObject.class);
Form<TestObject> testObjForm = testObjectForm.bind(json);
if(testObjForm.hasErrors){
doStuff()
}else{
return ok(json);
}
}
im trying to retrieve a get response from IMDB.com's or AnimeNewsNetwork's APIs using java EE
i started hacking this guy's really nice example of connecting to a weather website API (had to install vaadin view framework)
it looks like i've made some progress (especially in understanding what's going on) but im at a problem for a long time now (3 hours although a lot of that was failing to get github to work....)
i am getting, when i try to run the application:
javax.servlet.ServletException: com.vaadin.server.ServiceException: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyReader not found for media type=text/html; charset=utf-8, type=class org.example.domain.ForecastResponse, genericType=class org.example.domain.ForecastResponse.
i pasted
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.15</version>
</dependency>
into the pom thing and i found a button in netbeans that "checks for problems" then it automatically updated my project with dependencies which took ages and was rather shocking and i thought it'd work then
then i tried changing .request(MediaType.TEXT_HTML) back and forth to APPLICATION_JSON (imdb defaults to json) and TEXT_HTML_TYPE or whatever i tried.
lastly i tried changing my query input from a correct movie ID tt1285016 to an incorrect one to see if that would give a different result when it only returns 2 parameters instead of 20. this brings me to the question: how does my application know how to map the response from the API to the class? a normal response is a JSON string that has 20 fields so how does my application know where to put them? maybe this is the missing link. i just changed the movie classes attributes to Response and Error to match the 2 returns from sending an incorrect movie ID to the API but still same problem
(hours later): tried to run using the anime news network's API instead and getting similar error which differs in saying "messagbodyreader not found" for text/xml instead of text/html...
i've also tried adding a few more things to the pom file from jersey dependencies document and well one of them makes my project stop deploying even after i delete and rebuild it so i've had fun with that.
below i will paste the code i am using. please note i am just hacking this guy's example up trying to get my basic functionality working and avoiding changing anything else so i can understand what i'm supposed to do, before rewriting it from scratch just to prototype before beginning to plan the actual project!! i have a lot more tutorials/examples to look at in the future so don't worry about my code other than the problem i'm stuck on :/
this is the "view" java class that uses vaadin
VaadinUI.java
package org.example;
import com.vaadin.annotations.Theme;
import com.vaadin.cdi.CDIUI;
import com.vaadin.data.Property;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.NativeSelect;
import com.vaadin.ui.UI;
import javax.inject.Inject;
import org.vaadin.maddon.label.Header;
import org.vaadin.maddon.layouts.MVerticalLayout;
/**
* A simple example how to consume REST apis with JAX-RS and display that in
* a Vaadin UI.
*/
#CDIUI("")
#Theme("valo")
public class VaadinUI extends UI {
#Inject
JsonService service;
ForecastDisplay display = new ForecastDisplay();
NativeSelect citySelector = new NativeSelect("Choose city");
#Override
protected void init(VaadinRequest request) {
// citySelector.addItems("tt1285016","tt1285016");
// citySelector.addValueChangeListener(this::changeCity);
// citySelector.setValue("tt1285016");
setContent(new MVerticalLayout(
new Header("Simple REST weather"),
display));
display.setForecast(service.fetchMovie());
}
//// display.setForecast(service.getForecast(citySelector.getValue().toString()));
// }
}
JsonService.java
package org.example;
import com.vaadin.annotations.Theme;
import com.vaadin.cdi.CDIUI;
import com.vaadin.data.Property;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.NativeSelect;
import com.vaadin.ui.UI;
import javax.inject.Inject;
import org.vaadin.maddon.label.Header;
import org.vaadin.maddon.layouts.MVerticalLayout;
/**
* A simple example how to consume REST apis with JAX-RS and display that in
* a Vaadin UI.
*/
#CDIUI("")
#Theme("valo")
public class VaadinUI extends UI {
#Inject
JsonService service;
ForecastDisplay display = new ForecastDisplay();
NativeSelect citySelector = new NativeSelect("Choose city");
#Override
protected void init(VaadinRequest request) {
// citySelector.addItems("tt1285016","tt1285016");
// citySelector.addValueChangeListener(this::changeCity);
// citySelector.setValue("tt1285016");
setContent(new MVerticalLayout(
new Header("Simple REST weather"),
display));
display.setForecast(service.fetchMovie());
}
//// display.setForecast(service.getForecast(citySelector.getValue().toString()));
// }
}
ForecastDisplay.java
package org.example;
import java.util.Calendar;
import java.util.Date;
import org.example.domain.Forecast;
import org.example.domain.ForecastResponse;
import org.vaadin.maddon.label.Header;
import org.vaadin.maddon.label.RichText;
import org.vaadin.maddon.layouts.MVerticalLayout;
/**
*
* #author Matti Tahvonen <matti#vaadin.com>
*/
public class ForecastDisplay extends MVerticalLayout {
String mainTemplate = "Tomorrow in there will be ";
public ForecastDisplay() {
}
public void setForecast(ForecastResponse fr) {
// removeAllComponents();
removeAllComponents();
addComponents(
new Header(String.format(mainTemplate,fr.getMovie().getMovieInfo(),
fr.getMovie().getMovieInfo()))
);
//
// for (Forecast f : fr.getList()) {
// cal.add(Calendar.DAY_OF_MONTH, 1);
// Date date = cal.getTime();
// Double temperature = f.getTemp().getDay();
// String desc = f.getWeather().get(0).getDescription();
// String md = String.format(detailTemplate, date, temperature, desc);
// addComponent(new RichText().withMarkDown(md));
}
}
ForecastResponse.java class
package org.example.domain;
import java.util.ArrayList;
public class ForecastResponse {
// private String cod;
// private Double message;
// private City city;
// private Integer cnt;
// private java.util.List<Forecast> list = new ArrayList<>();
private Movie movie;
//
// public String getCod() {
// return cod;
// }
//
// public void setCod(String cod) {
// this.cod = cod;
// }
//
// public Double getMessage() {
// return message;
// }
//
// public void setMessage(Double message) {
// this.message = message;
// }
//
// public City getCity() {
// return city;
// }
//
// public void setCity(City city) {
// this.city = city;
// }
public Movie getMovie()
{
return movie;
}
public void setMovie(Movie movie) {
this.movie = movie;
}
//
// public Integer getCnt() {
// return cnt;
// }
//
// public void setCnt(Integer cnt) {
// this.cnt = cnt;
// }
//
// public java.util.List<Forecast> getList() {
// return list;
// }
//
// public void setList(java.util.List<Forecast> list) {
// this.list = list;
// }
}
Movie.java class
package org.example.domain;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Generated;
public class Movie {
private String Response;
private String Error;
public String getMovieInfo() {
return Response;
}
public void setMovieInfo(String name) {
this.Response = Response;
}
public String getMovieInfo2() {
return Error;
}
public void setMovieInfo2(String name) {
this.Error = Error;
}
}
the guy's project before i cut it up (you need vaadin plugin to run it)
https://github.com/mstahv/consuming-rest-apis
just to reiterate i am getting
javax.servlet.ServletException: com.vaadin.server.ServiceException: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyReader not found for media type=text/html; charset=utf-8, type=class org.example.domain.ForecastResponse, genericType=class org.example.domain.ForecastResponse.
when i try to run the application and have tried a number of solutions... but i think its because of something i changed because i had no problems getting the guy's original files to run
i have uploaded my current version to mirrorcreator (can't get git to work...sigh) : https://www.mirrorcreator.com/files/KQF7SBBH/restapiexampletest1zip._links
thankyou for reading . i have searched google a lot but not able to fix it using the suggestions like change the poms file or whatever....
My Spring Controller of Spring JSON application returns a JSONObject. On accessing the url, i am getting 406 error page.
It works when i return String or ArrayList.
Spring Controller:
package com.mkyong.common.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONException;
import org.json.JSONObject;
#Controller
public class JSONController {
#RequestMapping("/test")
#ResponseBody
public JSONObject test() {
try {
JSONObject result = new JSONObject();
result.put("name", "Dade")
.put("age", 23)
.put("married", false);
return result;
} catch (JSONException ex) {
Logger.getLogger(JSONController.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
}
How can i resolve this issue? Thanks for help. I am new to Spring MVC, couldn't found resolution to this issue in the existing SO answers.
You're trying to manually do something that Spring MVC already it automatically for you. Spring automatically deduces a representation of the returning type and does a converstion. How it does it you can learn from http://spring.io/blog/2013/05/11/content-negotiation-using-spring-mvc. In your case its converting to JSON.
It works when i return String or ArrayList
What happens under the hood is that Spring MVC is using Jackson library, to convert the return type to JSON. And since it has no issue converting the String or List type, all works OK.
What happens in the code you've posted is that, Jackson's object mapper is trying to convert JSONObject instance to JSON, and this fails, cause jackson expects a POJO object which JSONObject instance isn't.
To have it work you should simply write your POJO and return it. So something like
public class Person {
private String name;
private Integer age;
private Boolean married;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getMarried() {
return married;
}
public void setMarried(Boolean married) {
this.married = married;
}
}
and have your method changed to
#RequestMapping("/test")
#ResponseBody
public Person test() {
Person person = new Person();
person.setName("Dade");
person.setAge(23);
person.setMarried(false);
return person;
}
For what concerns your error, the same exception you will see in the working example if you for example delete getters and setters, or name them wrongly, an exception happens while trying to convert to a representation and you get a 406 error
I think you need to set headers in #RequestMapping and return HashMap.
#RequestMapping(value = "json", method = RequestMethod.GET, headers = "Accept=application/json")
public #ResponseBody
Map<String, String> helloJson() {
HashMap<String, String> map = new HashMap<String, String>();
map.put("k1", "v1");
map.put("k2", "v2");
map.put("k3", "v3");
return map;
}