/**
* Copyright (c) 2009 - 2011 AppWork UG(haftungsbeschränkt) <e-mail@appwork.org>
*
* This file is part of org.appwork.storage.config
*
* This software is licensed under the Artistic License 2.0,
* see the LICENSE file or http://www.opensource.org/licenses/artistic-license-2.0.php
* for details
*/
package org.appwork.storage.config;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import org.appwork.shutdown.ShutdownController;
import org.appwork.shutdown.ShutdownEvent;
import org.appwork.storage.InvalidTypeException;
import org.appwork.storage.JSonStorage;
import org.appwork.storage.JsonKeyValueStorage;
import org.appwork.storage.StorageException;
import org.appwork.storage.config.annotations.CryptedStorage;
import org.appwork.storage.config.annotations.DefaultFactory;
/**
* @author thomas
* @param <T>
*
*/
public class StorageHandler<T extends ConfigInterface> implements InvocationHandler {
private final Class<T> configInterface;
private HashMap<Method, KeyHandler> methodMap;
private final JsonKeyValueStorage primitiveStorage;
private boolean crypted;
private byte[] key = JSonStorage.KEY;
private File path;
private ConfigInterfaceEventSender<T> eventSender;
private T defaultFactory;
/**
* @param name
* @param configInterface
*/
@SuppressWarnings("unchecked")
public StorageHandler(final File name, final Class<T> configInterface) {
this.configInterface = configInterface;
this.eventSender = new ConfigInterfaceEventSender<T>();
final DefaultFactory defaultFactoryAnnotation = configInterface.getAnnotation(DefaultFactory.class);
if (defaultFactoryAnnotation != null && defaultFactoryAnnotation.value() != null) {
try {
final Class<? extends ConfigInterface> clazz = defaultFactoryAnnotation.value();
for (final Class<?> c : clazz.getInterfaces()) {
if (c == configInterface) {
this.defaultFactory = (T) defaultFactoryAnnotation.value().newInstance();
break;
}
}
if (this.defaultFactory == null) {
throw new InterfaceParseException("Defaultfactory " + clazz + " has to implement " + configInterface); }
} catch (final InstantiationException e) {
throw new InterfaceParseException(e);
} catch (final IllegalAccessException e) {
throw new InterfaceParseException(e);
}
}
this.path = name;
final CryptedStorage crypted = configInterface.getAnnotation(CryptedStorage.class);
if (crypted != null) {
this.crypted = true;
if (crypted.key() != null) {
this.primitiveStorage = new JsonKeyValueStorage(new File(this.path.getAbsolutePath() + ".ejs"), false, crypted.key());
this.key = crypted.key();
if (this.key.length != JSonStorage.KEY.length) { throw new InterfaceParseException("Crypt key for " + configInterface + " is invalid"); }
} else {
this.primitiveStorage = new JsonKeyValueStorage(new File(this.path.getAbsolutePath() + ".ejs"), false, this.key = JSonStorage.KEY);
}
} else {
this.crypted = false;
this.primitiveStorage = new JsonKeyValueStorage(new File(this.path.getAbsolutePath() + ".json"), true);
}
try {
this.parseInterface();
} catch (final Exception e) {
throw new InterfaceParseException(e);
}
ShutdownController.getInstance().addShutdownEvent(new ShutdownEvent() {
@Override
public void run() {
StorageHandler.this.primitiveStorage.save();
}
@Override
public String toString() {
return "Save " + StorageHandler.this.path + "[" + configInterface.getName() + "]";
}
});
}
public Class<T> getConfigInterface() {
return this.configInterface;
}
public T getDefaultFactory() {
return this.defaultFactory;
}
public ConfigInterfaceEventSender<T> getEventSender() {
return this.eventSender;
}
public byte[] getKey() {
return this.key;
}
/**
* @return
*/
public HashMap<Method, KeyHandler> getMap() {
// TODO Auto-generated method stub
return this.methodMap;
}
/**
* @return
*/
public File getPath() {
return this.path;
}
/**
* @param getter
* @return
*/
public Object getPrimitive(final MethodHandler getter) {
// only evaluate defaults of required
if (this.primitiveStorage.hasProperty(getter.getKey())) {
if (getter.getRawClass() == Boolean.class || getter.getRawClass() == boolean.class) {
return this.getPrimitive(getter.getKey(), false);
} else if (getter.getRawClass() == Long.class || getter.getRawClass() == long.class) {
return this.getPrimitive(getter.getKey(), 0l);
} else if (getter.getRawClass() == Integer.class || getter.getRawClass() == int.class) {
return this.getPrimitive(getter.getKey(), 0);
} else if (getter.getRawClass() == Float.class || getter.getRawClass() == float.class) {
return this.getPrimitive(getter.getKey(), 0.0f);
} else if (getter.getRawClass() == Byte.class || getter.getRawClass() == byte.class) {
return this.getPrimitive(getter.getKey(), (byte) 0);
} else if (getter.getRawClass() == String.class) {
return this.getPrimitive(getter.getKey(), (String) null);
// } else if (getter.getRawClass() == String[].class) {
// return this.get(getter.getKey(),
// getter.getDefaultStringArray());
} else if (getter.getRawClass().isEnum()) {
return this.getPrimitive(getter.getKey(), getter.getDefaultEnum());
} else if (getter.getRawClass() == Double.class | getter.getRawClass() == double.class) {
return this.getPrimitive(getter.getKey(), 0.0d);
} else {
throw new StorageException("Invalid datatype: " + getter.getRawClass());
}
} else {
if (getter.getRawClass() == Boolean.class || getter.getRawClass() == boolean.class) {
return this.getPrimitive(getter.getKey(), getter.getDefaultBoolean());
} else if (getter.getRawClass() == Long.class || getter.getRawClass() == long.class) {
return this.getPrimitive(getter.getKey(), getter.getDefaultLong());
} else if (getter.getRawClass() == Integer.class || getter.getRawClass() == int.class) {
return this.getPrimitive(getter.getKey(), getter.getDefaultInteger());
} else if (getter.getRawClass() == Float.class || getter.getRawClass() == float.class) {
return this.getPrimitive(getter.getKey(), getter.getDefaultFloat());
} else if (getter.getRawClass() == Byte.class || getter.getRawClass() == byte.class) {
return this.getPrimitive(getter.getKey(), getter.getDefaultByte());
} else if (getter.getRawClass() == String.class) {
return this.getPrimitive(getter.getKey(), getter.getDefaultString());
// } else if (getter.getRawClass() == String[].class) {
// return this.get(getter.getKey(),
// getter.getDefaultStringArray());
} else if (getter.getRawClass().isEnum()) {
return this.getPrimitive(getter.getKey(), getter.getDefaultEnum());
} else if (getter.getRawClass() == Double.class | getter.getRawClass() == double.class) {
return this.getPrimitive(getter.getKey(), getter.getDefaultDouble());
} else {
throw new StorageException("Invalid datatype: " + getter.getRawClass());
}
}
}
/**
* @param <E>
* @param key2
* @param defaultBoolean
* @return
*/
public <E> E getPrimitive(final String key, final E def) {
return this.primitiveStorage.get(key, def);
}
@SuppressWarnings("unchecked")
public Object invoke(final Object instance, final Method m, final Object[] parameter) throws Throwable {
if (m.getName().equals("toString")) {
return this.toString();
} else if (m.getName().equals("addListener")) {
this.eventSender.addListener((ConfigEventListener) parameter[0]);
return null;
} else if (m.getName().equals("removeListener")) {
this.eventSender.removeListener((ConfigEventListener) parameter[0]);
return null;
} else if (m.getName().equals("getStorageHandler")) {
return this;
} else {
final KeyHandler handler = this.methodMap.get(m);
if (handler.isGetter(m)) {
return handler.getValue();
} else {
handler.setValue(parameter[0]);
this.eventSender.fireEvent(new ConfigEvent<T>((T) instance, ConfigEvent.Types.VALUE_UPDATED, handler.getKey(), parameter[0]));
return null;
}
}
}
public boolean isCrypted() {
return this.crypted;
}
/**
* @throws ClassNotFoundException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws InstantiationException
* @throws NoSuchMethodException
* @throws IllegalArgumentException
* @throws SecurityException
*
*/
private void parseInterface() throws SecurityException, IllegalArgumentException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
this.methodMap = new HashMap<Method, KeyHandler>();
final HashMap<String, Method> keyGetterMap = new HashMap<String, Method>();
final HashMap<String, Method> keySetterMap = new HashMap<String, Method>();
String key;
final HashMap<String, KeyHandler> parseMap = new HashMap<String, KeyHandler>();
Class<?> clazz = this.configInterface;
while (clazz != null && clazz != ConfigInterface.class) {
for (final Method m : clazz.getDeclaredMethods()) {
if (m.getName().startsWith("get")) {
key = m.getName().substring(3).toLowerCase();
// we do not allow to setters/getters with the same name but
// different cases. this only confuses the user when editing
// the
// later config file
if (keyGetterMap.containsKey(key)) { throw new InterfaceParseException("Key " + key + " Dupe found! " + keyGetterMap.containsKey(key) + "<-->" + m); }
if (m.getParameterTypes().length > 0) { throw new InterfaceParseException("Getter " + m + " has parameters."); }
try {
JSonStorage.canStore(m.getGenericReturnType());
} catch (final InvalidTypeException e) {
throw new InterfaceParseException(e);
}
KeyHandler kh = parseMap.get(key);
if (kh == null) {
kh = new KeyHandler(this, key);
parseMap.put(key, kh);
}
final MethodHandler h = new MethodHandler(this, MethodHandler.Type.GETTER, key, m, JSonStorage.canStorePrimitive(m.getReturnType()));
kh.setGetter(h);
this.methodMap.put(m, kh);
} else if (m.getName().startsWith("is")) {
key = m.getName().substring(2).toLowerCase();
// we do not allow to setters/getters with the same name but
// different cases. this only confuses the user when editing
// the
// later config file
if (keyGetterMap.containsKey(key)) { throw new InterfaceParseException("Key " + key + " Dupe found! " + keyGetterMap.containsKey(key) + "<-->" + m); }
if (m.getParameterTypes().length > 0) { throw new InterfaceParseException("Getter " + m + " has parameters."); }
try {
JSonStorage.canStore(m.getGenericReturnType());
} catch (final InvalidTypeException e) {
throw new InterfaceParseException(e);
}
KeyHandler kh = parseMap.get(key);
if (kh == null) {
kh = new KeyHandler(this, key);
parseMap.put(key, kh);
}
final MethodHandler h = new MethodHandler(this, MethodHandler.Type.GETTER, key, m, JSonStorage.canStorePrimitive(m.getReturnType()));
kh.setGetter(h);
this.methodMap.put(m, kh);
} else if (m.getName().startsWith("set")) {
key = m.getName().substring(3).toLowerCase();
if (keySetterMap.containsKey(key)) { throw new InterfaceParseException("Key " + key + " Dupe found! " + keySetterMap.containsKey(key) + "<-->" + m); }
if (m.getParameterTypes().length != 1) { throw new InterfaceParseException("Setter " + m + " has !=1 parameters."); }
if (m.getReturnType() != void.class) { throw new InterfaceParseException("Setter " + m + " has a returntype != void"); }
try {
JSonStorage.canStore(m.getGenericParameterTypes()[0]);
} catch (final InvalidTypeException e) {
throw new InterfaceParseException(e);
}
KeyHandler kh = parseMap.get(key);
if (kh == null) {
kh = new KeyHandler(this, key);
parseMap.put(key, kh);
}
final MethodHandler h = new MethodHandler(this, MethodHandler.Type.SETTER, key, m, JSonStorage.canStorePrimitive(m.getParameterTypes()[0]));
kh.setSetter(h);
this.methodMap.put(m, kh);
} else {
throw new InterfaceParseException("Only getter and setter allowed:" + m);
}
}
// run down the calss hirarchy to find all methods. getMethods does
// not work, because it only finds public methods
final Class<?>[] interfaces = clazz.getInterfaces();
clazz = interfaces[0];
}
}
/**
* @param key
* @param object
*/
public void putPrimitive(final String key, final Boolean value) {
this.primitiveStorage.put(key, value);
}
/**
* @param key2
* @param object
*/
public void putPrimitive(final String key2, final Byte object) {
this.primitiveStorage.put(key2, object);
}
/**
* @param key2
* @param object
*/
public void putPrimitive(final String key2, final Double object) {
this.primitiveStorage.put(key2, object);
}
/**
* @param key2
* @param object
*/
public void putPrimitive(final String key2, final Enum<?> object) {
this.primitiveStorage.put(key2, object);
}
/**
* @param key2
* @param object
*/
public void putPrimitive(final String key2, final Float object) {
this.primitiveStorage.put(key2, object);
}
/**
* @param key2
* @param object
*/
public void putPrimitive(final String key2, final Integer object) {
this.primitiveStorage.put(key2, object);
}
/**
* @param key2
* @param object
*/
public void putPrimitive(final String key2, final Long object) {
this.primitiveStorage.put(key2, object);
}
/**
* @param key2
* @param object
*/
public void putPrimitive(final String key2, final String object) {
this.primitiveStorage.put(key2, object);
}
@Override
public String toString() {
final HashMap<String, Object> ret = new HashMap<String, Object>();
for (final KeyHandler h : this.methodMap.values()) {
try {
ret.put(h.getGetter().getKey(), this.invoke(null, h.getGetter().getMethod(), new Object[] {}));
} catch (final Throwable e) {
e.printStackTrace();
ret.put(h.getKey(), e.getMessage());
}
}
return JSonStorage.toString(ret);
}
/*
* (non-Javadoc)
*
* @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object,
* java.lang.reflect.Method, java.lang.Object[])
*/
}