How to use annotation in an annotation? - java

Given:
public #interface MyAnnotation(){
public SomeType[] value();
}
in Java 7 is it possible to do something like:
#MyAnnotation({
value1,
#MyAnnotation({subValue1, subvalue2, ...}) value2,
...
valueN
})
public Object someProperty;
?

You can. This is an example from Jackson library (leaving out the comments):
package com.fasterxml.jackson.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.FIELD,
ElementType.METHOD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotation
public #interface JsonSubTypes {
public Type[] value();
public #interface Type {
/**
* Class of the subtype
*/
public Class<?> value();
/**
* Logical type name used as the type identifier for the class
*/
public String name() default "";
}
}
And here is an example usage:
#JsonSubTypes({
#JsonSubTypes.Type(value = TestSystemEvent.class, name = "TestEvent"),
})
public interface SystemEvent {
}

How to use annotation in an annotation?
Maybe like this
public #interface MyAnnotation(){
public SomeType[] value();
public MyAnnotation[] refine();
}
#MyAnnotation(
{value1, value2},
refine={ #MyAnnotation({valueX, valueY}), #MyAnnotation(valueM) }
)
public Object someProperty;
Also, in Java 8, you can have Repeatable annotations - so you may refine or add to your 'primary' (e.g. the first) other refinements brought in by subsequent repetitions of the same annotation.

Related

Default values in custom annotation not working

I'm new in JAVA. I created a new custom annotation for custom validation in SPRING, but when I run the app I see this error:
The annotation #CourseCode must define the attribute groups
The annotation #CourseCode must define the attribute message
The annotation #CourseCode must define the attribute payload
The annotation #CourseCode must define the attribute value
Here is my implementation:
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.validation.Constraint;
import javax.validation.Payload;
#Constraint(validatedBy = CourseCodeConstraintValidator.class)
#Target({ ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface CourseCode {
public String value() default "course-";
public String message() default "must start with course-";
public Class<?>[] groups() default {};
public Class<? extends Payload>[] payload() default {};
}
Here is my usage
#CourseCode
private String courseCode;
Here is my CourseCodeConstraintValidator.
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class CourseCodeConstraintValidator implements ConstraintValidator<CourseCode, String>{
private String coursePrefix;
#Override
public void initialize(CourseCode courseCode) {
coursePrefix = courseCode.value();
}
#Override
public boolean isValid(String code, ConstraintValidatorContext constraintValidatorContext) {
boolean result = code.startsWith(coursePrefix);
return result;
}
}
I expected the default values I defined in the class would be used.
After lots of hours of research, I gave up for a couple of days. Now I came back to it and suddenly discovered that switching perspective in eclipse after restarting the PC has done the trick and now it works as I expected.
Thanks to everybody who tried to help.

How to create custom #ConditionalOnProperty for simpler usage?

In a Spring-Boot project, I use #ConditionalOnProperty to choose whether some Beans get loaded or not. It looks like the following:
#ConditionalOnProperty(
prefix = "myservice",
name = "implversion",
havingValue = "a"
)
#Service
public class MyServiceImplA implements MyService {
// ...
}
This allows me to choose with specific profiles which Bean should be loaded, for example different implementations of an interface, depending on the value of myservice.implversion being a or b or whatever other value.
I'd like to achieve the same effect with a user-friendlier annotation like such:
#OnMyServiceVersion(value = "a")
#Service
public class MyServiceImplA implements MyService {
// ...
}
How can one do this?
I've tried annotating my custom annotation with #Conditional and implementing the Condition interface but I don't understand how to check properties that way. The Spring-Boot OnPropertyCondition extends SpringBootCondition is not public so I cannot start from there, and extending annotations isn't allowed, so I'm kind of stuck.
I've also tried the following with no success:
// INVALID CODE, DO NOT USE
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#ConditionalOnProperty(
prefix = "myservice",
name = "implversion",
havingValue = OnMyServiceVersion.value()
)
public #interface OnMyServiceVersion {
String value();
}
You can annotate your #OnMyServiceVersion annotation with #ConditionalOnProperty and alias the value of your annotation to the havingValue attribute of #ConditionalOnProperty:
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
#ConditionalOnProperty(prefix = "myservice", name = "implversion")
public #interface OnMyServiceVersion {
#AliasFor(annotation = ConditionalOnProperty.class, attribute = "havingValue")
String value() default "";
}
Here's a complete example that shows this in action:
package com.example.demo;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Service;
#SpringBootApplication
public class CustomPropertyConditionApplication {
public static void main(String[] args) {
SpringApplication.run(CustomPropertyConditionApplication.class, "--myservice.implversion=b");
}
#Service
#OnMyServiceVersion("a")
static class ServiceA {
ServiceA() {
System.out.println("Service A");
}
}
#Service
#OnMyServiceVersion("b")
static class ServiceB {
ServiceB() {
System.out.println("Service B");
}
}
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
#ConditionalOnProperty(prefix = "myservice", name = "implversion")
static #interface OnMyServiceVersion {
#AliasFor(annotation = ConditionalOnProperty.class, attribute = "havingValue")
String value() default "";
}
}
This will output Service B when run. If you change the arguments in the main method to --myservice.implversion=a it will output Service A. If you remove the argument, it won't output either.
#Bean(name = "emailNotification")
#ConditionalOnProperty(prefix = "notification", name = "service")
public NotificationSender notificationSender() {
return new EmailNotification();
}
for reference
https://www.baeldung.com/spring-conditionalonproperty

