• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Jsondb / jsondb-core / e70db7a9-0b5c-4b3e-bcf3-97387c836ba7

24 Jun 2026 09:15PM UTC coverage: 81.262% (+0.7%) from 80.602%
e70db7a9-0b5c-4b3e-bcf3-97387c836ba7

push

circleci

FarooqKhan
Implement zip-based backup() and restore() for JsonDB collections.

Add JsonDbArchive for creating and reading zip backups of collection JSON
files, with replace and merge restore modes plus 13 unit tests.

585 of 700 branches covered (83.57%)

170 of 196 new or added lines in 2 files covered. (86.73%)

1726 of 2124 relevant lines covered (81.26%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.54
/src/main/java/io/jsondb/JsonDBTemplate.java
1
/*
2
 * Copyright (c) 2016 Farooq Khan
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining a copy
5
 * of this software and associated documentation files (the "Software"), to
6
 * deal in the Software without restriction, including without limitation the
7
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8
 * sell copies of the Software, and to permit persons to whom the Software is
9
 * furnished to do so, subject to the following conditions:
10
 *
11
 * The above copyright notice and this permission notice shall be included in
12
 * all copies or substantial portions of the Software.
13
 *
14
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
 */
21
package io.jsondb;
22

23
import java.io.File;
24
import java.io.FileNotFoundException;
25
import java.io.IOException;
26
import java.lang.reflect.InvocationTargetException;
27
import java.lang.reflect.Method;
28
import java.nio.charset.CharacterCodingException;
29
import java.nio.file.Files;
30
import java.util.ArrayList;
31
import java.util.Collection;
32
import java.util.Collections;
33
import java.util.Comparator;
34
import java.util.HashMap;
35
import java.util.HashSet;
36
import java.util.Iterator;
37
import java.util.LinkedHashMap;
38
import java.util.List;
39
import java.util.Map;
40
import java.util.Map.Entry;
41
import java.util.Set;
42
import java.util.concurrent.ConcurrentHashMap;
43
import java.util.concurrent.atomic.AtomicReference;
44

45
import org.apache.commons.beanutils.BeanUtils;
46
import org.apache.commons.jxpath.JXPathContext;
47
import org.slf4j.Logger;
48
import org.slf4j.LoggerFactory;
49

50
import com.fasterxml.jackson.core.JsonParseException;
51
import com.fasterxml.jackson.databind.JsonMappingException;
52

53
import io.jsondb.crypto.CryptoUtil;
54
import io.jsondb.crypto.ICipher;
55
import io.jsondb.events.CollectionFileChangeListener;
56
import io.jsondb.events.EventListenerList;
57
import io.jsondb.io.JsonDbArchive;
58
import io.jsondb.io.JsonFileLockException;
59
import io.jsondb.io.JsonReader;
60
import io.jsondb.io.JsonWriter;
61
import io.jsondb.query.Update;
62
import io.jsondb.query.ddl.AddOperation;
63
import io.jsondb.query.ddl.CollectionSchemaUpdate;
64
import io.jsondb.query.ddl.DeleteOperation;
65
import io.jsondb.query.ddl.RenameOperation;
66

67
/**
68
 * @version 1.0 25-Sep-2016
69
 */
70
public class JsonDBTemplate implements JsonDBOperations {
71
  private Logger logger = LoggerFactory.getLogger(JsonDBTemplate.class);
1✔
72

73
  private JsonDBConfig dbConfig = null;
1✔
74
  private final boolean encrypted;
75
  private File lockFilesLocation;
76
  private EventListenerList eventListenerList;
77

78
  private Map<String, CollectionMetaData> cmdMap;
79
  private AtomicReference<Map<String, File>> fileObjectsRef = new AtomicReference<Map<String, File>>(new ConcurrentHashMap<String, File>());
1✔
80
  private AtomicReference<Map<String, Map<Object, ?>>> collectionsRef = new AtomicReference<Map<String, Map<Object, ?>>>(new ConcurrentHashMap<String, Map<Object, ?>>());
1✔
81
  private AtomicReference<Map<String, JXPathContext>> contextsRef = new AtomicReference<Map<String, JXPathContext>>(new ConcurrentHashMap<String, JXPathContext>());
1✔
82

83
  public JsonDBTemplate(String dbFilesLocationString, String baseScanPackage) {
84
    this(dbFilesLocationString, baseScanPackage, null, false, null);
1✔
85
  }
1✔
86

87
  public JsonDBTemplate(String dbFilesLocationString, String baseScanPackage, boolean compatibilityMode, Comparator<String> schemaComparator) {
88
    this(dbFilesLocationString, baseScanPackage, null, compatibilityMode, schemaComparator);
1✔
89
  }
1✔
90

91
  public JsonDBTemplate(String dbFilesLocationString, String baseScanPackage, ICipher cipher) {
92
    this(dbFilesLocationString, baseScanPackage, cipher, false, null);
1✔
93
  }
1✔
94

95
  public JsonDBTemplate(String dbFilesLocationString, String baseScanPackage, ICipher cipher, boolean compatibilityMode, Comparator<String> schemaComparator) {
1✔
96
    dbConfig = new JsonDBConfig(dbFilesLocationString, baseScanPackage, cipher, compatibilityMode, schemaComparator);
1✔
97
    if (null == cipher) {
1✔
98
      logger.info("Encryption is not enabled for JSON DB");
1✔
99
      this.encrypted = false;
1✔
100
    } else {
101
      logger.info("Encryption is enabled for JSON DB");
1✔
102
      this.encrypted = true;
1✔
103
    }
104
    initialize();
1✔
105
    eventListenerList = new EventListenerList(dbConfig, cmdMap);
1✔
106
  }
1✔
107

108
  private void initialize(){
109
    this.lockFilesLocation = new File(dbConfig.getDbFilesLocation(), "lock");
1✔
110
    if(!lockFilesLocation.exists()) {
1✔
111
      lockFilesLocation.mkdirs();
1✔
112
    }
113
    if (!dbConfig.getDbFilesLocation().exists()) {
1!
114
      try {
115
        Files.createDirectory(dbConfig.getDbFilesPath());
×
116
      } catch (IOException e) {
×
117
        logger.error("DbFiles directory does not exist. Failed to create a new empty DBFiles directory {}", e);
×
118
        throw new InvalidJsonDbApiUsageException("DbFiles directory does not exist. Failed to create a new empty DBFiles directory " + dbConfig.getDbFilesLocationString());
×
119
      }
×
120
    } else if (dbConfig.getDbFilesLocation().isFile()) {
1✔
121
      throw new InvalidJsonDbApiUsageException("Specified DbFiles directory is actually a file cannot use it as a directory");
1✔
122
    }
123

124
    cmdMap = CollectionMetaData.builder(dbConfig);
1✔
125

126
    loadDB();
1✔
127

128
    // Auto-cleanup at shutdown
129
    Runtime.getRuntime().addShutdownHook(new Thread() {
1✔
130
      @Override
131
      public void run() {
132
        eventListenerList.shutdown();
1✔
133
      }
1✔
134
    });
135
  }
1✔
136

137
  /* (non-Javadoc)
138
   * @see io.jsondb.JsonDBOperations#reLoadDB()
139
   */
140
  @Override
141
  public void reLoadDB() {
142
    loadDB();
1✔
143
  }
1✔
144

145
  private synchronized void loadDB() {
146
    for(String collectionName : cmdMap.keySet()) {
1✔
147
      File collectionFile = new File(dbConfig.getDbFilesLocation(), collectionName + ".json");
1✔
148
      if(collectionFile.exists()) {
1✔
149
        reloadCollection(collectionName);
1✔
150
      } else if (collectionsRef.get().containsKey(collectionName)){
1✔
151
        //this probably is a reload attempt after a collection .json was deleted.
152
        //that is the reason even though the file does not exist a entry into collectionsRef still exists.
153
        contextsRef.get().remove(collectionName);
1✔
154
        collectionsRef.get().remove(collectionName);
1✔
155
      }
156
    }
1✔
157
  }
1✔
158

159
  /* (non-Javadoc)
160
   * @see io.jsondb.JsonDBOperations#reloadCollection(java.lang.String)
161
   */
162
  public void reloadCollection(String collectionName) {
163
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
164
    cmd.getCollectionLock().writeLock().lock();
1✔
165
    try {
166
      File collectionFile = fileObjectsRef.get().get(collectionName);
1✔
167
      if(null == collectionFile) {
1✔
168
        // Lets create a file now
169
        collectionFile = new File(dbConfig.getDbFilesLocation(), collectionName + ".json");
1✔
170
        if(!collectionFile.exists()) {
1!
171
          throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' cannot be found at " + collectionFile.getAbsolutePath());
×
172
        }
173
        Map<String, File> fileObjectMap = fileObjectsRef.get();
1✔
174
        Map<String, File> newFileObjectmap = new ConcurrentHashMap<String, File>(fileObjectMap);
1✔
175
        newFileObjectmap.put(collectionName, collectionFile);
1✔
176
        fileObjectsRef.set(newFileObjectmap);
1✔
177
      }
178
      Map<Object, ?> collection = loadCollection(collectionFile, collectionName, cmd);
1✔
179
      if (null != collection) {
1✔
180
        JXPathContext newContext = JXPathContext.newContext(collection.values());
1✔
181
        contextsRef.get().put(collectionName, newContext);
1✔
182
        collectionsRef.get().put(collectionName, collection);
1✔
183
      } else {
1✔
184
        //Since this is a reload attempt its possible the .json files have disappeared in the interim a very rare thing
185
        contextsRef.get().remove(collectionName);
1✔
186
        collectionsRef.get().remove(collectionName);
1✔
187
      }
188
    } finally {
189
      cmd.getCollectionLock().writeLock().unlock();
1✔
190
    }
191
  }
1✔
192

193
  private <T> Map<Object, T> loadCollection(File collectionFile, String collectionName, CollectionMetaData cmd) {
194
    @SuppressWarnings("unchecked")
195
    Class<T> entity = cmd.getClazz();
1✔
196
    Method getterMethodForId = cmd.getIdAnnotatedFieldGetterMethod();
1✔
197

198
    JsonReader jr = null;
1✔
199
    Map<Object, T> collection = new LinkedHashMap<Object, T>();
1✔
200

201
    String line = null;
1✔
202
    int lineNo = 1;
1✔
203
    try {
204
      jr = new JsonReader(dbConfig, collectionFile);
1✔
205

206
      while ((line = jr.readLine()) != null) {
1✔
207
        if (lineNo == 1) {
1✔
208
          SchemaVersion v = dbConfig.getObjectMapper().readValue(line, SchemaVersion.class);
1✔
209
          cmd.setActualSchemaVersion(v.getSchemaVersion());
1✔
210
        } else {
1✔
211
          T row = dbConfig.getObjectMapper().readValue(line, entity);
1✔
212
          Object id = Util.getIdForEntity(row, getterMethodForId);
1✔
213
          collection.put(id, row);
1✔
214
        }
215
        lineNo++;
1✔
216
      }
217
    } catch (JsonParseException je) {
1✔
218
      logger.error("Failed Json Parsing for file {} line {}", collectionFile.getName(), lineNo, je);
1✔
219
      return null;
1✔
220
    } catch (JsonMappingException jm) {
1✔
221
      logger.error("Failed Mapping Parsed Json to Entity {} for file {} line {}",
1✔
222
          entity.getSimpleName(), collectionFile.getName(), lineNo, jm);
1✔
223
      return null;
1✔
224
    } catch (CharacterCodingException ce) {
×
225
      logger.error("Unsupported Character Encoding in file {} expected Encoding {}",
×
226
          collectionFile.getName(), dbConfig.getCharset().displayName(), ce);
×
227
      return null;
×
228
    } catch (JsonFileLockException jfe) {
×
229
      logger.error("Failed to acquire lock for collection file {}", collectionFile.getName(), jfe);
×
230
      return null;
×
231
    } catch (FileNotFoundException fe) {
1✔
232
      logger.error("Collection file {} not found", collectionFile.getName(), fe);
1✔
233
      return null;
1✔
234
    } catch (IOException e) {
×
235
      logger.error("Some IO Exception reading the Json File {}", collectionFile.getName(), e);
×
236
      return null;
×
237
    } catch(Throwable t) {
×
238
      logger.error("Throwable Caught {}, {} ", collectionFile.getName(), t);
×
239
      return null;
×
240
    } finally {
241
      if (null != jr) {
1✔
242
        jr.close();
1✔
243
      }
244
    }
245
    return collection;
1✔
246
  }
247

248
  /* (non-Javadoc)
249
   * @see org.jsondb.JsonDBOperations#addCollectionFileChangeListener(org.jsondb.CollectionFileChangeListener)
250
   */
251
  @Override
252
  public void addCollectionFileChangeListener(CollectionFileChangeListener listener) {
253
    eventListenerList.addCollectionFileChangeListener(listener);
1✔
254
  }
1✔
255

256
  /* (non-Javadoc)
257
   * @see org.jsondb.JsonDBOperations#removeCollectionFileChangeListener(org.jsondb.CollectionFileChangeListener)
258
   */
259
  @Override
260
  public void removeCollectionFileChangeListener(CollectionFileChangeListener listener) {
261
    eventListenerList.removeCollectionFileChangeListener(listener);
1✔
262
  }
1✔
263

264
  /* (non-Javadoc)
265
   * @see io.jsondb.JsonDBOperations#hasCollectionFileChangeListener()
266
   */
267
  @Override
268
  public boolean hasCollectionFileChangeListener() {
269
    return eventListenerList.hasCollectionFileChangeListener();
1✔
270
  }
271

272
  /* (non-Javadoc)
273
   * @see io.jsondb.JsonDBOperations#createCollection(java.lang.Class)
274
   */
275
  @Override
276
  public <T> void createCollection(Class<T> entityClass) {
277
    createCollection(Util.determineCollectionName(entityClass));
1✔
278
  }
1✔
279

280
  /* (non-Javadoc)
281
   * @see io.jsondb.JsonDBOperations#createCollection(java.lang.String)
282
   */
283
  @Override
284
  public <T> void createCollection(String collectionName) {
285
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
286
    if (null == cmd) {
1✔
287
      throw new InvalidJsonDbApiUsageException(
1✔
288
          "No class found with @Document Annotation and attribute collectionName as: " + collectionName);
289
    }
290
    @SuppressWarnings("unchecked")
291
    Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
292
    if (null != collection) {
1✔
293
      throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' already exists.");
1✔
294
    }
295

296
    cmd.getCollectionLock().writeLock().lock();
1✔
297

298
    // Some other thread might have created same collection when this thread reached this point
299
    if(collectionsRef.get().get(collectionName) != null) {
1!
300
      return;
×
301
    }
302

303
    try {
304
      String collectionFileName = collectionName + ".json";
1✔
305
      File fileObject = new File(dbConfig.getDbFilesLocation(), collectionFileName);
1✔
306
      try {
307
        fileObject.createNewFile();
1✔
308
      } catch (IOException e) {
×
309
        logger.error("IO Exception creating the collection file {}", collectionFileName, e);
×
310
        throw new InvalidJsonDbApiUsageException("Unable to create a collection file for collection: " + collectionName);
×
311
      }
1✔
312

313
      if (Util.stampVersion(dbConfig, fileObject, cmd.getSchemaVersion())) {
1!
314
        collection = new LinkedHashMap<Object, T>();
1✔
315
        collectionsRef.get().put(collectionName, collection);
1✔
316
        contextsRef.get().put(collectionName, JXPathContext.newContext(collection.values())) ;
1✔
317
        fileObjectsRef.get().put(collectionName, fileObject);
1✔
318
        cmd.setActualSchemaVersion(cmd.getSchemaVersion());
1✔
319
      } else {
320
        fileObject.delete();
×
321
        throw new JsonDBException("Failed to stamp version for collection: " + collectionName);
×
322
      }
323
    } finally {
324
      cmd.getCollectionLock().writeLock().unlock();
1✔
325
    }
326
  }
1✔
327

328
  /* (non-Javadoc)
329
   * @see io.jsondb.JsonDBOperations#dropCollection(java.lang.Class)
330
   */
331
  @Override
332
  public <T> void dropCollection(Class<T> entityClass) {
333
    dropCollection(Util.determineCollectionName(entityClass));
1✔
334
  }
1✔
335

336
  /* (non-Javadoc)
337
   * @see io.jsondb.JsonDBOperations#dropCollection(java.lang.String)
338
   */
339
  @Override
340
  public void dropCollection(String collectionName) {
341
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
342
    if((null == cmd) || (!collectionsRef.get().containsKey(collectionName))) {
1!
343
      throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first.");
1✔
344
    }
345
    cmd.getCollectionLock().writeLock().lock();
1✔
346
    try {
347
      File toDelete = fileObjectsRef.get().get(collectionName);
1✔
348
      try {
349
        Files.deleteIfExists(toDelete.toPath());
1✔
350
      } catch (IOException e) {
×
351
        logger.error("IO Exception deleting the collection file {}", toDelete.getName(), e);
×
352
        throw new InvalidJsonDbApiUsageException("Unable to create a collection file for collection: " + collectionName);
×
353
      }
1✔
354
      //cmdMap.remove(collectionName); //Do not remove it from the CollectionMetaData Map.
355
      //Someone might want to re insert a new collection of this type.
356
      fileObjectsRef.get().remove(collectionName);
1✔
357
      collectionsRef.get().remove(collectionName);
1✔
358
      contextsRef.get().remove(collectionName);
1✔
359
    } finally {
360
      cmd.getCollectionLock().writeLock().unlock();
1✔
361
    }
362
  }
1✔
363

364
  /* (non-Javadoc)
365
   * @see org.jsondb.JsonDBOperations#updateCollectionSchema(org.jsondb.query.CollectionSchemaUpdate, java.lang.Class)
366
   */
367
  @Override
368
  public <T> void updateCollectionSchema(CollectionSchemaUpdate update, Class<T> entityClass) {
369
    updateCollectionSchema(update, Util.determineCollectionName(entityClass));
1✔
370
  }
1✔
371

372
  /* (non-Javadoc)
373
   * @see org.jsondb.JsonDBOperations#updateCollectionSchema(org.jsondb.query.CollectionSchemaUpdate, java.lang.String)
374
   */
375
  @Override
376
  public <T> void updateCollectionSchema(CollectionSchemaUpdate update, String collectionName) {
377
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
378
    @SuppressWarnings("unchecked")
379
    Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
380
    if((null == cmd) || (null == collection)) {
1!
381
      throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first.");
1✔
382
    }
383
    boolean reloadCollectionAsSomethingChanged = false;
1✔
384
    //We only take care of ADD and RENAME, the deletes will be taken care of automatically.
385
    if (null != update) {
1!
386
      Map<String, RenameOperation> renOps = update.getRenameOperations();
1✔
387
      if (renOps.size() > 0) {
1✔
388
        reloadCollectionAsSomethingChanged = true;
1✔
389
        cmd.getCollectionLock().writeLock().lock();
1✔
390
        
391
        for(Entry<String, RenameOperation> updateEntry: renOps.entrySet()) {
1✔
392
          String oldKey = updateEntry.getKey();
1✔
393
          
394
          RenameOperation op = updateEntry.getValue();
1✔
395
          String newKey = op.getNewName();
1✔
396

397
          JsonWriter jw;
398
          try {
399
            jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
400
          } catch (IOException ioe) {
×
401
            logger.error("Failed to obtain writer for " + collectionName, ioe);
×
402
            throw new JsonDBException("Failed to save " + collectionName, ioe);
×
403
          }
1✔
404
          jw.renameKeyInJsonFile(collection.values(), true, oldKey, newKey);
1✔
405
        }
1✔
406
        cmd.getCollectionLock().writeLock().unlock();
1✔
407
      }
408
      
409
      Map<String, AddOperation> addOps = update.getAddOperations();
1✔
410
      if (addOps.size() > 0) {
1✔
411
        reloadCollectionAsSomethingChanged = true;
1✔
412
        cmd.getCollectionLock().writeLock().lock();
1✔
413
        
414
        for(Entry<String, AddOperation> updateEntry: addOps.entrySet()) {
1✔
415
          AddOperation op = updateEntry.getValue();
1✔
416
          
417
          Object value = null;
1✔
418
          if (op.isSecret()) {
1!
419
            value = dbConfig.getCipher().encrypt((String)op.getDefaultValue());
×
420
          } else {
421
            value = op.getDefaultValue();
1✔
422
          }
423
          
424
          String fieldName = updateEntry.getKey();
1✔
425
          Method setterMethod = cmd.getSetterMethodForFieldName(fieldName);
1✔
426
          for(T object : collection.values()) {
1✔
427
            Util.setFieldValueForEntity(object, value, setterMethod);
1✔
428
          }
1✔
429
        }
1✔
430
        
431
        JsonWriter jw;
432
        try {
433
          jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
434
        } catch (IOException ioe) {
×
435
          logger.error("Failed to obtain writer for " + collectionName, ioe);
×
436
          throw new JsonDBException("Failed to save " + collectionName, ioe);
×
437
        }
1✔
438
        jw.reWriteJsonFile(collection.values(), true);
1✔
439
        cmd.getCollectionLock().writeLock().unlock();
1✔
440
      }
441
      
442
      Map<String, DeleteOperation> delOps = update.getDeleteOperations();
1✔
443
      if ((renOps.size() < 1 && addOps.size() < 1) && (delOps.size() > 0)) {
1!
444
        //There were no ADD operations but there are some DELETE operations so we have to just flush the collection once
445
        //This would not have been necessary if there was even 1 ADD operation
446
        
447
        reloadCollectionAsSomethingChanged = true;
1✔
448
        cmd.getCollectionLock().writeLock().lock();
1✔
449
        
450
        JsonWriter jw;
451
        try {
452
          jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
453
        } catch (IOException ioe) {
×
454
          logger.error("Failed to obtain writer for " + collectionName, ioe);
×
455
          throw new JsonDBException("Failed to save " + collectionName, ioe);
×
456
        }
1✔
457
        jw.reWriteJsonFile(collection.values(), true);
1✔
458
        cmd.getCollectionLock().writeLock().unlock();
1✔
459
      }
460
      if (reloadCollectionAsSomethingChanged) {
1!
461
        reloadCollection(collectionName);
1✔
462
      }
463
    }
464
  }
1✔
465

466
  /* (non-Javadoc)
467
   * @see io.jsondb.JsonDBOperations#getCollectionNames()
468
   */
469
  @Override
470
  public Set<String> getCollectionNames() {
471
    return collectionsRef.get().keySet();
1✔
472
  }
473

474
  /* (non-Javadoc)
475
   * @see io.jsondb.JsonDBOperations#getCollectionName(java.lang.Class)
476
   */
477
  @Override
478
  public String getCollectionName(Class<?> entityClass) {
479
    return Util.determineCollectionName(entityClass);
1✔
480
  }
481

482
  /* (non-Javadoc)
483
   * @see io.jsondb.JsonDBOperations#getCollection(java.lang.Class)
484
   */
485
  @SuppressWarnings("unchecked")
486
  @Override
487
  public <T> List<T> getCollection(Class<T> entityClass) {
488
    String collectionName = Util.determineCollectionName(entityClass);
1✔
489
    Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
490
    if (null == collection) {
1✔
491
      createCollection(collectionName);
1✔
492
      collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
493
    }
494

495
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
496
    List<T> newCollection = new ArrayList<T>();
1✔
497
    try {
498
      for (T document : collection.values()) {
1✔
499
        Object obj = Util.deepCopy(document);
1✔
500
        if(encrypted && cmd.hasSecret() && null != obj) {
1!
501
          CryptoUtil.decryptFields(obj, cmd, dbConfig.getCipher());
1✔
502
        }
503
        newCollection.add((T) obj);
1✔
504
      }
1✔
505
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
×
506
      logger.error("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
507
      throw new JsonDBException("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
508
    }
1✔
509
    return newCollection;
1✔
510
  }
511

512
  /* (non-Javadoc)
513
   * @see io.jsondb.JsonDBOperations#collectionExists(java.lang.Class)
514
   */
515
  @Override
516
  public <T> boolean collectionExists(Class<T> entityClass) {
517
    return collectionExists(Util.determineCollectionName(entityClass));
1✔
518
  }
519

520
  /* (non-Javadoc)
521
   * @see io.jsondb.JsonDBOperations#collectionExists(java.lang.String)
522
   */
523
  @Override
524
  public boolean collectionExists(String collectionName) {
525
    CollectionMetaData collectionMeta = cmdMap.get(collectionName);
1✔
526
    if(null == collectionMeta) {
1✔
527
      return false;
1✔
528
    }
529
    collectionMeta.getCollectionLock().readLock().lock();
1✔
530
    try {
531
      return collectionsRef.get().containsKey(collectionName);
1✔
532
    } finally {
533
      collectionMeta.getCollectionLock().readLock().unlock();
1✔
534
    }
535
  }
536

537
  /* (non-Javadoc)
538
   * @see io.jsondb.JsonDBOperations#isCollectionReadonly(java.lang.Class)
539
   */
540
  @Override
541
  public <T> boolean isCollectionReadonly(Class<T> entityClass) {
542
    return isCollectionReadonly(Util.determineCollectionName(entityClass));
1✔
543
  }
544

545
  /* (non-Javadoc)
546
   * @see io.jsondb.JsonDBOperations#isCollectionReadonly(java.lang.String)
547
   */
548
  @Override
549
  public <T> boolean isCollectionReadonly(String collectionName) {
550
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
551
    return cmd.isReadOnly();
1✔
552
  }
553

554
  /* (non-Javadoc)
555
   * @see io.jsondb.JsonDBOperations#find(java.lang.String, java.lang.Class)
556
   */
557
  @Override
558
  public <T> List<T> find(String jxQuery, Class<T> entityClass) {
559
    return find(jxQuery, Util.determineCollectionName(entityClass));
1✔
560
  }
561

562
  /* (non-Javadoc)
563
   * @see io.jsondb.JsonDBOperations#find(java.lang.String, java.lang.String)
564
   */
565
  @Override
566
  public <T> List<T> find(String jxQuery, String collectionName) {
567
    return find(jxQuery, collectionName, null);
1✔
568
  }
569

570
  /* (non-Javadoc)
571
   * @see io.jsondb.JsonDBOperations#find(java.lang.String, java.lang.String, java.util.Comparator)
572
   */
573
  @Override
574
  public <T> List<T> find(String jxQuery, Class<T> entityClass, Comparator<? super T> comparator) {
575
    return find(jxQuery, Util.determineCollectionName(entityClass), comparator);
1✔
576
  }
577

578
  /* (non-Javadoc)
579
   * @see io.jsondb.JsonDBOperations#find(java.lang.String, java.lang.String, java.util.Comparator)
580
   */
581
  @Override
582
  public <T> List<T> find(String jxQuery, String collectionName, Comparator<? super T> comparator) {
583
    return find(jxQuery, collectionName, comparator, null);
1✔
584
  }
585

586
  /* (non-Javadoc)
587
   * @see io.jsondb.JsonDBOperations#find(java.lang.String, java.lang.String, java.util.Comparator, java.lang.String)
588
   */
589
  @Override
590
  public <T> List<T> find(String jxQuery, Class<T> entityClass, Comparator<? super T> comparator, String slice) {
591
    return find(jxQuery, Util.determineCollectionName(entityClass), comparator, slice);
1✔
592
  }
593

594
  /* (non-Javadoc)
595
   * @see io.jsondb.JsonDBOperations#find(java.lang.String, java.lang.String, java.util.Comparator, java.lang.String)
596
   */
597
  @Override
598
  public <T> List<T> find(String jxQuery, String collectionName, Comparator<? super T> comparator, String slice) {
599
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
600
    Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
601
    if((null == cmd) || (null == collection)) {
1!
602
      throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first.");
×
603
    }
604
    cmd.getCollectionLock().readLock().lock();
1✔
605
    boolean isSliceable = Util.isSliceable(slice);
1✔
606
    try {
607
      JXPathContext context = contextsRef.get().get(collectionName);
1✔
608
      Iterator<T> resultItr = context.iterate(jxQuery);
1✔
609
      List<T> newCollection = new ArrayList<T>();
1✔
610
      while (resultItr.hasNext()) {
1✔
611
        T document = resultItr.next();
1✔
612
        if (isSliceable) {
1✔
613
          //Since slicing is enabled we defer the deepcopy and decryption to later stage.
614
          newCollection.add(document);
1✔
615
        } else {
616
          Object obj = Util.deepCopy(document);
1✔
617
          if (encrypted && cmd.hasSecret() && null != obj) {
1!
618
            CryptoUtil.decryptFields(obj, cmd, dbConfig.getCipher());
1✔
619
          }
620
          newCollection.add((T) obj);
1✔
621
        }
622
      }
1✔
623
      if (comparator != null) {
1✔
624
        // It is tempting to attempt to sort the objects in the while loop above, but it has no real benefit
625
        // See: https://stackoverflow.com/questions/24136930/sort-while-inserting-or-copy-and-sort
626
        newCollection.sort(comparator);
1✔
627
      }
628
      if (isSliceable) {
1✔
629
        List<Integer> indexes = Util.getSliceIndexes(slice, newCollection.size());
1✔
630
        if (indexes != null) {
1!
631
          List<T> slicedCollection = new ArrayList<T>(indexes.size());
1✔
632
          for (int index : indexes) {
1✔
633
            Object obj = Util.deepCopy(newCollection.get(index));
1✔
634
            if (encrypted && cmd.hasSecret() && null != obj) {
1!
635
              CryptoUtil.decryptFields(obj, cmd, dbConfig.getCipher());
1✔
636
            }
637
            slicedCollection.add((T) obj);
1✔
638
          }
1✔
639
          return slicedCollection;
1✔
640
        }
641
      }
642
      return newCollection;
1✔
643
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
×
644
      logger.error("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
645
      throw new JsonDBException("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
646
    } finally {
647
      cmd.getCollectionLock().readLock().unlock();
1✔
648
    }
649
  }
650

651
  /* (non-Javadoc)
652
   * @see io.jsondb.JsonDBOperations#findAll(java.lang.Class)
653
   */
654
  @Override
655
  public <T> List<T> findAll(Class<T> entityClass) {
656
    return findAll(Util.determineCollectionName(entityClass));
1✔
657
  }
658

659
  /* (non-Javadoc)
660
   * @see io.jsondb.JsonDBOperations#findAll(java.lang.String)
661
   */
662
  @SuppressWarnings("unchecked")
663
  @Override
664
  public <T> List<T> findAll(String collectionName) {
665
    return findAll(collectionName, null);
1✔
666
  }
667

668
  /* (non-Javadoc)
669
   * @see io.jsondb.JsonDBOperations#findAll(java.lang.Class, java.util.Comparator)
670
   */
671
  @Override
672
  public <T> List<T> findAll(Class<T> entityClass, Comparator<? super T> comparator) {
673
    return findAll(Util.determineCollectionName(entityClass), comparator);
1✔
674
  }
675

676
  /* (non-Javadoc)
677
   * @see io.jsondb.JsonDBOperations#findAll(java.lang.String, java.util.Comparator)
678
   */
679
  @Override
680
  public <T> List<T> findAll(String collectionName, Comparator<? super T> comparator) {
681
    return findAll(collectionName, comparator, null);
1✔
682
  }
683

684
  /* (non-Javadoc)
685
   * @see io.jsondb.JsonDBOperations#findAll(java.lang.Class, java.util.Comparator, java.lang.String)
686
   */
687
  @Override
688
  public <T> List<T> findAll(Class<T> entityClass, Comparator<? super T> comparator, String slice) {
689
    return findAll(Util.determineCollectionName(entityClass), comparator, slice);
1✔
690
  }
691

692
  /* (non-Javadoc)
693
   * @see io.jsondb.JsonDBOperations#findAll(java.lang.String, java.util.Comparator, java.lang.String)
694
   */
695
  @SuppressWarnings("unchecked")
696
  @Override
697
  public <T> List<T> findAll(String collectionName, Comparator<? super T> comparator, String slice) {
698
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
699
    Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
700
    if((null == cmd) || (null == collection)) {
1!
701
      throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first.");
1✔
702
    }
703
    cmd.getCollectionLock().readLock().lock();
1✔
704
    boolean isSliceable = Util.isSliceable(slice);
1✔
705
    try {
706
      List<T> newCollection = new ArrayList<T>();
1✔
707
      for (T document : collection.values()) {
1✔
708
        if (isSliceable) {
1✔
709
          //Since slicing is enabled we defer the deepcopy and decryption to later stage.
710
          newCollection.add(document);
1✔
711
        } else {
712
          T obj = (T)Util.deepCopy(document);
1✔
713
          if (encrypted && cmd.hasSecret() && null != obj) {
1!
714
            CryptoUtil.decryptFields(obj, cmd, dbConfig.getCipher());
1✔
715
          }
716
          newCollection.add((T) obj);
1✔
717
        }
718
      }
1✔
719
      if (comparator != null) {
1✔
720
        // It is tempting to attempt to sort the obejcts in the while loop above, but it has no real benefit
721
        // See: https://stackoverflow.com/questions/24136930/sort-while-inserting-or-copy-and-sort
722
        newCollection.sort(comparator);
1✔
723
      }
724
      if (isSliceable) {
1✔
725
        List<Integer> indexes = Util.getSliceIndexes(slice, newCollection.size());
1✔
726
        if (indexes != null) {
1!
727
          List<T> slicedCollection = new ArrayList<T>(indexes.size());
1✔
728
          for (int index : indexes) {
1✔
729
            Object obj = Util.deepCopy(newCollection.get(index));
1✔
730
            if (encrypted && cmd.hasSecret() && null != obj) {
1!
731
              CryptoUtil.decryptFields(obj, cmd, dbConfig.getCipher());
1✔
732
            }
733
            slicedCollection.add((T) obj);
1✔
734
          }
1✔
735
          return slicedCollection;
1✔
736
        }
737
      }
738
      return newCollection;
1✔
739
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
×
740
      logger.error("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
741
      throw new JsonDBException("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
742
    } finally {
743
      cmd.getCollectionLock().readLock().unlock();
1✔
744
    }
745
  }
746

747
  /* (non-Javadoc)
748
   * @see io.jsondb.JsonDBOperations#findById(java.lang.Object, java.lang.Class)
749
   */
750
  @Override
751
  public <T> T findById(Object id, Class<T> entityClass) {
752
    return findById(id, Util.determineCollectionName(entityClass));
1✔
753
  }
754

755
  /* (non-Javadoc)
756
   * @see io.jsondb.JsonDBOperations#findById(java.lang.Object, java.lang.String)
757
   */
758
  @SuppressWarnings("unchecked")
759
  @Override
760
  public <T> T findById(Object id, String collectionName) {
761
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
762
    Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
763
    if((null == cmd) || null == collection) {
1!
764
      throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first.");
1✔
765
    }
766
    cmd.getCollectionLock().readLock().lock();
1✔
767
    try {
768
      Object obj = Util.deepCopy(collection.get(id));
1✔
769
      if(encrypted && cmd.hasSecret() && null != obj){
1!
770
        CryptoUtil.decryptFields(obj, cmd, dbConfig.getCipher());
1✔
771
      }
772
      return (T) obj;
1✔
773
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
×
774
      logger.error("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
775
      throw new JsonDBException("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
776
    } finally {
777
      cmd.getCollectionLock().readLock().unlock();
1✔
778
    }
779
  }
780

781
  /* (non-Javadoc)
782
   * @see io.jsondb.JsonDBOperations#findOne(java.lang.String, java.lang.Class)
783
   */
784
  @Override
785
  public <T> T findOne(String jxQuery, Class<T> entityClass) {
786
    return findOne(jxQuery, Util.determineCollectionName(entityClass));
1✔
787
  }
788

789
  /* (non-Javadoc)
790
   * @see io.jsondb.JsonDBOperations#findOne(java.lang.String, java.lang.String)
791
   */
792
  @SuppressWarnings("unchecked")
793
  @Override
794
  public <T> T findOne(String jxQuery, String collectionName) {
795
    CollectionMetaData collectionMeta = cmdMap.get(collectionName);
1✔
796
    if((null == collectionMeta) || (!collectionsRef.get().containsKey(collectionName))) {
1!
797
      throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first");
1✔
798
    }
799
    collectionMeta.getCollectionLock().readLock().lock();
1✔
800
    try {
801
      JXPathContext context = contextsRef.get().get(collectionName);
1✔
802
      Iterator<T> resultItr = context.iterate(jxQuery);
1✔
803
      while (resultItr.hasNext()) {
1✔
804
        T document = resultItr.next();
1✔
805
        Object obj = Util.deepCopy(document);
1✔
806
        if(encrypted && collectionMeta.hasSecret() && null!= obj){
1!
807
          CryptoUtil.decryptFields(obj, collectionMeta, dbConfig.getCipher());
1✔
808
        }
809
        return (T) obj; // Return the first element we find.
1✔
810
      }
811
      return null;
1✔
812
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
×
813
      logger.error("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
814
      throw new JsonDBException("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
815
    } finally {
816
      collectionMeta.getCollectionLock().readLock().unlock();
1✔
817
    }
818
  }
819

820
  /* (non-Javadoc)
821
   * @see io.jsondb.JsonDBOperations#insert(java.lang.Object)
822
   */
823
  @Override
824
  public <T> void insert(Object objectToSave) {
825
    if (null == objectToSave) {
1✔
826
      throw new InvalidJsonDbApiUsageException("Null Object cannot be inserted into DB");
1✔
827
    }
828
    Util.ensureNotRestricted(objectToSave);
1✔
829
    insert(objectToSave, Util.determineEntityCollectionName(objectToSave));
1✔
830
  }
1✔
831

832
  /* (non-Javadoc)
833
   * @see io.jsondb.JsonDBOperations#insert(java.lang.Object, java.lang.String)
834
   */
835
  @SuppressWarnings("unchecked")
836
  @Override
837
  public <T> void insert(Object objectToSave, String collectionName) {
838
    if (null == objectToSave) {
1✔
839
      throw new InvalidJsonDbApiUsageException("Null Object cannot be inserted into DB");
1✔
840
    }
841
    Util.ensureNotRestricted(objectToSave);
1✔
842
    Object objToSave = Util.deepCopy(objectToSave);
1✔
843
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
844
    cmd.getCollectionLock().writeLock().lock();
1✔
845
    try {
846
      Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
847
      if (null == collection) {
1✔
848
        throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first");
1✔
849
      }
850
      Object id = Util.getIdForEntity(objectToSave, cmd.getIdAnnotatedFieldGetterMethod());
1✔
851
      if(encrypted && cmd.hasSecret()){
1✔
852
        CryptoUtil.encryptFields(objToSave, cmd, dbConfig.getCipher());
1✔
853
      }
854
      if (null == id) {
1✔
855
        id = Util.setIdForEntity(objToSave, cmd.getIdAnnotatedFieldSetterMethod());
1✔
856
      } else if (collection.containsKey(id)) {
1✔
857
        throw new InvalidJsonDbApiUsageException("Object already present in Collection. Use Update or Upsert operation instead of Insert");
1✔
858
      }
859

860
      JsonWriter jw;
861
      try {
862
        jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
863
      } catch (IOException ioe) {
×
864
        logger.error("Failed to obtain writer for " + collectionName, ioe);
×
865
        throw new JsonDBException("Failed to save " + collectionName, ioe);
×
866
      }
1✔
867

868
      boolean appendResult = jw.appendToJsonFile(collection.values(), objToSave);
1✔
869

870
      if(appendResult) {
1!
871
        collection.put(Util.deepCopy(id), (T) objToSave);
1✔
872
      }
873
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
×
874
      logger.error("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
875
      throw new JsonDBException("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
876
    } finally {
877
      cmd.getCollectionLock().writeLock().unlock();
1✔
878
    }
879
  }
1✔
880

881
  /* (non-Javadoc)
882
   * @see io.jsondb.JsonDBOperations#insert(java.util.Collection, java.lang.Class)
883
   */
884
  @Override
885
  public <T> void insert(Collection<? extends T> batchToSave, Class<T> entityClass) {
886
    insert(batchToSave, Util.determineCollectionName(entityClass));
1✔
887
  }
1✔
888

889
  /* (non-Javadoc)
890
   * @see io.jsondb.JsonDBOperations#insert(java.util.Collection, java.lang.String)
891
   */
892
  @SuppressWarnings("unchecked")
893
  @Override
894
  public <T> void insert(Collection<? extends T> batchToSave, String collectionName) {
895
    if (null == batchToSave) {
1✔
896
      throw new InvalidJsonDbApiUsageException("Null Object batch cannot be inserted into DB");
1✔
897
    }
898
    CollectionMetaData collectionMeta = cmdMap.get(collectionName);
1✔
899
    collectionMeta.getCollectionLock().writeLock().lock();
1✔
900
    try {
901
      Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
902
      if (null == collection) {
1✔
903
        throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first");
1✔
904
      }
905
      CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
906
      Set<Object> uniqueIds = new HashSet<Object>();
1✔
907
      Map<Object, T> newCollection = new LinkedHashMap<Object, T>();
1✔
908
      for (T o : batchToSave) {
1✔
909
        Object obj = Util.deepCopy(o);
1✔
910
        Object id = Util.getIdForEntity(obj, cmd.getIdAnnotatedFieldGetterMethod());
1✔
911
        if(encrypted && cmd.hasSecret()){
1!
912
          CryptoUtil.encryptFields(obj, cmd, dbConfig.getCipher());
1✔
913
        }
914
        if (null == id) {
1!
915
          id = Util.setIdForEntity(obj, cmd.getIdAnnotatedFieldSetterMethod());
×
916
        } else if (collection.containsKey(id)) {
1✔
917
          throw new InvalidJsonDbApiUsageException("Object already present in Collection. Use Update or Upsert operation instead of Insert");
1✔
918
        }
919
        if (!uniqueIds.add(id)) {
1✔
920
          throw new InvalidJsonDbApiUsageException("Duplicate object with id: " + id + " within the passed in parameter");
1✔
921
        }
922
        newCollection.put(Util.deepCopy(id), (T) obj);
1✔
923
      }
1✔
924

925
      JsonWriter jw;
926
      try {
927
        jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
928
      } catch (IOException ioe) {
×
929
        logger.error("Failed to obtain writer for " + collectionName, ioe);
×
930
        throw new JsonDBException("Failed to save " + collectionName, ioe);
×
931
      }
1✔
932
      boolean appendResult = jw.appendToJsonFile(collection.values(), newCollection.values());
1✔
933

934
      if(appendResult) {
1!
935
        collection.putAll(newCollection);
1✔
936
      }
937
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
×
938
      logger.error("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
939
      throw new JsonDBException("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
940
    } finally {
941
      collectionMeta.getCollectionLock().writeLock().unlock();
1✔
942
    }
943
  }
1✔
944

945
  /* (non-Javadoc)
946
   * @see io.jsondb.JsonDBOperations#save(java.lang.Object, java.lang.Class)
947
   */
948
  @Override
949
  public <T> void save(Object objectToSave, Class<T> entityClass) {
950
    save(objectToSave, Util.determineCollectionName(entityClass));
1✔
951
  }
1✔
952

953
  /* (non-Javadoc)
954
   * @see io.jsondb.JsonDBOperations#save(java.lang.Object, java.lang.String)
955
   */
956
  @Override
957
  public <T> void save(Object objectToSave, String collectionName) {
958
    if (null == objectToSave) {
1✔
959
      throw new InvalidJsonDbApiUsageException("Null Object cannot be updated into DB");
1✔
960
    }
961
    Util.ensureNotRestricted(objectToSave);
1✔
962
    Object objToSave = Util.deepCopy(objectToSave);
1✔
963
    CollectionMetaData collectionMeta = cmdMap.get(collectionName);
1✔
964
    collectionMeta.getCollectionLock().writeLock().lock();
1✔
965
    try {
966
      @SuppressWarnings("unchecked")
967
      Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
968
      if (null == collection) {
1✔
969
        throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first.");
1✔
970
      }
971

972
      CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
973
      Object id = Util.getIdForEntity(objToSave, cmd.getIdAnnotatedFieldGetterMethod());
1✔
974

975
      T existingObject = collection.get(id);
1✔
976
      if (null == existingObject) {
1✔
977
        throw new InvalidJsonDbApiUsageException(
1✔
978
            String.format("Document with Id: '%s' not found in Collection by name '%s' not found. Insert or Upsert the object first.",
1✔
979
                id, collectionName));
980
      }
981
      if(encrypted && cmd.hasSecret()){
1!
982
        CryptoUtil.encryptFields(objToSave, cmd, dbConfig.getCipher());
1✔
983
      }
984
      JsonWriter jw = null;
1✔
985
      try {
986
        jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
987
      } catch (IOException ioe) {
×
988
        logger.error("Failed to obtain writer for " + collectionName, ioe);
×
989
        throw new JsonDBException("Failed to save " + collectionName, ioe);
×
990
      }
1✔
991
      @SuppressWarnings("unchecked")
992
      boolean updateResult = jw.updateInJsonFile(collection, id, (T)objToSave);
1✔
993
      if (updateResult) {
1!
994
        @SuppressWarnings("unchecked")
995
        T newObject = (T) objToSave;
1✔
996
        collection.put(id, newObject);
1✔
997
      }
998
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
×
999
      logger.error("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
1000
      throw new JsonDBException("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
1001
    } finally {
1002
      collectionMeta.getCollectionLock().writeLock().unlock();
1✔
1003
    }
1004
  }
1✔
1005

1006
  /* (non-Javadoc)
1007
   * @see org.jsondb.JsonDBOperations#remove(java.lang.Object)
1008
   */
1009
  @Override
1010
  public <T> T remove(Object objectToRemove) {
1011
    return remove(objectToRemove, Util.determineEntityCollectionName(objectToRemove));
1✔
1012
  }
1013

1014
  /* (non-Javadoc)
1015
   * @see org.jsondb.JsonDBOperations#remove(java.lang.Object, java.lang.Class)
1016
   */
1017
  @Override
1018
  public <T> T remove(Object objectToRemove, Class<T> entityClass) {
1019
    return remove(objectToRemove, Util.determineCollectionName(entityClass));
1✔
1020
  }
1021

1022
  /* (non-Javadoc)
1023
   * @see org.jsondb.JsonDBOperations#remove(java.lang.Object, java.lang.String)
1024
   */
1025
  @Override
1026
  public <T> T remove(Object objectToRemove, String collectionName) {
1027
    if (null == objectToRemove) {
1✔
1028
      throw new InvalidJsonDbApiUsageException("Null Object cannot be removed from DB");
1✔
1029
    }
1030
    Util.ensureNotRestricted(objectToRemove);
1✔
1031

1032
    CollectionMetaData collectionMeta = cmdMap.get(collectionName);
1✔
1033
    collectionMeta.getCollectionLock().writeLock().lock();
1✔
1034
    try {
1035
      @SuppressWarnings("unchecked")
1036
      Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
1037
      if (null == collection) {
1✔
1038
        throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first.");
1✔
1039
      }
1040

1041
      CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
1042
      Object id = Util.getIdForEntity(objectToRemove, cmd.getIdAnnotatedFieldGetterMethod());
1✔
1043
      if (!collection.containsKey(id)) {
1✔
1044
        throw new InvalidJsonDbApiUsageException(String.format("Objects with Id %s not found in collection %s", id, collectionName));
1✔
1045
      }
1046

1047
      JsonWriter jw;
1048
      try {
1049
        jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
1050
      } catch (IOException ioe) {
×
1051
        logger.error("Failed to obtain writer for " + collectionName, ioe);
×
1052
        throw new JsonDBException("Failed to save " + collectionName, ioe);
×
1053
      }
1✔
1054
      boolean substractResult = jw.removeFromJsonFile(collection, id);
1✔
1055
      if(substractResult) {
1!
1056
        T objectRemoved = collection.remove(id);
1✔
1057
        // Don't need to clone it, this object no more exists in the collection
1058
        return objectRemoved;
1✔
1059
      } else {
1060
        return null;
×
1061
      }
1062
    } finally {
1063
      collectionMeta.getCollectionLock().writeLock().unlock();
1✔
1064
    }
1065
  }
1066

1067
  /* (non-Javadoc)
1068
   * @see org.jsondb.JsonDBOperations#remove(java.util.Collection, java.lang.Class)
1069
   */
1070
  @Override
1071
  public <T> List<T> remove(Collection<? extends T> batchToRemove, Class<T> entityClass) {
1072
    return remove(batchToRemove, Util.determineCollectionName(entityClass));
1✔
1073
  }
1074

1075
  /* (non-Javadoc)
1076
   * @see org.jsondb.JsonDBOperations#remove(java.util.Collection, java.lang.String)
1077
   */
1078
  @Override
1079
  public <T> List<T> remove(Collection<? extends T> batchToRemove, String collectionName) {
1080
    if (null == batchToRemove) {
1✔
1081
      throw new InvalidJsonDbApiUsageException("Null Object batch cannot be removed from DB");
1✔
1082
    }
1083
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
1084
    cmd.getCollectionLock().writeLock().lock();
1✔
1085
    try {
1086
      @SuppressWarnings("unchecked")
1087
      Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
1088
      if (null == collection) {
1✔
1089
        throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first.");
1✔
1090
      }
1091

1092
      Set<Object> removeIds = new HashSet<Object>();
1✔
1093

1094
      for (T o : batchToRemove) {
1✔
1095
        Object id = Util.getIdForEntity(o, cmd.getIdAnnotatedFieldGetterMethod());
1✔
1096
        if (collection.containsKey(id)) {
1!
1097
          removeIds.add(id);
1✔
1098
        }
1099
      }
1✔
1100

1101
      if(removeIds.size() < 1) {
1!
1102
        return null;
×
1103
      }
1104

1105
      JsonWriter jw;
1106
      try {
1107
        jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
1108
      } catch (IOException ioe) {
×
1109
        logger.error("Failed to obtain writer for " + collectionName, ioe);
×
1110
        throw new JsonDBException("Failed to save " + collectionName, ioe);
×
1111
      }
1✔
1112
      boolean substractResult = jw.removeFromJsonFile(collection, removeIds);
1✔
1113

1114
      List<T> removedObjects = null;
1✔
1115
      if(substractResult) {
1!
1116
        removedObjects = new ArrayList<T>();
1✔
1117
        for (Object id : removeIds) {
1✔
1118
          // Don't need to clone it, this object no more exists in the collection
1119
          removedObjects.add(collection.remove(id));
1✔
1120
        }
1✔
1121
      }
1122
      return removedObjects;
1✔
1123
    } finally {
1124
      cmd.getCollectionLock().writeLock().unlock();
1✔
1125
    }
1126
  }
1127

1128
  /* (non-Javadoc)
1129
   * @see org.jsondb.JsonDBOperations#upsert(java.lang.Object)
1130
   */
1131
  @Override
1132
  public <T> void upsert(Object objectToSave) {
1133
    if (null == objectToSave) {
1✔
1134
      throw new InvalidJsonDbApiUsageException("Null Object cannot be upserted into DB");
1✔
1135
    }
1136
    Util.ensureNotRestricted(objectToSave);
1✔
1137
    upsert(objectToSave, Util.determineEntityCollectionName(objectToSave));
1✔
1138
  }
1✔
1139

1140
  /* (non-Javadoc)
1141
   * @see org.jsondb.JsonDBOperations#upsert(java.lang.Object, java.lang.String)
1142
   */
1143
  @SuppressWarnings("unchecked")
1144
  @Override
1145
  public <T> void upsert(Object objectToSave, String collectionName) {
1146
    if (null == objectToSave) {
1✔
1147
      throw new InvalidJsonDbApiUsageException("Null Object cannot be upserted into DB");
1✔
1148
    }
1149
    Util.ensureNotRestricted(objectToSave);
1✔
1150
    Object objToSave = Util.deepCopy(objectToSave);
1✔
1151
    CollectionMetaData collectionMeta = cmdMap.get(collectionName);
1✔
1152
    collectionMeta.getCollectionLock().writeLock().lock();
1✔
1153
    try {
1154
      Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
1155
      if (null == collection) {
1✔
1156
        throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first");
1✔
1157
      }
1158
      CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
1159
      Object id = Util.getIdForEntity(objectToSave, cmd.getIdAnnotatedFieldGetterMethod());
1✔
1160
      if(encrypted && cmd.hasSecret()){
1✔
1161
        CryptoUtil.encryptFields(objToSave, cmd, dbConfig.getCipher());
1✔
1162
      }
1163

1164
      boolean insert = true;
1✔
1165
      if (null == id) {
1✔
1166
        id = Util.setIdForEntity(objToSave, cmd.getIdAnnotatedFieldSetterMethod());
1✔
1167
      } else if (collection.containsKey(id)) {
1✔
1168
        insert = false;
1✔
1169
      }
1170

1171
      JsonWriter jw;
1172
      try {
1173
        jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
1174
      } catch (IOException ioe) {
×
1175
        logger.error("Failed to obtain writer for " + collectionName, ioe);
×
1176
        throw new JsonDBException("Failed to save " + collectionName, ioe);
×
1177
      }
1✔
1178

1179
      if (insert) {
1✔
1180
        boolean insertResult = jw.appendToJsonFile(collection.values(), objToSave);
1✔
1181
        if(insertResult) {
1!
1182
          collection.put(Util.deepCopy(id), (T) objToSave);
1✔
1183
        }
1184
      } else {
1✔
1185
        boolean updateResult = jw.updateInJsonFile(collection, id, (T)objToSave);
1✔
1186
        if (updateResult) {
1!
1187
          T newObject = (T) objToSave;
1✔
1188
          collection.put(id, newObject);
1✔
1189
        }
1190
      }
1191
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
×
1192
      logger.error("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
1193
      throw new JsonDBException("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
1194
    } finally {
1195
      collectionMeta.getCollectionLock().writeLock().unlock();
1✔
1196
    }
1197
  }
1✔
1198

1199
  /* (non-Javadoc)
1200
   * @see org.jsondb.JsonDBOperations#upsert(java.util.Collection, java.lang.Class)
1201
   */
1202
  @Override
1203
  public <T> void upsert(Collection<? extends T> batchToSave, Class<T> entityClass) {
1204
    upsert(batchToSave, Util.determineCollectionName(entityClass));
1✔
1205
  }
1✔
1206

1207
  /* (non-Javadoc)
1208
   * @see org.jsondb.JsonDBOperations#upsert(java.util.Collection, java.lang.String)
1209
   */
1210
  @SuppressWarnings("unchecked")
1211
  @Override
1212
  public <T> void upsert(Collection<? extends T> batchToSave, String collectionName) {
1213
    if (null == batchToSave) {
1✔
1214
      throw new InvalidJsonDbApiUsageException("Null Object batch cannot be upserted into DB");
1✔
1215
    }
1216
    CollectionMetaData collectionMeta = cmdMap.get(collectionName);
1✔
1217
    collectionMeta.getCollectionLock().writeLock().lock();
1✔
1218
    try {
1219
      Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
1220
      if (null == collection) {
1✔
1221
        throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first");
1✔
1222
      }
1223
      CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
1224
      Set<Object> uniqueIds = new HashSet<Object>();
1✔
1225

1226
      Map<Object, T> collectionToInsert = new LinkedHashMap<Object, T>();
1✔
1227
      Map<Object, T> collectionToUpdate = new LinkedHashMap<Object, T>();
1✔
1228

1229
      for (T o : batchToSave) {
1✔
1230
        Object obj = Util.deepCopy(o);
1✔
1231
        Object id = Util.getIdForEntity(obj, cmd.getIdAnnotatedFieldGetterMethod());
1✔
1232
        if(encrypted && cmd.hasSecret()){
1!
1233
          CryptoUtil.encryptFields(obj, cmd, dbConfig.getCipher());
1✔
1234
        }
1235
        boolean insert = true;
1✔
1236
        if (null == id) {
1!
1237
          id = Util.setIdForEntity(obj, cmd.getIdAnnotatedFieldSetterMethod());
×
1238
        } else if (collection.containsKey(id)) {
1✔
1239
          insert = false;
1✔
1240
        }
1241
        if (!uniqueIds.add(id)) {
1✔
1242
          throw new InvalidJsonDbApiUsageException("Duplicate object with id: " + id + " within the passed in parameter");
1✔
1243
        }
1244
        if (insert) {
1✔
1245
          collectionToInsert.put(Util.deepCopy(id), (T) obj);
1✔
1246
        } else {
1247
          collectionToUpdate.put(Util.deepCopy(id), (T) obj);
1✔
1248
        }
1249
      }
1✔
1250

1251
      JsonWriter jw;
1252
      try {
1253
        jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
1254
      } catch (IOException ioe) {
×
1255
        logger.error("Failed to obtain writer for " + collectionName, ioe);
×
1256
        throw new JsonDBException("Failed to save " + collectionName, ioe);
×
1257
      }
1✔
1258

1259
      if (collectionToInsert.size() > 0) {
1!
1260
        boolean insertResult = jw.appendToJsonFile(collection.values(), collectionToInsert.values());
1✔
1261
        if(insertResult) {
1!
1262
          collection.putAll(collectionToInsert);
1✔
1263
        }
1264
      }
1265

1266
      if (collectionToUpdate.size() > 0) {
1✔
1267
        boolean updateResult = jw.updateInJsonFile(collection, collectionToUpdate);
1✔
1268
        if (updateResult) {
1!
1269
         collection.putAll(collectionToUpdate);
×
1270
        }
1271
      }
1272
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
×
1273
      logger.error("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
1274
      throw new JsonDBException("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
1275
    } finally {
1276
      collectionMeta.getCollectionLock().writeLock().unlock();
1✔
1277
    }
1278
  }
1✔
1279

1280
  /* (non-Javadoc)
1281
   * @see org.jsondb.JsonDBOperations#findAndRemove(java.lang.String, java.lang.Class)
1282
   */
1283
  @Override
1284
  public <T> T findAndRemove(String jxQuery, Class<T> entityClass) {
1285
    return findAndRemove(jxQuery, Util.determineCollectionName(entityClass));
1✔
1286
  }
1287

1288
  /* (non-Javadoc)
1289
   * @see org.jsondb.JsonDBOperations#findAndRemove(java.lang.String, java.lang.String)
1290
   */
1291
  @Override
1292
  public <T> T findAndRemove(String jxQuery, String collectionName) {
1293
    if (null == jxQuery) {
1✔
1294
      throw new InvalidJsonDbApiUsageException("Query string cannot be null.");
1✔
1295
    }
1296
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
1297
    @SuppressWarnings("unchecked")
1298
    Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
1299
    if((null == cmd) || (null == collection)) {
1!
1300
      throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first.");
1✔
1301
    }
1302
    cmd.getCollectionLock().writeLock().lock();
1✔
1303
    try {
1304
      JXPathContext context = contextsRef.get().get(collectionName);
1✔
1305
      @SuppressWarnings("unchecked")
1306
      Iterator<T> resultItr = context.iterate(jxQuery);
1✔
1307
      T objectToRemove = null;
1✔
1308
      while (resultItr.hasNext()) {
1✔
1309
        objectToRemove = resultItr.next();
1✔
1310
        break; // Use only the first element we find.
1311
      }
1312
      if (null != objectToRemove) {
1✔
1313
        Object idToRemove = Util.getIdForEntity(objectToRemove, cmd.getIdAnnotatedFieldGetterMethod());
1✔
1314
        if (!collection.containsKey(idToRemove)) { //This will never happen since the object was located based of jxQuery
1!
1315
          throw new InvalidJsonDbApiUsageException(String.format("Objects with Id %s not found in collection %s", idToRemove, collectionName));
×
1316
        }
1317

1318
        JsonWriter jw;
1319
        try {
1320
          jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
1321
        } catch (IOException ioe) {
×
1322
          logger.error("Failed to obtain writer for " + collectionName, ioe);
×
1323
          throw new JsonDBException("Failed to save " + collectionName, ioe);
×
1324
        }
1✔
1325
        boolean substractResult = jw.removeFromJsonFile(collection, idToRemove);
1✔
1326
        if (substractResult) {
1!
1327
          T objectRemoved = collection.remove(idToRemove);
1✔
1328
          // Don't need to clone it, this object no more exists in the collection
1329
          return objectRemoved;
1✔
1330
        } else {
1331
          logger.error("Unexpected, Failed to substract the object");
×
1332
        }
1333
      }
1334
      return null; //Either the jxQuery found nothing or actual FileIO failed to substract it.
1✔
1335
    } finally {
1336
      cmd.getCollectionLock().writeLock().unlock();
1✔
1337
    }
1338
  }
1339

1340
  /* (non-Javadoc)
1341
   * @see org.jsondb.JsonDBOperations#findAllAndRemove(java.lang.String, java.lang.Class)
1342
   */
1343
  @Override
1344
  public <T> List<T> findAllAndRemove(String jxQuery, Class<T> entityClass) {
1345
    return findAllAndRemove(jxQuery, Util.determineCollectionName(entityClass));
1✔
1346
  }
1347

1348
  /* (non-Javadoc)
1349
   * @see org.jsondb.JsonDBOperations#findAllAndRemove(java.lang.String, java.lang.String)
1350
   */
1351
  @Override
1352
  public <T> List<T> findAllAndRemove(String jxQuery, String collectionName) {
1353
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
1354
    @SuppressWarnings("unchecked")
1355
    Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
1356
    if((null == cmd) || (null == collection)) {
1!
1357
      throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first.");
1✔
1358
    }
1359
    cmd.getCollectionLock().writeLock().lock();
1✔
1360
    try {
1361
      JXPathContext context = contextsRef.get().get(collectionName);
1✔
1362
      @SuppressWarnings("unchecked")
1363
      Iterator<T> resultItr = context.iterate(jxQuery);
1✔
1364
      Set<Object> removeIds = new HashSet<Object>();
1✔
1365
      while (resultItr.hasNext()) {
1✔
1366
        T objectToRemove = resultItr.next();
1✔
1367
        Object idToRemove = Util.getIdForEntity(objectToRemove, cmd.getIdAnnotatedFieldGetterMethod());
1✔
1368
        removeIds.add(idToRemove);
1✔
1369
      }
1✔
1370

1371
      if(removeIds.size() < 1) {
1!
1372
        return null;
×
1373
      }
1374

1375
      JsonWriter jw;
1376
      try {
1377
        jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
1378
      } catch (IOException ioe) {
×
1379
        logger.error("Failed to obtain writer for " + collectionName, ioe);
×
1380
        throw new JsonDBException("Failed to save " + collectionName, ioe);
×
1381
      }
1✔
1382
      boolean substractResult = jw.removeFromJsonFile(collection, removeIds);
1✔
1383

1384
      List<T> removedObjects = null;
1✔
1385
      if(substractResult) {
1!
1386
        removedObjects = new ArrayList<T>();
1✔
1387
        for (Object id : removeIds) {
1✔
1388
          // Don't need to clone it, this object no more exists in the collection
1389
          removedObjects.add(collection.remove(id));
1✔
1390
        }
1✔
1391
      }
1392
      return removedObjects;
1✔
1393

1394
    } finally {
1395
      cmd.getCollectionLock().writeLock().unlock();
1✔
1396
    }
1397
  }
1398

1399
  /* (non-Javadoc)
1400
   * @see org.jsondb.JsonDBOperations#findAndModify(java.lang.String, org.jsondb.query.Update, java.lang.Class)
1401
   */
1402
  @Override
1403
  public <T> T findAndModify(String jxQuery, Update update, Class<T> entityClass) {
1404
    return findAndModify(jxQuery, update, Util.determineCollectionName(entityClass));
1✔
1405
  }
1406

1407
  /* (non-Javadoc)
1408
   * @see org.jsondb.JsonDBOperations#findAndModify(java.lang.String, org.jsondb.query.Update, java.lang.String)
1409
   */
1410
  @SuppressWarnings("unchecked")
1411
  @Override
1412
  public <T> T findAndModify(String jxQuery, Update update, String collectionName) {
1413
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
1414
    Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
1415
    if((null == cmd) || (null == collection)) {
1!
1416
      throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first.");
1✔
1417
    }
1418
    cmd.getCollectionLock().writeLock().lock();
1✔
1419
    try {
1420
      JXPathContext context = contextsRef.get().get(collectionName);
1✔
1421
      Iterator<T> resultItr = context.iterate(jxQuery);
1✔
1422
      T objectToModify = null;
1✔
1423
      T clonedModifiedObject = null;
1✔
1424

1425
      while (resultItr.hasNext()) {
1✔
1426
        objectToModify = resultItr.next();
1✔
1427
        break; // Use only the first element we find.
1428
      }
1429
      if (null != objectToModify) {
1✔
1430
        //Clone it because we dont want to touch the in-memory object until we have really saved it
1431
        clonedModifiedObject = (T) Util.deepCopy(objectToModify);
1✔
1432
        for (Entry<String, Object> entry : update.getUpdateData().entrySet()) {
1✔
1433
          Object newValue = Util.deepCopy(entry.getValue());
1✔
1434
          if(encrypted && cmd.hasSecret() && cmd.isSecretField(entry.getKey())){
1!
1435
            newValue = dbConfig.getCipher().encrypt(newValue.toString());
1✔
1436
          }
1437
          try {
1438
            BeanUtils.copyProperty(clonedModifiedObject, entry.getKey(), newValue);
1✔
1439
          } catch (IllegalAccessException | InvocationTargetException e) {
×
1440
            logger.error("Failed to copy updated data into existing collection document using BeanUtils", e);
×
1441
            return null;
×
1442
          }
1✔
1443
        }
1✔
1444

1445
        Object idToModify = Util.getIdForEntity(clonedModifiedObject, cmd.getIdAnnotatedFieldGetterMethod());
1✔
1446
        JsonWriter jw = null;
1✔
1447
        try {
1448
          jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
1449
        } catch (IOException ioe) {
×
1450
          logger.error("Failed to obtain writer for " + collectionName, ioe);
×
1451
          throw new JsonDBException("Failed to save " + collectionName, ioe);
×
1452
        }
1✔
1453
        boolean updateResult = jw.updateInJsonFile(collection, idToModify, clonedModifiedObject);
1✔
1454
        if (updateResult) {
1!
1455
         collection.put(idToModify, clonedModifiedObject);
1✔
1456
         //Clone it once more because we want to disconnect it from the in-memory objects before returning.
1457
         T returnObj = (T) Util.deepCopy(clonedModifiedObject);
1✔
1458
         if(encrypted && cmd.hasSecret() && null!= returnObj){
1!
1459
           CryptoUtil.decryptFields(returnObj, cmd, dbConfig.getCipher());
1✔
1460
         }
1461
         return returnObj;
1✔
1462
        }
1463
      }
1464
      return null;
1✔
1465
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
×
1466
      logger.error("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
1467
      throw new JsonDBException("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
1468
    } finally {
1469
      cmd.getCollectionLock().writeLock().unlock();
1✔
1470
    }
1471
  }
1472

1473

1474
  /* (non-Javadoc)
1475
   * @see io.jsondb.JsonDBOperations#findAllAndModify(java.lang.String, io.jsondb.query.Update, java.lang.Class)
1476
   */
1477
  @Override
1478
  public <T> List<T> findAllAndModify(String jxQuery, Update update, Class<T> entityClass) {
1479
    return findAllAndModify(jxQuery, update, Util.determineCollectionName(entityClass));
1✔
1480
  }
1481

1482
  /* (non-Javadoc)
1483
   * @see io.jsondb.JsonDBOperations#findAllAndModify(java.lang.String, io.jsondb.query.Update, java.lang.String)
1484
   */
1485
  @SuppressWarnings("unchecked")
1486
  @Override
1487
  public <T> List<T> findAllAndModify(String jxQuery, Update update, String collectionName) {
1488
    CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
1489
    Map<Object, T> collection = (Map<Object, T>) collectionsRef.get().get(collectionName);
1✔
1490
    if((null == cmd) || (null == collection)) {
1!
1491
      throw new InvalidJsonDbApiUsageException("Collection by name '" + collectionName + "' not found. Create collection first.");
1✔
1492
    }
1493
    cmd.getCollectionLock().writeLock().lock();
1✔
1494
    try {
1495
      JXPathContext context = contextsRef.get().get(collectionName);
1✔
1496
      Iterator<T> resultItr = context.iterate(jxQuery);
1✔
1497
      Map<Object, T> clonedModifiedObjects = new HashMap<Object, T>();
1✔
1498

1499
      while (resultItr.hasNext()) {
1✔
1500
        T objectToModify = resultItr.next();
1✔
1501
        T clonedModifiedObject = (T) Util.deepCopy(objectToModify);
1✔
1502

1503
        for (Entry<String, Object> entry : update.getUpdateData().entrySet()) {
1✔
1504
          Object newValue = Util.deepCopy(entry.getValue());
1✔
1505
          if(encrypted && cmd.hasSecret() && cmd.isSecretField(entry.getKey())){
1!
1506
            newValue = dbConfig.getCipher().encrypt(newValue.toString());
1✔
1507
          }
1508
          try {
1509
            BeanUtils.copyProperty(clonedModifiedObject, entry.getKey(), newValue);
1✔
1510
          } catch (IllegalAccessException | InvocationTargetException e) {
×
1511
            logger.error("Failed to copy updated data into existing collection document using BeanUtils", e);
×
1512
            return null;
×
1513
          }
1✔
1514
        }
1✔
1515
        Object id = Util.getIdForEntity(clonedModifiedObject, cmd.getIdAnnotatedFieldGetterMethod());
1✔
1516
        clonedModifiedObjects.put(id, clonedModifiedObject);
1✔
1517
      }
1✔
1518

1519
      JsonWriter jw = null;
1✔
1520
      try {
1521
        jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
1522
      } catch (IOException ioe) {
×
1523
        logger.error("Failed to obtain writer for " + collectionName, ioe);
×
1524
        throw new JsonDBException("Failed to save " + collectionName, ioe);
×
1525
      }
1✔
1526
      boolean updateResult = jw.updateInJsonFile(collection, clonedModifiedObjects);
1✔
1527
      if (updateResult) {
1!
1528
       collection.putAll(clonedModifiedObjects);
1✔
1529
       //Clone it once more because we want to disconnect it from the in-memory objects before returning.
1530
       List<T> returnObjects = new ArrayList<T>();
1✔
1531
       for (T obj : clonedModifiedObjects.values()) {
1✔
1532
         //Clone it once more because we want to disconnect it from the in-memory objects before returning.
1533
         T returnObj = (T) Util.deepCopy(obj);
1✔
1534
         if(encrypted && cmd.hasSecret() && null!= returnObj){
1!
1535
           CryptoUtil.decryptFields(returnObj, cmd, dbConfig.getCipher());
1✔
1536
         }
1537
         returnObjects.add(returnObj);
1✔
1538
       }
1✔
1539
       return returnObjects;
1✔
1540
      }
1541
      return null;
×
1542
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
×
1543
      logger.error("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
1544
      throw new JsonDBException("Error when decrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
1545
    } finally {
1546
      cmd.getCollectionLock().writeLock().unlock();
1✔
1547
    }
1548
  }
1549

1550

1551

1552
  /* (non-Javadoc)
1553
   * @see io.jsondb.JsonDBOperations#changeEncryption(io.jsondb.crypto.ICipher)
1554
   */
1555
  @SuppressWarnings("unchecked")
1556
  @Override
1557
  public <T> void changeEncryption(ICipher newCipher) {
1558
    if (!encrypted) {
1✔
1559
      throw new InvalidJsonDbApiUsageException("DB is not encrypted, nothing to change for EncryptionKey");
1✔
1560
    }
1561

1562
    for (Entry<String, Map<Object, ?>> entry : collectionsRef.get().entrySet()) {
1✔
1563
      CollectionMetaData cmd = cmdMap.get(entry.getKey());
1✔
1564
      if (cmd.hasSecret()) {
1!
1565
        cmd.getCollectionLock().writeLock().lock();
1✔
1566
      }
1567
    }
1✔
1568
    String collectionName = null;
1✔
1569
    try {
1570
      for (Entry<String, Map<Object, ?>> entry : collectionsRef.get().entrySet()) {
1✔
1571
        collectionName = entry.getKey();
1✔
1572
        Map<Object, T> collection = (Map<Object, T>) entry.getValue();
1✔
1573

1574
        CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
1575
        if (cmd.hasSecret()) {
1!
1576
          Map<Object, T> reCryptedObjects = new LinkedHashMap<Object, T>();
1✔
1577
          for (Entry<Object, T> object : collection.entrySet()) {
1✔
1578
            T clonedObject = (T) Util.deepCopy(object.getValue());
1✔
1579
            CryptoUtil.decryptFields(clonedObject, cmd, dbConfig.getCipher());
1✔
1580
            CryptoUtil.encryptFields(clonedObject, cmd, newCipher);
1✔
1581
            //We will reuse the Id in the previous collection, should hopefully not cause any issues
1582
            reCryptedObjects.put(object.getKey(), clonedObject);
1✔
1583
          }
1✔
1584
          JsonWriter jw = null;
1✔
1585
          try {
1586
            jw = new JsonWriter(dbConfig, cmd, collectionName, fileObjectsRef.get().get(collectionName));
1✔
1587
          } catch (IOException ioe) {
×
1588
            logger.error("Failed to obtain writer for " + collectionName, ioe);
×
1589
            throw new JsonDBException("Failed to save " + collectionName, ioe);
×
1590
          }
1✔
1591
          boolean updateResult = jw.updateInJsonFile(collection, reCryptedObjects);
1✔
1592
          if (!updateResult) {
1!
1593
            throw new JsonDBException("Failed to write re-crypted collection data to .json files, database might have become insconsistent");
×
1594
          }
1595
          collection.putAll(reCryptedObjects);
1✔
1596
        }
1597
      }
1✔
1598
      dbConfig.setCipher(newCipher);
1✔
1599
    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
×
1600
      logger.error("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
1601
      throw new JsonDBException("Error when encrypting value for a @Secret annotated field for entity: " + collectionName, e);
×
1602
    } finally {
1603
      for (Entry<String, Map<Object, ?>> entry : collectionsRef.get().entrySet()) {
1✔
1604
        CollectionMetaData cmd = cmdMap.get(entry.getKey());
1✔
1605
        if (cmd.hasSecret()) {
1!
1606
          cmd.getCollectionLock().writeLock().unlock();
1✔
1607
        }
1608
      }
1✔
1609
    }
1610
  }
1✔
1611

1612
  /* (non-Javadoc)
1613
   * @see org.jsondb.JsonDBOperations#backup(java.lang.String)
1614
   */
1615
  @Override
1616
  public void backup(String backupPath) {
1617
    File zipFile;
1618
    try {
1619
      zipFile = JsonDbArchive.resolveBackupZipFile(backupPath);
1✔
1620
    } catch (IllegalArgumentException e) {
1✔
1621
      throw new InvalidJsonDbApiUsageException(e.getMessage());
1✔
1622
    }
1✔
1623
    File dbDirectory = dbConfig.getDbFilesLocation();
1✔
1624
    lockAllCollectionsWrite();
1✔
1625
    try {
1626
      JsonDbArchive.createBackupZip(dbDirectory, zipFile);
1✔
1627
      logger.info("Created JsonDB backup at {}", zipFile.getAbsolutePath());
1✔
NEW
1628
    } catch (IOException e) {
×
NEW
1629
      logger.error("Failed to create JsonDB backup at {}", zipFile.getAbsolutePath(), e);
×
NEW
1630
      throw new JsonDBException("Failed to create JsonDB backup at " + zipFile.getAbsolutePath(), e);
×
1631
    } finally {
1632
      unlockAllCollectionsWrite();
1✔
1633
    }
1634
  }
1✔
1635

1636
  /* (non-Javadoc)
1637
   * @see org.jsondb.JsonDBOperations#restore(java.lang.String, boolean)
1638
   */
1639
  @Override
1640
  public void restore(String restorePath, boolean merge) {
1641
    File zipFile;
1642
    try {
1643
      zipFile = JsonDbArchive.resolveRestoreZipFile(restorePath);
1✔
NEW
1644
    } catch (IllegalArgumentException e) {
×
NEW
1645
      throw new InvalidJsonDbApiUsageException(e.getMessage());
×
1646
    }
1✔
1647
    if (!zipFile.isFile()) {
1✔
1648
      throw new InvalidJsonDbApiUsageException("Restore backup file not found: " + zipFile.getAbsolutePath());
1✔
1649
    }
1650
    if (merge) {
1✔
1651
      restoreMerge(zipFile);
1✔
1652
    } else {
1653
      restoreReplace(zipFile);
1✔
1654
    }
1655
  }
1✔
1656

1657
  private void restoreReplace(File zipFile) {
1658
    File dbDirectory = dbConfig.getDbFilesLocation();
1✔
1659
    lockAllCollectionsWrite();
1✔
1660
    try {
1661
      JsonDbArchive.extractReplaceCollectionFiles(dbDirectory, zipFile);
1✔
1662
      reLoadDB();
1✔
1663
      logger.info("Restored JsonDB from backup {} (replace mode)", zipFile.getAbsolutePath());
1✔
NEW
1664
    } catch (IOException e) {
×
NEW
1665
      logger.error("Failed to restore JsonDB from backup {}", zipFile.getAbsolutePath(), e);
×
NEW
1666
      throw new JsonDBException("Failed to restore JsonDB from backup " + zipFile.getAbsolutePath(), e);
×
1667
    } finally {
1668
      unlockAllCollectionsWrite();
1✔
1669
    }
1670
  }
1✔
1671

1672
  private void restoreMerge(File zipFile) {
1673
    File tempDirectory = null;
1✔
1674
    try {
1675
      tempDirectory = Files.createTempDirectory("jsondb-restore-").toFile();
1✔
1676
      Map<String, File> extractedCollections = JsonDbArchive.extractToDirectory(tempDirectory, zipFile);
1✔
1677
      File dbDirectory = dbConfig.getDbFilesLocation();
1✔
1678

1679
      for (Entry<String, File> entry : extractedCollections.entrySet()) {
1✔
1680
        String collectionName = entry.getKey();
1✔
1681
        File backupCollectionFile = entry.getValue();
1✔
1682
        CollectionMetaData cmd = cmdMap.get(collectionName);
1✔
1683

1684
        if (null == cmd) {
1!
NEW
1685
          File targetFile = new File(dbDirectory, backupCollectionFile.getName());
×
NEW
1686
          JsonDbArchive.copyCollectionFile(backupCollectionFile, targetFile);
×
NEW
1687
          continue;
×
1688
        }
1689

1690
        if (collectionExists(collectionName)) {
1✔
1691
          mergeCollectionFromBackup(collectionName, cmd, backupCollectionFile);
1✔
1692
        } else {
1693
          File targetFile = new File(dbDirectory, backupCollectionFile.getName());
1✔
1694
          JsonDbArchive.copyCollectionFile(backupCollectionFile, targetFile);
1✔
1695
          reloadCollection(collectionName);
1✔
1696
        }
1697
      }
1✔
1698
      logger.info("Restored JsonDB from backup {} (merge mode)", zipFile.getAbsolutePath());
1✔
NEW
1699
    } catch (IOException e) {
×
NEW
1700
      logger.error("Failed to merge JsonDB from backup {}", zipFile.getAbsolutePath(), e);
×
NEW
1701
      throw new JsonDBException("Failed to merge JsonDB from backup " + zipFile.getAbsolutePath(), e);
×
1702
    } finally {
1703
      if (null != tempDirectory) {
1!
1704
        Util.delete(tempDirectory);
1✔
1705
      }
1706
    }
1707
  }
1✔
1708

1709
  @SuppressWarnings("unchecked")
1710
  private void mergeCollectionFromBackup(String collectionName, CollectionMetaData cmd, File backupCollectionFile) {
1711
    List<Object> documents = readEntityDocumentsFromFile(backupCollectionFile, cmd);
1✔
1712
    for (Object document : documents) {
1✔
1713
      upsert(document, collectionName);
1✔
1714
    }
1✔
1715
  }
1✔
1716

1717
  @SuppressWarnings("unchecked")
1718
  private <T> List<T> readEntityDocumentsFromFile(File collectionFile, CollectionMetaData cmd) {
1719
    Class<T> entity = cmd.getClazz();
1✔
1720
    List<T> documents = new ArrayList<T>();
1✔
1721
    JsonReader jr = null;
1✔
1722
    try {
1723
      jr = new JsonReader(dbConfig, collectionFile);
1✔
1724
      String line = null;
1✔
1725
      int lineNo = 1;
1✔
1726
      while ((line = jr.readLine()) != null) {
1✔
1727
        if (lineNo++ > 1) {
1✔
1728
          documents.add(dbConfig.getObjectMapper().readValue(line, entity));
1✔
1729
        }
1730
      }
NEW
1731
    } catch (IOException e) {
×
NEW
1732
      logger.error("Failed to read documents from backup file {}", collectionFile.getName(), e);
×
NEW
1733
      throw new JsonDBException("Failed to read documents from backup file " + collectionFile.getName(), e);
×
1734
    } finally {
1735
      if (null != jr) {
1!
1736
        jr.close();
1✔
1737
      }
1738
    }
1739
    return documents;
1✔
1740
  }
1741

1742
  private void lockAllCollectionsWrite() {
1743
    List<String> collectionNames = new ArrayList<String>(cmdMap.keySet());
1✔
1744
    Collections.sort(collectionNames);
1✔
1745
    for (String collectionName : collectionNames) {
1✔
1746
      cmdMap.get(collectionName).getCollectionLock().writeLock().lock();
1✔
1747
    }
1✔
1748
  }
1✔
1749

1750
  private void unlockAllCollectionsWrite() {
1751
    List<String> collectionNames = new ArrayList<String>(cmdMap.keySet());
1✔
1752
    Collections.sort(collectionNames);
1✔
1753
    for (int i = collectionNames.size() - 1; i >= 0; i--) {
1✔
1754
      cmdMap.get(collectionNames.get(i)).getCollectionLock().writeLock().unlock();
1✔
1755
    }
1756
  }
1✔
1757
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc