Allow custom annotation only one one method per class - java

I have a annotation #ToolExecution. At the moment I use annotation processing and throwing Error to ensure that only one method is annotated with this annotation.
Is there an native way to create a constraint and allow my custom annotation to be only applied to one annotation per class?
This should not be possible
#Tool(id = "scheduledtool")
public class ScheduledTool extends SimpleTool {
private String parameter;
#ToolExecution
public void configuration(#ToolParameters(fields = {"config"}, credentials = true) ToolParameter parameters) {
String parameter = parameters.getParameter("config");
this.parameter = parameter;
}
#ToolExecution
public void execute() {
toolLog(Level.INFO, "Configured Param: " + parameter);
toolLog(Level.INFO, "Finished scheduled tool");
}
}

You can't enforce a single method annotation per class at compile time, you can only do that type of validation at runtime.

Related

Make method accept only parameter with particular annotation

I have a method
public static void injectConfiguration(#Configurable Object bean) {}
And I have a class which holds field
public class LauncherComponentsHolder {
#Configurable
public RoomDao roomDao;
And I have main class, where I call that method and pass him that:
LauncherComponentsHolder root = new LauncherComponentsHolder();
root.roomDao = new RoomDaoImpl();
root.guestDao = new GuestDaoImpl();
root.maintenanceDao = new MaintenanceDaoImpl();
ConfigInjector.injectConfiguration(root.roomDao);
ConfigInjector.injectConfiguration(root.guestDao);
ConfigInjector.injectConfiguration(root.maintenanceDao);
Problem is that the method accepts all the 3 parameters, (no warnings, errors, nothing) however only roomDao is annotated. Annotation itself:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.PARAMETER, ElementType.FIELD})
public #interface Configurable {
}
How to make the restriction, so that injectConfiguration(#Configurable Object bean) would accept only field (or class instance) annotated with Configurable ?
You can accomplish this by using an annotation processor.
An example of such a tool is the Checker Framework.
It enables you to write type annotations in your program, then it type-checks the type annotations at compile time. It issues a warning if the type annotations in your program are not consistent with one another.
The easiest way for you to implement the checking would be to use the Subtyping Checker.
Here is an example from its manual:
import myPackage.qual.Encrypted;
...
public #Encrypted String encrypt(String text) {
// ...
}
// Only send encrypted data!
public void sendOverInternet(#Encrypted String msg) {
// ...
}
void sendText() {
// ...
#Encrypted String ciphertext = encrypt(plaintext);
sendOverInternet(ciphertext);
// ...
}
void sendPassword() {
String password = getUserPassword();
sendOverInternet(password);
}
When you invoke javac using a couple extra command-line arguments, javac issues an error for the second invocation of sendOverInternet but not the first one:
YourProgram.java:42: incompatible types.
found : #PossiblyUnencrypted java.lang.String
required: #Encrypted java.lang.String
sendOverInternet(password);
^

How to create an invocation counter annotation in Spring?

Is it possible to create a custom annotation that simply tracks invocations of some methods, without having to add a service method call in every method explicit?
#InvocationCounter(path = "/test1") //I'm looking for this
#GetMapping("/person/{id}")
public Person getPerson(Long id) {
//...
}
On every getPerson() call, I want an invocation counter to record the invocation, like:
#Service
public class InvocationCounterService {
Map<String, AtomicInteger> counter;
public void count(String path) {
if (counter.get(path) == null) counter.put(path, new AtomicInteger()));
counter.get(path).incrementAndGet();
}
#Scheduled(fixedRate = 60000)
public void persist() {
//optionally process or persist the invocations
}
}
Question: how could I possibly tell Spring to invoke the count() service method on each annotated controller method?
The annotation InvocationCounter:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface InvocationCounter {
String path();
}
The Aspect InvocationCounterAspect:
#Aspect
#Component
public class InvocationCounterAspect {
#Autowired
InvocationCounterService invocationCounterService;
#Around("#annotation(InvocationCounter)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
InvocationCounter invocationCounter = signature.getMethod().getAnnotation(InvocationCounter.class);
final String path = invocationCounter.path(); //retrieve path from the annotation
invocationCounterService.count(path); //call the counter service
return joinPoint.proceed(); //proceed executing the annotated method
}
}
You should be looking at micrometer.io, Spring boot application natively supports metrics collection, including simple counter.
An annotation is a form of syntactic metadata that can be added to Java source code.
So you can add informations to your source with an annotation but you can't add code to be executed.
You can do with aspects as mentioned by pleft in the comments.
Or you can create a countExecutions function that takes your Function as a lambda expression to count the number of executions as follow:
public R countExecutions(Function<R, T> fun, T data) {
// invoke counter
return fun.apply(data);
}
and rewriting your code as
#GetMapping("/person/{id}")
public Person getPerson(Long id) {
return countExecutions( /* code previously in the getPerson as a Function */ , id);
}

