package org.appwork.storage; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import org.appwork.shutdown.ShutdownController; import org.appwork.shutdown.ShutdownEvent; import org.appwork.utils.Application; import org.appwork.utils.IO; import org.appwork.utils.Regex; import org.appwork.utils.crypto.Crypto; import org.appwork.utils.logging.Log; import org.appwork.utils.reflection.Clazz; import org.codehaus.jackson.JsonGenerationException; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.map.JsonMappingException; import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl; import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl; public class JSonStorage { /* hash map contains file location as string and the storage instance */ private static final HashMap<String, Storage> MAP = new HashMap<String, Storage>(); public static final Object LOCK = new Object(); private static JSONMapper JSON_MAPPER = new SimpleMapper(); /* default key for encrypted json */ static public byte[] KEY = new byte[] { 0x01, 0x02, 0x11, 0x01, 0x01, 0x54, 0x01, 0x01, 0x01, 0x01, 0x12, 0x01, 0x01, 0x01, 0x22, 0x01 }; static { /* shutdown hook to save all open Storages */ ShutdownController.getInstance().addShutdownEvent(new ShutdownEvent() { @Override public void run() { JSonStorage.save(); } }); } public static boolean addStorage(final Storage storage) { synchronized (JSonStorage.MAP) { final Storage ret = JSonStorage.MAP.get(storage.getID()); if (ret == null) { JSonStorage.MAP.put(storage.getID(), storage); return true; } return false; } } /** * Cecks of the JSOn Mapper can map this Type * * @param genericReturnType * @throws InvalidTypeException */ public static void canStore(final Type gType) throws InvalidTypeException { JSonStorage.canStoreIntern(gType, gType.toString()); } /** * @param gType * @param string * @throws InvalidTypeException */ private static void canStoreIntern(final Type gType, final String path) throws InvalidTypeException { if (gType instanceof Class) { final Class<?> type = (Class<?>) gType; if (type == void.class) { throw new InvalidTypeException("Void is not accepted: " + path); } if (type.isPrimitive()) { return; } if (type == Boolean.class || type == Long.class || type == Integer.class || type == Byte.class || type == Double.class || type == Float.class || type == String.class) { return; } if (type.isEnum()) { return; } if (type.isArray()) { final Class<?> arrayType = type.getComponentType(); JSonStorage.canStoreIntern(arrayType, path + "[" + arrayType + "]"); return; } // we need an empty constructor if (Storable.class.isAssignableFrom(type)) { try { type.getDeclaredConstructor(new Class[] {}); for (final Method m : type.getDeclaredMethods()) { if (m.getName().startsWith("get")) { if (m.getParameterTypes().length > 0) { throw new InvalidTypeException("Getter " + path + "." + m + " has parameters."); } JSonStorage.canStoreIntern(m.getGenericReturnType(), path + "->" + m.getGenericReturnType()); } else if (m.getName().startsWith("set")) { if (m.getParameterTypes().length != 1) { throw new InvalidTypeException("Setter " + path + "." + m + " has != Parameters."); } } } return; } catch (final NoSuchMethodException e) { throw new InvalidTypeException("Storable " + path + " has no empty Constructor"); } } if (List.class.isAssignableFrom(type)) { return; } if (Map.class.isAssignableFrom(type)) { return; } } else if (gType instanceof ParameterizedTypeImpl) { final ParameterizedTypeImpl ptype = (ParameterizedTypeImpl) gType; final Class<?> raw = ((ParameterizedTypeImpl) gType).getRawType(); JSonStorage.canStoreIntern(raw, path); for (final Type t : ptype.getActualTypeArguments()) { JSonStorage.canStoreIntern(t, path + "(" + t + ")"); } return; } else if (gType instanceof GenericArrayTypeImpl) { final GenericArrayTypeImpl atype = (GenericArrayTypeImpl) gType; final Type t = atype.getGenericComponentType(); JSonStorage.canStoreIntern(t, path + "[" + t + "]"); return; } else { throw new InvalidTypeException("Generic Type Structure not implemented: " + gType.getClass() + " in " + path); } throw new InvalidTypeException("Type " + path + " is not supported."); } /** * @param returnType * @return */ public static boolean canStorePrimitive(final Class<?> type) { return Clazz.isPrimitive(type) || type == String.class || type.isEnum(); } public static JSONMapper getMapper() { return JSonStorage.JSON_MAPPER; } public static Storage getPlainStorage(final File file) throws StorageException { synchronized (JSonStorage.MAP) { final String id = file.getAbsolutePath(); Storage ret = JSonStorage.MAP.get(id); if (ret == null) { ret = new JsonKeyValueStorage(file, true); JSonStorage.MAP.put(ret.getID(), ret); } return ret; } } /** * TODO: Difference to {@link #getStorage(String)} ? */ public static Storage getPlainStorage(final String name) throws StorageException { synchronized (JSonStorage.MAP) { final String id = Application.getResource("cfg/" + name + ".json").getAbsolutePath(); Storage ret = JSonStorage.MAP.get(id); if (ret == null) { ret = new JsonKeyValueStorage(name, true); JSonStorage.MAP.put(ret.getID(), ret); } return ret; } } public static Storage getStorage(final String name) throws StorageException { synchronized (JSonStorage.MAP) { final String id = Application.getResource("cfg/" + name + ".ejs").getAbsolutePath(); Storage ret = JSonStorage.MAP.get(id); if (ret == null) { ret = new JsonKeyValueStorage(name); JSonStorage.MAP.put(ret.getID(), ret); } return ret; } } public static boolean removeStorage(final Storage storage) { synchronized (JSonStorage.MAP) { final Storage ret = JSonStorage.MAP.remove(storage.getID()); if (ret == null) { return false; } return true; } } public static <E> E restoreFrom(final File file, final boolean plain, final byte[] key, final TypeRef<E> type, final E def) { synchronized (JSonStorage.LOCK) { String stri = null; byte[] str = null; try { final File tmpfile = new File(file.getAbsolutePath() + ".tmp"); if (tmpfile.exists() && tmpfile.length() > 0) { /* tmp files exists, try to restore */ Log.L.warning("TMP file " + tmpfile.getAbsolutePath() + " found"); try { // load it str = IO.readFile(tmpfile); E ret; // try to parse it if (plain) { ret = JSonStorage.restoreFromString(stri = new String(str, "UTF-8"), type, def); } else { ret = JSonStorage.restoreFromString(stri = Crypto.decrypt(str, key), type, def); } Log.L.warning("Could restore tmp file"); // replace normal file with tmp file file.delete(); tmpfile.renameTo(file); if (ret == null) { return def; } return ret; } catch (final Exception e) { Log.L.warning("Could not restore tmp file"); Log.exception(Level.WARNING, e); } finally { /* tmp file must be gone after read */ tmpfile.delete(); } } final File res = file; if (!res.exists() || res.length() == 0) { return def; } str = IO.readFile(res); if (plain) { return JSonStorage.restoreFromString(stri = new String(str, "UTF-8"), type, def); } else { return JSonStorage.restoreFromString(stri = Crypto.decrypt(str, key), type, def); } } catch (final Throwable e) { Log.L.warning(file.getAbsolutePath() + ":read:" + stri); try { if (str != null) { Log.L.severe(file.getAbsolutePath() + ":original:" + new String(str, "UTF-8")); } } catch (final Throwable e2) { Log.exception(e2); } Log.exception(e); } return def; } } public static <E> E restoreFrom(final File file, final E def) { final E ret = JSonStorage.restoreFrom(file, true, null, null, def); if (ret == null) { return def; } return ret; } public static <E> E restoreFrom(final String string, final E def) { final boolean plain = string.toLowerCase().endsWith(".json"); return JSonStorage.restoreFrom(Application.getResource(string), plain, JSonStorage.KEY, null, def); } /** * restores a store json object * * @param <E> * @param string * name of the json object. example: cfg/savedobject.json * @param type * TypeRef instance. This is important for generic classes. for * example: new TypeRef<ArrayList<Contact>>(){} to restore type * ArrayList<Contact> * @param def * defaultvalue. if typeref is not set, the method tries to use * the class of def as restoreclass * @return */ public static <E> E restoreFrom(final String string, final TypeRef<E> type, final E def) { final boolean plain = string.toLowerCase().endsWith(".json"); return JSonStorage.restoreFrom(Application.getResource(string), plain, JSonStorage.KEY, type, def); } /** * @param <T> * @param string * @param class1 * @throws IOException * @throws JsonMappingException * @throws JsonParseException */ public static <T> T restoreFromString(final String string, final Class<T> class1) throws StorageException { synchronized (JSonStorage.LOCK) { try { return JSonStorage.JSON_MAPPER.stringToObject(string, class1); } catch (final Exception e) { throw new StorageException(string, e); } finally { } } } @SuppressWarnings("unchecked") public static <E> E restoreFromString(final String string, final TypeRef<E> type, final E def) { if (string == null) { return def; } try { synchronized (JSonStorage.LOCK) { if (type != null) { return JSonStorage.JSON_MAPPER.stringToObject(string, type); } else { return (E) JSonStorage.JSON_MAPPER.stringToObject(string, def.getClass()); } } } catch (final Exception e) { Log.exception(Level.WARNING, e); Log.L.warning(string); return def; } } public static void save() { synchronized (JSonStorage.MAP) { for (final Entry<String, Storage> entry : JSonStorage.MAP.entrySet()) { try { entry.getValue().save(); } catch (final Throwable e) { Log.exception(e); } finally { try { entry.getValue().close(); } catch (final Throwable e2) { Log.exception(e2); } } } } } public static void saveTo(final File file, final boolean plain, final byte[] key, final String json) throws StorageException { synchronized (JSonStorage.LOCK) { try { final File tmp = new File(file.getAbsolutePath() + ".tmp"); tmp.getParentFile().mkdirs(); tmp.delete(); if (plain) { /* uncrypted */ IO.writeToFile(tmp, json.getBytes("UTF-8")); } else { /* encrypted */ IO.writeToFile(tmp, Crypto.encrypt(json, key)); } if (file.exists()) { if (!file.delete()) { throw new StorageException("Could not overwrite file: " + file.getAbsolutePath()); } } if (!tmp.renameTo(file)) { throw new StorageException("Could not rename file: " + tmp + " to " + file); } } catch (final IOException e) { throw new StorageException(e); } } } /** * @param file * @param packageData */ public static void saveTo(final File file, final Object packageData) { final boolean plain = file.getName().toLowerCase().endsWith(".json"); JSonStorage.saveTo(file, plain, JSonStorage.KEY, JSonStorage.serializeToJson(packageData)); } /** * @param pathname * @param json * @throws StorageException */ public static void saveTo(final String pathname, final String json) throws StorageException { JSonStorage.saveTo(pathname, json, JSonStorage.KEY); } /** * @param pathname * @param json * @param kEY2 */ public static void saveTo(final String pathname, final String json, final byte[] key) { synchronized (JSonStorage.LOCK) { try { final File file = Application.getResource(pathname); final File tmp = new File(file.getParentFile(), file.getName() + ".tmp"); tmp.getParentFile().mkdirs(); tmp.delete(); if (new Regex(pathname, ".+\\.json").matches()) { /* uncrypted */ IO.writeToFile(tmp, json.getBytes("UTF-8")); } else { /* encrypted */ IO.writeToFile(tmp, Crypto.encrypt(json, key)); } if (file.exists()) { if (!file.delete()) { throw new StorageException("Could not overwrite file: " + file); } } if (!tmp.renameTo(file)) { throw new StorageException("Could not rename file: " + tmp + " to " + file); } } catch (final IOException e) { throw new StorageException(e); } } } /** * @param list * @return * @throws IOException * @throws JsonMappingException * @throws JsonGenerationException */ public static String serializeToJson(final Object list) throws StorageException { synchronized (JSonStorage.LOCK) { try { return JSonStorage.JSON_MAPPER.objectToString(list); } catch (final Exception e) { throw new StorageException(e); } finally { } } } public static void setMapper(final JSONMapper mapper) { JSonStorage.JSON_MAPPER = mapper; } /** * @param string * @param list */ public static void storeTo(final String string, final Object list) { synchronized (JSonStorage.LOCK) { try { JSonStorage.saveTo(string, JSonStorage.serializeToJson(list)); } catch (final Exception e) { throw new StorageException(e); } finally { } } } public static String toString(final Object list) { synchronized (JSonStorage.LOCK) { try { return JSonStorage.JSON_MAPPER.objectToString(list); } catch (final Throwable e) { e.printStackTrace(); } return list.toString(); } } }