I have the following:
Aspect:
#Aspect
#Component
public class AuthorizeUserAspect {
#Autowired
PermissionService permissionService;
#Around("#annotation(AuthorizeUser)")
public Object authorize(ProceedingJoinPoint joinPoint) throws Throwable {
...
}
}
Interface:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface AuthorizeUser {
}
Controller:
#GetMapping("/businesses/{businessId}/test")
public ResponseEntity<List<BusinessDto>> getBusinessListAsClient(Principal jwtAuthUser, #PathVariable String businessId) throws Exception {
...
}
I need to pass in the named parameters from the method signature, plus an additional custom permission parameter (READ / WRITE), so something like this:
#AuthorizeUser(Principal jwtAuthUser, String businessId, permission = <Enum or String>)
#GetMapping("/businesses/{businessId}/test")
Is this possible? I don't really care how it's done as long as I can get these values in my authorize function to use them.
I know I can do something like:
joinPoint.getArgs();
to get the parameters by position, but I want this to be more generic, and certain controllers can have different params as 1st and second for example, so that would not work, plus I have no idea
how to get the last value.
Note: The authorize is just an example, but I'd like to do this for other custom annotation parameters as well.
Maybe you should read the Spring AOP manual. How about this?
#Around("#annotation(authorizeUser)")
public Object authorize(ProceedingJoinPoint joinPoint, AuthorizeUser authorizeUser)
Related
I want to mark a field of a class with my custom annotation. And whenever any method is invoke I want to do some modification on that field.
public class Message{
public Integer id;
#FreeText // this is my custom annotation
public String htmlMsg;
public String textMsg ;
}
This annotation (#FreeText) can be used in any class.
In seasar framework, I can do this by create an interceptor and override invoke method. The I can get the object of this class and the find the field that marked with my annotation and modify it. However, i cannot find a way to do it in Spring.
In spring, I found some method like MethodInvocationInterceptor, but I don't know how to implement it. Can you suggest any way to do this in Spring?
Seasar2 and Spring are very close. I have not tested but you can do something like this.
First create FreeText custom annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#Documented
public #interface FreeText {}
Then create the following interceptor
public class EncryptSensitiveDataInterceptor extends MethodInterceptor {
#Override
public Object invoke(MethodInvocation in) throws Throwable {
Object[] params = in.getArguments();
Object param = params[0];
for (Field field : param.getClass().getDeclaredFields()) {
for (Annotation anno : field.getDeclaredAnnotations()) {
if (anno instanceof FreeText) {
field.set(param, [YOUR CUSTOM LOGIC METHOD]);
}
}
}
return in.proceed();
}
Hope this help.
I have a custom Annotation like this -
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface ControllerAction {
String value();
}
I have a class that uses this annotation like this -
public class TestController extends AbstractController {
public TestController () {
super();
}
#ControllerAction("add")
public void addCandidate(){
}
}
The super class looks like this -
public abstract class AbstractController {
public AbstractController (){
}
public CustomBean processRequest(ServletAction action, HttpServletRequest request) {
Class<AbstractController > controllerClass = AbstractController.class;
for (Method method : controllerClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(ControllerAction.class)) {
Annotation annotation = (ControllerAction) method.getAnnotation(ControllerAction.class);
if(annotation != null){
if(annotation.value().equals(action.getAction())){
method.invoke(controllerClass.newInstance());
}
}
}
}
return null;
}
}
The processRequest(...) method in AbstractController is called from a servlet directly. The processRequest() method figures out the servlet action, and based on that, it should call the method appropriately.
For example, if the ServletAction.getAction() == 'add', processRequest() should automatically call addCandidate() in TestController. But I am not able to get the value of the Annotation. Somehow annotation.value() is giving a compilation error in eclipse. Eclipse is not showing any method I can use to get the annotation value.
I want to know if there is a way to get value() of the Custom Annotation. I dont want to define my Annotation with anything else other than String value(). I want to know if it is possible to achieve what I want with just String value() in my custom Annotation?
Any help is greatly appreciated. Thanks.
You probably need to change
Annotation annotation = (ControllerAction) method.getAnnotation(ControllerAction.class);
To
ControllerAction annotation = method.getAnnotation(ControllerAction.class);
Otherwise the methods specific to ControllerAction will not be known to the compiler as annotation is of type Annotation
Additionally - as pointed out by Sharon Ben Asher - instead of AbstractController.class you should use getClass() to get the class of the actual implementation at runtime. Given the current code only the methods of AbstractController will be checked but not the ones of implementing classes.
I have routes like:
GET /job$id<[0-9]+>/ controllers.Jobs.index(id)
POST /job$id<[0-9]+>/done controllers.Jobs.done(id)
POST /job$id<[0-9]+>/update controllers.Jobs.update(id)
DELETE /job$id<[0-9]+>/remove controllers.Jobs.remove(id)
and I' like to secure it. Each job has an owner. So I made
public class Secured extends Security.Authenticator {
...
}
Then I tryed to secure all my Actions by "#With()" annotatian, but I need to pass "Id" param to my secure method, so I wrote smth like:
#With(IsOwner.class)
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface Owner {
long value() default 0;
}
public static class IsOwner extends Action<Owner>{
#Override
public Promise<SimpleResult> call(Context ctx) throws Throwable {
if(!isOwner(configuration.value())){
return Promise.pure(redirect(..));
}
return delegate.call(ctx);
}
}
But I can't pass my action parametr to annotation.
#Security.Authenticated(Secured.class)
#Owner(id) //**this part I want to work**
public class Jobs extends Controller {
//#Owner(id) - or at list there
public static Result index(Long Id){
//I have too many Actions and don't want to do every time this
/*if(!Secured.isOwnerMethod(Id)){
return forbidden();
}*/
return ok();
}
Other way I see is get params from Context in IsOwner.call() method...
Please, give me a recommendation or good practise for such situations.
Thanks.
I ran into this problem and came up with a Play extension that exposes all url parameters as common request parameters... You can check it out here => https://github.com/asouza/reverse-route-plugin
Basically you have to extend from my Global class.
Cheers,
Alberto
Hej,
I want to use the #Validated(group=Foo.class) annotation to validate an argument before executing a method like following:
public void doFoo(Foo #Validated(groups=Foo.class) foo){}
When i put this method in the Controller of my Spring application, the #Validated is executed and throws an error when the Foo object is not valid. However if I put the same thing in a method in the Service layer of my application, the validation is not executed and the method just runs even when the Foo object isn't valid.
Can't you use the #Validated annotation in the service layer ? Or do I have to do configure something extra to make it work ?
Update:
I have added the following two beans to my service.xml:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
and replaced the #Validate with #Null like so:
public void doFoo(Foo #Null(groups=Foo.class) foo){}
I know it is a pretty silly annotation to do but I wanted to check that if I call the method now and passing null it would throw an violation exception which it does. So why does it execute the #Null annotation and not the #Validate annotation ? I know one is from javax.validation and the other is from Spring but I do not think that has anything to do with it ?
In the eyes of a Spring MVC stack, there is no such thing as a service layer. The reason it works for #Controller class handler methods is that Spring uses a special HandlerMethodArgumentResolver called ModelAttributeMethodProcessor which performs validation before resolving the argument to use in your handler method.
The service layer, as we call it, is just a plain bean with no additional behavior added to it from the MVC (DispatcherServlet) stack. As such you cannot expect any validation from Spring. You need to roll your own, probably with AOP.
With MethodValidationPostProcessor, take a look at the javadoc
Applicable methods have JSR-303 constraint annotations on their
parameters and/or on their return value (in the latter case specified
at the method level, typically as inline annotation).
Validation groups can be specified through Spring's Validated
annotation at the type level of the containing target class, applying
to all public service methods of that class. By default, JSR-303 will
validate against its default group only.
The #Validated annotation is only used to specify a validation group, it doesn't itself force any validation. You need to use one of the javax.validation annotations like #Null or #Valid. Remember that you can use as many annotations as you would like on a method parameter.
As a side note on Spring Validation for methods:
Since Spring uses interceptors in its approach, the validation itself is only performed when you're talking to a Bean's method:
When talking to an instance of this bean through the Spring or JSR-303 Validator interfaces, you'll be talking to the default Validator of the underlying ValidatorFactory. This is very convenient in that you don't have to perform yet another call on the factory, assuming that you will almost always use the default Validator anyway.
This is important because if you're trying to implement a validation in such a way for method calls within the class, it won't work. E.g.:
#Autowired
WannaValidate service;
//...
service.callMeOutside(new Form);
#Service
public class WannaValidate {
/* Spring Validation will work fine when executed from outside, as above */
#Validated
public void callMeOutside(#Valid Form form) {
AnotherForm anotherForm = new AnotherForm(form);
callMeInside(anotherForm);
}
/* Spring Validation won't work for AnotherForm if executed from inner method */
#Validated
public void callMeInside(#Valid AnotherForm form) {
// stuff
}
}
Hope someone finds this helpful. Tested with Spring 4.3, so things might be different for other versions.
#pgiecek You don't need to create a new Annotation. You can use:
#Validated
public class MyClass {
#Validated({Group1.class})
public myMethod1(#Valid Foo foo) { ... }
#Validated({Group2.class})
public myMethod2(#Valid Foo foo) { ... }
...
}
Be careful with rubensa's approach.
This only works when you declare #Valid as the only annotation. When you combine it with other annotations like #NotNull everything except the #Valid will be ignored.
The following will not work and the #NotNull will be ignored:
#Validated
public class MyClass {
#Validated(Group1.class)
public void myMethod1(#NotNull #Valid Foo foo) { ... }
#Validated(Group2.class)
public void myMethod2(#NotNull #Valid Foo foo) { ... }
}
In combination with other annotations you need to declare the javax.validation.groups.Default Group as well, like this:
#Validated
public class MyClass {
#Validated({ Default.class, Group1.class })
public void myMethod1(#NotNull #Valid Foo foo) { ... }
#Validated({ Default.class, Group2.class })
public void myMethod2(#NotNull #Valid Foo foo) { ... }
}
As stated above to specify validation groups is possible only through #Validated annotation at class level. However, it is not very convenient since sometimes you have a class containing several methods with the same entity as a parameter but each of which requiring different subset of properties to validate. It was also my case and below you can find several steps to take to solve it.
1) Implement custom annotation that enables to specify validation groups at method level in addition to groups specified through #Validated at class level.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface ValidatedGroups {
Class<?>[] value() default {};
}
2) Extend MethodValidationInterceptor and override determineValidationGroups method as follows.
#Override
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
final Class<?>[] classLevelGroups = super.determineValidationGroups(invocation);
final ValidatedGroups validatedGroups = AnnotationUtils.findAnnotation(
invocation.getMethod(), ValidatedGroups.class);
final Class<?>[] methodLevelGroups = validatedGroups != null ? validatedGroups.value() : new Class<?>[0];
if (methodLevelGroups.length == 0) {
return classLevelGroups;
}
final int newLength = classLevelGroups.length + methodLevelGroups.length;
final Class<?>[] mergedGroups = Arrays.copyOf(classLevelGroups, newLength);
System.arraycopy(methodLevelGroups, 0, mergedGroups, classLevelGroups.length, methodLevelGroups.length);
return mergedGroups;
}
3) Implement your own MethodValidationPostProcessor (just copy the Spring one) and in the method afterPropertiesSet use validation interceptor implemented in step 2.
#Override
public void afterPropertiesSet() throws Exception {
Pointcut pointcut = new AnnotationMatchingPointcut(Validated.class, true);
Advice advice = (this.validator != null ? new ValidatedGroupsAwareMethodValidationInterceptor(this.validator) :
new ValidatedGroupsAwareMethodValidationInterceptor());
this.advisor = new DefaultPointcutAdvisor(pointcut, advice);
}
4) Register your validation post processor instead of Spring one.
<bean class="my.package.ValidatedGroupsAwareMethodValidationPostProcessor"/>
That's it. Now you can use it as follows.
#Validated(groups = Group1.class)
public class MyClass {
#ValidatedGroups(Group2.class)
public myMethod1(Foo foo) { ... }
public myMethod2(Foo foo) { ... }
...
}
I am trying to understand CDI, with very few results at the moment. :(
Actually I am stucked with a web service facade like this example:
#GET
#Path("/greeting/{name}")
#produces(mediatype.text_plain)
public String sayHello(#PathParam("name") String name)
{
return "Hello " + name;
}
Because using CDI annotation I'd like to make a check on server side every time a string is sent as parameter, to have comething like : (note #NameValidator annotation)
#GET
#Path("/greeting/{name}")
#Produces(mediatype.text_plain)
public String sayHello( #NameValidator #PathParam("name") String name)
{
return "Hello " + name;
}
The validaton can be anything like that, in an external class:
if (name == "Andrea") {
throw new Exception();}
How does it work? Is it possible? If not, what are alternatives?
Thank you!
Andrea
You could use CDI's interceptor annotations and do something like:
...
#Validated
public String sayHello(#Validator(MyValidator.class) String name) {
...
Where #Validated binds the method to a ValidationInterceptor class or so, where you use an #AroundInvoke method and its InvocationContext parameter to inspect the passed parameters and validate them.
One "difficulty" with this approach is that you will have to use reflection to get the #Validator annotation and specified validator class for each parameter, then create/retrieve corresponding instances before you can finally do the actual validation.
A slightly different approach would be to inject an Instance<T> of a common superclass/interface of your validators into the ValidationInterceptor and annotate the sayHello parameters with validator qualifiers:
// in ValidationInterceptor:
#Inject
private Instance<Validator> validatorInstance;
#AroundInvoke
public Object validate(InvocationContext context) {
// get Annotation instances for target method parameters
// ...
Validator validator =
validatorInstance.select(annotations).get();
// ... validator.validate(parameter); ...
}
// sayHello:
...
#Validated
public String sayHello(#NameValidator String name) {
...
To get parameter annotations from InvocationContext, do something like:
Annotation[][] annotations = context.getMethod().getParameterAnnotations();
You may also consider pre-processing these annotations in an #AfterBeanDiscovery event handler.
For that validation issue I would propose Seam Validation, which bridges CDI to Hibernate-Validator. That hooks you on the well-defined validation-API of Hibernate end enables you to write code like this:
public void registerUser(#Valid UserData data) {...};
If you can't live with the Seam 3 dependency you can easily take the relevant source code of the CDI-extension, it's just a few dozens lines of code.