Can I explicitly call custom validator from service in Spring Boot?

I have a custom validator class that implements Validator, like this:
public class MyCustomValidator implements Validator
I want to be able to call its validate() method from a Service.
This is how this method looks:
#Override
public void validate(Object target, Errors errors) {
// validation goes here
MyClass request = (MyClass) target;
if (request.getId() == null) {
errors.reject("content.id", "Id is missing";
}
}
I don't want to have this validator in my endpoint, because I need to fetch the object to be validated from the database and then call the validation on it, so I need to do it from my service.
Can you please guide me on how to achieve this?
Use validation annotations in class but don't use #Valid on request body, then spring won't validate your class.
public class MyClass{
#NotNull
private Integer id;
#NotBlank
private String data;
}
Autowired Validator first
#Autowired
private final Validator validator;
Then for class validate using the validator conditionally when needed.
if(isValidate) {
Set<ConstraintViolation<MyClass>> violations = validator.validate(myClassObj);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(new HashSet<ConstraintViolation<?>>(violations));
}
}
The Validator interface is, as far as i understand it, called as soon as a matching object (determined by the public boolean Validator.supports(Class clazz) method).
However, your goal seems to be to validate an object of MyClass only at a specific time, coming from your persistence layer to your service layer.
There are multiple ways to achieve this.
The first and most obvious one is to not extend any classes, but to use a custom component with some notion of a validation function:
#Component
public class CustomValidator{
public void validate(MyClass target) throws ValidationException {
// validation goes here
if (target.getId() == null) {
throw new ValidationException("Id is missing");
}
}
}
And inject/autowire it into your service object:
#Component
public class MyClassService{
// will be injected in first instance of this component
#Autowired
private CustomValidator validator
public MyClass get(MyClass target) {
try {
validator.validate(target);
return dao.retrieve(target);
} catch (ValidationException) {
// handle validation error
} catch (DataAccessException) {
// handle dao exception
}
}
}
This has the benefit that you yourself can control the validation, and error handling.
The negative side is the relatively high boilerplate.
However, if you want different Validators for different CRUD-Operations (or Service Methods), you may be interested in the Spring Validation Groups Feature.
First, you create a simple marker interface for each Operation you want to differ:
interface OnCreate {};
interface OnUpdate {};
Then, all you need to do is use the marker interfaces in the fields of your entity class,
using the Bean Validation Annotations:
public class MyClass{
#Null(groups = OnCreate.class)
#NotNull(groups = OnUpdate.class)
String id;
}
In order to use those groups in your Service Class, you will have to use the #Validated annotation.
#Validated
#Service
public class MyService {
#Validated(OnCreate.class)
void validateForCreate(#Valid InputWithGroups input){
// do something
}
#Validated(OnUpdate.class)
void validateForUpdate(#Valid InputWithGroups input){
// do something
}
}
Note that #Validated is applied to the service class as well as the methods. You can also set the group for the whole service, if you plan on using multiple services.
I for once mostly use the built-in Jakarta Bean Validation annotations in combination with marker interfaces, because of their ease of use and almost no boilerplate, while staying somewhat flexible and adjustable.
You could inject Validator and call validate
#Autowired
Validator validator;
And then call validate:
Set<ConstraintViolation<Driver>> violations = validator.validate(yourObjectToValidate);

Java - Not able to get value of Custom Annotation

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.

No annotations present when reflecting a method using class.getDeclaredMethod

