Get entity field annotations from hibernate interceptor - java

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.

Related

Spring Boot - Bean Validation, constraint annotation with generics

Spring Boot 2.3.1, with OpenJDK 14
Constraint Annotation:
#Documented
#Constraint(validatedBy = {MyConstraintAnnotationValidator.class})
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface MyConstraintAnnotation {
String message() default "";
Class[] groups() default {};
Class[] payload() default {};
Class clazz();
// or, not sure: Class<?> clazz();
}
Constraint Annotation Validator:
public class MyConstraintAnnotationValidator implements ConstraintValidator<MyConstraintAnnotation, String> {
#Override
public void initialize(MyConstraintAnnotation constraintAnnotation) {
// cac is: configurableApplicationContext
Object obj = cac.getBean(constraintAnnotation.clazz());
// problem: obj doesn't provide the methods to access
// on the left side of the variable shall be also the clazz type
// which is passed to the constraint annotation
}
#Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
return true;
}
}
Usage of constraint annotation:
public class MyPojo {
...
#MyConstraintAnnotation(clazz = MyProperties.class)
private String field1;
...
// Getter/ Setter
}
Properties class:
#Component
#PropertySource("classpath:myproperties.properties")
#ConfigurationProperties(prefix = "myprefix")
public class MyProperties {
// fields
// Getter / Setter
}
Via the constraint annotation I want to specify my properties class to be used in the validation process within the validator class.
But within the initialize(..) method I cannot access the Getter / Setter from my properties class (MyProperties.class), because configurableApplicationContext.getBean(...) returns Object, shall create a variable and cast it to the passed class (like MyProperties.class).
How to create a variable which has on the left side the same type which is passed to the constraint annotation?

How to get Column names of JPA entity

