#GetMapping, ambigious mapping - java

I'm trying to create several routes for #GetMapping. For example, localhost:8080/tasks and localhost:8080/tasks/?status=...
So I created several methods as below.
Controller
#RestController
#RequestMapping(value = "/tasks", produces = MediaType.APPLICATION_JSON_VALUE)
#ExposesResourceFor(Task.class)
public class TaskRepresentation {
private final TaskResource taskResource;
public TaskRepresentation(TaskResource taskResource) {
this.taskResource = taskResource;
}
#GetMapping
public ResponseEntity<?> getAllTasks() {
return new ResponseEntity<>(this.taskResource.findAll(), HttpStatus.OK);
}
#GetMapping
public ResponseEntity<?> getTasksStatus(#RequestParam("status") int status) {
return new ResponseEntity<>(this.taskResource.getTasksByStatus(status), HttpStatus.OK);
}
}
Resource
#RepositoryRestResource(collectionResourceRel = "task")
public interface TaskResource extends JpaRepository<Task, String> {
#GetMapping
List<Tache> getTasksByStatus(#RequestParam int status);
}
Error
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'taskRepresentation' method
public org.springframework.http.ResponseEntity<?> org.miage.tache.boundary.TacheRepresentation.getTasksStatus(int)
to {GET /tasks, produces [application/json]}: There is already 'taskRepresentation' bean method
(The only solution is to create only one route for #GetMapping with optionnal params?)
Can you help me ?
Thanks for help.

Coming from the other answer, as this one more specific.
You can narrow down your endpoint mapping by specifying the needed query parameters.
#GetMapping
public ResponseEntity<?> getAllTasks() {
return ResponseEntity.ok().body(this.taskResource.findAll());
}
#GetMapping(params = "status")
public ResponseEntity<?> getAllTasksWithStatus(#RequestParam("status") final int status) {
return ResponseEntity.ok().body(this.tacheResource.getTachesByEtat(status));
}
Docs link.
Note : As params is an array, you can specify multiple values with
#GetMapping(params = { "status", "date" })

You can do something like this :
#RestController
#RequestMapping(value = "/tasks", produces = MediaType.APPLICATION_JSON_VALUE)
#ExposesResourceFor(Task.class)
public class TaskRepresentation {
private final TaskResource taskResource;
public TaskRepresentation(TaskResource taskResource) {
this.taskResource = taskResource;
}
#GetMapping
public ResponseEntity<?> getTasksStatus(#RequestParam(value="status", required=false) Integer status) {
if(status==null){
return new ResponseEntity<>(this.taskResource.findAll(), HttpStatus.OK);
}
return new ResponseEntity<>(this.taskResource.getTasksByStatus(status.intValue()), HttpStatus.OK);
}
}

Related

how can I do to make it return error when restful url has invalid parameter

#RestController()
#RequestMapping(path = "/users")
public class UserController {
#GetMapping()
public #ResponseBody Page<User> getAllUsers(#RequestParam Integer pageSize, UserRequest userRequest) {
//TODO: some implementation
}}
public class UserRequest{
public String name;
public String age;
}
send the request with invalid parameter, like localhost:8800/users?name1=1234, I want to return error. but in fact it ignore the invalid parameter name1.
I tried to add the user defined annotation on the method parameter and on the class , codes like below
#RestController()
#RequestMapping(path = "/users")
#Validated
public class UserController {
#GetMapping()
public #ResponseBody Page<User> getAllUsers(#RequestParam #Validated Integer pageSize, #Validated UserRequest userRequest} {
//TODO: some implementation
}
}
But it does not working.
I think it is happened because framework has ignore the invalid parameter before the method was called.
where did framework handle the url and how can I do to make it return error instead of ignore?
You can reject parameters that are not valid. You can do so in a HandlerInterceptor class.
Reference: Rejecting GET requests with additional query params
In addition to what is done in the above reference, in your addInterceptors, you can specify the path that is intercepted.
Like this:
#Configuration
public class WebConfig implements WebMvcConfigurer {
private String USERS_PATH = "/users";
// If you want to cover all endpoints in your controller
// private String USERS_PATH = List.of("/users", "/users/**");
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new FooHandlerInterceptor()).addPathPatterns(USERS_PATH);
}
}

Java Spring 5 Get and findById mongo MonoOnErrorResume

