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
Related
Given the following basic domain model:
abstract class BaseData { ... }
class DataA extends BaseData { ... }
class DataB extends BaseData { ... }
I want to write a Spring MVC controller endpoint thus ...
#PostMapping(path="/{typeOfData}", ...)
ResponseEntity<Void> postData(#RequestBody BaseData baseData) { ... }
The required concrete type of baseData can be inferred from the typeOfData in the path.
This allows me to have a single method that can handle multiple URLs with different body payloads. I would have a concrete type for each payload but I don't want to have to create multiple controller methods that all do the same thing (albeit each would do very little).
The challenge that I am facing is how to "inform" the deserialization process so that the correct concrete type is instantiated.
I can think of two ways to do this.
First use a custom HttpMessageConverter ...
#Bean
HttpMessageConverter httpMessageConverter() {
return new MappingJackson2HttpMessageConverter() {
#Override
public Object read(final Type type, final Class<?> contextClass, final HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// TODO How can I set this dynamically ?
final Type subType = DataA.class;
return super.read(subType, contextClass, inputMessage);
}
};
}
... which gives me the challenge to determine the subType based on the HttpInputMessage. Possibly I could use a Filter to set a custom header earlier when the URL is available to me, or I could use a ThreadLocal also set via a Filter. Neither sounds ideal to me.
My second approach would be to again use a Filter and this time wrap the incoming payload in an outer object which would then provide the type in a way that enables Jackson to do the work via #JsonTypeInfo. At the moment this is probably my preferred approach.
I have investigated HandlerMethodArgumentResolver but if I try to register a custom one it is registered AFTER the RequestResponseBodyMethodProcessor and that class takes priority.
Hmm, so after typing all of that out I had a quick check of something in the RequestResponseBodyMethodProcessor before posting the question and found another avenue to explore, which worked neatly.
Excuse the #Configuration / #RestController / WebMvcConfigurer mash-up and public fields, all for brevity. Here's what worked for me and achieved exactly what I wanted:
#Configuration
#RestController
#RequestMapping("/dummy")
public class DummyController implements WebMvcConfigurer {
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#interface BaseData {}
public static class AbstractBaseData {}
public static class DataA extends AbstractBaseData {
public String a;
}
public static class DataB extends AbstractBaseData {
public String b;
}
private final MappingJackson2HttpMessageConverter converter;
DummyController(final MappingJackson2HttpMessageConverter converter) {
this.converter = converter;
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(
new RequestResponseBodyMethodProcessor(Collections.singletonList(converter)) {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(BaseData.class)
&& parameter.getParameterType() == AbstractBaseData.class;
}
#Override
protected <T> Object readWithMessageConverters(
NativeWebRequest webRequest, MethodParameter parameter, Type paramType)
throws IOException, HttpMediaTypeNotSupportedException,
HttpMessageNotReadableException {
final String uri =
webRequest.getNativeRequest(HttpServletRequest.class).getRequestURI();
return super.readWithMessageConverters(
webRequest, parameter, determineActualType(webRequest, uri));
}
private Type determineActualType(NativeWebRequest webRequest, String uri) {
if (uri.endsWith("data-a")) {
return DataA.class;
} else if (uri.endsWith("data-b")) {
return DataB.class;
}
throw new HttpMessageNotReadableException(
"Unable to determine actual type for request URI",
new ServletServerHttpRequest(
webRequest.getNativeRequest(HttpServletRequest.class)));
}
});
}
#PostMapping(
path = "/{type}",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<? extends AbstractBaseData> post(#BaseData AbstractBaseData baseData) {
return ResponseEntity.ok(baseData);
}
}
The key to this is that I stopped using #RequestBody because that is what was preventing me overriding the built-in behaviour. By using #BaseData instead I get a HandlerMethodArgumentResolver that uniquely supports the parameter.
Other than that it was a case of assembling the two objects that already did what I needed, so autowire a MappingJackson2HttpMessageConverter and instantiate a RequestResponseBodyMethodProcessor with that one converter. Then pick the right method to override so that I could control what parameter type was used at a point that I had access to the URI.
Quick test. Given the following payload for both requests ...
{
"a": "A",
"b": "B"
}
POST http://localhost:8081/dummy/data-a
... gives a response of ...
{
"a": "A"
}
POST http://localhost:8081/dummy/data-b
... gives a response of ...
{
"b": "B"
}
In our real-world example this means that we will be able to write one method each that supports the POST / PUT. We need to build the objects and configure the validation possibly - or alternatively if we use OpenAPI 3.0 which we are investigating we could generate the model and validate without writing any further code ... but that's a separate task ;)
tl;dr: I'm overriding a method annotation of an interface on a child-child class, but the annotation's value is not being override.
I'm surprised I didn't found the answer for this question on this or any other site, so I think it must be something wrong with my implementation, but I can't figure it out.
I have the following custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
#Inherited //This only applies to class annotations, but I tried it anyway
public #interface MyRolesAllowed {
String[] value();
}
And it's used on this interface:
#Produces(MediaType.APPLICATION_JSON)
public interface IRESTfulCRUDResource<T> {
#GET
#Path("/")
#MyRolesAllowed("ADMIN")
public Response ws(#Context UriInfo uriInfo);
}
That's implemented as:
#Stateless
#Produces(MediaType.APPLICATION_JSON)
public abstract class BaseCRUDResource<T> implements IRESTfulCRUDResource<T> {
#Override
public Response wsQuery(#Context UriInfo uriInfo) {
//...
}
}
That I just override in this class to change the default annotation:
#Stateless
#Path("/something")
public class SomeResource extends BaseCRUDResource<Someclass> {
#Override
#MyRolesAllowed({"ADMIN", "USER"})
public Response wsQuery(#Context UriInfo uriInfo) {
return super.wsQuery(uriInfo);
}
}
This is all hosted on a Wildfly server, and I'm using the following interceptor to process the annotation:
#Provider
#Priority(Priorities.AUTHORIZATION)
public class AuthorizationInterceptor implements ContainerRequestFilter {
public void filter(ContainerRequestContext requestContext) throws IOException {
Method resourceMethod = resourceInfo.getResourceMethod();
// I checked and at this point:
// resourceInfo.getResourceClass().getSimpleName() == "SomeResource"
String[] roles = resourceMethod.getAnnotation(MyRolesAllowed.class).value();
// Now, I checked and at this point:
// roles = {"ADMIN"}
// In my understanding, it should be {"ADMIN", "USER"},
// because I'm overriding the annotation on the child
// class, and resourceMethod points to the child method.
// What's wrong?
}
}
So, as stated on the last comment, I was expecting the parent's annotation to be override by the child's one, but it's not happening. This question is an example of annotation's value override.
I tried to use resourceMethod.getAnnotationsByType as well, but it provides the same values.
Am I misunderstanding something?
Update: as #JohnBollinger pointed in the comments section, I checked and resourceMethod.getDeclaringClass() == IRESTfulCRUDResource, so it's counterintuitive but ResourceInfo's getResourceMethod points to the parent method but getResourceClass points to the child class.
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 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);
}
}
}
As of Spring MVC 3, AbstractCommandController is deprecated so you can no longer specify the command class in setCommandClass(). Instead you hard-code the command class in the parameter list of a request handler. For example,
#RequestMapping(method = RequestMethod.POST)
public void show(HttpServletRequest request, #ModelAttribute("employee") Employee employee)
My problem is that I'm developing a generic page that allows the user to edit a generic bean, so the command class isn't known until the run-time. If the variable beanClass holds the command class, with AbstractCommandController, you would simply do the following,
setCommandClass(beanClass)
Since I can't declare the command object as a method parameter, is there any way to have Spring bind request parameters to a generic bean in the body of the request handler?
Instantiation of the command object is the only place where Spring needs to know a command class. However, you can override it with #ModelAttribute-annotated method:
#RequestMapping(method = RequestMethod.POST)
public void show(HttpServletRequest request,
#ModelAttribute("objectToShow") Object objectToShow)
{
...
}
#ModelAttribute("objectToShow")
public Object createCommandObject() {
return getCommandClass().newInstance();
}
By the way, Spring also works fine with the real generics:
public abstract class GenericController<T> {
#RequestMapping("/edit")
public ModelAndView edit(#ModelAttribute("t") T t) { ... }
}
#Controller #RequestMapping("/foo")
public class FooController extends GenericController<Foo> { ... }