All my JPA entity classes implement an interface called Entity which is defined like this:
public interface Entity extends Serializable {
// some methods
}
Some of the fields of my JPA entity have #Column annotation on top of them and some don't. MyEntity class is defined like below:
#Entity
public class MyEntity implements Entity {
#Id
private Long id; // Assume that it is auto-generated using a sequence.
#Column(name="field1")
private String field1;
private SecureString field2; //SecureString is a custom class
//getters and setters
}
My delete method accepts an Entity.
#Override
public void delete(Entity baseEntity) {
em.remove(baseEntity); //em is entityManager
}
Whenever the delete method is invoked I want three things inside my delete method:
1) Fields of MyEntity that are of type SecureString
2) Column name of that particular field in DB (The field may or may not have #Column annotation)
3) The value of id field
Note that when the delete() method is invoked, we don't know for which entity it is invoked, it may be for MyEntity1, MyEntity2 etc.
I have tried doing something like below:
for (Field field : baseEntity.getClass().getFields()) {
if (SecureString.class.isAssignableFrom(field.getType())) {
// But the field doesn't have annotation #Column specified
Column column = field.getAnnotation(Column.class);
String columnName = column.name();
}
}
But this will only work if the field has #Column annotation. Also it doesn't get me other two things that I need. Any ideas?
Hibernate can use different naming strategies to map property names, which are defined implicitly (without #Column(name = "...")). To have a 'physical' names you need to dive into Hibernate internals. First, you have to wire an EntityManagerFactory to your service.
#Autowired
private EntityManagerFactory entityManagerFactory;
Second, you have to retrieve an AbstractEntityPersister for your class
SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
AbstractEntityPersister persister = ((AbstractEntityPersister)sessionFactory.getClassMetadata(baseEntity.getClass()));
Third, you're almost there with your code. You just have to handle both cases - with and without #Column annotation. Try this:
for (Field field : baseEntity.getClass().getFields()) {
if (SecureString.class.isAssignableFrom(field.getType())) {
String columnName;
if (field.isAnnotationPresent(Column.class)) {
columnName = field.getAnnotation(Column.class).name();
} else {
String[] columnNames = persister.getPropertyColumnNames(field.getName());
if (columnNames.length > 0) {
columnName = columnNames[0];
}
}
}
}
Note that getPropertyColumnNames() retrieves only 'property' fields, that are not a part of primary key. To retrieve key column names, use getKeyColumnNames().
And about id field. Do you really need to have all #Id's in child classes? Maybe would better to move #Id to Entity class and mark this class with #MappedSuperclass annotation? Then you can retrieve it just with baseEntity.getId();

Making a Jersey bean validation annotation generic

I have a Jersey Rest API like this:
#POST
#Path("/doorder")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces("text/plain")
public String doOrder(#BeanParam final #Valid OrderBean order) {
// Some implementation here
}
All my inputs are store in this bean:
#AddressAtLeastOne
public final class OrderBean {
#FormDataParam("address")
private String address;
#FormDataParam("city")
private String city;
#FormDataParam("postcode")
private String postcode;
// Other member variables
// Getters and setters
}
I added an annotation to validate the address (#AddressAtLeastOne). The address is valid if at least one of the 3 fields has a value.
Here's the annotation definition:
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
#Retention(RUNTIME)
#Constraint(validatedBy = AddressAtLeastOneValidator.class)
#Documented
public #interface AddressAtLeastOne {
String message() default "Address requires at least one field";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And here's the validator:
public class AddressAtLeastOneValidator implements ConstraintValidator<AddressAllOrNone, OrderBean> {
#Override
public boolean isValid(OrderBean demoBean, ConstraintValidatorContext constraintValidatorContext) {
// Check for at least one value
if((demoBean.getAddress() != null && !demoBean.getAddress().equals("") ||
(demoBean.getCity() != null && !demoBean.getCity().equals("")) ||
(demoBean.getPostcode() != null && !demoBean.getPostcode().equals("")))) {
return true;
}
return false;
}
}
Everything is fine! But now I want to rename the annotation #AddressAtLeastOne to #AtLeastOne and make it generic, so that I can apply it to any class. I need a mechanism where I can specify which member variables are part of the group I want to validate with #AtLeastOne. How can I do that?
One approach of doing this is to use Reflection -
Create a custom annotation suppose #GroupNotNullField and apply this annotation to all fields in bean class in which at least one field should have value. By this way, you can skip some fields in which validation is not required.
In the validator class, get all the fields of the bean class using Reflection
Check all the fields which are annotated with #GroupNotNullField annotation
Get the value of all such fields and check that at least one has value.
Return true or false depending on validation check.

call further validators from isValid() method?

suppose i have the following scenario:
public class EntityA {
private List<EntityB> listOfBs;
}
im trying to cascade validation to the list of Bs only if running under a certain validation group. so ideally, this:
public class EntityA {
#Valid(groups = {SomeSpecificGroup.class})
private List<EntityB> listOfBs;
}
unfortunately, #Valid does not have a groups() property. so i figured i'd try something like:
#Constraint(validatedBy = { CascadedValidator.class })
#Target({ ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface CascadedValidation {
Class<?>[] groups() default { };
}
and write a validator (CascadedValidator) that upon activation will do the cascade (==will validate all elements of the collection its placed on).
my issue is how do i perform the cascaded validation?
so far i have this:
public class CascadedValidator implements ConstraintValidator<CascadedValidation, Object>{
private Class<?>[] groups;
#Override public void initialize(CascadedValidation constraintAnnotation) {
groups = constraintAnnotation.groups();
}
#Override public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null || !(value instanceof Iterable)) {
return true;
}
for (Object item : (Iterable)value) {
//validate item using the groups?!
}
}
}
i know i could implement the actual validation by creating another Validator "inline":
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Object>> violations;
if (decideIfCascade(groups)) {
for (Object item : (Iterable)value) {
if (groups!=null && groups.length>0) {
violations = validator.validate(item, groups);
} else {
violations = validator.validate(item);
}
if (!violations.isEmpty()) {
return false;
}
}
}
return true;
but this just smells bad to me.
surely there's a sane/normal/easy way of doing this?
EDIT - the actual use case
my API accept both EntityA (which has a list of Bs) and EntityB as top-level entities (so you can send a single B directly). both A and B have an id property, but i only require a non-null id on the top level object submitted. so if the service gets an A with an idea and several "blank" Bs its ok, but if i get a B as a top level parameter it must have an id.
You should not invoke the validation engine from within a ConstraintValidator implementation.
If you are on Bean Validation 1.1, have a look at group conversions which give you control over the validation groups propagated upon cascaded validation. E.g. you could do the following:
#Valid
#ConvertGroup(from = Default.class, to = SomeSpecificGroup.class)
private List<EntityB> listOfBs;

Is there something like Annotation Inheritance in java?

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()));

Categories

Resources