Spring newbie here, trying to make a GET http query in a mongo db via findById(id, Object).
But it doesn't seem to be working. I can POST and PUT but when calling a query via ID i get this err MonoOnErrorResume
I'm using EmbeddedMongoDB
Controller
public class ContentController {
public static final String CONTENT_V_1_CONT = "/contents/v1/cont/";
private final ContentService contentService;
#Autowired
public ContentController(ContentService contentService) {
this.contentService = contentService;
}
#GetMapping(path = "{id}", produces =
MediaType.APPLICATION_JSON_UTF8_VALUE)
public Mono<Content> getContent(#PathVariable String id) {
System.out.println(contentService.getContent(id)); //
MonoOnErrorResume
return contentService.getContent(id);
}
#PostMapping(path = "", produces =
MediaType.APPLICATION_JSON_UTF8_VALUE, consumes =
MediaType.APPLICATION_JSON_UTF8_VALUE)
public Mono<Content> createContent(#RequestBody Mono<Content> content){
return contentService.createContent(content);
}
Service Implmentation
public final ReactiveMongoOperations reactiveMongoOperations;
#Autowired
public ContentServiceImplementation(ReactiveMongoOperations reactiveMongoOperations) {
this.reactiveMongoOperations = reactiveMongoOperations;
}
#Override
public Mono<Content> getContent(String id) {
return reactiveMongoOperations.findById(id, Content.class);
}
#Override
public Mono<Content> createContent(Mono<Content> contentMono) {
return reactiveMongoOperations.save(contentMono);
}
Data Config Dont know is this is useful
#Bean
public ReactiveMongoDatabaseFactory mongoDatabaseFactory(MongoClient mongoClient){
return new SimpleReactiveMongoDatabaseFactory(mongoClient, DATABASE_NAME);
}
#Bean
public ReactiveMongoOperations reactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory){
return new ReactiveMongoTemplate(mongoDatabaseFactory);
}
Lmk if i'm missing some critical info
Your problem may come from your controller, you declare your path like so:
#GetMapping(path = "{id}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
So unless you have a / at the end of your controller class mapping you will have issues because your final URL will look like this :
http://localhost:8080/my/route/get1
instead of :
http://localhost:8080/my/route/get/1
Your #PathVariable looks strange as well, try doing this instead :
#PathVariable("id") String id
To ensure Spring is going to map {id} to your #PathVariable

Spring RestController POST 400 Bad Request

I have a Spring RestController that any attempt to post to it returns 400 Bad Request despite seeing the correct data being sent in Chrome Developer Tools. The #Valid annotation is kicking it out because the ParameterDTO object is not being populated at all.
My Controller
#RestController
#RequestMapping(path = "/api/parameters", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_VALUE})
public class ParameterResource {
private final ParameterService parameterService;
#Autowired
public ParameterResource(ParameterService parameterService) {
this.parameterService = parameterService;
}
#GetMapping
public ResponseEntity<?> getParameters(#RequestParam(value = "subGroupId", required = false) Integer subGroupId) {
if (subGroupId != null) {
return ResponseEntity.ok(parameterService.getParameters(subGroupId));
}
return ResponseEntity.ok(parameterService.getParameters());
}
#PostMapping
public ResponseEntity<?> createParameter(#Valid ParameterDTO parameterData) {
int id = parameterService.saveParameter(parameterData);
URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(id).toUri();
return ResponseEntity.created(uri).build();
}
#GetMapping(path = "/levels")
public ResponseEntity<?> getParameterLevels() {
return ResponseEntity.ok(ParameterLevels.getParameterLevelMap());
}
#GetMapping(path = "/levels/{id}/values")
public ResponseEntity<?> getLevelValues(#PathVariable("id") int levelId) {
return ResponseEntity.ok(parameterService.getParameterLevelValues(levelId));
}
#GetMapping(path = "/types")
public ResponseEntity<?> getParameterTypes() {
return ResponseEntity.ok(parameterService.getParameterTypes());
}
}
I was using axios from JavaScript and though my problem might be there but I have the same issue using Postman. I am setting the Content-Type and Accept header. It seems like Spring is not deserializing the data at all.
You need to add #RequestBody annotation before ParameterDTO parameterData declaration, like below:
#PostMapping
public ResponseEntity<?> createParameter(#RequestBody #Valid ParameterDTO parameterData) {
int id = parameterService.saveParameter(parameterData);
URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(id).toUri();
return ResponseEntity.created(uri).build();
}

Spring Data REST JPA: Integrate automatic CRUD operations with manual controller

