/* * Copyright 2015 Victor Albertos * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.rx_cache2.internal; import io.rx_cache2.internal.encrypt.FileEncryptor; import io.victoralbertos.jolyglot.JolyglotGenerics; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Array; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import javax.inject.Inject; /** * Save objects in disk and evict them too. It uses Gson as json parser. */ public final class Disk implements Persistence { private final File cacheDirectory; private final FileEncryptor fileEncryptor; private final JolyglotGenerics jolyglot; @Inject public Disk(File cacheDirectory, FileEncryptor fileEncryptor, JolyglotGenerics jolyglot) { this.cacheDirectory = cacheDirectory; this.fileEncryptor = fileEncryptor; this.jolyglot = jolyglot; } /** * Save in disk the Record passed. * * @param key the key whereby the Record could be retrieved/deleted later. @see evict and @see * retrieve. * @param record the record to be persisted. * @param isEncrypted If the persisted record is encrypted or not. * @param encryptKey The key used to encrypt/decrypt the record to be persisted. */ @Override public void saveRecord(String key, io.rx_cache2.internal.Record record, boolean isEncrypted, String encryptKey) { save(key, record, isEncrypted, encryptKey); } /** * Retrieve the names from all files in dir */ @Override public List<String> allKeys() { List<String> nameFiles = new ArrayList<>(); File[] files = cacheDirectory.listFiles(); if (files == null) return nameFiles; for (File file : files) { if (file.isFile()) { nameFiles.add(file.getName()); } } return nameFiles; } /** * Retrieve records accumulated memory in megabyte */ @Override public int storedMB() { long bytes = 0; final File[] files = cacheDirectory.listFiles(); if (files == null) return 0; for (File file : files) { bytes += file.length(); } double megabytes = Math.ceil((double) bytes / 1024 / 1024); return (int) megabytes; } /** * Save in disk the object passed. * * @param key the key whereby the object could be retrieved/deleted later. @see evict and @see * retrieve. * @param data the object to be persisted. * @param isEncrypted If the persisted record is encrypted or not. * @param encryptKey The key used to encrypt/decrypt the record to be persisted. */ public void save(String key, Object data, boolean isEncrypted, String encryptKey) { key = safetyKey(key); String wrapperJSONSerialized; if (data instanceof io.rx_cache2.internal.Record) { Type type = jolyglot.newParameterizedType(data.getClass(), Object.class); wrapperJSONSerialized = jolyglot.toJson(data, type); } else { wrapperJSONSerialized = jolyglot.toJson(data); } FileWriter fileWriter = null; try { File file = new File(cacheDirectory, key); fileWriter = new FileWriter(file, false); fileWriter.write(wrapperJSONSerialized); fileWriter.flush(); fileWriter.close(); fileWriter = null; if (isEncrypted) { fileEncryptor.encrypt(encryptKey, new File(cacheDirectory, key)); } } catch (Exception e) { throw new RuntimeException(e); } finally { try { if (fileWriter != null) { fileWriter.flush(); fileWriter.close(); } } catch (IOException e) { e.printStackTrace(); } } } /** * Delete the object previously saved. * * @param key the key whereby the object could be deleted. */ @Override public void evict(String key) { key = safetyKey(key); final File file = new File(cacheDirectory, key); file.delete(); } /** * Delete all objects previously saved. */ @Override public void evictAll() { File[] files = cacheDirectory.listFiles(); if (null != files) { for (File file : files) { if (file != null) file.delete(); } } } /** * Retrieve the object previously saved. * * @param key the key whereby the object could be retrieved. * @param clazz the type of class against the object need to be serialized * @param isEncrypted If the persisted record is encrypted or not. * @param encryptKey The key used to encrypt/decrypt the record to be persisted. */ public <T> T retrieve(String key, final Class<T> clazz, boolean isEncrypted, String encryptKey) { key = safetyKey(key); File file = new File(cacheDirectory, key); if (isEncrypted) { file = fileEncryptor.decrypt(encryptKey, file); } try { T data = jolyglot.fromJson(file, clazz); return data; } catch (Exception ignore) { return null; } finally { if (isEncrypted) { file.delete(); } } } /** * Retrieve the Record previously saved. * * @param key the key whereby the object could be retrieved. * @param isEncrypted If the persisted record is encrypted or not. * @param encryptKey The key used to encrypt/decrypt the record to be persisted. */ @Override public <T> io.rx_cache2.internal.Record<T> retrieveRecord(String key, boolean isEncrypted, String encryptKey) { key = safetyKey(key); File file = new File(cacheDirectory, key); try { if (isEncrypted) { file = fileEncryptor.decrypt(encryptKey, file); } Type partialType = jolyglot.newParameterizedType(io.rx_cache2.internal.Record.class, Object.class); io.rx_cache2.internal.Record tempDiskRecord = jolyglot.fromJson(file, partialType); Class classData = Class.forName(tempDiskRecord.getDataClassName()); Class classCollectionData = tempDiskRecord.getDataCollectionClassName() == null ? Object.class : Class.forName(tempDiskRecord.getDataCollectionClassName()); boolean isCollection = Collection.class.isAssignableFrom(classCollectionData); boolean isArray = classCollectionData.isArray(); boolean isMap = Map.class.isAssignableFrom(classCollectionData); io.rx_cache2.internal.Record<T> diskRecord; if (isCollection) { Type typeCollection = jolyglot.newParameterizedType(classCollectionData, classData); Type typeRecord = jolyglot.newParameterizedType(io.rx_cache2.internal.Record.class, typeCollection); diskRecord = jolyglot.fromJson(file.getAbsoluteFile(), typeRecord); } else if (isArray) { Type typeRecord = jolyglot.newParameterizedType(io.rx_cache2.internal.Record.class, classCollectionData); diskRecord = jolyglot.fromJson(file.getAbsoluteFile(), typeRecord); } else if (isMap) { Class classKeyMap = Class.forName(tempDiskRecord.getDataKeyMapClassName()); Type typeMap = jolyglot.newParameterizedType(classCollectionData, classKeyMap, classData); Type typeRecord = jolyglot.newParameterizedType(io.rx_cache2.internal.Record.class, typeMap); diskRecord = jolyglot.fromJson(file.getAbsoluteFile(), typeRecord); } else { Type type = jolyglot.newParameterizedType(io.rx_cache2.internal.Record.class, classData); diskRecord = jolyglot.fromJson(file.getAbsoluteFile(), type); } diskRecord.setSizeOnMb(file.length() / 1024f / 1024f); return diskRecord; } catch (Exception ignore) { return null; } finally { if (isEncrypted) { file.delete(); } } } /** * Retrieve a collection previously saved. * * @param key the key whereby the object could be retrieved. * @param classCollection type class collection * @param classData type class contained by the collection, not the collection itself */ public <C extends Collection<T>, T> C retrieveCollection(String key, Class<C> classCollection, Class<T> classData) { key = safetyKey(key); try { File file = new File(cacheDirectory, key); Type typeCollection = jolyglot.newParameterizedType(classCollection, classData); T data = jolyglot.fromJson(file, typeCollection); return (C) data; } catch (Exception e) { return null; } } /** * Retrieve a Map previously saved. * * @param key the key whereby the object could be retrieved. * @param classMap type class Map * @param classMapKey type class of the Map key * @param classMapValue type class of the Map value */ public <M extends Map<K, V>, K, V> M retrieveMap(String key, Class classMap, Class<K> classMapKey, Class<V> classMapValue) { key = safetyKey(key); try { File file = new File(cacheDirectory, key); Type typeMap = jolyglot.newParameterizedType(classMap, classMapKey, classMapValue); Object data = jolyglot.fromJson(file, typeMap); return (M) data; } catch (Exception e) { return null; } } /** * Retrieve an Array previously saved. * * @param key the key whereby the object could be retrieved. * @param classData type class contained by the Array */ public <T> T[] retrieveArray(String key, Class<T> classData) { key = safetyKey(key); try { File file = new File(cacheDirectory, key); Class<?> clazzArray = Array.newInstance(classData, 1).getClass(); Object data = jolyglot.fromJson(file, clazzArray); return (T[]) data; } catch (Exception e) { return null; } } private String safetyKey(String key) { return key.replaceAll("/", "_"); } }