I have been working with custom attribute types, and JPA's Attribute Converter.
For an example #Entity class like so:
#Entity
public class ExampleEntity {
private CustomAttribute customAttribute = new CustomAttribute();
public CustomAttribute getCustomAttribute() {
if(this.customAttribute == null){
this.customAttribute = new CustomAttribute();
}
return this.customAttribute;
}
}
My CustomAttribute has some attributes of itself, and I do not want it to be null (ever). I had to choose between two alternatives, and both of those were examplified simultaneously:
Instantiate the object on attribute declaration (like I did on the example for the field).
Instantiate a new object for that attribute in case of a true null checking on a getter.
Since my #Entity on production application has more than 10 attributes, it would be too verbose to apply any of those alternatives on all fields/getters.
Question: Is there any way to instantiate the CustomAttribute object on null field access?
I could not find any answer for this specific question on my research online, so any insight would be appreciated.
EDIT: My question is not about lazy instantiating of fields on relationship mappings, it is about instantiating custom attribute objects on access.
Bit of a hacky solution, you may prefer it plain java approach you yourself suggested:
I created a class called MyClass, which has following consturctor:
#Data // Lombok annotation
public class ExampleEntity {
#NotNullGetter
private CustomAttribute customAttribute;
public ExampleEntity() {
Class<?> cls = this.getClass();
for(Field field: cls.getDeclaredFields()) {
if(field.isAnnotationPresent(NotNullGetter.class)) {
Class clazz = field.getType();
try {
for(Method method: cls.getDeclaredMethods()) {
if(method.getName().startsWith("set") && method.getName().toLowerCase().endsWith(field.getName().toLowerCase())) {
// This will create not null object.
method.invoke(this, field.getType().newInstance());
}
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}
And my #NotNullGetter annotation
import java.lang.annotation.*;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#Documented
public #interface NotNullGetter {
}
Related
I'm trying to use bytebuddy to redefine existing classes. I'm looking for fields that are annotated with a specific annotation. I've got that figure out with code something like this:
new ByteBuddy()
.redefine(<some class>)
.field(
ElementMatchers.isAnnotatedWith(<some annotation>)
)
...
What I'd like to do is further refine my ElementMatcher to include a check for an attribute on the specified annotation - something like this:
new ByteBuddy()
.redefine(<some class>)
.field(
ElementMatchers.isAnnotatedWith(<some annotation>)
.havingAttribute(<some attribute>, "value")
)
What I'm looking for is the way to do the "havingAttribute" part. Is this possible or am I approaching this the wrong way? Any insight is appreciated.
One approach would be to create a Advice.OffsetMapping.Factory which lets you inject the annotation value in your advice like this:
#Advice.OnMethodEnter(suppress = Throwable.class)
public static void onMethodEnter(#AnnotationValueExtractor(annotationClassName = "co.elastic.apm.api.CaptureSpan", method = "value") String spanName) {
if (spanName.equals("foo")) {
// do something special
}
}
See also:
https://github.com/elastic/apm-agent-java/blob/f6781c3d740602f000332f9b4a5b5ecb0d01627a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/AnnotationValueOffsetMappingFactory.java
I ended up implementing a custom ElementMatcher like this:
public class NeedsLazyToOneNoProxy<T extends AnnotationSource> extends ElementMatcher.Junction.AbstractBase<T> {
public boolean matches(T target) {
AnnotationDescription oneToOneAnnotation = target.getDeclaredAnnotations().ofType(OneToOne.class);
try {
if (oneToOneAnnotation != null) {
OneToOne oneToOne = (OneToOne) ((AnnotationDescription.Loadable) oneToOneAnnotation).load();
FetchType fetchType = oneToOne.fetch();
return fetchType == FetchType.LAZY;
}
return false;
}
catch (ClassNotFoundException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
I use this element matcher to determine if I should add a #LazyToOne annotation to an existing #OneToOne relationship.
We are creating a REST API which is documented using Swagger's #ApiModelProperty annotations. I am writing end-to-end tests for the API, and I need to generate the JSON body for some of the requests. Assume I need to post the following JSON to an endpoint:
{ "name": "dan", "age": "33" }
So far I created a separate class containing all the necessary properties and which can be serialized to JSON using Jackson:
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyPostRequest {
private String name;
private String age;
// getters and fluid setters omitted...
public static MyPostRequest getExample() {
return new MyPostRequest().setName("dan").setAge("33");
}
}
However, we noticed that we already have a very similar class in the codebase which defines the model that the API accepts. In this model class, the example values for each property are already defined in #ApiModelProperty:
#ApiModel(value = "MyAPIModel")
public class MyAPIModel extends AbstractModel {
#ApiModelProperty(required = true, example = "dan")
private String name;
#ApiModelProperty(required = true, example = "33")
private String age;
}
Is there a simple way to generate an instance of MyAPIModel filled with the example values for each property? Note: I need to be able to modify single properties in my end-to-end test before converting to JSON in order to test different edge cases. Therefore it is not sufficient to generate the example JSON directly.
Essentially, can I write a static method getExample() on MyAPIModel (or even better on the base class AbstractModel) which returns an example instance of MyAPIModel as specified in the Swagger annotations?
This does not seem to be possible as of the time of this answer. The closest possibilities I found are:
io.swagger.converter.ModelConverters: The method read() creates Model objects, but the example member in those models is null. The examples are present in the properties member in String form (taken directly from the APIModelParameter annotations).
io.swagger.codegen.examples.ExampleGenerator: The method resolveModelToExample() takes the output from ModelConverters.read(), and generates a Map representing the object with its properties (while also parsing non-string properties such as nested models). This method is used for serializing to JSON. Unfortunately, resolveModelToExample() is private. If it were publicly accessible, code to generate a model default for an annotated Swagger API model class might look like this:
protected <T extends AbstractModel> T getModelExample(Class<T> clazz) {
// Get the swagger model instance including properties list with examples
Map<String,Model> models = ModelConverters.getInstance().read(clazz);
// Parse non-string example values into proper objects, and compile a map of properties representing an example object
ExampleGenerator eg = new ExampleGenerator(models);
Object resolved = eg.resolveModelToExample(clazz.getSimpleName(), null, new HashSet<String>());
if (!(resolved instanceof Map<?,?>)) {
// Model is not an instance of io.swagger.models.ModelImpl, and therefore no example can be resolved
return null;
}
T result = clazz.newInstance();
BeanUtils.populate(result, (Map<?,?>) resolved);
return result;
}
Since in our case all we need are String, boolean and int properties, there is at least the possibility to parse the annotations ourselves in a crazy hackish manner:
protected <T extends MyModelBaseClass> T getModelExample(Class<T> clazz) {
try {
T result = clazz.newInstance();
for(Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(ApiModelProperty.class)) {
String exampleValue = field.getAnnotation(ApiModelProperty.class).example();
if (exampleValue != null) {
boolean accessible = field.isAccessible();
field.setAccessible(true);
setField(result, field, exampleValue);
field.setAccessible(accessible);
}
}
}
return result;
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalArgumentException("Could not create model example", e);
}
}
private <T extends MyModelBaseClass> void setField(T model, Field field, String value) throws IllegalArgumentException, IllegalAccessException {
Class<?> type = field.getType();
LOGGER.info(type.toString());
if (String.class.equals(type)) {
field.set(model, value);
} else if (Boolean.TYPE.equals(type) || Boolean.class.equals(type)) {
field.set(model, Boolean.parseBoolean(value));
} else if (Integer.TYPE.equals(type) || Integer.class.equals(type)) {
field.set(model, Integer.parseInt(value));
}
}
I might open an Issue / PR on Github later to propose adding functionality to Swagger. I am very surprised that nobody else has seemed to request this feature, given that our use case of sending exemplary model instances to the API as a test should be common.
I need to do some additional business logic for specified filed (with a custom annotation) after hibernate load this entity. So, I created a hibernate interceptor like this. But what confused me is that I can't get the annotation information. The encryptAnnotation is always null in the following codes.
public class HibernateInterceptor extends EmptyInterceptor {
public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
for (int i = 0; i < types.length; i++) {
Type type = types[i];
if (type instanceof StringType) {
StringType stringType = (StringType) types[i];
Encrypt encryptAnnotation = stringType.getJavaTypeDescriptor().getJavaTypeClass().getAnnotation(Encrypt.class);
if (encryptAnnotation != null) {
//todo: decrypt field
return true;
}
}
}
return false;
}
}
Here is the Entity and annotation definition:
#Entity
#Table(name = "table_name")
public class Trade implements Serializable {
#Encrypt
private String shiptoAddr;
}
#Target({ElementType.FIELD, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Encrypt {
}
You are trying to obtain the annotation from the mapping information and basically in the end you are trying to find the annotation on the String class, that is obviously not going to work.
Instead you need to detected all fields on the passed in entity object and check if the annotation is present on a field.
I want to add annotations on my classes properties, and then iterate all my properties with the ability to lookup the annotations also.
So for example, I have a class like:
public class User {
#Annotation1
private int id;
#Annotation2
private String name;
private int age;
// getters and setters
}
Now I want to be able to loop through my properties, and be able to know what annotation (if any) is on the property.
I want to know how to do this using just java, but also curious if using either spring, guava or google guice would make this any easier (if they have any helpers to do this easier).
Here is an example that utilizes the (barely maintained) bean instrospection framework. It's an all Java solution that you can extend to fit your needs.
public class BeanProcessor {
public static void main(String[] args) {
try {
final Class<?> beanClazz = BBean.class;
BeanInfo info = Introspector.getBeanInfo(beanClazz);
PropertyDescriptor[] propertyInfo = info.getPropertyDescriptors();
for (final PropertyDescriptor descriptor : propertyInfo) {
try {
final Field field = beanClazz.getDeclaredField(descriptor
.getName());
System.out.println(field);
for (final Annotation annotation : field
.getDeclaredAnnotations()) {
System.out.println("Annotation: " + annotation);
}
} catch (final NoSuchFieldException nsfe) {
// ignore these
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Below is the way to create your own annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Annotation1 {
public String name();
public String value();
}
After defining your annotation, use the annotation as you mentioned in your question and you can use the below reflection method to get the annotated class details
Class aClass = User.class;
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof Annotation1){
Annotation1 myAnnotation = (Annotation1) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
I created the method below which creates a stream of all fields in a class and it's superclasses which have a specific annotation.
There are other ways to do it. But I think this solution is very easy to reuse and practical because when you need to know those fields, it is usually to do an action on each field. And a Stream is exactly what you need to do that.
public static Stream<Field> getAnnotatedFieldStream(Class<?> theClass, Class<? extends Annotation> annotationType) {
Class<?> classOrSuperClass = theClass;
Stream<Field> stream = Stream.empty();
while(classOrSuperClass != Object.class) {
stream = Stream.concat(stream, Stream.of(classOrSuperClass.getDeclaredFields()));
classOrSuperClass = classOrSuperClass.getSuperclass();
}
return stream.filter(f -> f.isAnnotationPresent(annotationType));
}
you would use reflection to get the fields of the class and then call something like getAnnotations() on each field.
I'm exploring annotations and came to a point where some annotations seems to have a hierarchy among them.
I'm using annotations to generate code in the background for Cards. There are different Card types (thus different code and annotations) but there are certain elements that are common among them like a name.
#Target(value = {ElementType.TYPE})
public #interface Move extends Page{
String method1();
String method2();
}
And this would be the common Annotation:
#Target(value = {ElementType.TYPE})
public #interface Page{
String method3();
}
In the example above I would expect Move to inherit method3 but I get a warning saying that extends is not valid with annotations. I was trying to have an Annotation extends a common base one but that doesn't work. Is that even possible or is just a design issue?
You can annotate your annotation with a base annotation instead of inheritance. This is used in Spring framework.
To give an example
#Target(value = {ElementType.ANNOTATION_TYPE})
public #interface Vehicle {
}
#Target(value = {ElementType.TYPE})
#Vehicle
public #interface Car {
}
#Car
class Foo {
}
You can then check if a class is annotated with Vehicle using Spring's AnnotationUtils:
Vehicle vehicleAnnotation = AnnotationUtils.findAnnotation (Foo.class, Vehicle.class);
boolean isAnnotated = vehicleAnnotation != null;
This method is implemented as:
public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
return findAnnotation(clazz, annotationType, new HashSet<Annotation>());
}
#SuppressWarnings("unchecked")
private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, Set<Annotation> visited) {
try {
Annotation[] anns = clazz.getDeclaredAnnotations();
for (Annotation ann : anns) {
if (ann.annotationType() == annotationType) {
return (A) ann;
}
}
for (Annotation ann : anns) {
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
A annotation = findAnnotation(ann.annotationType(), annotationType, visited);
if (annotation != null) {
return annotation;
}
}
}
}
catch (Exception ex) {
handleIntrospectionFailure(clazz, ex);
return null;
}
for (Class<?> ifc : clazz.getInterfaces()) {
A annotation = findAnnotation(ifc, annotationType, visited);
if (annotation != null) {
return annotation;
}
}
Class<?> superclass = clazz.getSuperclass();
if (superclass == null || Object.class == superclass) {
return null;
}
return findAnnotation(superclass, annotationType, visited);
}
AnnotationUtils also contains additional methods for searching for annotations on methods and other annotated elements. The Spring class is also powerful enough to search through bridged methods, proxies, and other corner-cases, particularly those encountered in Spring.
Unfortunately, no. Apparently it has something to do with programs that read the annotations on a class without loading them all the way. See Why is it not possible to extend annotations in Java?
However, types do inherit the annotations of their superclass if those annotations are #Inherited.
Also, unless you need those methods to interact, you could just stack the annotations on your class:
#Move
#Page
public class myAwesomeClass {}
Is there some reason that wouldn't work for you?
In addition to Grygoriys answer of annotating annotations.
You can check e.g. methods for containing a #Qualifier annotation (or an annotation annotated with #Qualifier) by this loop:
for (Annotation a : method.getAnnotations()) {
if (a.annotationType().isAnnotationPresent(Qualifier.class)) {
System.out.println("found #Qualifier annotation");//found annotation having Qualifier annotation itself
}
}
What you're basically doing, is to get all annotations present on the method and of those annotations you get their types and check those types if they're annotated with #Qualifier. Your annotation needs to be Target.Annotation_type enabled as well to get this working.
Check out https://github.com/blindpirate/annotation-magic , which is a library I developed when I had the same question.
#interface Animal {
boolean fluffy() default false;
String name() default "";
}
#Extends(Animal.class)
#Animal(fluffy = true)
#interface Pet {
String name();
}
#Extends(Pet.class)
#interface Cat {
#AliasFor("name")
String value();
}
#Extends(Pet.class)
#interface Dog {
String name();
}
#interface Rat {
#AliasFor(target = Animal.class, value = "name")
String value();
}
#Cat("Tom")
class MyClass {
#Dog(name = "Spike")
#Rat("Jerry")
public void foo() {
}
}
Pet petAnnotation = AnnotationMagic.getOneAnnotationOnClassOrNull(MyClass.class, Pet.class);
assertEquals("Tom", petAnnotation.name());
assertTrue(AnnotationMagic.instanceOf(petAnnotation, Animal.class));
Animal animalAnnotation = AnnotationMagic.getOneAnnotationOnClassOrNull(MyClass.class, Animal.class);
assertTrue(animalAnnotation.fluffy());
Method fooMethod = MyClass.class.getMethod("foo");
List<Animal> animalAnnotations = AnnotationMagic.getAnnotationsOnMethod(fooMethod, Animal.class);
assertEquals(Arrays.asList("Spike", "Jerry"), animalAnnotations.stream().map(Animal::name).collect(toList()));