Create a custom annotation to set a bunch of other annotation

I have some repeated annotations in multiple classes. It looks like this -
#Configuration
#Data
#Slf4j
#ConfigurationProperties(prefix = "hello")
#PropertySource(value = "classpath:hello.yml", factory = YamlSourceFactory.class)`
I would like to combine and make a single Annotation class out of it.
I want to write something like this -
#Configuration
#Data
#Documented
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#PropertySource(value = this.value, factory = this.factory)
#ConfigurationProperties(prefix = this.key)
public #interface CustomAnnotation {
String name() default "";
String[] key();
String[] value();
Class<? extends PropertySourceFactory> factory() default YamlSourceFactory.class;
}
so that in all my other files I can write only one line and it is taken care of by my Custom Annotation -
#CustomAnnotation(key = "hello", value = "classpath:hello.yml")
Is this something possible to achieve ?
I faced similar problem and this solution works for me:
This is the case:
package com.something.something;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Primary
#Scope
#Component
#Lazy
public #interface CustomComponent{
/**
* Setting the scope of the beans
* Means: #Scope(value = "scope")
* #return
*/
#AliasFor(
annotation = Scope.class,
attribute = "value"
)
String scope() default SCOPE_PROTOTYPE;
/**
* Setting the name of the beans.
* I don't know why but if I renamed this, it didn't work. I could rename the others.
* Means: #Component(value = "name")
* #return
*/
#AliasFor(
annotation = Component.class,
attribute = "value"
)
String value() default "";
/**
* Setting the bean is lazy or not
* Means: #Lazy(value = true)
* #return
*/
#AliasFor(
annotation = Lazy.class,
attribute = "value"
)
boolean isLazy() default false;
}
How to use:
#CustomComponent( scope = "SCOPE_SINGLETON", value = "bean-name", isLazy = true);
I hope you can transform it to your case.

How to create an aspect class that will implement the logging functionality

How to create an aspect class that will implement the logging functionality. Logged classes/packages/methods need to be defined in the configuration file.
#Aspect
public class LoggingAspect {
private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);
#Autowired
private List<PackageProperties> packageProperties;
#Pointcut("execution(* org.epam.operations.Operations.removeStudentFromList())")
public void removeStudentLog() {
}
#After("removeStudentLog()")
public void applicationLogger() {
log.info("Student deleted");
}
}
application.properties
remove.packageName = org.epam.operations
remove.className = Operations
remove.methodName = removeStudentFromList
add.packageName = org.epam.operations
add.className = Operations
add.methodName = addStudent
For loading the value from properties file you have many ways, which mostly are different in initialization order in IOC container, two of them are as follow
Implement EnvironmentAware interface
public class YourAspectClass implements EnvironmentAware {
private Environment environment;
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
Using of #Value annotation to access property .
package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Value {
String value();
}
#Value("#{pointer_of_property}") String value

Validation Constraint not working on a List of String

I have created a custom validator to validate Strings. It works on a single String but not on a List of Strings. This is what I have tried so far:
#Get("/test1")
public String test1(
#QueryValue(value = "ids") List<#DurationPattern String> ids) { //NOT WORKING
return "not working";
}
#Get("/test2")
public String test2(
#QueryValue(value = "id") #DurationPattern String id){ //WORKS
//it does not get here which is what I want.
return "done";
}
My #DurationPattern code:
package my.package;
import javax.validation.Constraint;
import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import my.package.DurationPattern.List;
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE, TYPE, LOCAL_VARIABLE, PACKAGE, TYPE_PARAMETER, MODULE })
#Repeatable(List.class)
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { })
public #interface DurationPattern {
String message() default "invalid duration ({validatedValue})";
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE, TYPE, LOCAL_VARIABLE, PACKAGE, TYPE_PARAMETER, MODULE})
#Retention(RUNTIME)
#Documented
public #interface List {
DurationPattern[] value();
}
}
Actual Validator:
package my.package;
import io.micronaut.context.annotation.Factory;
import io.micronaut.validation.validator.constraints.ConstraintValidator;
import javax.inject.Singleton;
#Factory
public class MyValidatorFactory {
#Singleton
ConstraintValidator<DurationPattern, CharSequence> durationPatternValidator() {
return (value, annotationMetadata, context) -> {
System.out.println("Please Print!!! It doesn't for Strings within List");
return value == null || value.toString().matches("^PT?[\\d]+[SMHD]{1}$");
};
}
}
Basically, create an implementation of io.micronaut.validation.validator.constraints.ConstraintValidator and provide them inside #Constraint(validatedBy = { })
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
#Repeatable(Regexp.List.class)
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = {DurationPatternValidator.class, DurationPatternValidatorList.class}) //to validate a single string or a list of strings
public #interface DurationPattern {
...
}
#Singleton
public class RegexValidatorList implements ConstraintValidator<DurationPattern, Collection<CharSequence>> {
#Override
public boolean isValid(...) {
....
}
And then in the controller's handler methods you would use
#Get("/test1")
public String test1(
#QueryValue(value = "ids") #DurationPattern List<String> ids) { //#DurationPattern would appear outside of diamond brackets for it to work.
return "working now";
}

Categories

Resources