I was wondering if it is possible to chain #ModelAttribute methods by having an #ModelAttribute annotated, but not request mapped, method use another ModelAttribute in the method signature.
This would be in a controller.
ie
#ModelAttribute("attrOne")
public AttrOne getAttrOne() {
return service.getAttOne();
}
#ModelAttribute("attrTwo")
public AttrTwo getAttrTwo(#ModelAttribute("attrOne") AttrOne attrOne){
return anotherservice.getAttrTwo(attrOne);
}
Then if there was a request mapped method that did this:
#RequestMapping(method=RequestMethod.GET)
public String doSomething(#ModelAttribute("attrTwo") AttrTwo attrTwo )
would this work?
I seem to get a null object for AttrOne in the second annotated method... as the first annotated method is not called by the second one...
Cheers
I ran into the same situation by learning from the spring documention:
#ModelAttribute is also used at the method level [..]. For this usage the method signature can contain the same types as documented above for the #RequestMapping annotation.
I found SPR-6299 which faces this problem. In the comments you can find a workaround by providing only one #ModelAttribute annotated method which sets the attributes into the model:
#ModelAttribute
public void populateModel(Model model) {
model.addAttribute("attrOne", getAttrOne());
model.addAttribute("attrTwo", getAttrTwo());
}
According to SPR-6299, this will be possible in Spring 4.1 RC1 or later.
Related
I have a DTO class with fields that either have javax or custom constraints put on them so everytime I hit this endpoint I am guaranteed to take in data that meets my requirements.
#PostMapping
public ResponseEntity<SomeResource> create(#Valid #RequestBody SomeDTO someDTO)
For reasons I do not want to go into, I am forced to validate this incoming data elsewhere (preferably in a separate service method) so I have tried doing:
#PostMapping
public ResponseEntity<SomeResource> create(#RequestBody SomeDTO someDTO) {
someService.validate(someDTO);
}
where the called method's signature is defined as
validate(#Valid SomeDTO someDTO)
Though I quickly figured out this does not actually do any other argument validation other than user input. With the Spring annotations not being particularly helpful, are there any other annotations out there that can validate an object passed in as a parameter to ensure the constraints are not violated?
Problem
I have the following constraints on userUuid and itemUuid:
Both strings must not be null.
Both strings must be UUIDs (eg. f1aecbba-d454-40fd-83d6-a547ff6ff09e).
The composition (userUuid, itemUuid) must be unique.
I tried to implement the validation in my controller like:
#RestController
#Validated // (1)
public class CartItemController {
#PostMapping("/me/carts/{itemUuid}")
#ResponseStatus(HttpStatus.CREATED)
public void addItem(#PathVariable("itemUuid") String itemUuid,
Authentication auth) {
CartItemId id = getCartItemId(getUserUuidFrom(auth), itemUuid);
...
}
#Unique // (4)
public CartItemId getCartItemId(#NotNull #Uuid String userUuid, // (2)
#NotNull #Uuid String itemUuid) { // (3)
return new CartItemId(userUuid, itemUuid);
}
...
}
#Uuid and #Unique are custom constraints. Method validation is enabled in (1). (2) are the contraints for the user UUID. (3) are the constraints for the item UUID. The unique constraint is applied to the returned CartItemId in (4). However, the parameters and the return value are never validated. Neither for the standard #NotNull constraint nor for my custom constraints. I receive HTTP status 201 Created instead of 400 Bad Request.
What am I doing wrong?
Stuff that works
The following code works for the item UUID:
#RestController
#Validated
public class CartItemController {
#PostMapping("/me/{itemUuid}")
#ResponseStatus(HttpStatus.CREATED)
public void addItem(#PathVariable("itemUuid") #Uuid String itemUuid, // (1)
Authentication auth) {
...
}
}
Adding the #Uuid to the path variable parameter works. Values like anInvalidUuid are rejected. I also tested the #Unique constraint in other use cases and it worked perfectly.
What is the difference between addItem() and toId()?
Versions
I am using Java 1.8 and Spring Boot 2.0.0.RELEASE. org.hibernate.validator:hibernate-validator:6.0.7.Final is on my classpath.
Validation of method arguments is based on AOP: a validating proxy intercepts the method call and validates the argument before delegating (if everything is valid), to the actual method.
You're calling the getCartItemId() method from another method of the same class. So the method call doesn't go through the proxy. Only inter-bean calls can be intercepted.
So, in short, getCartItemId should be in a separate bean, injected into your controller.
I have several APIs which retain a parameter "feature" from the url (path param). To avoid retrieving it in each method endpoint (eg.)
#GET
public void findAll(#PathParam("feature") String feature);
am trying to implement AOP using AspectJ.
Following is the implementation of the Aspect
#Aspect
public class FeatureAOP {
#Pointcut("execution(* x.y.z.rest.ModifiersFacadeWrapper.*(..)) && args(feature)")
public void pointCut(String feature) {
}
#Before("x.y.z.rest.aop.FeatureAOP.pointCut(feature)")
public void parseParams(JoinPoint jp, String feature) {
Object[] x = jp.getArgs();
System.out.println("Feature: " + feature);
}
}
The above method gives me the value of "feature" in the Aspect class but if I change the method findAll to following signature, it doesn't works.
#GET
public void findAll();
What I understand is the control is transferred to the Aspect after the parameters are resolved and removing it from the method definition is failing it.
Doing so, thus takes me to the same point where I have to define all method endpoints with the parameter in its signature. I would like to know if there is a way I can get the PathParams in the Aspect class without having to define my methods with the designated parameters.
I think you could probably do it by putting the resolved params in a globally accessible data structure (e.g. a Singleton having some sort of Map or Set), but
I wouldn't recommend that kind of approach. I don't know why you don't like having all the params in your method signatures, but that is the intended way of declaring rest services, e.g.
#GET
#Path("{feature}")
#Produces("text/plain")
public String getFeature(#PathParam("feature") String feature) {
return feature;
}
This way you don't have to write any code for retrieving the params, the rest library you are using (be it Jersey or a different one) will just do everything for you.
In my Java Spring MVC 4 project, I have an AbstractRESTController with an update method:
#RequestMapping(
value="/{id}",
method=RequestMethod.PUT,
consumes={MediaType.APPLICATION_JSON_VALUE}
)
public #ResponseBody ResponseEntity<T> update(#PathVariable ID id,
#RequestParam String token, #RequestBody T json) {
[do fancy stuff]
}
and an extending class, let's call it MyController. Usually I want to use the method from the abstract class, but in MyController I have a special case (yay!), so I need to do further work.
My idea was to just override the #RequestMapping in the child class, do my additional fancy stuff and afterwards call the super class' update method from the MyController.update method. But this does not work, because I get an ambiguous mapping error during compilation.
Is there a way to make Spring override the parent class request mapping? I would like to avoid splitting the routes.
As you have noticed you can't do this because the ambiguous mapping.
If you want execute some additional code, you can use something like hook methods. So, define in your AbstractRESTController an empty method like this:
protected void doFancyStuff() {
}
Obs.: the empty method is a better choice here, and not an abstract one, to avoid the need to implement even with empty method body in all concrete controller.
Change the update method to call the hook method:
#RequestMapping(
value="/{id}",
method=RequestMethod.PUT,
consumes={MediaType.APPLICATION_JSON_VALUE}
)
public #ResponseBody ResponseEntity<T> update(#PathVariable ID id,
#RequestParam String token, #RequestBody T json) {
doFancyStuff();
}
And in MyController you will override and implement doFancyStuff method.
I'd like to implement declarative security with Spring/AOP and annotations.
As you see in the next code sample I have the Restricted Annotations with the paramter "allowedRoles" for defining who is allowed to execute an adviced method.
#Restricted(allowedRoles="jira-administrators")
public void setPassword(...) throws UserMgmtException {
// set password code
...
}
Now, the problem is that in my Advice I have no access to the defined Annotations:
public Object checkPermission(ProceedingJoinPoint pjp) throws Throwable {
Signature signature = pjp.getSignature();
System.out.println("Allowed:" + rolesAllowedForJoinPoint(pjp));
...
}
private Restricted rolesAllowedForJoinPoint(ProceedingJoinPoint thisJoinPoint)
{
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
Method targetMethod = methodSignature.getMethod();
return targetMethod.getAnnotation(Restricted.class);
}
The method above always returns null (there are no annotations found at all).
Is there a simple solution to this?
I read something about using the AspectJ agent but I would prefer not to use this agent.
To whoever is still having problem after changing annotation retention to Runtime, you might be having the same problem I had: getMethod() returns interface method instead of the implementing class. So, if you have your annotations in the class then naturally getAnnotations() on the interface method returns null.
The following solution solved this problem:
final String methodName = pjp.getSignature().getName();
final MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
Method method = methodSignature.getMethod();
if (method.getDeclaringClass().isInterface()) {
method = pjp.getTarget().getClass().getDeclaredMethod(methodName, method.getParameterTypes());
}
and if you like, you have the option of handling interface annotations here too.
Some more comments available here:
getting template method instance from ProceedingJoinPoint
Oleg
I assume #Restricted is your annotation. If that is the case, make sure you have:
#Retention(RetentionPolicy.RUNTIME)
in your annotation definition. This means that the annotation is retained at runtime.
Even after changing the retention policy like Bozho mentioned this call to get annotation returns null:
targetMethod.getAnnotation(Restricted.class);
What I found is you have to bind the annotation. Given the interface is declared like this:
#Retention(RetentionPolicy.RUNTIME)
public #interface Restricted {
String[] allowedRoles();
}
The advice would need to be declared like this:
#Before("#annotation( restrictedAnnotation )")
public Object processRequest(final ProceedingJoinPoint pjp, Restricted restrictedAnnotation) throws Throwable {
String[] roles = restrictedAnnotation.allowedRoles();
System.out.println("Allowed:" + roles);
}
What this does is bind the annotation to the parameter in the method signature, restrictedAnnotation. The part I am not sure about is how it gets the annotation type, it seems to be based on the parameter. And once you have the annotation you can get the values.
Why don't you just use Spring Security ? It's a brief to implement and use, I don't really see the point in wasting time reinventing the wheel.
Whith Spring AOP if you have a situation like MyManagerImpl implements MyManager the pointcut is applied to the interface method so MethodSignature describes the method defined on MyManager that doesn't have any annotation. the only way I've found to fix this is to inspect the class of the jp.getTarget() object and retrieve the corresponding method.