This is my Controller:
package com.hodor.booking.controller;
import com.hodor.booking.jpa.domain.Vehicle;
import com.hodor.booking.service.VehicleService;
import com.wordnik.swagger.annotations.Api;
import org.apache.commons.lang.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
#RestController
#RequestMapping("/api/v1/vehicles")
#Api(value = "vehicles", description = "Vehicle resource endpoint")
public class VehicleController {
private static final Logger log = LoggerFactory.getLogger(VehicleController.class);
#Autowired
private VehicleService vehicleService;
#RequestMapping(method = RequestMethod.GET)
public List<Vehicle> index() {
log.debug("Getting all vehicles");
return vehicleService.findAll();
}
#RequestMapping(value="/save", method=RequestMethod.POST, consumes="application/json")
#ResponseBody
public Vehicle setVehicle(#RequestBody Vehicle vehicle) {
log.debug("Inserting vehicle");
if (vehicle.getLicensePlate() == null){
return new ResponseEntity<Void>(HttpStatus.CONFLICT);
}
return vehicleService.saveVehicle(vehicle);
}
}
What I want to achieve in above If-Guard is that, in case the vehicle Object does not have the LicensePlate Member, send back an according HTTP Status Header CONFLICT or something.
I am coming from a Node and Express background and I am used to set my header, send the response and be done with it. However in this case (JPA) it does not seem to work. Any ideas?
One alternative is to take advantage of Spring's validation support to declarative add validation of your POJOs. Basically, you add annotations to your Vehicle class like:
public class Vehicle {
#NotNull
private LicensePlate licensePlate;
// getters, setters
}
And you add a #Valid annotation to your controller method:
#ResponseBody
public Vehicle setVehicle(#RequestBody #Valid Vehicle vehicle) {
log.debug("Inserting vehicle");
return vehicleService.saveVehicle(vehicle);
}
If the validation fails, Spring will return a 400 response.
Make sure that you have JSR-303/JSR-349 Bean Validation implementation such as the Hibernate Validator (it can be used without Hibernate's ORM support) on your classpath.
More information can be found in the validation chapter of the Spring reference docs.
What version of Spring MVC are you using? From another post How to respond with HTTP 400 error in a Spring MVC #ResponseBody method returning String?. It states that Spring MVC 4.1 and later using different syntax.
Related
I have two applications A and B communicating with each other using FeignClient.
As an application A I want to have validation on the data that is returned by application B. If I want to validate request parameters I can easily use #Valid annotation and annotate object with correct spring validation annotations. What about response ?
#FeignClient()
public interface AccountClient {
#PostMapping("/accounts/account/create")
void createAccount(#Valid CreateAccountRequest request);
#PostMapping("/accounts/account/get")
AccountResponse getAccount(AccountRequest request);
}
public classs AccountResponse {
#NotNull
public String status;
}
Code as an example. I can easily validate CreateAccountRequest in application B. But what about AccountResponse? In that case #NotNull is not working. I would prefer to avoid getting the response and manually checking if status != null cause I would have much more fields like this.
In this case, the response validation should work if you place #Validated onto AccountClient interface and then #Valid onto getAccount method.
This is standard Spring validation feature, applicable not only for Feign
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
#Validated
#FeignClient
public interface AccountClient {
#PostMapping("/accounts/account/create")
void createAccount(#Valid CreateAccountRequest request);
#Valid
#PostMapping("/accounts/account/get")
AccountResponse getAccount(AccountRequest request);
}
When i go (hit URL in the browser) to some URL
I got a json response matching my Class fields (GET RESPONSE).
I want to converter this json to my object without using some Java converters like jackson.
What spring annotation could i use here?
What i try to do is something like that, so it automatically translates json to object:
#RequestMapping("/getCar")
public Car getCar(#SOMEANNOTATION(Car car)
{ return car;}
you can do it using RestTemplate:
RestTemplate restTemplate = new RestTemplate();
Car car = restTemplate.getForObject("http://localhost:8080/getCar", Car.class);
you need a Class car, so Spring can map it:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Car {
...
}
but you need to understand, that Spring will still use some converter under the hood to create/read the data...
you can try this #Consumes({ "application/json", "application/xml" })
There's another solution from Sring Boot documentation Building a RESTful Web Service.
Basically, the serialization/deserialization of the object is handled automatically by Spring converter. In other word, you have nothing to do in this case.
Consider a basic REST controller :
package hello;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
}
Explanation
The Greeting object must be converted to JSON. Thanks to Spring’s HTTP
message converter support, you don’t need to do this conversion
manually. Because Jackson 2 is on the classpath, Spring’s
MappingJackson2HttpMessageConverter is automatically chosen to convert
the Greeting instance to JSON.
You have to use something like this to automatically convert your request body json to a Java Object.
#RequestMapping("/getCar", method =RequestMethod.POST,
produces=MediaType.APPLICATION_JSON_VALUE,consumes=MediaType.APPLICATION_JSON_VALUE)
public Car getCar(#RequestBody Car car){
return car;
}
However, Spring will use an HttpMessageConverter at the backend to convert your json request to a POJO. Something like this should be added to your #Configuration class
public void configureMessageConverters() {
List<HttpMessageConverter<?> messageConverters = new ArrayList<>();
messageConverters.add(new MappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
I am trying to write the spring REST code to get the parameters send by the android users. For example android users fills out the form and click send button. Now I want to receive the values or parameters in REST API. I search google but could not figure out how to do it. Below is the code I tried but it didn't work
EmailController.java
package com.intern.training
import java.awt.PageAttributes.MediaType;
import java.util.Map;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
#RestController
#RequestMapping("/email")
public class EmailController
{
#RequestMapping(method= RequestMethod.GET)
public void getAll(WebRequest webRequest)
{
Map<String,String[]>params=webRequest.getParameterMap();
System.out.println(params);
}
}
I strongly advice you to not send parameters with a GET request. Prefer a POST request. (See Cross-site request forgery)
Then, create a class which represents the parameters that you are to receive :
public class RequestParams {
private String name;
private String surname;
//Getters, Setters...
}
Then expect this object as a param of your method :
#RequestMapping(method= RequestMethod.POST)
/**
Pay attention to the above #RequestBody annotation
or you will get null instead of the parameters
**/
public void getAll(#RequestBody RequestParams request)
{
request.getName();
request.getSurname();
//...
System.out.println(request.getName() + " " + request.getSurname());
}
We are building a webapp that communicates with a remote API. I would like to design the client for this remote API like this:
def RemoteApi
constructor (username, password)
getCurrentUser() //implementation will use username and password
getEmployees() //implementation will use username and password
...
The point being, I want to pass in the credentials to this client during construction, and have all the other methods use these credentials. My second requirement is I want this RemoteApi instance to be in the session.
I have found out how to pass dynamic constructor arguments here.
I have found out how to create a session attribute here.
But I can't figure out a way to combine these two techniques. From what I gather, you have to instantiate a session attribute in its own getter-like method. This getter-like method won't have access to the form's fields so I won't be able to pass in credentials at this point.
Here's where I'm stuck:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import java.util.Map;
#Controller
#SessionAttributes("remoteClient")
public class LoginController {
#Value("${company.name}")
private String companyName;
#Autowired
private RemoteClient remoteClient;
#ModelAttribute("remoteClient")
public RemoteClient addRemoteClientToSession() {
return remoteClient; //how do initialize this with credentials in here?
}
#RequestMapping("/")
public String showLogin(Model model) {
model.addAttribute("credentials", new Credentials());
return "login";
}
#RequestMapping("/login")
public String login(#ModelAttribute("credentials") Credentials credentials, Map<String, Object> model) {
if (remoteClient.auth(companyName, credentials.getUsername(), credentials.getPassword())) {
model.put("fullname", remoteClient.findCurrentUser().getName());
return "projectView";
} else {
return "login";
}
}
}
Update: Maybe the solution has to do with this technique. I didn't know about ModelAndView before this.
The article I linked to in the question did have the necessary technique in it. What I would do is create an #Autowired RemoteApiFactory that has a newInstance(Credentials) method and pass that into addObject. The implementation of newInstance would probably end up using the new keyword. If anyone knows how to avoid this detail, I'd love to hear it.
Here's its example tweaked for my needs:
#Controller
#SessionAttributes("remoteApi")
public class SingleFieldController {
#Autowired
private RemoteApiFactory remoteApiFactory;
#RequestMapping(value="/single-field")
public ModelAndView singleFieldPage() {
return new ModelAndView("single-field-page");
}
#RequestMapping(value="/remember")
public ModelAndView rememberThought(#MethodAttribute("credentials") Credentials credentials) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("remoteApi", remoteApiFactory.newInstance(credentials));
modelAndView.setViewName("single-field-page");
return modelAndView;
}
}
I have a basic rest controller taking parameters.
How can I refuse the connection if the query string contains parameters that I did not define?
#RestController
#RequestMapping("/")
public class MyRest {
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
#ResponseBody
public String content(#PathVariable id, #RequestParam(value = "page", required = false) int page) {
return id;
}
}
localhost:8080/myapp/123?pagggge=1
Currently when calling this url, the method is executed with just the id, and the unknown paggge parameter is just neglected. Which is fine in general, but how can I validate them and in case return a HTTP status code?
You may get all parameters incoming and handle in the way you want.
Quoting spring documentation:
When an #RequestParam annotation is used on a Map<String, String> or MultiValueMap<String, String> argument, the map is populated with all request parameters.
In your controller method, you can include an argument of type #RequestParam Map<String, String> to get access to all query parameters passed in. A generic ArgsChecker service class can be used to check whether the user passed in an invalid argument. If so, you can throw an exception, which can be handled by an #ControllerAdvice class.
#RestController
#ExposesResourceFor(Widget.class)
#RequestMapping("/widgets")
public class WidgetController {
#Autowired
ArgsChecker<Widget> widgetArgsChecker;
#RequestMapping(value = "", method = RequestMethod.GET, produces = {"application/hal+json"})
public HttpEntity<PagedResources<WidgetResource>> findAll(#RequestParam #ApiIgnore Map<String, String> allRequestParams, Pageable pageable, PagedResourcesAssembler pageAssembler) {
Set<String> invalidArgs = widgetArgsChecker.getInvalidArgs(allRequestParams.keySet());
if (invalidArgs.size() > 0) {
throw new QueryParameterNotSupportedException("The user supplied query parameter(s) that are not supported: " + invalidArgs + " . See below for a list of query paramters that are supported by the widget endpoint.", invalidArgs, widgetArgsChecker.getValidArgs());
}
The ArgsChecker can be defined as follows:
import com.widgetstore.api.annotation.Queryable;
import lombok.Getter;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
public class ArgsChecker<T> {
#Getter
private Set<String> validArgs;
private ArgsChecker(){};
public ArgsChecker(Class<T> someEntityClass){
validArgs= FieldUtils.getFieldsListWithAnnotation(someEntityClass,Queryable.class)
.stream()
.map(Field::getName)
.collect(Collectors.toSet());
validArgs.add("page");
validArgs.add("size");
}
public Set<String> getInvalidArgs(final Set<String> args){
Set<String> invalidArgs=new HashSet<>(args);
invalidArgs.removeAll(validArgs);
return invalidArgs;
}
}
, which uses reflection to find fields which are annotated with the "#Queryable" annotation:
package com.widgetstore.api.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface Queryable {
}
Now mark the fields of your domain class which you want queryable with that
annotation:
#Getter
#Setter
public class Widget {
#Queryable
private String productGuid;
#Queryable
private String serialNumber;
private String manufacturer;
Now make sure the ArgsChecker bean is created at application startup:
#SpringBootApplication
public class StartWidgetApi{
public static void main(String[] args){
SpringApplication.run(StartWidgetApi.class);
}
#Bean(name="widgetArgsChecker")
public ArgsChecker<Widget> widgetArgsChecker(){
return new ArgsChecker<Widget>(Widget.class);
}
//Other ArgsCheckers of different types may be used by other controllers.
#Bean(name="fooArgsChecker")
public ArgsChecker<Foo> fooArgsChecker(){
return new ArgsChecker<Foo>(Foo.class);
}
}
Finally,
Define a #ControllerAdvice class which will listen for exceptions thrown by your application:
package com.widgetstore.api.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
#ControllerAdvice
#RequestMapping(produces = "application/json")
#ResponseBody
public class RestControllerAdvice {
#ExceptionHandler(QueryParameterNotSupportedException.class)
public ResponseEntity<Map<String,Object>> unrecogonizedParameter(final QueryParameterNotSupportedException e){
Map<String,Object> errorInfo = new LinkedHashMap<>();
errorInfo.put("timestamp",new Date());
errorInfo.put("errorMessage",e.getMessage());
errorInfo.put("allowableParameters",e.getValidArgs());
return new ResponseEntity<Map<String, Object>>(errorInfo,HttpStatus.BAD_REQUEST);
}
}
, and define the QueryParameterNotSupportedException:
import lombok.Getter;
import java.util.Set;
#Getter
public class QueryParameterNotSupportedException extends RuntimeException{
private Set<String> invalidArgs;
private Set<String> validArgs;
public QueryParameterNotSupportedException(String msg, Set<String> invalidArgs, Set<String> validArgs){
super(msg);
this.invalidArgs=invalidArgs;
this.validArgs=validArgs;
}
}
Now, when the user hits /widgets?someUnknownField=abc&someOtherField=xyz he will get a json response along the lines of
{"timestamp": 2017-01-10'T'blahblah, "errorMessage": "The user supplied query parameter(s) that are not supported: ["someUnknownField","someOtherField"]. See below for a list of allowed query parameters." ,
"allowableParameters": ["productGuid","serialNumber"]
}
Add HttpServletRequest request in method parameters, do
String query = request.getQueryString()
in the method body and validate that.
I wanted to share a different way since I found ametiste's answer too manual and mancini0's overly verbose.
Suppose, you use Spring framework validation API to bind your request parameters to an object ApiRequest.
#InitBinder
public void initBinder(WebDataBinder binder, WebRequest request) {
binder.setAllowedFields(ApiRequest.getAllowedRequestParameters());
}
#RequestMapping("/api")
public String content(#Valid ApiRequest request, BindingResult bindingResult) {
return request.getId();
}
With following ApiRequest definition.
public class ApiRequest {
private String id;
public static String[] getAllowedRequestParameters() {
return new String[]{
"id"
};
}
}
Then you can use the BindingResult to perform the check whether there were any unexpected request parameters, like so:
String[] suppressedFields = bindingResult.getSuppressedFields();
if (suppressedFields.length > 0) {
// your code here
}