package net.sourceforge.javautil.database.encryption;
import java.lang.ref.SoftReference;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.Embeddable;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import net.sourceforge.javautil.common.ByteUtil;
import net.sourceforge.javautil.common.ReflectionUtil;
import net.sourceforge.javautil.common.encode.Base64EncodingAlgorithm;
import net.sourceforge.javautil.common.encryption.IEncryptionProvider;
import net.sourceforge.javautil.common.encryption.impl.SimpleEncryptionKey;
import net.sourceforge.javautil.common.encryption.impl.SimpleEncryptionProvider;
import net.sourceforge.javautil.common.encryption.impl.SimpleEncryptionKey.Strength;
import net.sourceforge.javautil.common.exception.ThrowableManagerRegistry;
import net.sourceforge.javautil.common.password.IPassword;
import net.sourceforge.javautil.common.password.PasswordContext;
import net.sourceforge.javautil.common.reflection.cache.ClassCache;
import net.sourceforge.javautil.common.reflection.cache.ClassDescriptor;
import net.sourceforge.javautil.common.reflection.cache.ClassProperty;
import net.sourceforge.javautil.database.encryption.annotation.Encrypted;
import net.sourceforge.javautil.database.encryption.annotation.EncryptedFields;
import net.sourceforge.javautil.database.encryption.annotation.EncryptionConfig;
import net.sourceforge.javautil.database.encryption.annotation.EncryptionKey;
import net.sourceforge.javautil.database.encryption.annotation.EncryptionConfig.Source;
/**
* This allows easy declaration of entities that desire encryption support for one or more fields.
*
* @author elponderador
* @author $Author$
* @version $Id$
*/
public class EncryptionCallBackListener {
protected static Map<String, SoftReference<IEncryptionProvider>> cache = new HashMap<String, SoftReference<IEncryptionProvider>>();
protected static final byte[] emptyString = "".getBytes();
protected IEncryptionProvider provider;
protected Base64EncodingAlgorithm base64 = new Base64EncodingAlgorithm();
public EncryptionCallBackListener () {}
@PrePersist @PreUpdate
public <T> void encrypt (T entity) { this.encrypt(entity, ClassCache.getFor((Class<T>)entity.getClass())); }
protected <T> void encrypt (T entity, ClassDescriptor<T> desc) {
IEncryptionProvider provider = this.getProvider(desc, entity);
this.encrypt(entity, desc, provider);
}
protected <T> void encrypt (T entity, ClassDescriptor<?> desc, IEncryptionProvider provider) {
for (ClassProperty property : desc.getProperties(Encrypted.class)) {
this.encrypt(entity, provider, property);
}
EncryptedFields fields = desc.getAnnotation(EncryptedFields.class);
if (fields != null) {
for (String name : fields.value()) {
this.encrypt(entity, provider, desc.getProperty(name));
}
}
}
protected <T> void encrypt (T entity, IEncryptionProvider provider, ClassProperty property) {
Object value = property.getValue(entity);
if (value == null) return;
if (property.getTypeDescriptor().getAnnotation(Embeddable.class) != null) {
this.encrypt(value, ClassCache.getFor(value.getClass()), provider);
return;
}
byte[] data = ReflectionUtil.coerce(byte[].class, value);
if (ByteUtil.equals(data, emptyString)) return;
try {
property.setValue(entity, ReflectionUtil.coerce(property.getType(), base64.encode( provider.encrypt(data) )));
} catch (Exception e) {
ThrowableManagerRegistry.caught(e);
}
}
@PostPersist @PostUpdate public <T> void decryptLoad (T entity) {
this.decrypt(entity);
}
@PostLoad public <T> void decrypt (T entity) {
this.decrypt(entity, ClassCache.getFor((Class<T>)entity.getClass()));
}
protected <T> void decrypt (T entity, ClassDescriptor<T> desc) {
IEncryptionProvider provider = this.getProvider(desc, entity);
this.decrypt(entity, desc, provider);
}
protected <T> void decrypt (T entity, ClassDescriptor<?> desc, IEncryptionProvider provider) {
for (ClassProperty property : desc.getProperties(Encrypted.class)) {
this.decrypt(entity, provider, property);
}
EncryptedFields fields = desc.getAnnotation(EncryptedFields.class);
if (fields != null) {
for (String name : fields.value()) {
this.decrypt(entity, provider, desc.getProperty(name));
}
}
}
protected <T> void decrypt (T entity, IEncryptionProvider provider, ClassProperty property) {
Object value = property.getValue(entity);
if (value == null) return;
if (property.getTypeDescriptor().getAnnotation(Embeddable.class) != null) {
this.decrypt(value, ClassCache.getFor(value.getClass()), provider);
return;
}
byte[] data = ReflectionUtil.coerce(byte[].class, value);
if (ByteUtil.equals(data, emptyString)) return;
try {
property.setValue(entity, ReflectionUtil.coerce(property.getType(), provider.decrypt(base64.decode( data ))));
} catch (Throwable t) {
ThrowableManagerRegistry.caught(t);
}
}
private <T> IEncryptionProvider getProvider (ClassDescriptor<T> clazz, T instance) {
if (cache.containsKey(clazz.getDescribedClass().getName())) {
IEncryptionProvider provider = cache.get(clazz.getDescribedClass().getName()).get();
if (provider != null) return provider;
}
EncryptionConfig config = clazz.getAnnotation(EncryptionConfig.class);
byte[] key = null;
if (config != null && config.source() == Source.PASSWORD_LOCKER) {
if ("".equals( config.sourceName() )) throw new IllegalArgumentException("Password locker source name must be provider for: " + clazz);
if (PasswordContext.get() == null) throw new IllegalStateException("No password locker available for the current context");
IPassword pw = PasswordContext.get().getPassword(config.sourceName());
if (pw == null) throw new IllegalArgumentException("No such password in current context locker: " + config.sourceName());
key = pw.getPassword();
} else {
ClassProperty property = clazz.getProperty(EncryptionKey.class);
if (property == null) throw new IllegalStateException("No encryption key property available for: " + clazz);
key = ReflectionUtil.coerce(byte[].class, property.getValue(instance));
}
try {
IEncryptionProvider provider = new SimpleEncryptionProvider(SimpleEncryptionKey.createUsing(Strength.STRONG, config == null ? "AES" : config.algorithm(), key));
cache.put(clazz.getDescribedClass().getName(), new SoftReference<IEncryptionProvider>(provider));
return provider;
} catch (InvalidKeyException e) {
throw ThrowableManagerRegistry.caught(e);
}
}
}