#RestController over interface + spring - spring-boot

I accidentally found this technique of using RestController annotation.
(I saw this not on the internet, but in one of the examples that I was shown. I couldn't find it on the internet)
#RestController
#RequestMapping("api/user/point")
public interface ExampleRestController {
#PostMapping("{key}" )
#Operation(summary = "Manage...",
description = "Allow user ...")
public HttpStatus changePoints(
#PathVariable
#NotBlank
#Parameter(description = "Id user") String key,
#RequestParam("point")
#Min(0) #Parameter(description = "count something..", required = true) Long point,
#RequestParam("type")
#Parameter(description = "Type of...", required = true) TypeOperation type
);
}
What is this technique, who knows ?
I've seen the Rest Api just like this. Can Spring also dynamically create an endpoint method in the RestController, as it does when executing ...extend CrudRepository ? And how does AOP work in this case ? After all, in order for the bean to be configured, we need to put the annotation above the class (like how it is done at the service level), if you put it above the interface, the full automatic configuration of the bean is not guaranteed

It is spring-interface-driven-controllers.
This is new feature of Spring MVC. ( starting with Spring 5.1)

Related

Spring RESTful application - POST method request body mandatory attributes

I am building a RESTful app in Spring Boot and i want to make few attributes in my POST method's request body mandatory.
In swagger yaml, i mark them as required "true", but when i generate the classes using swagger editor, i dont see that impacting in any way, i.e i can't see even a #NotNull annotation or anything of that sort.
How do i mark them as mandatory in my java model class ? Is #NotNull the way to go?
If yes, should i do that in my request body class, or in the jpa document class or both ?
Thanks !
Yes, #NotNull is a way to go.
But also You need to use #Valid annotation.
check example:
#RequestMapping(value = "/appointments", method = RequestMethod.POST)
public String add(#Valid AppointmentForm form, BindingResult result) {
....
}
static class AppointmentForm {
#NotNull
private Date date;
}

How to restrict JSON payload from containing additional fields with Spring?

I have a basic User DTO class...
public class User {
#JsonProperty("firstName")
private String firstName;
#JsonProperty("lastName")
private String lastName;
}
...and a basic request handler in a #RestController class:
#RequestMapping(path = "/users", method = RequestMethod.POST, consumes = { MediaType.APPLICATION_JSON_VALUE })
public UserMessage createUser(#RequestBody User user){
return userService.createUser(user);
}
How can I restrict incoming JSON payloads to contain at most only the required keys?
i.e. accept this payload:
{
"firstName":"foo",
"lastName":"bar"
}
And throw a custom exception on this:
{
"firstName":"foo",
"lastName":"bar",
"type":"admin",
"asdf":"asdf"
}
I read about custom Converters, ArgumentResolvers, and I believe I could simply put an additional Map parameter in the handler and validate before service call, however I'd like to know the "best" way of handling this issue.
Regarding the User bean in your example it also already not possible, that potential other JSON fields than firstName and lastName could be mapped, simply because there are no fields in User which could hold the relevant data.
Should the User bean in your question be not complete, e.g. for simplicity reasons, and contain more fields, also then should everything be fine, as long as you did not configure your your ObjectMapper with com.fasterxml.jackson.databind.DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES => false or you use the annotation #JsonIgnoreProperties(ignoreUnknown = true) on your bean.
To sum it up: Jackson's default behavior is FAIL_ON_UNKNOWN_PROPERTIES (default: true)
For further information you can also consult the respective Deserialization docs.
Solved the issue, this thread helped
#JsonIgnoreProperties(ignoreUnknown=false) is not working in Spring 4.2.0 and upper version
mle, your answer wasn't right, since I was using the latest version of Spring Framework and the ObjectMapper's FAIL_ON_UNKNOWN_PROPERTIES is turned off by default. Additionally I was needed to set #JsonIgnoreProperties(ignoreUnknown = false) in my User DTO class (as the actual class' superclass had this set to true).
Tested it, runs like a charm, while custom errors can be handled in a #ExceptionHandler(HttpMessageNotReadableException.class) annotated handler.

Best way how to describe "ModelAttribute" with Swagger

