I need a spring rest controller for inserting data. This is what i already got:
#RestController
#Transactional
public abstract class AbstractRESTController<E extends Identifiable<P>, P extends Serializable> {
#RequestMapping(method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public void create(#RequestBody final E entity) {
service.create(entity);
}
}
So i am able to insert a entity at http://mycontext/
What i need now is a method which accepts a list of entitys at the same path. Basiclly this:
#RequestMapping(method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public void createAll(#RequestBody final List<E> entities) {
for (E entity : entities) {
service.create(entity);
}
}
So how can i make spring aware of that im sending a array and not a single entity and then use the other function?
My error:
Caused by: java.lang.IllegalStateException: Ambiguous mapping found. Cannot map 'sfusersRESTController' bean method
public void AbstractRESTController.createAll(java.util.List<E>)
to {[/sfusers],methods=[POST],params=[],headers=[],consumes=[application/json],produces=[],custom=[]}: There is already 'sfusersRESTController' bean method
public void AbstractRESTController.create(E) mapped.
If you want to map more than one request to a given path, you will have to use different HTTP methods; eg. POST, PUT.
In your situation, I would make the URLs different; i.e. one /mycontext/as-entity and /mycontext/as-list.
Or you MUST have the same URL - it must be able to handle all kinds of request bodies. So you could have one RequestMapping() which expects an Object - and then handle that Object - either as an Entity or a List.
Personally, I would still prefer different RequestMapping paths.
define different request mapping paths so that it can make corresponding callback using
#RequestMapping(value = "")
Now your rest controller will look like this:
#RestController
#Transactional
public abstract class AbstractRESTController<E extends Identifiable<P>, P extends Serializable> {
#RequestMapping(value = "create", method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public void create(#RequestBody final E entity) {
service.create(entity);
}
#RequestMapping(value = "createAll", method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public void createAll(#RequestBody final List<E> entities) {
for (E entity : entities) {
service.create(entity);
}
}
}
Related
I'm attempting to add some additional business logic to the auto-generated endpoints from the RepositoryRestResource. Please see the code below:
Resource:
#RepositoryRestResource(collectionResourceRel="event", path="event")
public interface EventRepository extends PagingAndSortingRepository<Event, Long> {
}
Controller:
#RepositoryRestController
#RequestMapping(value = "/event")
public class EventController {
#Autowired
private EventRepository eventRepository;
#Autowired
private PagedResourcesAssembler<Event> pagedResourcesAssembler;
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedResources<PersistentEntityResource> getEvents(Pageable pageable,
PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
Page<Event> events = eventRepository.findAll(pageable);
return pagedResourcesAssembler.toResource(events, persistentEntityResourceAssembler);
}
}
I've looked at the following two stackoverflow articles:
Can I make a custom controller mirror the formatting of Spring-Data-Rest / Spring-Hateoas generated classes?
Enable HAL serialization in Spring Boot for custom controller method
I feel like I am close, but the problem that I am facing is that:
return pagedResourcesAssembler.toResource(events, persistentEntityResourceAssembler);
returns an error saying:
"The method toResource(Page<Event>, Link) in the type PagedResourcesAssembler<Event> is not applicable
for the arguments (Page<Event>, PersistentEntityResourceAssembler)".
The toResource method has a method signature that accepts a ResourceAssembler, but I'm not sure how to properly implement this and I can't find any documentation on the matter.
Thanks in advance,
- Brian
Edit
My issue was that I thought I could override the controller methods that are auto-created from #RepositoryRestResource annotation without having to create my own resource and resource assembler. After creating the resource and resource assembler I was able to add my business logic to the endpoint.
Resource:
public class EventResource extends ResourceSupport {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Resource Assembler:
#Component
public class EventResourceAssembler extends ResourceAssemblerSupport<Event, EventResource> {
public EventResourceAssembler() {
super(EventController.class, EventResource.class);
}
#Override
public EventResource toResource(Event entity) {
EventResource eventResource = createResourceWithId(entity.getId(), entity);
eventResource.setName(entity.getName());
return eventResource;
}
}
Updated Controller:
#RepositoryRestController
#RequestMapping(value = "/event")
public class EventController {
#Autowired
private EventRepository eventRepository;
#Autowired
private EventResourceAssembler eventResourceAssembler;
#Autowired
private PagedResourcesAssembler<Event> pageAssembler;
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedResources<EventResource> getEvents(Pageable pageable) {
Page<Event> events = eventRepository.findAll(pageable);
// business logic
return pageAssembler.toResource(events, eventResourceAssembler);
}
}
The thing I don't like about this is that it seems to defeat the purpose of having a RepositoryRestResource. The other approach would be to use event handlers that would get called before and/or after the create, save, delete operations.
#RepositoryEventHandler(Event.class)
public class EventRepositoryEventHandler {
#HandleBeforeCreate
private void handleEventCreate(Event event) {
System.out.println("1");
}
}
There doesn't seem to be any events for the findAll or findOne operations. Anyways, both these approaches seem to solve my problem of extending the auto generated controller methods from RepositoryRestResource.
It requires a PagedResourcesAssembler, Spring will inject one for you if you ask.
public PagedResources<Foo> get(Pageable page, PagedResourcesAssembler<Foo> assembler) {
// ...
}
In this case the resource is Foo. It seems in your case the resource you're trying to return is an Event. If that's so, I would expect your code to look something like:
private ResourceAssembler<Event> eventAssembler = ...;
public PagedResources<Event> get(Pageable page, PagedResourcesAssembler<Event> pageAssembler) {
Event event = ...;
return eventAssembler.toResource(event, pageAssembler);
}
You provide the ResourceAssembler<Event> that tells Spring how to turn Event into a Resource. Spring injects the PagedResourcesAssembler<Event> into your controller method to handle the pagination links. Combine them by calling toResource and passing in the injected pageAssembler.
The final result can be returned simply as a body as above. You could also use things like HttpEntity to gain more control over status codes and headers.
Note: The ResourceAssembler you provide can literally be something as simple as wrapping the resource, such as Event, with a Resource object. Generally you'll want to add any relevant links though.
To hack it you can use just PagedResourcesAssembler<Object> like:
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedModel<PersistentEntityResource> getEvents(
Pageable pageable,
PersistentEntityResourceAssembler persistentAssembler,
PagedResourcesAssembler<Object> pageableAssembler
) {
return pageableAssembler.toModel(
(Page<Object>) repository.findAll(pageable),
persistentAssembler
);
}
I want to create a REST-GET controller in spring-mvc that takes a list of objects, eg 10 ids as follows:
#RestController
public class MyRest {
#RequestMapping(method = RequestMethod.GET)
public Object test(#RequestParam value="id" required=false) List<Integer> ids) {
Sysout(ids);
}
}
Anyway when I call it, I have to repeat the id param multiple times:
localhost:8080/app?id=1&id=2&id=3&...
It is possible to change the param to some kind of list? Eg
app?id=1,2,3,4,5
And if yes, is this advisable? What's better from the client point of view?
Its better to use POST message with JSON or XML as request body.
As you never know how many id's will be passed.
#RestController
public class MyRest {
#RequestMapping(method = RequestMethod.POST)
public Object test(#RequestBody IDRequest request) {
Sysout(ids);
}
public static final class IDRequest {
List<Integer> ids;
<!-- getter/setters--->
}
}
where the request will be some kind of a JSON or XML like this
{"ids":[1,2,3,4,5,6,7,8,9]}
You can provide list of objects to rest service as request param.Here is the example
#RequestMapping(value = "/animals, method = RequestMethod.GET)
public void test(#RequestParam(value="animalsNames[]") String[] animalsNames) {
Sysout(animalsNames);
}
And your request looks like
http://localhost:8080/appname/animals?animalsNames[]=dog,horse
HTTP Method type : GET
Controller :
public #ResponseBody String getInfos(HttpServletRequest request,
#RequestParam #DateTimeFormat( #RequestParam List<Long> ids) {...}
Request :
http://localhost:8080/test/api?ids=1,2,3
#RequestMapping(method = RequestMethod.GET)
public HttpEntity<Object> list(WebRequest req) {
I have this code in my application, I want to subclass the class that has this method and create a new method like this:
#RequestMapping(method = RequestMethod.GET)
public HttpEntity<Object> list(WebRequest req, #RequestParam(defaultValue = "false", required= true) String includehardcoded) {
Now I am getting: java.lang.IllegalStateException: Ambiguous mapping found when my beans are created. I get that the declaration is ambigous but is there any way to make the method in the parent class accept no request parameters at all?
This works:
#RequestMapping(method = RequestMethod.GET, params="!includehardcoded")
public HttpEntity<Object> list(WebRequest req) {
but then it is an ugly hack that uses information from the subclass in the parent class (and I inherit this parent class multiple times).
I have a parent class P which defines one request mapping like this:
public abstract class P {
#RequestMapping(value = "/a/b/c", method = RequestMethod.POST)
public String productLink(#RequestParam("abc") String json) throws Exception {
return getProductLinks(json);
}
}
and I have couple of children Controller classes and ClassImpl is one of them:
#Controller
public class ClassImpl extends P {
#RequestMapping(value = "/start", method = RequestMethod.GET)
public String start(#RequestParam(value = "keyword", required = true) String keyword,
#RequestParam(value = "keywordId", required = true) long keywordId) throws Exception {
//Something
}
}
If I run this app with only one child class, it works fine but it causes issues with multiple child controllers.
When I run my application, I get an error saying "Cannot map handler ClassImpl to URL path [/a/b/c]: There is already handler [a.b.c.d.ClassImpl#a92aaa] mapped"
It seems that because of multiple child classes, it is unable to find the controller for this mapping which is understood.
Is defining #RequestMapping in each class (or one separate class) the only way? I don't want to put similar code at all the places. Is there any workaround for this to keep it in parent class and keep using it?
Thanks,
Is defining #RequestMapping in each class (or one separate class) the only way?
The short answer is yes. Personally I think that it belongs in a separate class.
Why exactly do you want to put productLink() in the parent class, anyway? It's not an abstract method and you're not overriding it, so to me it doesn't make much sense.
You should not use #RequestMapping in abstract classe. This annotation is for real controller, so concrete classe.
Use Abstract class as they are intented to use, ie to factorize code, not to do the work.
Here you can do something like :
public abstract class P {
public String productLink(String json) throws Exception {
return getProductLinks(json);
}
}
and then
#Controller
public class ClassImpl extends P {
#RequestMapping(value = "/start", method = RequestMethod.GET)
public String start(#RequestParam(value = "keyword", required = true) String keyword,
#RequestParam(value = "keywordId", required = true) long keywordId) throws Exception {
//Something
}
//here reusing the code from superclass
#RequestMapping(value = "/a/b/c", method = RequestMethod.POST)
public String productLink(#RequestParam("abc") String json) throws Exception {
return super.getProductLinks(json);
}
}
This add a bit of boilerplate code, but this is the way to do it IMHO.
I'm trying out Spring MVC 3.0 for the first time and like to make it RESTfull.
This is my controller:
#Controller
#RequestMapping(value = "/product")
#SessionAttributes("product")
public class ProductController {
#Autowired
private ProductService productService;
public void setProductValidator(ProductValidator productValidator, ProductService productService) {
this.productService = productService;
}
#RequestMapping(method = RequestMethod.GET)
public Product create() {
//model.addAttribute(new Product());
return new Product();
}
#RequestMapping(method = RequestMethod.POST)
public String create(#Valid Product product, BindingResult result) {
if (result.hasErrors()) {
return "product/create";
}
productService.add(product);
return "redirect:/product/show/" + product.getId();
}
#RequestMapping(value = "/show/{id}", method = RequestMethod.GET)
public Product show(#PathVariable int id) {
Product product = productService.getProductWithID(id);
if (product == null) {
//throw new ResourceNotFoundException(id);
}
return product;
}
#RequestMapping(method = RequestMethod.GET)
public List<Product> list()
{
return productService.getProducts();
}
}
I have 2 questions about this.
I'm a believer in Convention over Configuration and therefor my views are in jsp/product/ folder and are called create.jsp , list.jsp and show.jsp this works relatively well until I add the #PathVariable attribute. When I hit root/product/show/1 I get the following error:
../jsp/product/show/1.jsp" not found how do I tell this method to use the show.jsp view ?
If I don't add the RequestMapping on class level my show method will be mapped to root/show instead of root/owner/show how do I solve this ? I'd like to avoid using the class level RequestMapping.
add your 'product' to Model and return a String /product/show instead of Product. In your show.jsp, you can access the product object form pageContext
Check out the section in the manual about "Supported handler method arguments and return types".
Basically, when your #RequestMapping method returns just an object, then Spring uses this as a single model attribute, and, I'm guessing, attempts to use the request URL as the basis for the view name.
The easiest way to return the view and data you want from the same method is probably to just have the method return a ModelAndView, so you can explicitly specify the viewName and the model data.