I'm using Spring Data REST JPA to build a RESTful web service. So far, Spring is auto-generating all the responses for all the possible methods and for listing all the resources available and even for searches over them:
#RepositoryRestResource(collectionResourceRel = "scans", path = "scans")
public interface ScanRepository extends PagingAndSortingRepository<Scan, Long> {
List<Scan> findByProjectId(#Param("pid") String pid);
}
Now I would like to modify what is returned "only" to POST requests while leaving intact the support to all the others.
I thought I'd create a controller for this purpose like the following:
#Controller
public class ScanController {
#RequestMapping(value = "/scans", method = POST, produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody Result parseScan(#RequestParam String projectId, #RequestParam String tool) {
return null;
}
However when I do this, the JPA-data auto-generated responses for all the other methods and searches etc. ceases to exist. For instance, I get "Method not allowed" if I forward a GET request.
Besides, how could I access a JSON payload from the controller?
UPDATE
Now only one of the exposed resource does back to the default methods for requests not manually handled in my own controller. However I have no idea why it does and why this doesn't happen for any of the other resources.*
Despite they all only differ in their entity's attributes.
The following particular resource is the one that does back to the default request handlers for anything that is not POST scan/ or GET /scan/// which I declared in the controller:
#Controller
public class ScanController {
#Autowired
private ScanService scanService;
#RequestMapping(
value = "/scan",
method = POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
Scan parseScan(#RequestBody Scan rbody) {
<...do something...>
}
#RequestMapping(value = "/scans/{id}/{totvuln}/{nth}", method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
Scan getScan(#PathVariable String id, #PathVariable int totvuln, #PathVariable int nth) throws ScanNotFound {
<...do something...>
}
It has the following repository interface:
public interface ScanRepository extends PagingAndSortingRepository<Scan, Long> {}
and the following service:
#Service
public class ScanServiceImpl implements ScanService {
#Resource
private ScanRepository scanRepository;
#Resource
private ResultRepository resultRepository;
#Override
#Transactional
public Scan create(Scan shop) {
<some code>
}
#Override
#Transactional
public Scan findById(long id) {
<some code>
}
#Override
#Transactional(rollbackFor = ScanNotFound.class)
public Scan delete(long id) throws ScanNotFound {
<some code>
}
#Override
#Transactional
public List<Scan> findAll() {
<some code>
}
#Override
#Transactional(rollbackFor = ScanNotFound.class)
public Scan update(Scan scan) throws ScanNotFound {
<some code>
}
}
and the resource itself has the following attributes:
#Entity
public class Scan {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Long projectId;
#OneToMany
private Collection<Result> result;
private int totV;
<getters and setters>
}
While the following semi-identical resource "Rules" does not back to any of the default request handlers. It returns "Method not Allowed" for anything different from POST /rule:
#Controller
public class RulesController {
#Autowired
private RulesService rService;
#Resource
private ScanRepository scanRepository;
#RequestMapping(
value = "/rule",
method = POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
Rules generateRules(#RequestBody Scan rbody) throws Exception {
<do something>
}
}
It has the same repository interface:
public interface RulesRepository extends PagingAndSortingRepository<Rules, Long> {}
and also the same service implementation:
#Service
public class RulesServiceImpl implements RulesService {
#Resource
private RulesRepository rRepository;
#Resource
private ResultRepository resultRepository;
#Override
#Transactional
public Rules create(Rules shop) {
<do something>
}
#Override
#Transactional
public Rules findById(long id) {
<do something>
}
#Override
#Transactional(rollbackFor = RulesNotFound.class)
public Rules delete(long id) throws RulesNotFound {
<do something>
}
#Override
#Transactional
public List<Rules> findAll() {
<do something>
}
#Override
#Transactional
public Rules findByScanId(long id) throws RulesNotFound {
<do something>
}
#Override
#Transactional(rollbackFor = RulesNotFound.class)
public Rules update(Rules scan) throws RulesNotFound {
<do something>
}
}
and the resource Rules itself has the following attributes:
#Entity
public class Rules {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne
private Scan scan;
#OneToMany
private Collection<Result> result;
private String rules;
<getters and setters>
}
Why isn't Spring exposing the default request handlers also for "Rules" for any request that hasn't been specified manually in my controller class?
I would truly appreciate if you could point out why. Thank you so much!
I've figured out how to access a JSON payload from the controller:
#RequestMapping(
value = "/scan",
method = POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
Scan parseScan(#RequestBody Scan rbody) {
Scan scan = new Scan();
scan.setProjectId(rbody.getProjectId());
scan.setTool(rbody.getTool());
return scan;
}
Also I've realised the automatic CRUD operations were actually being already supported for every request not handled by my own controller: I was just requesting the wrong URL.
I got the list of correct URLs to use by requesting "curl http://localhost:8080"
However, the preferred URL for any of the auto-generated operations can be set with
#RepositoryRestResource(collectionResourceRel = pref_URL_suffix, path = pref_URL_suffix)
^^somehow during all the changes I tried, that line above went missing.

Is it possible in Spring MVC to have void handlers for requests?

Is it possible in Spring MVC to have void handler for request?
Suppose I have a simple controller, which doesn't need to interact with any view.
#Controller
#RequestMapping("/cursor")
public class CursorController {
#RequestMapping(value = "/{id}", method = PUT)
public void setter(#PathVariable("id") int id) {
AnswerController.setCursor(id);
}
}
UPD
#Controller
#RequestMapping("/cursor")
public class CursorController {
#RequestMapping(value = "/{id}", method = PUT)
public ResponseEntity<String> update(#PathVariable("id") int id) {
AnswerController.setCursor(id);
return new ResponseEntity<String>(HttpStatus.NO_CONTENT);
}
}
you can return void, then you have to mark the method with
#ResponseStatus(value = HttpStatus.OK) you don't need #ResponseBody
#RequestMapping(value = "/updateSomeData" method = RequestMethod.POST)
#ResponseStatus(value = HttpStatus.OK)
public void defaultMethod(...) {
...
}
Only get methods return a 200 status code implicity, all others you have do one of three things:
Return void and mark the method with #ResponseStatus(value = HttpStatus.OK)
Return An object and mark it with #ResponseBody
Return an HttpEntity instance
Also refer this for interesting information.

Categories

Resources