I am trying to integrate Swagger2 to my Spring Boot based application. The issue is that swagger does not consider model attributes.
#GetMapping(value = "/events", produces = MediaType.APPLICATION_JSON_VALUE)
public PagedResources<EventResource> getEvents(
#Valid SearchCriteria searchQuery,
BindingResult result,
PageableResourcesAssembler<EventResource> assembler){
// code
}
As you can see SearchCriteria is a class which gets automatically binded by Spring.
public class SearchCriteria {
private List<EventType> eventTypes;
private LocalDateTime from;
// getters setters
}
But what swagger generates is following:
which is not expected. The desired result might be generated by annotation getEvents method by
#ApiImplicitParams({
#ApiImplicitParam(name = "eventTypes", paramType = "query"),
#ApiImplicitParam(name = "from", paramType = "query")
})
PagedResources<EventResource> getEvents(#ApiParam(hidden = true ) #Valid SearchCriteria searchQuery
but the #ApiParam(hidden = true ) does not work, because in the Swagger UI is the searchQuery parameter still present.
What is the proper way how to describe request parameters contained in a POJO using swagger? To me the best way would by annotation SearchCriteria class with #ApiModel but it does not work.
This bug was fixed in Springfox v2.7.0.
Original Answer:
The #Valid-annotation actually does that the param will be seen as body-param.
As this shouldn't do this I've opened an issue on the springfox github page.
but the #ApiParam(hidden = true ) does not work
Springfox provides for that the springfox.documentation.annotations.ApiIgnore-annotation which should work.
Like written in this issue using the annotation from springfox is the right way.

How is a custom derived query POST added to spring data REST?

I have a Spring data REST project I am using to learn.
Right now I want to set up a query in this repository:
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserRepository extends PagingAndSortingRepository<User, Long>
namely, this guy:
#RestResource(path = "login", rel = "login")
User findByUsernameAndPassword(String username, String password);
A basic repository is easy to set up. So are custom GET requests like:
List<Item> findByType(#Param("type") String type);
or
#RestResource(path = "byMaxPrice")
#Query("SELECT i FROM Item i WHERE i.price <= :maxPrice")
List<Item> findItemsLessThan(#Param("maxPrice") double maxPrice);
But these are still GET requests. I would like to use a POST request. The method = RequestMapping.POST markup isn't accepted by #RestResource .. and I dont see any mention of different request types in the documentation. how do I do this?
You need to define a custom Controller to handle POST requests. If you just want to do POST for the default Repository methods, unfortunately, you still have to make a pass through Controller
#RepositoryRestController
public class MyController implements Serializable {
...
#RequestMapping(value = "/myConstroller/myPostMethod", method = RequestMethod.POST
, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody List<MyObjects> updateMyObjectList(
#RequestBody List<MyObjects> objects) {
// Call your repository method here, or a custom service, or whatever
}
See Spring MVC docs for further information, specifically section 5.2.1, which describes the default HTTP Method support for Repositories, and 16.4, which gives a custom Controller example.
From 5.2.1:
POST
Creates a new entity from the given request body.
Thus, POST is supported, but not to do what you are trying to do. If you want to "hide" URL parameters by using POST instead of GET, you need a custom Controller.

Custom nested actuator endpoint

Using Spring Boot 1.0, I was able to customize the actuator endpoints as follows...
endpoints.beans.id=foo/springbeans
This would expose the spring beans endpoint at /foo/springbeans. However, in the latest Spring Boot this is not possible due to the following code in the AbstractEndpoint...
#NotNull
#Pattern(regexp = "\\w+", message = "ID must only contains letters, numbers and '_'")
private String id;
I tried using the underscore, but that just exposes the endpoint at /foo_springbeans. This lead me to try to add a view controller so I could at least redirect or forward to the default endpoint, but I couldn't find an easy way to do that either. How can I configure the endpoint or a redirect?
After opening an issue with Spring Boot and being told to simply move the entire management context as suggested by Rafal, I was able to achieve what I was looking for, albeit with more code than I'd like. I created a custom MvcEndpoint as follows...
#Named
public class MyCustomHealthCheck extends EndpointMvcAdapter {
private HealthEndpoint delegate;
#Inject
public MyCustomHealthCheck(HealthEndpoint delegate) {
super(delegate);
this.delegate = delegate;
}
#ResponseBody
#RequestMapping(value = "/springbeans", method = GET)
public Health foo() {
return delegate.invoke();
}
}
The code above creates the /springbeans path underwhatever path the HealthEndpoint is mapped to, which is fine enough for my usecase. If I wanted it mapped to an entirely separate path, I would have needed to create a dummy endpoint and stick this MvcEndpoint under that.
For Spring 1.x Following property should help you:
endpoints.beans.path: /foo/springbeans
You can use it with any standard endpoint and if you want to use it with custom endpoint that extends AbstractEndpoint then you need additional annotation:
#ConfigurationProperties(prefix = "endpoints.customEndpoint")
and then use property:
endpoints.customEndpoint.path: /custom/endpoint

Resources