Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I would like to know if there is a way with Hibernate to perform a programmatic configuration of ColumnTransformer ?

Tags:

hibernate

I currently have a field annotated like this :

ColumnTransformer(
          read="AES_DECRYPT(C_first_name, 'yourkey')",
          write="AES_ENCRYPT(?, 'yourkey')")
public String getFirstName() {
   return firstName;
}

This is working properly with a Mysql database, but I need this configuration to be optional, because our application can use another database (HsqlDB) depending on start parameters. So what I need is a way to use a ColumnTransformer only when a specific start parameter is used (and no ColumnTransformer for HsqlDB, which cant use "AES_ENCRYPT")

Can someone help me with this ?

like image 792
user2888996 Avatar asked Oct 23 '13 12:10

user2888996


2 Answers

I had same problem, I want key to be configurable. The only solution i found for this item is to update annotation values at runtime. Yes, i know that this sounds awful, but as far as i know there is no other way.

Entity class:

@Entity
@Table(name = "user")
public class User implements Serializable {
    @Column(name = "password")
    @ColumnTransformer(read = "AES_DECRYPT(password, '${encryption.key}')", write = "AES_ENCRYPT(?, '${encryption.key}')")
    private String password;
}

I implemented class that replaces ${encryption.key} to the some other value (in my case loaded from Spring application context)

import org.hibernate.annotations.ColumnTransformer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

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

import javax.annotation.PostConstruct;

@Component(value = "transformerColumnKeyLoader")
public class TransformerColumnKeyLoader {

    public static final String KEY_ANNOTATION_PROPERTY = "${encryption.key}"; 

    @Value(value = "${secret.key}")
    private String key;

    @PostConstruct
    public void postConstruct() {
        setKey(User.class, "password");
    }

    private void setKey(Class<?> clazz, String columnName) {
        try {
            Field field = clazz.getDeclaredField(columnName);

            ColumnTransformer columnTransformer = field.getDeclaredAnnotation(ColumnTransformer.class);
            updateAnnotationValue(columnTransformer, "read");
            updateAnnotationValue(columnTransformer, "write");
        } catch (NoSuchFieldException | SecurityException e) {
            throw new RuntimeException(
                    String.format("Encryption key cannot be loaded into %s,%s", clazz.getName(), columnName));
        }
    }

    @SuppressWarnings("unchecked")
    private void updateAnnotationValue(Annotation annotation, String annotationProperty) {
        Object handler = Proxy.getInvocationHandler(annotation);
        Field merberValuesField;
        try {
            merberValuesField = handler.getClass().getDeclaredField("memberValues");
        } catch (NoSuchFieldException | SecurityException e) {
            throw new IllegalStateException(e);
        }
        merberValuesField.setAccessible(true);
        Map<String, Object> memberValues;
        try {
            memberValues = (Map<String, Object>) merberValuesField.get(handler);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
        Object oldValue = memberValues.get(annotationProperty);
        if (oldValue == null || oldValue.getClass() != String.class) {
            throw new IllegalArgumentException(String.format(
                    "Annotation value should be String. Current value is of type: %s", oldValue.getClass().getName()));
        }

        String oldValueString = oldValue.toString();
        if (!oldValueString.contains(TransformerColumnKeyLoader.KEY_ANNOTATION_PROPERTY)) {
            throw new IllegalArgumentException(
                    String.format("Annotation value should be contain %s. Current value is : %s",
                            TransformerColumnKeyLoader.KEY_ANNOTATION_PROPERTY, oldValueString));
        }
        String newValueString = oldValueString.replace(TransformerColumnKeyLoader.KEY_ANNOTATION_PROPERTY, key);

        memberValues.put(annotationProperty, newValueString);
    }
}

This code should be run before creating EntityManager. In my case i used depends-on (for xml config or @DependsOn for java config).

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" depends-on="transformerColumnKeyLoader"> ... </bean>
like image 163
ivanka Avatar answered Nov 15 '22 12:11

ivanka


The hibernate configuration is essentially static. It is not intended to be modified at runtime. But it can be done if you do it carefully.

Basically, the usual way to build a SessionFactory is by doing something like this:

  AnnotationConfiguration conf = new AnnotationConfiguration().configure();
  sessionFactory = conf.buildSessionFactory();

Most of the time this code is part a of framework (for instance, with Spring you must look into SessionFactoryBean to find it). So the first thing to do is indentify this portion of code and overriding the framework component doing it so that you get an access to the conf object BEFORE it is use to buildSessionFactory().

Then you have to modify the AnnotationConfiguration to remove/add the data related to optional annotations:

  {
      ...
      AnnotationConfiguration conf = new AnnotationConfiguration().configure();
      if(FLAG_INDICATING_TO_REMOVE_SOME_ANNOTATION){
          manipulateHibernateConfig(conf);
      }
      sessionFactory = conf.buildSessionFactory();
      ...
  }

  private void manipulateHibernateConfig(AnnotationConfiguration conf){
      ...
     //this is the tricky part because lot of fields and setters are either
     //final or private so it requires reflection etc...

     //you must also be sure that those manipulation won't break the config !
  }
like image 25
ben75 Avatar answered Nov 15 '22 10:11

ben75