I'm created a interface to abstract some common methods on 2 classes, here is the code
package br.canabarro.resource;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Repository;
#Repository
public interface BaseResource<T, ID> {
ResponseEntity save( T entity);
ResponseEntity delete(ID id);
ResponseEntity getAll();
ResponseEntity getById(ID id);
ResponseEntity update(ID id, T entity);
}
I'm have another file called ProductResource
#Override
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity save(#RequestBody DtoProduct productDTO) {
final Product product = new Product(productDTO.getName());
return ResponseEntity.status(HttpStatus.ACCEPTED).body(productService.save(product));
}
The save method are implementend on this function, but the compiler returns Method does not override method from its superclass, but if i change the function parameter to Product productParam will pass, the class Product have and id and name on the parameters.
The last part is my ProductToDto and this method also implement a interface called SuperConverter
package br.canabarro.dto.Conversor;
import br.canabarro.dto.DtoProduct;
import br.canabarro.entity.Category;
import br.canabarro.entity.Product;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
#Component
public class ProductToDto implements SuperConverter<Product, DtoProduct> {
#Override
public DtoProduct apply(Product product) {
List<String> categoriesID = product
.getCategories()
.stream()
.map(Category::getId)
.collect(Collectors.toList());
return DtoProduct
.build()
.id(product.getId())
.name(product.getName())
.builder();
}
}
How can i change the BaseResoucer to make java understand what i want is passing a template T and not the class name like DtoProduct.
I'm sorry if the question became so long but this is a important question for me.
On the pastebin i'm posting my SuperConverter and the DtoProduct
SuperConverter: https://pastebin.com/TmFRLQVz
DtoProduct: https://pastebin.com/VTSnMVhq
ProductResource is defined as:
public class ProductResource implements BaseResource<Product, String>
So Product becomes your T in BaseResource. In such case it is expected that ProductResource implements the following method:
ResponseEntity save(Product entity);
And it does not do that.
You would need to define your ProductResource as:
public class ProductResource implements BaseResource<DtoProduct, String>
or if you want to keep it as Product then implement the following method:
ResponseEntity save(Product entity);
It is not really Spring issue but Java generics. Have a look at https://docs.oracle.com/javase/tutorial/java/generics/index.html
Related
I'm working on this springboot application where I need to do some validations on values passed from http call and I'm using class level validation as explained here.
I'm using somethink like this:
#ValidRequest
public class EventRequest {
String date;
}
Response create(#Valid EventRequest request) {
..
}
Response update(Long entityId, #Valid EventRequest request) {
...
}
public class ValidRequestValidator
implements ConstraintValidator<ValidRequest, EventRequest> {
In the class ValidRequestValidator, where I implement the ConstraintValidator interface, I need to check if there is another Event entity in the database that meet some conditions on field date. When I want to create a new entity is simple, I perform a query, but when I need to update I need to exclude the entity I'm currently trying to update.
Is there a way to pass entityId parameter to #ValidRequest custom validator?
I know a way is to add the field entityId to the class EventRequest, but I would like to maintain this separation because entityId is coming from a query parameter.
Thank for your help!
Additional to the field-specific(Single Parameter Constraint) you can implement constraint for the whole method(Cross-Parameter Constraint). This will provide ability to pass all parameters of certain method to validator.
Annotation definition:
Annotation used two validators and can be applied to the Method or Type.
#Constraint(validatedBy = {ValidRequestMethodValidator.class, ValidRequestTypeValidator.class})
#Target({ ElementType.METHOD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidRequest {
String message() default "Request is invalid!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;
}
Constraint Validator which will handle single parameter:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class ValidRequestTypeValidator implements ConstraintValidator<ValidRequest, EventRequest> {
#Override
public boolean isValid(EventRequest request, ConstraintValidatorContext constraintValidatorContext) {
// logic here
return false;
}
}
Constraint Validator which will handle all parameters of specific method:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;
#SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ValidRequestMethodValidator implements ConstraintValidator<ValidRequest, Object[]> {
#Override
public boolean isValid(Object[] objects, ConstraintValidatorContext constraintValidatorContext) {
Long entityId = null;
EventRequest eventRequest = null;
if (objects[0] instanceof Long) {
entityId = (Long) objects[0];
}
if (objects[0] instanceof EventRequest) {
eventRequest = (EventRequest) objects[0];
}
if (objects[1] instanceof EventRequest) {
eventRequest = (EventRequest) objects[1];
}
//logic here
return false;
}
}
Please note, we have to annotate the beans, which shall be validated, with #org.springframework.validation.annotation.Validated annotation to get method validators to work automatically.
Example of usage:
Mixed usage, #ValidRequest annotation defined on method and single parameter level.
#ValidRequest
public class EventRequest {
public String value;
}
#RestController
#Validated
public class Controller {
Response create(#Valid EventRequest request) {
return new Response();
}
#ValidRequest(validationAppliesTo = ConstraintTarget.PARAMETERS)
Response update(Long entityId, EventRequest request) {
return new Response();
}
}
For create method ValidRequestTypeValidator will be executed.
For update method ValidRequestMethodValidator will be executed.
2. Define annotation only for methods
#RestController
#Validated
public class Controller {
#ValidRequest(validationAppliesTo = ConstraintTarget.PARAMETERS)
Response create(EventRequest request) {
return new Response();
}
#ValidRequest(validationAppliesTo = ConstraintTarget.PARAMETERS)
Response update(Long entityId, EventRequest request) {
return new Response();
}
}
For create method ValidRequestMethodValidator will be executed with one element objects array
For update method ValidRequestMethodValidator will be executed with two elements objects array
3. Define annotation for a single parameter and method at the same time
#ValidRequest
public class EventRequest {
public String value;
}
#RestController
#Validated
public class Controller {
#ValidRequest(validationAppliesTo = ConstraintTarget.PARAMETERS)
Response update(Long entityId, #Valid EventRequest request) {
return new Response();
}
}
First will be executed single parameter validator ValidRequestTypeValidator.
If it will passed validation then second method validator ValidRequestMethodValidator will be executed.
Probably only one method-level validation will be sufficient to handle your issue. I described all variants, just for information maybe will be useful.
I'm trying to retrieve data from a database to an mobile app through a REST web service. I've managed to make some basic functions but when I try to add functions I run into problems. For example I want to be able to find "Customers" by their Id and their name. When I have two Get methods, one with "/{id}" and one with "/{name}" the app does not know what to use. What can I do to search by name?
This is the controller from the web service.
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#RestController
#RequestMapping("/customers")
public class CustomerController {
private CustomerRepository repository;
#Autowired
public CustomerController(CustomerRepository repository) {
this.repository = repository;
}
#RequestMapping(value = "/{name}", method = RequestMethod.GET)
public ResponseEntity<Customer> get(#PathVariable("name") String name) {
Customer customer = repository.findByName(name);
if (null == customer) {
return new ResponseEntity<Customer>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<Customer>(customer, HttpStatus.OK);
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ResponseEntity<Customer> get(#PathVariable("id") Long id) {
Customer customer = repository.findOne(id);
if (null == customer) {
return new ResponseEntity<Customer>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<Customer>(customer, HttpStatus.OK);*
}
#RequestMapping(value = "/new", method = RequestMethod.POST)
public ResponseEntity<Customer> update(#RequestBody Customer customer) {
repository.save(customer);
return get(customer.getName());
}
#RequestMapping
public List<Customer> all() {
return repository.findAll();
}
}
This is the service from the android application
package com.ermehtar.poppins;
import java.util.List;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.PATCH;
import retrofit2.http.POST;
import retrofit2.http.Path;
public interface CustomerService {
#GET("customers")
Call<List<Customer>> all();
#GET("customers/{id}")
Call<Customer> getUser(#Path("id") Long id);
#GET("customers/{name}")
Call<Customer> getUser(#Path("name") String name);
#POST("customers/new")
Call<Customer> create(#Body Customer customer);
}
Then this is the function that I use to call the service by name. The response.body will be null when both /name and /id functions are in the web service controller but when one of them is commented out this works just fine.
findUsernameButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Call<Customer> createCall = service.getUser("John");
createCall.enqueue(new Callback<Customer>() {
#Override
public void onResponse(Call<Customer> _, Response<Customer> resp) {
findUsernameButton.setText(resp.body().name);
}
#Override
public void onFailure(Call<Customer> _, Throwable t) {
t.printStackTrace();
allCustomers.setText(t.getMessage());
}
});
}
});
Hope I've made myself understandable. Please ask if there is something unclear or you need more information.
Your restful design can improve. I suggest defining something like this:
New:
/customers/new
This is not correct, in restful a resource creation should be defined by method type. I suggest this:
/customers with POST method.
Search by ID:
/customers/{id}
This is correct, in restful a resource should be access by id using path variable.
Search by name:
/customers/{name}
This is not correct, here you are querying the customers resource, so, you should use query params, I suggest this:
/customers?name=<<name>>
If you have multiple query methods, you will get a conflict because you cannot have more than one GET method in a controller with the same path. So, you can modify #RequestMapping to explicit assert which query params are required like this:
#RequestMapping(value = "/", method = RequestMethod.GET, , params = "name")
public ResponseEntity<Customer> getByName(#RequestParam("name") String name) {
...
}
#RequestMapping(value = "/", method = RequestMethod.GET, , params = "lastname")
public ResponseEntity<Customer> getByLastname(#RequestParam("lastname") String lastname) {
...
}
Differentiate the URLs with a different path which will also make them more RESTful.
Search by name:
/customers/names/{name}
Search by ID:
/customers/ids/{id}
In the future you might want to add another search, perhaps by city:
/customers/cities/{city}
Your controller has ambiguous handler methods mapped so when calling the endpoint you will actually get an exception. Fix this by creating different mappings for get by id and get by name.
Resource is uniquely identified by its PATH (and not by it's params). So, there're few resources with the same PATH: "customers/"
You can create two different resources like:
#RequestMapping(value = "/id", method = RequestMethod.GET)
#RequestMapping(value = "/name", method = RequestMethod.GET)
Or you can make one resource with many request parameters:
#RequestMapping(value = "/get", method = RequestMethod.GET)
public ResponseEntity<Customer> get(#RequestParam ("id") Long id, #RequestParam ("name") String name)
I have a little problem. I'm calling AfterReturning function after some function returns and my AfterReturning function's working twice and I don't want that. Here is the code:
#Aspect
#Component
#Configuration
public class AspectOP {
LogController logcontroller = new LogController();
#AfterReturning("execution(* com..*save*(..))")
public void doSomething() {
logcontroller.guestbook("XD");
}
}
I have 2 save function and we changed names but it's same again. I've tried remove #Component or #Aspect then it's not working.
EDIT
My save function
#Controller
#RequestMapping("/api")
public class EntryController {
#Autowired
EntryRepository repo;
Account account;
#RequestMapping(path = "/getir", method = RequestMethod.GET)
public #ResponseBody List<Entries> getir(){
return repo.findAll();
}
#RequestMapping(path = "/saveentry", method = RequestMethod.POST, consumes = "application/json")
public #ResponseBody Entries save(#RequestBody Entries entry) {
return repo.save(entry);
}
}
LogController.class
#Controller
public class LogController {
#MessageMapping("/guestbook")
#SendTo("/topic/entries")
public Log guestbook(String message) {
System.out.println("Received message: " + message);
return new Log(message);
}
}
My main objective is when something is saved, I send something to my socket. It's working but doSomething functions is working twice.
seems advice applied to your EntryRepository class as well. change your pointcut expression to something like, to be only applied to EntryController's save method
#AfterReturning("execution(* com.xyz.EntryController.save*(..))")
Examples here
I have the the following classes:
public class ItemController implements ApiController<Item> {
#RequestMapping(value = "/", method = RequestMethod.POST)
public Item create(#RequestBody final CreateItemRequest request) {
// ...
}
}
public interface ApiController<T> {
T create(#RequestBody final ApiRequest request);
}
public class CreateItemRequest implements ApiRequest {
// ...
}
public interface ApiRequest {
// ...
}
I'm warned by Intellij that ItemController does not implement ApiController. My working assumption is that CreateItemRequest, as an implementation of ApiRequest, is a valid type for the signature of the create method as it's written above. However, Intellij insists that the type of the request parameter should be ApiRequest.
My goal here is to have a common interface for classes like CreateItemRequest to implement. That interface would be referred to in a generic-y sort of way within ApiController methods.
Obviously, I'm not a generics wizard, so what have I mixed up here?
edit: ThingRequest properly implements ApiRequest.
Update:
You can not narrow method parameters while implementing an interface! - The
You have an interface ApiController<T> with the method T create(final ApiRequest request). This interface tells a client that he can invoke the method create with ANY!!! class that implements ApiRequest, therefore it is not allowed to have a implementation of ApiController that restrict the argument type of create to CreateItemRequest!!
old answer:
You wrote:
public interface ApiController<T> {
T create(#RequestBody final ApiRequest request);
}
and
public class ItemController implements ApiController<Item> {
#RequestMapping(value = "/", method = RequestMethod.POST)
public Thing create(#RequestBody final CreateItemRequest request) {
// ...
}
}
One problem is the method argument, the problem is the return type! Thing is not Item!
OK you told that this is an typo, so read the update above
BTW: I strongly recommend to add the #Override annotation to methods that implement/override other methods
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
}