Java tutorial
/* * Copyright (c) 2016 Farooq Khan * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package jsondb; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.charset.CharacterCodingException; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.jxpath.JXPathContext; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import jsondb.annotation.DBRef; import jsondb.crypto.CryptoUtil; import jsondb.crypto.ICipher; import jsondb.events.CollectionFileChangeListener; import jsondb.events.EventListenerList; import jsondb.io.JsonFileLockException; import jsondb.io.JsonReader; import jsondb.io.JsonWriter; import jsondb.query.Update; import jsondb.query.ddl.AddOperation; import jsondb.query.ddl.CollectionSchemaUpdate; import jsondb.query.ddl.DeleteOperation; import jsondb.query.ddl.RenameOperation; import java.lang.reflect.Field; import java.util.UUID; import java.util.logging.Level; import org.pmw.tinylog.Logger; /** * @version 1.0 25-Sep-2016 */ public class JsonDBTemplate implements JsonDBOperations { private JsonDBConfig dbConfig = null; private final boolean encrypted; private File lockFilesLocation; private EventListenerList eventListenerList; private Map<String, CollectionMetaData> cmdMap; private AtomicReference<Map<String, File>> fileObjectsRef = new AtomicReference<>(new ConcurrentHashMap<>()); private AtomicReference<Map<String, Map<Object, ?>>> collectionsRef = new AtomicReference<Map<String, Map<Object, ?>>>( new ConcurrentHashMap<String, Map<Object, ?>>()); private AtomicReference<Map<String, JXPathContext>> contextsRef = new AtomicReference<Map<String, JXPathContext>>( new ConcurrentHashMap<String, JXPathContext>()); public JsonDBTemplate(JsonDBConfig config) { this.dbConfig = config; if (dbConfig.getCipher() == null) { Logger.info("Die Verschlsselung ist deaktiviert"); this.encrypted = false; } else { Logger.info("Die Verschlsselung ist aktiviert"); this.encrypted = true; } this.initialize(); eventListenerList = new EventListenerList(dbConfig, cmdMap); } public JsonDBTemplate(String dbFilesLocation, String iojsondbtestsmodel, String pre, Object object, boolean b, Object object0) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } private void initialize() { if (!dbConfig.getDbFilesLocation().exists()) { try { Files.createDirectory(dbConfig.getDbFilesPath()); } catch (IOException e) { Logger.error("DbFiles directory does not exist. Failed to create a new empty DBFiles directory {}", e); throw new InvalidJsonDbApiUsageException( "DbFiles directory does not exist. Failed to create a new empty DBFiles directory " + dbConfig.getDbFilesLocationString()); } } else if (dbConfig.getDbFilesLocation().isFile()) { throw new InvalidJsonDbApiUsageException( "Specified DbFiles directory is actually a file cannot use it as a directory"); } this.lockFilesLocation = new File(dbConfig.getDbFilesLocation(), "lock"); if (!lockFilesLocation.exists()) { lockFilesLocation.mkdirs(); } cmdMap = CollectionMetaData.builder(dbConfig); loadDB(); // Auto-cleanup at shutdown Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { eventListenerList.shutdown(); } }); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#reLoadDB() */ @Override public void reLoadDB() { loadDB(); } private synchronized void loadDB() { for (String collectionName : cmdMap.keySet()) { File collectionFile = new File(dbConfig.getDbFilesLocation(), dbConfig.getDBPrefix() + collectionName + ".json"); if (collectionFile.exists()) { reloadCollection(collectionName); } else if (collectionsRef.get().containsKey(collectionName)) { //this probably is a reload attempt after a collection .json was deleted. //that is the reason even though the file does not exist a entry into collectionsRef still exists. contextsRef.get().remove(collectionName); collectionsRef.get().remove(collectionName); } } } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#reloadCollection(java.lang.String) */ public void reloadCollection(String collectionName) { CollectionMetaData cmd = cmdMap.get(collectionName); cmd.getCollectionLock().writeLock().lock(); try { File collectionFile = fileObjectsRef.get().get(collectionName); if (null == collectionFile) { // Lets create a file now collectionFile = new File(dbConfig.getDbFilesLocation(), dbConfig.getDBPrefix() + collectionName + ".json"); if (!collectionFile.exists()) { throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' cannot be found at " + collectionFile.getAbsolutePath()); } Map<String, File> fileObjectMap = fileObjectsRef.get(); Map<String, File> newFileObjectmap = new ConcurrentHashMap<String, File>(fileObjectMap); newFileObjectmap.put(collectionName, collectionFile); fileObjectsRef.set(newFileObjectmap); } if (null != cmd && null != collectionFile) { Map<Object, ?> collection = loadCollection(collectionFile, collectionName, cmd); if (null != collection) { JXPathContext newContext = JXPathContext.newContext(collection.values()); contextsRef.get().put(collectionName, newContext); collectionsRef.get().put(collectionName, collection); } else { //Since this is a reload attempt its possible the .json files have disappeared in the interim a very rare thing contextsRef.get().remove(collectionName); collectionsRef.get().remove(collectionName); } } } finally { cmd.getCollectionLock().writeLock().unlock(); } } private <T> Map<Object, T> loadCollection(File collectionFile, String collectionName, CollectionMetaData cmd) { @SuppressWarnings("unchecked") Class<T> entity = cmd.getClazz(); Method getterMethodForId = cmd.getIdAnnotatedFieldGetterMethod(); JsonReader jr = null; Map<Object, T> collection = new LinkedHashMap<Object, T>(); String line = null; int lineNo = 1; try { jr = new JsonReader(dbConfig, collectionFile); while ((line = jr.readLine()) != null) { if (lineNo == 1) { SchemaVersion v = dbConfig.getObjectMapper().readValue(line, SchemaVersion.class); cmd.setActualSchemaVersion(v.getSchemaVersion()); } else { T row = dbConfig.getObjectMapper().readValue(line, entity); Object id = Util.getIdForEntity(row, getterMethodForId); collection.put(id, row); } lineNo++; } } catch (JsonParseException je) { Logger.error("Failed Json Parsing for file {} line {}", collectionFile.getName(), lineNo, je); return null; } catch (JsonMappingException jm) { Logger.error("Failed Mapping Parsed Json to Entity {} for file {} line {}", entity.getSimpleName(), collectionFile.getName(), lineNo, jm); return null; } catch (CharacterCodingException ce) { Logger.error("Unsupported Character Encoding in file {} expected Encoding {}", collectionFile.getName(), dbConfig.getCharset().displayName(), ce); return null; } catch (JsonFileLockException jfe) { Logger.error("Failed to acquire lock for collection file {}", collectionFile.getName(), jfe); return null; } catch (FileNotFoundException fe) { Logger.error("Collection file {} not found", collectionFile.getName(), fe); return null; } catch (IOException e) { Logger.error("Some IO Exception reading the Json File {}", collectionFile.getName(), e); return null; } catch (Throwable t) { Logger.error("Throwable Caught ", collectionFile.getName(), t); return null; } finally { if (null != jr) { jr.close(); } } return collection; } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#addCollectionFileChangeListener(org.jsondb.CollectionFileChangeListener) */ @Override public void addCollectionFileChangeListener(CollectionFileChangeListener listener) { eventListenerList.addCollectionFileChangeListener(listener); } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#removeCollectionFileChangeListener(org.jsondb.CollectionFileChangeListener) */ @Override public void removeCollectionFileChangeListener(CollectionFileChangeListener listener) { eventListenerList.removeCollectionFileChangeListener(listener); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#hasCollectionFileChangeListener() */ @Override public boolean hasCollectionFileChangeListener() { return eventListenerList.hasCollectionFileChangeListener(); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#createCollection(java.lang.Class) */ @Override public <T> void createCollection(Class<T> entityClass) { createCollection(Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#createCollection(java.lang.String) */ @Override public <T> void createCollection(String collectionName) { CollectionMetaData cmd = cmdMap.get(collectionName); if (null == cmd) { throw new InvalidJsonDbApiUsageException( "No class found with @Document Annotation and attribute collectionName as: " + collectionName); } @SuppressWarnings("unchecked") Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if (null != collection) { throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' already exists."); } cmd.getCollectionLock().writeLock().lock(); // Some other thread might have created same collection when this thread reached this point if (collectionsRef.get().get(collectionName) != null) { return; } try { String collectionFileName = dbConfig.getDBPrefix() + collectionName + ".json"; File fileObject = new File(dbConfig.getDbFilesLocation(), collectionFileName); try { fileObject.createNewFile(); } catch (IOException e) { Logger.error("IO Exception creating the collection file {}", collectionFileName, e); throw new InvalidJsonDbApiUsageException( "Unable to create a collection file for collection: " + collectionName); } if (Util.stampVersion(dbConfig, fileObject, cmd.getSchemaVersion())) { collection = new LinkedHashMap<Object, T>(); collectionsRef.get().put(collectionName, collection); contextsRef.get().put(collectionName, JXPathContext.newContext(collection.values())); fileObjectsRef.get().put(collectionName, fileObject); cmd.setActualSchemaVersion(cmd.getSchemaVersion()); } else { fileObject.delete(); throw new JsonDBException("Failed to stamp version for collection: " + collectionName); } } finally { cmd.getCollectionLock().writeLock().unlock(); } } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#dropCollection(java.lang.Class) */ @Override public <T> void dropCollection(Class<T> entityClass) { dropCollection(Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#dropCollection(java.lang.String) */ @Override public void dropCollection(String collectionName) { CollectionMetaData cmd = cmdMap.get(collectionName); if ((null == cmd) || (!collectionsRef.get().containsKey(collectionName))) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first."); } cmd.getCollectionLock().writeLock().lock(); try { File toDelete = fileObjectsRef.get().get(collectionName); try { Files.deleteIfExists(toDelete.toPath()); } catch (IOException e) { Logger.error("IO Exception deleting the collection file {}", toDelete.getName(), e); throw new InvalidJsonDbApiUsageException( "Unable to create a collection file for collection: " + collectionName); } //cmdMap.remove(collectionName); //Do not remove it from the CollectionMetaData Map. //Someone might want to re insert a new collection of this type. fileObjectsRef.get().remove(collectionName); collectionsRef.get().remove(collectionName); contextsRef.get().remove(collectionName); } finally { cmd.getCollectionLock().writeLock().unlock(); } } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#updateCollectionSchema(org.jsondb.query.CollectionSchemaUpdate, java.lang.Class) */ @Override public <T> void updateCollectionSchema(CollectionSchemaUpdate update, Class<T> entityClass) { updateCollectionSchema(update, Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#updateCollectionSchema(org.jsondb.query.CollectionSchemaUpdate, java.lang.String) */ @Override public <T> void updateCollectionSchema(CollectionSchemaUpdate update, String collectionName) { CollectionMetaData cmd = cmdMap.get(collectionName); @SuppressWarnings("unchecked") Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if ((null == cmd) || (null == collection)) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first."); } boolean reloadCollectionAsSomethingChanged = false; //We only take care of ADD and RENAME, the deletes will be taken care of automatically. if (null != update) { Map<String, RenameOperation> renOps = update.getRenameOperations(); if (renOps.size() > 0) { reloadCollectionAsSomethingChanged = true; cmd.getCollectionLock().writeLock().lock(); for (Entry<String, RenameOperation> updateEntry : renOps.entrySet()) { String oldKey = updateEntry.getKey(); RenameOperation op = updateEntry.getValue(); String newKey = op.getNewName(); JsonWriter jw; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } jw.renameKeyInJsonFile(collection.values(), true, oldKey, newKey); } cmd.getCollectionLock().writeLock().unlock(); } Map<String, AddOperation> addOps = update.getAddOperations(); if (addOps.size() > 0) { reloadCollectionAsSomethingChanged = true; cmd.getCollectionLock().writeLock().lock(); for (Entry<String, AddOperation> updateEntry : addOps.entrySet()) { AddOperation op = updateEntry.getValue(); Object value = null; if (op.isSecret()) { value = dbConfig.getCipher().encrypt((String) op.getDefaultValue()); } else { value = op.getDefaultValue(); } String fieldName = updateEntry.getKey(); Method setterMethod = cmd.getSetterMethodForFieldName(fieldName); for (T object : collection.values()) { Util.setFieldValueForEntity(object, value, setterMethod); } } JsonWriter jw; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } jw.reWriteJsonFile(collection.values(), true); cmd.getCollectionLock().writeLock().unlock(); } Map<String, DeleteOperation> delOps = update.getDeleteOperations(); if ((renOps.size() < 1 && addOps.size() < 1) && (delOps.size() > 0)) { //There were no ADD operations but there are some DELETE operations so we have to just flush the collection once //This would not have been necessary if there was even 1 ADD operation reloadCollectionAsSomethingChanged = true; cmd.getCollectionLock().writeLock().lock(); JsonWriter jw; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } jw.reWriteJsonFile(collection.values(), true); cmd.getCollectionLock().writeLock().unlock(); } if (reloadCollectionAsSomethingChanged) { reloadCollection(collectionName); } } } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#getCollectionNames() */ @Override public Set<String> getCollectionNames() { return collectionsRef.get().keySet(); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#getCollectionName(java.lang.Class) */ @Override public String getCollectionName(Class<?> entityClass) { return Util.determineCollectionName(entityClass); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#getCollection(java.lang.Class) */ @SuppressWarnings("unchecked") @Override public <T> List<T> getCollection(Class<T> entityClass) { String collectionName = Util.determineCollectionName(entityClass); Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if (null == collection) { createCollection(collectionName); collection = (Map<Object, T>) collectionsRef.get().get(collectionName); } CollectionMetaData cmd = cmdMap.get(collectionName); List<T> newCollection = new ArrayList<T>(); try { for (T document : collection.values()) { Object obj = Util.deepCopy(document); if (encrypted && cmd.hasSecret() && null != obj) { CryptoUtil.decryptFields(obj, cmd, dbConfig.getCipher()); } newCollection.add((T) obj); } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { Logger.error("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e); throw new JsonDBException( "Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e); } return newCollection; } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#collectionExists(java.lang.Class) */ @Override public <T> boolean collectionExists(Class<T> entityClass) { return collectionExists(Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#collectionExists(java.lang.String) */ @Override public boolean collectionExists(String collectionName) { CollectionMetaData collectionMeta = cmdMap.get(collectionName); if (null == collectionMeta) { return false; } collectionMeta.getCollectionLock().readLock().lock(); try { return collectionsRef.get().containsKey(collectionName); } finally { collectionMeta.getCollectionLock().readLock().unlock(); } } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#isCollectionReadonly(java.lang.Class) */ @Override public <T> boolean isCollectionReadonly(Class<T> entityClass) { return isCollectionReadonly(Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#isCollectionReadonly(java.lang.String) */ @Override public <T> boolean isCollectionReadonly(String collectionName) { CollectionMetaData cmd = cmdMap.get(collectionName); return cmd.isReadOnly(); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#find(java.lang.String, java.lang.Class) */ @Override public <T> List<T> find(String jxQuery, Class<T> entityClass) { return find(jxQuery, Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#find(java.lang.String, java.lang.String) */ @SuppressWarnings("unchecked") @Override public <T> List<T> find(String jxQuery, String collectionName) { CollectionMetaData cmd = cmdMap.get(collectionName); Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if ((null == cmd) || (null == collection)) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first."); } cmd.getCollectionLock().readLock().lock(); try { JXPathContext context = contextsRef.get().get(collectionName); Iterator<T> resultItr = context.iterate(jxQuery); List<T> newCollection = new ArrayList<T>(); while (resultItr.hasNext()) { T document = resultItr.next(); Object obj = Util.deepCopy(document); if (encrypted && cmd.hasSecret() && null != obj) { CryptoUtil.decryptFields(obj, cmd, dbConfig.getCipher()); } newCollection.add((T) obj); } return newCollection; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { Logger.error("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e); throw new JsonDBException( "Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e); } finally { cmd.getCollectionLock().readLock().unlock(); } } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#findAll(java.lang.Class) */ @Override public <T> List<T> findAll(Class<T> entityClass) { return findAll(Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#findAll(java.lang.String) */ @SuppressWarnings("unchecked") @Override public <T> List<T> findAll(String collectionName) { CollectionMetaData cmd = cmdMap.get(collectionName); Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if ((null == cmd) || (null == collection)) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first."); } cmd.getCollectionLock().readLock().lock(); try { List<T> newCollection = new ArrayList<T>(); for (T document : collection.values()) { T obj = (T) Util.deepCopy(document); if (encrypted && cmd.hasSecret() && null != obj) { CryptoUtil.decryptFields(obj, cmd, dbConfig.getCipher()); newCollection.add(obj); } else { newCollection.add((T) obj); } } return newCollection; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { Logger.error("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e); throw new JsonDBException( "Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e); } finally { cmd.getCollectionLock().readLock().unlock(); } } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#findById(java.lang.Object, java.lang.Class) */ @Override public <T> T findById(Object id, Class<T> entityClass) { return findById(id, Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#findById(java.lang.Object, java.lang.String) */ @SuppressWarnings("unchecked") @Override public <T> T findById(Object id, String collectionName) { CollectionMetaData cmd = cmdMap.get(collectionName); Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if ((null == cmd) || null == collection) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first."); } cmd.getCollectionLock().readLock().lock(); try { Object obj = Util.deepCopy(collection.get(id)); if (encrypted && cmd.hasSecret() && null != obj) { CryptoUtil.decryptFields(obj, cmd, dbConfig.getCipher()); } return (T) obj; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { Logger.error("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e); throw new JsonDBException( "Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e); } finally { cmd.getCollectionLock().readLock().unlock(); } } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#findOne(java.lang.String, java.lang.Class) */ @Override public <T> T findOne(String jxQuery, Class<T> entityClass) { return findOne(jxQuery, Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#findOne(java.lang.String, java.lang.String) */ @SuppressWarnings("unchecked") @Override public <T> T findOne(String jxQuery, String collectionName) { CollectionMetaData collectionMeta = cmdMap.get(collectionName); if ((null == collectionMeta) || (!collectionsRef.get().containsKey(collectionName))) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first"); } collectionMeta.getCollectionLock().readLock().lock(); try { JXPathContext context = contextsRef.get().get(collectionName); Iterator<T> resultItr = context.iterate(jxQuery); while (resultItr.hasNext()) { T document = resultItr.next(); Object obj = Util.deepCopy(document); if (encrypted && collectionMeta.hasSecret() && null != obj) { CryptoUtil.decryptFields(obj, collectionMeta, dbConfig.getCipher()); } return (T) obj; // Return the first element we find. } return null; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { Logger.error("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e); throw new JsonDBException( "Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e); } finally { collectionMeta.getCollectionLock().readLock().unlock(); } } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#insert(java.lang.Object) */ @Override public <T> void insert(Object objectToSave) { if (null == objectToSave) { throw new InvalidJsonDbApiUsageException("Null Object cannot be inserted into DB"); } Util.ensureNotRestricted(objectToSave); System.out.println("entity collection name: " + Util.determineEntityCollectionName(objectToSave)); insert(objectToSave, Util.determineEntityCollectionName(objectToSave)); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#insert(java.lang.Object, java.lang.String) */ @SuppressWarnings("unchecked") @Override public <T> void insert(Object objectToSave, String collectionName) { if (null == objectToSave) { throw new InvalidJsonDbApiUsageException("Null Object cannot be inserted into DB"); } Util.ensureNotRestricted(objectToSave); Object objToSave = Util.deepCopy(objectToSave); CollectionMetaData cmd = cmdMap.get(collectionName); cmd.getCollectionLock().writeLock().lock(); try { Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if (null == collection) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first"); } Object id = Util.getIdForEntity(objectToSave, cmd.getIdAnnotatedFieldGetterMethod()); if (encrypted && cmd.hasSecret()) { CryptoUtil.encryptFields(objToSave, cmd, dbConfig.getCipher()); } if (cmd.hasDBRef()) { } if (null == id) { id = Util.setIdForEntity(objToSave, cmd.getIdAnnotatedFieldSetterMethod()); } else if (collection.containsKey(id)) { throw new InvalidJsonDbApiUsageException( "Object already present in Collection. Use Update or Upsert operation instead of Insert"); } JsonWriter jw; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } boolean appendResult = jw.appendToJsonFile(collection.values(), objToSave); if (appendResult) { collection.put(Util.deepCopy(id), (T) objToSave); } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { Logger.error("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e); throw new JsonDBException( "Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e); } finally { cmd.getCollectionLock().writeLock().unlock(); } } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#insert(java.util.Collection, java.lang.Class) */ @Override public <T> void insert(Collection<? extends T> batchToSave, Class<T> entityClass) { insert(batchToSave, Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#insert(java.util.Collection, java.lang.String) */ @SuppressWarnings("unchecked") @Override public <T> void insert(Collection<? extends T> batchToSave, String collectionName) { if (null == batchToSave) { throw new InvalidJsonDbApiUsageException("Null Object batch cannot be inserted into DB"); } CollectionMetaData collectionMeta = cmdMap.get(collectionName); collectionMeta.getCollectionLock().writeLock().lock(); try { Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if (null == collection) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first"); } CollectionMetaData cmd = cmdMap.get(collectionName); Set<Object> uniqueIds = new HashSet<Object>(); Map<Object, T> newCollection = new LinkedHashMap<Object, T>(); for (T o : batchToSave) { Object obj = Util.deepCopy(o); Object id = Util.getIdForEntity(obj, cmd.getIdAnnotatedFieldGetterMethod()); if (encrypted && cmd.hasSecret()) { CryptoUtil.encryptFields(obj, cmd, dbConfig.getCipher()); } if (null == id) { id = Util.setIdForEntity(obj, cmd.getIdAnnotatedFieldSetterMethod()); } else if (collection.containsKey(id)) { throw new InvalidJsonDbApiUsageException( "Object already present in Collection. Use Update or Upsert operation instead of Insert"); } if (!uniqueIds.add(id)) { throw new InvalidJsonDbApiUsageException( "Duplicate object with id: " + id + " within the passed in parameter"); } newCollection.put(Util.deepCopy(id), (T) obj); } JsonWriter jw; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } boolean appendResult = jw.appendToJsonFile(collection.values(), newCollection.values()); if (appendResult) { collection.putAll(newCollection); } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { Logger.error("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e); throw new JsonDBException( "Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e); } finally { collectionMeta.getCollectionLock().writeLock().unlock(); } } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#save(java.lang.Object, java.lang.Class) */ @Override public <T> void save(Object objectToSave, Class<T> entityClass) { save(objectToSave, Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#save(java.lang.Object, java.lang.String) */ @Override public <T> void save(Object objectToSave, String collectionName) { if (null == objectToSave) { throw new InvalidJsonDbApiUsageException("Null Object cannot be updated into DB"); } Util.ensureNotRestricted(objectToSave); Object objToSave = Util.deepCopy(objectToSave); CollectionMetaData collectionMeta = cmdMap.get(collectionName); collectionMeta.getCollectionLock().writeLock().lock(); try { @SuppressWarnings("unchecked") Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if (null == collection) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first."); } CollectionMetaData cmd = cmdMap.get(collectionName); Object id = Util.getIdForEntity(objToSave, cmd.getIdAnnotatedFieldGetterMethod()); T existingObject = collection.get(id); if (null == existingObject) { throw new InvalidJsonDbApiUsageException(String.format( "Document with Id: '%s' not found in Collection by name '%s' not found. Insert or Upsert the object first.", id, collectionName)); } if (encrypted && cmd.hasSecret()) { CryptoUtil.encryptFields(objToSave, cmd, dbConfig.getCipher()); } JsonWriter jw = null; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } @SuppressWarnings("unchecked") boolean updateResult = jw.updateInJsonFile(collection, id, (T) objToSave); if (updateResult) { @SuppressWarnings("unchecked") T newObject = (T) objToSave; collection.put(id, newObject); } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { Logger.error("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e); throw new JsonDBException( "Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e); } finally { collectionMeta.getCollectionLock().writeLock().unlock(); } } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#remove(java.lang.Object, java.lang.Class) */ @Override public <T> T remove(Object objectToRemove, Class<T> entityClass) { return remove(objectToRemove, Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#remove(java.lang.Object, java.lang.String) */ @Override public <T> T remove(Object objectToRemove, String collectionName) { if (null == objectToRemove) { throw new InvalidJsonDbApiUsageException("Null Object cannot be removed from DB"); } Util.ensureNotRestricted(objectToRemove); CollectionMetaData collectionMeta = cmdMap.get(collectionName); collectionMeta.getCollectionLock().writeLock().lock(); try { @SuppressWarnings("unchecked") Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if (null == collection) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first."); } CollectionMetaData cmd = cmdMap.get(collectionName); Object id = Util.getIdForEntity(objectToRemove, cmd.getIdAnnotatedFieldGetterMethod()); if (!collection.containsKey(id)) { throw new InvalidJsonDbApiUsageException( String.format("Objects with Id %s not found in collection %s", id, collectionName)); } JsonWriter jw; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } boolean substractResult = jw.removeFromJsonFile(collection, id); if (substractResult) { T objectRemoved = collection.remove(id); // Don't need to clone it, this object no more exists in the collection return objectRemoved; } else { return null; } } finally { collectionMeta.getCollectionLock().writeLock().unlock(); } } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#remove(java.util.Collection, java.lang.Class) */ @Override public <T> List<T> remove(Collection<? extends T> batchToRemove, Class<T> entityClass) { return remove(batchToRemove, Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#remove(java.util.Collection, java.lang.String) */ @Override public <T> List<T> remove(Collection<? extends T> batchToRemove, String collectionName) { if (null == batchToRemove) { throw new InvalidJsonDbApiUsageException("Null Object batch cannot be removed from DB"); } CollectionMetaData cmd = cmdMap.get(collectionName); cmd.getCollectionLock().writeLock().lock(); try { @SuppressWarnings("unchecked") Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if (null == collection) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first."); } Set<Object> removeIds = new HashSet<Object>(); for (T o : batchToRemove) { Object id = Util.getIdForEntity(o, cmd.getIdAnnotatedFieldGetterMethod()); if (collection.containsKey(id)) { removeIds.add(id); } } if (removeIds.size() < 1) { return null; } JsonWriter jw; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } boolean substractResult = jw.removeFromJsonFile(collection, removeIds); List<T> removedObjects = null; if (substractResult) { removedObjects = new ArrayList<T>(); for (Object id : removeIds) { // Don't need to clone it, this object no more exists in the collection removedObjects.add(collection.remove(id)); } } return removedObjects; } finally { cmd.getCollectionLock().writeLock().unlock(); } } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#upsert(java.lang.Object) */ @Override public <T> void upsert(Object objectToSave) { if (null == objectToSave) { throw new InvalidJsonDbApiUsageException("Null Object cannot be upserted into DB"); } Util.ensureNotRestricted(objectToSave); upsert(objectToSave, Util.determineEntityCollectionName(objectToSave)); } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#upsert(java.lang.Object, java.lang.String) */ @SuppressWarnings("unchecked") @Override public <T> void upsert(Object objectToSave, String collectionName) { if (null == objectToSave) { throw new InvalidJsonDbApiUsageException("Null Object cannot be upserted into DB"); } Util.ensureNotRestricted(objectToSave); Object objToSave = Util.deepCopy(objectToSave); CollectionMetaData collectionMeta = cmdMap.get(collectionName); collectionMeta.getCollectionLock().writeLock().lock(); try { Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if (null == collection) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first"); } CollectionMetaData cmd = cmdMap.get(collectionName); Object id = Util.getIdForEntity(objectToSave, cmd.getIdAnnotatedFieldGetterMethod()); if (encrypted && cmd.hasSecret()) { CryptoUtil.encryptFields(objToSave, cmd, dbConfig.getCipher()); } boolean insert = true; if (null == id) { id = Util.setIdForEntity(objToSave, cmd.getIdAnnotatedFieldSetterMethod()); } else if (collection.containsKey(id)) { insert = false; } JsonWriter jw; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } if (insert) { boolean insertResult = jw.appendToJsonFile(collection.values(), objToSave); if (insertResult) { collection.put(Util.deepCopy(id), (T) objToSave); } } else { boolean updateResult = jw.updateInJsonFile(collection, id, (T) objToSave); if (updateResult) { T newObject = (T) objToSave; collection.put(id, newObject); } } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { Logger.error("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e); throw new JsonDBException( "Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e); } finally { collectionMeta.getCollectionLock().writeLock().unlock(); } } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#upsert(java.util.Collection, java.lang.Class) */ @Override public <T> void upsert(Collection<? extends T> batchToSave, Class<T> entityClass) { upsert(batchToSave, Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#upsert(java.util.Collection, java.lang.String) */ @SuppressWarnings("unchecked") @Override public <T> void upsert(Collection<? extends T> batchToSave, String collectionName) { if (null == batchToSave) { throw new InvalidJsonDbApiUsageException("Null Object batch cannot be upserted into DB"); } CollectionMetaData collectionMeta = cmdMap.get(collectionName); collectionMeta.getCollectionLock().writeLock().lock(); try { Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if (null == collection) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first"); } CollectionMetaData cmd = cmdMap.get(collectionName); Set<Object> uniqueIds = new HashSet<Object>(); Map<Object, T> collectionToInsert = new LinkedHashMap<Object, T>(); Map<Object, T> collectionToUpdate = new LinkedHashMap<Object, T>(); for (T o : batchToSave) { Object obj = Util.deepCopy(o); Object id = Util.getIdForEntity(obj, cmd.getIdAnnotatedFieldGetterMethod()); if (encrypted && cmd.hasSecret()) { CryptoUtil.encryptFields(obj, cmd, dbConfig.getCipher()); } boolean insert = true; if (null == id) { id = Util.setIdForEntity(obj, cmd.getIdAnnotatedFieldSetterMethod()); } else if (collection.containsKey(id)) { insert = false; } if (!uniqueIds.add(id)) { throw new InvalidJsonDbApiUsageException( "Duplicate object with id: " + id + " within the passed in parameter"); } if (insert) { collectionToInsert.put(Util.deepCopy(id), (T) obj); } else { collectionToUpdate.put(Util.deepCopy(id), (T) obj); } } JsonWriter jw; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } if (collectionToInsert.size() > 0) { boolean insertResult = jw.appendToJsonFile(collection.values(), collectionToInsert.values()); if (insertResult) { collection.putAll(collectionToInsert); } } if (collectionToUpdate.size() > 0) { boolean updateResult = jw.updateInJsonFile(collection, collectionToUpdate); if (updateResult) { collection.putAll(collectionToUpdate); } } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { Logger.error("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e); throw new JsonDBException( "Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e); } finally { collectionMeta.getCollectionLock().writeLock().unlock(); } } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#findAndRemove(java.lang.String, java.lang.Class) */ @Override public <T> T findAndRemove(String jxQuery, Class<T> entityClass) { return findAndRemove(jxQuery, Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#findAndRemove(java.lang.String, java.lang.String) */ @Override public <T> T findAndRemove(String jxQuery, String collectionName) { if (null == jxQuery) { throw new InvalidJsonDbApiUsageException("Query string cannot be null."); } CollectionMetaData cmd = cmdMap.get(collectionName); @SuppressWarnings("unchecked") Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if ((null == cmd) || (null == collection)) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first."); } cmd.getCollectionLock().writeLock().lock(); try { JXPathContext context = contextsRef.get().get(collectionName); @SuppressWarnings("unchecked") Iterator<T> resultItr = context.iterate(jxQuery); T objectToRemove = null; while (resultItr.hasNext()) { objectToRemove = resultItr.next(); break; // Use only the first element we find. } if (null != objectToRemove) { Object idToRemove = Util.getIdForEntity(objectToRemove, cmd.getIdAnnotatedFieldGetterMethod()); if (!collection.containsKey(idToRemove)) { //This will never happen since the object was located based of jxQuery throw new InvalidJsonDbApiUsageException(String .format("Objects with Id %s not found in collection %s", idToRemove, collectionName)); } JsonWriter jw; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } boolean substractResult = jw.removeFromJsonFile(collection, idToRemove); if (substractResult) { T objectRemoved = collection.remove(idToRemove); // Don't need to clone it, this object no more exists in the collection return objectRemoved; } else { Logger.error("Unexpected, Failed to substract the object"); } } return null; //Either the jxQuery found nothing or actual FileIO failed to substract it. } finally { cmd.getCollectionLock().writeLock().unlock(); } } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#findAllAndRemove(java.lang.String, java.lang.Class) */ @Override public <T> List<T> findAllAndRemove(String jxQuery, Class<T> entityClass) { return findAllAndRemove(jxQuery, Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#findAllAndRemove(java.lang.String, java.lang.String) */ @Override public <T> List<T> findAllAndRemove(String jxQuery, String collectionName) { CollectionMetaData cmd = cmdMap.get(collectionName); @SuppressWarnings("unchecked") Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if ((null == cmd) || (null == collection)) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first."); } cmd.getCollectionLock().writeLock().lock(); try { JXPathContext context = contextsRef.get().get(collectionName); @SuppressWarnings("unchecked") Iterator<T> resultItr = context.iterate(jxQuery); Set<Object> removeIds = new HashSet<Object>(); while (resultItr.hasNext()) { T objectToRemove = resultItr.next(); Object idToRemove = Util.getIdForEntity(objectToRemove, cmd.getIdAnnotatedFieldGetterMethod()); removeIds.add(idToRemove); } if (removeIds.size() < 1) { return null; } JsonWriter jw; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } boolean substractResult = jw.removeFromJsonFile(collection, removeIds); List<T> removedObjects = null; if (substractResult) { removedObjects = new ArrayList<T>(); for (Object id : removeIds) { // Don't need to clone it, this object no more exists in the collection removedObjects.add(collection.remove(id)); } } return removedObjects; } finally { cmd.getCollectionLock().writeLock().unlock(); } } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#findAndModify(java.lang.String, org.jsondb.query.Update, java.lang.Class) */ @Override public <T> T findAndModify(String jxQuery, Update update, Class<T> entityClass) { return findAndModify(jxQuery, update, Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#findAndModify(java.lang.String, org.jsondb.query.Update, java.lang.String) */ @SuppressWarnings("unchecked") @Override public <T> T findAndModify(String jxQuery, Update update, String collectionName) { CollectionMetaData cmd = cmdMap.get(collectionName); Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if ((null == cmd) || (null == collection)) { throw new InvalidJsonDbApiUsageException( "Collection by name '" + collectionName + "' not found. Create collection first."); } cmd.getCollectionLock().writeLock().lock(); try { JXPathContext context = contextsRef.get().get(collectionName); Iterator<T> resultItr = context.iterate(jxQuery); T objectToModify = null; T clonedModifiedObject = null; while (resultItr.hasNext()) { objectToModify = resultItr.next(); break; // Use only the first element we find. } if (null != objectToModify) { //Clone it because we dont want to touch the in-memory object until we have really saved it clonedModifiedObject = (T) Util.deepCopy(objectToModify); for (Entry<String, Object> entry : update.getUpdateData().entrySet()) { Object newValue = Util.deepCopy(entry.getValue()); if (encrypted && cmd.hasSecret() && cmd.isSecretField(entry.getKey())) { newValue = dbConfig.getCipher().encrypt(newValue.toString()); } try { BeanUtils.copyProperty(clonedModifiedObject, entry.getKey(), newValue); } catch (IllegalAccessException | InvocationTargetException e) { Logger.error( "Failed to copy updated data into existing collection document using BeanUtils", e); return null; } } Object idToModify = Util.getIdForEntity(clonedModifiedObject, cmd.getIdAnnotatedFieldGetterMethod()); JsonWriter jw = null; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } boolean updateResult = jw.updateInJsonFile(collection, idToModify, clonedModifiedObject); if (updateResult) { collection.put(idToModify, clonedModifiedObject); //Clone it once more because we want to disconnect it from the in-memory objects before returning. T returnObj = (T) Util.deepCopy(clonedModifiedObject); if (encrypted && cmd.hasSecret() && null != returnObj) { CryptoUtil.decryptFields(returnObj, cmd, dbConfig.getCipher()); } return returnObj; } } return null; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { Logger.error("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e); throw new JsonDBException( "Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e); } finally { cmd.getCollectionLock().writeLock().unlock(); } } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#findAllAndModify(java.lang.String, io.jsondb.query.Update, java.lang.Class) */ @Override public <T> List<T> findAllAndModify(String jxQuery, Update update, Class<T> entityClass) { return findAllAndModify(jxQuery, update, Util.determineCollectionName(entityClass)); } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#findAllAndModify(java.lang.String, io.jsondb.query.Update, java.lang.String) */ @SuppressWarnings("unchecked") @Override public <T> List<T> findAllAndModify(String jxQuery, Update update, String collectionName) { CollectionMetaData cmd = cmdMap.get(collectionName); Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName); if ((null == cmd) || (null == collection)) { throw new InvalidJsonDbApiUsageException("Die Collection mit dem Namen '" + collectionName + "' wurde nicht gefunden. Bitte zuvor anlegen."); } cmd.getCollectionLock().writeLock().lock(); try { JXPathContext context = contextsRef.get().get(collectionName); Iterator<T> resultItr = context.iterate(jxQuery); Map<Object, T> clonedModifiedObjects = new HashMap<Object, T>(); while (resultItr.hasNext()) { T objectToModify = resultItr.next(); T clonedModifiedObject = (T) Util.deepCopy(objectToModify); for (Entry<String, Object> entry : update.getUpdateData().entrySet()) { Object newValue = Util.deepCopy(entry.getValue()); if (encrypted && cmd.hasSecret() && cmd.isSecretField(entry.getKey())) { newValue = dbConfig.getCipher().encrypt(newValue.toString()); } try { BeanUtils.copyProperty(clonedModifiedObject, entry.getKey(), newValue); } catch (IllegalAccessException | InvocationTargetException e) { Logger.error( "Failed to copy updated data into existing collection document using BeanUtils", e); return null; } } Object id = Util.getIdForEntity(clonedModifiedObject, cmd.getIdAnnotatedFieldGetterMethod()); clonedModifiedObjects.put(id, clonedModifiedObject); } JsonWriter jw = null; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } boolean updateResult = jw.updateInJsonFile(collection, clonedModifiedObjects); if (updateResult) { collection.putAll(clonedModifiedObjects); //Clone it once more because we want to disconnect it from the in-memory objects before returning. List<T> returnObjects = new ArrayList<T>(); for (T obj : clonedModifiedObjects.values()) { //Clone it once more because we want to disconnect it from the in-memory objects before returning. T returnObj = (T) Util.deepCopy(obj); if (encrypted && cmd.hasSecret() && null != returnObj) { CryptoUtil.decryptFields(returnObj, cmd, dbConfig.getCipher()); } returnObjects.add(returnObj); } return returnObjects; } return null; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { Logger.error("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e); throw new JsonDBException( "Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e); } finally { cmd.getCollectionLock().writeLock().unlock(); } } /* (non-Javadoc) * @see io.jsondb.JsonDBOperations#changeEncryption(io.jsondb.crypto.ICipher) */ @SuppressWarnings("unchecked") @Override public <T> void changeEncryption(ICipher newCipher) { if (!encrypted) { throw new InvalidJsonDbApiUsageException("DB is not encrypted, nothing to change for EncryptionKey"); } for (Entry<String, Map<Object, ?>> entry : collectionsRef.get().entrySet()) { CollectionMetaData cmd = cmdMap.get(entry.getKey()); if (cmd.hasSecret()) { cmd.getCollectionLock().writeLock().lock(); } } String collectionName = null; try { for (Entry<String, Map<Object, ?>> entry : collectionsRef.get().entrySet()) { collectionName = entry.getKey(); Map<Object, T> collection = (Map<Object, T>) entry.getValue(); CollectionMetaData cmd = cmdMap.get(collectionName); if (cmd.hasSecret()) { Map<Object, T> reCryptedObjects = new LinkedHashMap<Object, T>(); for (Entry<Object, T> object : collection.entrySet()) { T clonedObject = (T) Util.deepCopy(object.getValue()); CryptoUtil.decryptFields(clonedObject, cmd, dbConfig.getCipher()); CryptoUtil.encryptFields(clonedObject, cmd, newCipher); //We will reuse the Id in the previous collection, should hopefully not cause any issues reCryptedObjects.put(object.getKey(), clonedObject); } JsonWriter jw = null; try { jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName)); } catch (IOException ioe) { Logger.error("Failed to obtain writer for " + collectionName, ioe); throw new JsonDBException("Failed to save " + collectionName, ioe); } boolean updateResult = jw.updateInJsonFile(collection, reCryptedObjects); if (!updateResult) { throw new JsonDBException( "Failed to write re-crypted collection data to .json files, database might have become insconsistent"); } collection.putAll(reCryptedObjects); } } dbConfig.setCipher(newCipher); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { Logger.error("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e); throw new JsonDBException( "Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e); } finally { for (Entry<String, Map<Object, ?>> entry : collectionsRef.get().entrySet()) { CollectionMetaData cmd = cmdMap.get(entry.getKey()); if (cmd.hasSecret()) { cmd.getCollectionLock().writeLock().unlock(); } } } } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#backup(java.lang.String) */ @Override public void backup(String backupPath) { // TODO Auto-generated method stub } /* (non-Javadoc) * @see org.jsondb.JsonDBOperations#restore(java.lang.String, boolean) */ @Override public void restore(String restorePath, boolean merge) { // TODO Auto-generated method stub } public void join(String sourceID, String sourceCollection, String destID, String destiCollection) { Object sourceObj = this.findById(sourceID, sourceCollection); Object destObj = this.findById(destID, destiCollection); Class sc = sourceObj.getClass(); Class dc = destObj.getClass(); String scgettermethod = null; String scsettermethod = null; String destCollection = null; String destField = null; String dcgettermethod = null; String dcsettermethod = null; Field[] fields = sc.getDeclaredFields(); for (Field field : fields) { DBRef d = field.getDeclaredAnnotation(DBRef.class); if (d != null) { // destCollection = d.destcollection(); destField = d.destfieldname(); // sourcefield = field.getName(); scgettermethod = "get" + field.getName(); scsettermethod = "set" + field.getName(); Method[] methods = sc.getDeclaredMethods(); for (Method method : methods) { if (method.getName().equalsIgnoreCase(scsettermethod)) { scsettermethod = method.getName(); } if (method.getName().equalsIgnoreCase(scgettermethod)) { scgettermethod = method.getName(); } } } } try { Method s = sc.getDeclaredMethod(scgettermethod, (Class[]) null); String p = (String) s.invoke(sourceObj); Field df = dc.getDeclaredField(destField); dcgettermethod = "get" + df.getName(); dcsettermethod = "set" + df.getName(); Method[] methods = dc.getDeclaredMethods(); for (Method method : methods) { if (method.getName().equalsIgnoreCase(dcsettermethod)) { dcsettermethod = method.getName(); } if (method.getName().equalsIgnoreCase(dcgettermethod)) { dcgettermethod = method.getName(); } } if (p == null) { String rs = UUID.randomUUID().toString(); Method sm = sc.getDeclaredMethod(scsettermethod, rs.getClass()); sm.invoke(sourceObj, rs); Method dm = dc.getDeclaredMethod(dcsettermethod, rs.getClass()); dm.invoke(destObj, rs); } else { Method d = dc.getDeclaredMethod(dcsettermethod, p.getClass()); d.invoke(destObj, p); } this.upsert(sourceObj); this.upsert(destObj); } catch (NoSuchMethodException ex) { Logger.error("NoSuchMethodExeption"); java.util.logging.Logger.getLogger(JsonDBTemplate.class.getName()).log(Level.SEVERE, null, ex); } catch (SecurityException ex) { Logger.error("SecurityException"); java.util.logging.Logger.getLogger(JsonDBTemplate.class.getName()).log(Level.SEVERE, null, ex); } catch (IllegalAccessException ex) { Logger.error("IllegalAccessException"); java.util.logging.Logger.getLogger(JsonDBTemplate.class.getName()).log(Level.SEVERE, null, ex); } catch (IllegalArgumentException ex) { Logger.error("IllegalArgumentException"); java.util.logging.Logger.getLogger(JsonDBTemplate.class.getName()).log(Level.SEVERE, null, ex); } catch (InvocationTargetException ex) { Logger.error("InvokationTargetException"); java.util.logging.Logger.getLogger(JsonDBTemplate.class.getName()).log(Level.SEVERE, null, ex); } catch (NoSuchFieldException ex) { Logger.error("Feld not found"); java.util.logging.Logger.getLogger(JsonDBTemplate.class.getName()).log(Level.SEVERE, null, ex); } this.reloadCollection(destCollection); this.reloadCollection(sourceCollection); } }