1

I am trying to change the value of @ColumnTransformer at runtime so that I can store my encryption keys somewhere else on a server. How can I achieve this ?

Below is my main code:

List<Field> fields = Arrays.asList(User.class.getDeclaredFields());
for(Field field:fields)
{
    if(field.toString().endsWith("secretfield"))
    {
        System.out.println(field);
        List<Annotation> annotations = Arrays.asList(field.getDeclaredAnnotations());
        for(Annotation annotation : annotations)
        {
            if(annotation.annotationType().toString().endsWith("ColumnTransformer"))
            {
                Annotation newAnnotation = new ColumnTransformer(){
                    @Override
                    public String forColumn() {
                        return "secretfield";
                    }

                    @Override
                    public String read(){
                        return "This is Sparta";
                    }

                    @Override
                    public String write(){
                        return "This is also sparta";
                    }

                    @Override
                    public Class<? extends Annotation> annotationType() {
                        return annotation.annotationType();
                    }
                };
                Field field1 = User.class.getDeclaredField("secretfield");
                field1.setAccessible(true);
                Map<Class<? extends Annotation>, Annotation> annotations1 = (Map<Class<? extends Annotation>, Annotation>) field1.get(User.class);
                annotations1.put(ColumnTransformer.class, newAnnotation);
            }
        }
    }
}

And below is my Pojo:

package Model;

import org.hibernate.annotations.ColumnTransformer;


import javax.persistence.*;
import javax.sql.rowset.serial.SerialBlob;
import java.sql.Blob;
import java.io.Serializable;

@Entity(name = "user")
@Table(name = "user")
public class User implements Serializable {

    @Column(name = "secretfield", nullable = true)
    @ColumnTransformer(read = "TEST")
    private String secretfield;

    public String getSecretfield() {
        return secretfield;
    }

    public void setSecretfield(String secretfield) {
        this.secretfield = secretfield;
    }
}

I am getting this error:

Exception in thread "main" java.lang.IllegalArgumentException: Can not set java.lang.String field Model.User.secretfield to null value
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
    at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58)
    at sun.reflect.UnsafeObjectFieldAccessorImpl.get(UnsafeObjectFieldAccessorImpl.java:36)
    at java.lang.reflect.Field.get(Field.java:393)
    at Runner.main(Runner.java:65)
samabcde
  • 6,988
  • 2
  • 25
  • 41

1 Answers1

0

This line

Map<Class<? extends Annotation>, Annotation> annotations1 = (Map<Class<? extends Annotation>, Annotation>) field1.get(User.class);

is incorrect. field1.get is retrieving the field value, instead of annotation map.

As you only need to modify the parameter inside the annotation, you don't really need to create a new instance of ColumnTransformer. Please refer to Modify a class definition's annotation string parameter at runtime for details.

changeAnnotationValue is slightly modified to fix the NPE due to ColumnTransformer is missing default value, as shown below.

import org.hibernate.annotations.ColumnTransformer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.Map;

public class FieldGetAnnotation {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Field field = User.class.getDeclaredField("secretfield");
        field.setAccessible(true);
        final ColumnTransformer fieldAnnotation = field.getAnnotation(ColumnTransformer.class);
        System.out.println("old FieldAnnotation = " + fieldAnnotation.forColumn());
        changeAnnotationValue(fieldAnnotation, "forColumn", "secretfield");
        System.out.println("modified FieldAnnotation = " + fieldAnnotation.forColumn());
        System.out.println("old FieldAnnotation = " + fieldAnnotation.read());
        changeAnnotationValue(fieldAnnotation, "read", "This is Sparta");
        System.out.println("modified FieldAnnotation = " + fieldAnnotation.read());
        System.out.println("old FieldAnnotation = " + fieldAnnotation.write());
        changeAnnotationValue(fieldAnnotation, "write", "This is also sparta");
        System.out.println("modified FieldAnnotation = " + fieldAnnotation.write());
    }

    @SuppressWarnings("unchecked")
    public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue) {
        Object handler = Proxy.getInvocationHandler(annotation);
        Field f;
        try {
            f = handler.getClass().getDeclaredField("memberValues");
        } catch (NoSuchFieldException | SecurityException e) {
            throw new IllegalStateException(e);
        }
        f.setAccessible(true);
        Map<String, Object> memberValues;
        try {
            memberValues = (Map<String, Object>) f.get(handler);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
        Object oldValue = memberValues.get(key);
        if (oldValue != null && oldValue.getClass() != newValue.getClass()) {
            throw new IllegalArgumentException();
        }
        memberValues.put(key, newValue);
        return oldValue;
    }
}
samabcde
  • 6,988
  • 2
  • 25
  • 41
  • i was following this link only and i am not able to figure this out. can you please help me ? – Harshit Vijayvargia Oct 05 '21 at 15:09
  • @HarshitVijayvargia, I added an example program in my update, please check. – samabcde Oct 05 '21 at 15:13
  • thank you so much. I will verify it. btw, I am doing this for encryption, i am gonna store my key on another server and fetch it to update in the column transformer, using AES_ENCRYPT() and AES_DECRYPT() function of mysql. do you think that is a good approach ? If it is, how would one go about rotation of the key in future ? – Harshit Vijayvargia Oct 05 '21 at 18:51
  • I would suggest you create a new question on this as what your question currently is focusing on how to change the annotation in runtime. Using comment here is not suitable. What I can tell is I don't really get why you need to change the annotation, and I feel it is a bit hacky to do so, there may be some easier approach. – samabcde Oct 06 '21 at 13:12
  • can you look this one up? https://stackoverflow.com/questions/69673897/how-to-call-a-method-before-anything-in-springboot – Harshit Vijayvargia Oct 22 '21 at 08:44