I'm writing some unit tests using reflection, and I'm having trouble retrieving annotations from method parameters.
I declared this interface:
private interface Provider {
void mock(#Email String email);
}
And I'm trying to reflect this method, as follows:
Class stringClass = String.class;
Method method = Provider.class.getDeclaredMethod("mock", String.class);
AnnotatedType annotatedType = method.getAnnotatedParameterTypes()[0];
Annotation annotation = annotatedType.getAnnotation(Annotation.class);
I'm expecting that annotation variable holds an instance of #Email annotation, but instead, its value is null.
Even this simple check returns false:
method.isAnnotationPresent(Email.class)
So, how can I retrieve the annotations for an specific param when reflecting a method?
Updated
It seems that in order to retrieve the parameters annotation I need to call method.getParameterAnnotations(). But the problem with this is that I don't know what annotations belong to what methods.
If you want annotation to be visible during program execution, you need to annotate it with #Retention(RetentionPolicy.RUNTIME):
private interface Provider {
void mock(#Email String email);
}
#Retention(RetentionPolicy.RUNTIME)
public #interface Email{}
#Test
public void test_annotation_existence() throws NoSuchMethodException {
Method method = Provider.class.getDeclaredMethod("mock", String.class);
Annotation[] firstParameterAnnotationsArray = method.getParameterAnnotations()[0];
boolean isAnnotationPresent = isAnnotationPresent(firstParameterAnnotationsArray, Email.class);
Assert.assertTrue("Annotation not present!", isAnnotationPresent);
}
private boolean isAnnotationPresent(Annotation[] annotationsArray, Class clazz) {
if (annotationsArray == null)
throw new IllegalArgumentException("Please pass a non-null array of Annotations.");
for(int i = 0; i < annotationsArray.length; i++ ) {
if (annotationsArray[i].annotationType().equals(clazz))
return true;
}
return false;
}
You have to make a distinction between the Java 8 type annotations and the (since Java 5) parameter annotations. The crucial thing about type annotations, is, that you have to declare the possibility of using your annotation as type annotation explicitly.
Consider the following example:
public class AnnoTest {
#Retention(RetentionPolicy.RUNTIME)
#interface Email {}
void example(#Email String arg) {}
public static void main(String[] args) throws ReflectiveOperationException {
Method method=AnnoTest.class.getDeclaredMethod("example", String.class);
System.out.println("parameter type annotations:");
AnnotatedType annotatedType = method.getAnnotatedParameterTypes()[0];
//Annotation annotation = annotatedType.getAnnotation(Annotation.class);
System.out.println(Arrays.toString(annotatedType.getAnnotations()));
System.out.println("parameter annotations:");
System.out.println(Arrays.toString(method.getParameterAnnotations()[0]));
}
}
it will print
parameter type annotations:
[]
parameter annotations:
[#AnnoTest$Email()]
In this case the annotation is a property of the parameter.
Now change it to (note the #Target)
public class AnnoTest {
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE_USE)
#interface Email {}
void example(#Email String arg) {}
public static void main(String[] args) throws ReflectiveOperationException {
Method method=AnnoTest.class.getDeclaredMethod("example", String.class);
System.out.println("parameter type annotations:");
AnnotatedType annotatedType = method.getAnnotatedParameterTypes()[0];
//Annotation annotation = annotatedType.getAnnotation(Annotation.class);
System.out.println(Arrays.toString(annotatedType.getAnnotations()));
System.out.println("parameter annotations:");
System.out.println(Arrays.toString(method.getParameterAnnotations()[0]));
}
}
which will print
parameter type annotations:
[#AnnoTest$Email()]
parameter annotations:
[]
instead. So now, the annotation is a feature of the parameter type, i.e. String. Conceptionally, the parameter type of the method is now #Email String (which seems to be the most logical choice, as it allows declaring types like List<#Email String>, but you have to understand how these new type annotations work and it doesn’t work together with pre-Java 8 libraries).
Care must be taken when enabling an annotation for both, parameters and type use, as this can create ambiguous annotations.
If that happens, the compiler will record the annotations for both, the parameter and the type, e.g.
when you change the target in the example to #Target({ElementType.TYPE_USE, ElementType.PARAMETER}), it will print
parameter type annotations:
[#AnnoTest$Email()]
parameter annotations:
[#AnnoTest$Email()]
similar issues may arise at method return types, resp. field types when enabling an annotation for “type use” and methods, resp. fields.

Categories

Resources