1 /**
2 * Copyright 2005 Steve Molloy
3 *
4 * This file is part of OV4J.
5 *
6 * OV4J is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
8 *
9 * OV4J is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
10 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License along with OV4J; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
14 *
15 * This file uses DB4O which is also free software which can be redistributed and/or modified under the GNU General
16 * Public License. DB4O can be obtained at http://www.db4o.com
17 *
18 */
19 package org.ov4j.db4oImpl;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.Serializable;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.Timer;
29 import java.util.TimerTask;
30 import java.util.TreeSet;
31 import java.util.logging.Level;
32 import java.util.logging.Logger;
33
34 import org.ov4j.Config;
35 import org.ov4j.IContainer;
36 import org.ov4j.data.ClassComparable;
37 import org.ov4j.data.Item;
38 import org.ov4j.data.Version;
39
40 import com.db4o.Db4o;
41 import com.db4o.ObjectSet;
42 import com.db4o.config.ConfigScope;
43 import com.db4o.ext.ExtDb4o;
44 import com.db4o.ext.ExtObjectContainer;
45 import com.db4o.ext.MemoryFile;
46 import com.db4o.ext.StoredClass;
47 import com.db4o.query.Query;
48 import com.db4o.replication.ReplicationConflictHandler;
49 import com.db4o.replication.ReplicationProcess;
50
51 /**
52 * Implementation of IContainer using DB4O memory databases.
53 *
54 * @author smolloy
55 *
56 * @deprecated The MemoryContainer relied on the db4o replication mechanisms which is now external in dRS, the
57 * MemoryContainer will no longer be extended.
58 */
59 @Deprecated
60 public class MemoryContainer<T extends Comparable<? super T> & Cloneable & Serializable, C extends Comparable<? super C>>
61 implements IContainer<T, C>, ReplicationConflictHandler {
62 /**
63 * Logger for this class
64 */
65 private static final Logger logger = Logger.getLogger(MemoryContainer.class.getName());
66
67 /** Lock to avoid concurrent backups or defragmentations of multiple databases. */
68 private static final Object classLock = new Object();
69
70 /** Block size for underlying DB, size limit will be roughly blockSize * 2GB. */
71 private static int blockSize = 1;
72
73 /**
74 * @return the blockSize
75 */
76 public static int getBlockSize() {
77 return MemoryContainer.blockSize;
78 }
79
80 /**
81 * @param blockSize
82 * the blockSize to set
83 */
84 public static void setBlockSize(final int blockSize) {
85 MemoryContainer.blockSize = Math.max(blockSize, 1);
86 }
87
88 /** The data storing database. */
89 protected ExtObjectContainer db;
90
91 /** The ID map database. */
92 protected ExtObjectContainer mapDB;
93
94 /** Database storing map of deleted IDs. */
95 protected ExtObjectContainer unmapDB;
96
97 /** The database name. */
98 private final String dbName;
99
100 /** The timer used to periodically backup to file. */
101 private final Timer theTimer;
102
103 /** Shutdown hook to make sure memory is saved to file when VM shuts down. */
104 private final Thread hookThread;
105
106 /** Lock used to avoid concurrent saving */
107 private final Object saveLock = new Object();
108
109 /** List of pending tasks to execute. */
110 private final ArrayList<Runnable> pendingTasks = new ArrayList<Runnable>();
111
112 /** Flag for closing. */
113 private boolean closing = false;
114
115 /** Flag for performing pending tasks. */
116 private boolean runningPendingTasks = false;
117
118 /** Number of pending save or delete operations. */
119 private int pendingSaves = 0;
120
121 /** Thread executing the background tasks. */
122 private final Thread taskExecutor;
123
124 /** Whether or not the task executor thread has already been started. */
125 private boolean taskExecutorStarted = false;
126
127 /**
128 * Constructor.
129 *
130 * @param dbName
131 * Name of the database.
132 * @param delay
133 * Delay before the first save.
134 * @param saveInterval
135 * Interval (in ms) between periodic replication to backup database.
136 */
137 public MemoryContainer(final String dbName, final long delay, final long saveInterval) {
138 theTimer = new Timer();
139 this.dbName = dbName;
140 configure();
141 ExtObjectContainer fileDB = null;
142 ExtObjectContainer unmapFileDB = null;
143 try {
144 fileDB = Db4o.openFile(dbName).ext();
145 unmapFileDB = Db4o.openFile(dbName + "_del").ext();
146 } catch (final Throwable ex) {
147 if (MemoryContainer.logger.isLoggable(Level.FINE)) {
148 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "MemoryContainer(String=" + dbName
149 + ", long=" + delay + ", long=" + saveInterval + ")", "exception ignored", ex);
150 }
151 }
152 try {
153 db = ExtDb4o.openMemoryFile(new MemoryFile()).ext();
154 mapDB = ExtDb4o.openMemoryFile(new MemoryFile()).ext();
155 unmapDB = ExtDb4o.openMemoryFile(new MemoryFile()).ext();
156 } catch (final Throwable ex) {
157 if (MemoryContainer.logger.isLoggable(Level.FINE)) {
158 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "MemoryContainer(String=" + dbName
159 + ", long=" + delay + ", long=" + saveInterval + ")", "exception ignored", ex);
160 }
161 }
162 if (db != null && fileDB != null) {
163 replicate(fileDB, db);
164 fileDB.close();
165 replicate(unmapFileDB, unmapDB);
166 unmapFileDB.close();
167 createMap();
168 }
169 final TimerTask saveMemoryTask = new TimerTask() {
170 public void run() {
171 while (!Config.checkMemory()) {
172 try {
173 Thread.sleep(30000);
174 } catch (InterruptedException e) {
175 if (MemoryContainer.logger.isLoggable(Level.FINE)) {
176 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "Save$TimerTask.run()",
177 "exception ignored", e);
178 }
179 }
180 }
181 backup();
182 }
183 };
184 theTimer.scheduleAtFixedRate(saveMemoryTask, delay, saveInterval);
185 taskExecutor = new Thread() {
186 public void run() {
187 while (!closing) {
188 if (pendingTasks != null && pendingTasks.size() > 0) {
189 runningPendingTasks = true;
190 Runnable[] tasks = null;
191 synchronized (pendingTasks) {
192 tasks = pendingTasks.toArray(new Runnable[0]);
193 pendingTasks.clear();
194 }
195 for (int i = 0; i < tasks.length; i++) {
196 tasks[i].run();
197 }
198 runningPendingTasks = false;
199 }
200 try {
201 Thread.sleep(10);
202 } catch (final InterruptedException e) {
203 if (MemoryContainer.logger.isLoggable(Level.FINE)) {
204 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "TaskExecutor$Thread.run()",
205 "exception ignored", e);
206 }
207 }
208 }
209 }
210 };
211 final Runnable saveHook = new Runnable() {
212 public void run() {
213 close(false);
214 }
215 };
216 hookThread = new Thread(saveHook);
217 Runtime.getRuntime().addShutdownHook(hookThread);
218 }
219
220 /**
221 * Backup the data and ID map databases.
222 *
223 */
224 protected synchronized void backup() {
225 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
226 MemoryContainer.logger.entering("MemoryContainer", "backup()", "start");
227 }
228
229 if (db != null && !db.isClosed()) {
230 try {
231 commit();
232 final File backup = new File(dbName);
233 if (backup.exists()) {
234 backup.delete();
235 }
236 final ExtObjectContainer tmp = Db4o.openFile(dbName).ext();
237 replicate(db, tmp);
238 if (backup.exists()) {
239 backup.delete();
240 }
241 final ExtObjectContainer unmaptmp = Db4o.openFile(dbName + "del").ext();
242 replicate(unmapDB, unmaptmp);
243 unmaptmp.close();
244 } catch (final Throwable ex) {
245 if (MemoryContainer.logger.isLoggable(Level.FINE)) {
246 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "backup()", "exception ignored", ex);
247 }
248 }
249 }
250
251 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
252 MemoryContainer.logger.exiting("MemoryContainer", "backup()", "end");
253 }
254 }
255
256 /**
257 * @see org.ov4j.IContainer#batchDelete(org.ov4j.data.Item<T>[])
258 */
259 public void batchDelete(final Item<T, C>[] items, final boolean keepTrack) {
260 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
261 MemoryContainer.logger.entering("MemoryContainer", "batchDelete(Item<T,C>[]=" + Arrays.toString(items)
262 + ", boolean=" + keepTrack + ")", "start");
263 }
264
265 for (int i = 0; i < items.length; i++) {
266 delete(items[i], keepTrack);
267 }
268
269 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
270 MemoryContainer.logger.exiting("MemoryContainer", "batchDelete(Item<T,C>[]=" + Arrays.toString(items)
271 + ", boolean=" + keepTrack + ")", "end");
272 }
273 }
274
275 /**
276 * @see org.ov4j.IContainer#batchMerge(java.lang.Comparable[])
277 */
278 public void batchMerge(final C[] ids) {
279 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
280 MemoryContainer.logger.entering("MemoryContainer", "batchMerge(C[]=" + Arrays.toString(ids) + ")", "start");
281 }
282
283 final Runnable t = new Runnable() {
284 public void run() {
285 synchronized (saveLock) {
286 Query q = db.query();
287 q.constrain(Item.class);
288 q.constrain(new MultipleIDValidator<C>(ids));
289 ObjectSet<Item<T, C>> set = q.execute();
290 ArrayList<Item<T, C>> res = new ArrayList<Item<T, C>>();
291 while (set.hasNext()) {
292 res.add((Item<T, C>) set.next());
293 }
294 Collections.sort(res, new Comparator<Item<T, C>>() {
295 public int compare(Item<T, C> it1, Item<T, C> it2) {
296 if (it1 == null) {
297 return (it2 == null) ? 0 : -1;
298 }
299
300 if (it2 == null) {
301 return 1;
302 }
303
304 return (it1.getId().compareTo(it2.getId()));
305 }
306 });
307 ArrayList<Item<T, C>> mergedList = new ArrayList<Item<T, C>>();
308 ArrayList<Item<T, C>> deletedList = new ArrayList<Item<T, C>>();
309 C lastId = null;
310 long lastTStamp = -1;
311 for (int i = 0; i < res.size(); i++) {
312 Item<T, C> current = res.get(i);
313 if (lastId == null || lastId.compareTo(current.getId()) != 0) {
314 mergedList.add(0, current);
315 lastId = current.getId();
316 lastTStamp = current.getModificationStamp();
317 } else {
318 if (current.getModificationStamp() > lastTStamp) {
319 deletedList.add(0, mergedList.remove(0));
320 } else {
321 deletedList.add(current);
322 }
323 }
324 }
325 for (int i = 0; i < mergedList.size(); i++) {
326 Item<T, C> it = mergedList.get(i);
327 db.set(it);
328 map(it);
329 }
330 for (int i = 0; i < deletedList.size(); i++) {
331 Item<T, C> it = deletedList.get(i);
332 db.delete(it);
333 unmap(it, false);
334 }
335 }
336 pendingSaves--;
337 }
338 };
339 synchronized (pendingTasks) {
340 pendingSaves++;
341 pendingTasks.add(t);
342 }
343
344 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
345 MemoryContainer.logger.exiting("MemoryContainer", "batchMerge(C[]=" + Arrays.toString(ids) + ")", "end");
346 }
347 }
348
349 /**
350 * @see org.ov4j.IContainer#batchSave(org.ov4j.data.Item<T>[], boolean)
351 */
352 public void batchSave(final Item<T, C>[] items, final boolean abortOnDuplicate) throws IOException {
353 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
354 MemoryContainer.logger.entering("MemoryContainer", "batchSave(Item<T,C>[]=" + Arrays.toString(items)
355 + ", boolean=" + abortOnDuplicate + ")", "start");
356 }
357
358 final Runnable t = new Runnable() {
359 public void run() {
360 synchronized (saveLock) {
361 C[] ids = (C[]) new Comparable[items.length];
362 for (int i = 0; i < items.length; i++) {
363 ids[i] = items[i].getId();
364 }
365 Query q = mapDB.query();
366 q.constrain(MappedField.class);
367 q.constrain(new MultipleIDValidator<C>(ids));
368 ObjectSet<MappedField> set = q.execute();
369 if (!abortOnDuplicate || set.size() < 1) {
370 TreeSet<C> idSet = new TreeSet<C>();
371 while (set.hasNext()) {
372 idSet.add((C) ((MappedField) set.next()).getObject());
373 }
374 for (int i = 0; i < items.length; i++) {
375 db.set(items[i]);
376 if (items[i] != null && !idSet.contains(items[i].getId())) {
377 long id = db.getID(items[i]);
378 MappedField mf = new MappedField();
379 mf.setModificationStamp(items[i].getModificationStamp());
380 mf.setObject(items[i].getId());
381 mf.setId(id);
382 mapDB.set(mf);
383 } else if (items[i] != null) {
384 idSet.remove(items[i].getId());
385 }
386 }
387 }
388 }
389 pendingSaves--;
390 }
391 };
392 synchronized (pendingTasks) {
393 pendingSaves++;
394 pendingTasks.add(t);
395 }
396
397 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
398 MemoryContainer.logger.exiting("MemoryContainer", "batchSave(Item<T,C>[]=" + Arrays.toString(items)
399 + ", boolean=" + abortOnDuplicate + ")", "end");
400 }
401 }
402
403 /**
404 * Clear the database, will not clear the backups.
405 *
406 * WARNING: THIS OPERATION CAN BE VERY HARMFUL AS ALL DATA IN THE DB WILL BE LOST!!!
407 *
408 * @see org.ov4j.IContainer#clear()
409 */
410 public void clear() {
411 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
412 MemoryContainer.logger.entering("MemoryContainer", "clear()", "start");
413 }
414
415 try {
416 db.close();
417 mapDB.close();
418 unmapDB.close();
419 db = ExtDb4o.openMemoryFile(new MemoryFile()).ext();
420 mapDB = ExtDb4o.openMemoryFile(new MemoryFile()).ext();
421 unmapDB = ExtDb4o.openMemoryFile(new MemoryFile()).ext();
422 } catch (final Throwable ex) {
423 if (MemoryContainer.logger.isLoggable(Level.FINE)) {
424 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "clear()", "exception ignored", ex);
425 }
426 }
427 backup();
428
429 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
430 MemoryContainer.logger.exiting("MemoryContainer", "clear()", "end");
431 }
432 }
433
434 /**
435 * @see org.ov4j.IContainer#clearDeletedIDs()
436 */
437 public void clearDeletedIDs() {
438 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
439 MemoryContainer.logger.entering("MemoryContainer", "clearDeletedIDs()", "start");
440 }
441
442 unmapDB.close();
443 unmapDB = ExtDb4o.openMemoryFile(new MemoryFile()).ext();
444
445 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
446 MemoryContainer.logger.exiting("MemoryContainer", "clearDeletedIDs()", "end");
447 }
448 }
449
450 /**
451 * @see org.ov4j.IContainer#close()
452 */
453 public synchronized void close() {
454 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
455 MemoryContainer.logger.entering("MemoryContainer", "close()", "start");
456 }
457
458 close(true);
459
460 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
461 MemoryContainer.logger.exiting("MemoryContainer", "close()", "end");
462 }
463 }
464
465 /**
466 * Close the container, possibly removing shutdown hook.
467 *
468 * @param removeHook
469 * Whether or not the shutdown hook should be removed first.
470 */
471 public synchronized void close(final boolean removeHook) {
472 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
473 MemoryContainer.logger.entering("MemoryContainer", "close(boolean=" + removeHook + ")", "start");
474 }
475
476 waitForPendingTasks();
477 closing = true;
478 if (removeHook) {
479 Runtime.getRuntime().removeShutdownHook(hookThread);
480 }
481 if (pendingTasks != null && pendingTasks.size() > 0) {
482 runningPendingTasks = true;
483 Runnable[] tasks = null;
484 synchronized (pendingTasks) {
485 tasks = pendingTasks.toArray(new Runnable[0]);
486 pendingTasks.clear();
487 }
488 for (int i = 0; i < tasks.length; i++) {
489 tasks[i].run();
490 }
491 runningPendingTasks = false;
492 }
493 backup();
494 if (theTimer != null) {
495 theTimer.cancel();
496 }
497 if (db != null) {
498 while (!db.close()) {
499 ;
500 }
501 }
502 if (mapDB != null) {
503 while (!mapDB.close()) {
504 ;
505 }
506 }
507 if (unmapDB != null) {
508 while (!unmapDB.close()) {
509 ;
510 }
511 }
512
513 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
514 MemoryContainer.logger.exiting("MemoryContainer", "close(boolean=" + removeHook + ")", "end");
515 }
516 }
517
518 /**
519 * @see org.ov4j.IContainer#commit()
520 */
521 public void commit() {
522 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
523 MemoryContainer.logger.entering("MemoryContainer", "commit()", "start");
524 }
525
526 final Runnable t = new Runnable() {
527 public void run() {
528 synchronized (saveLock) {
529 db.commit();
530 mapDB.commit();
531 }
532 }
533 };
534 synchronized (pendingTasks) {
535 pendingTasks.add(t);
536 }
537
538 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
539 MemoryContainer.logger.exiting("MemoryContainer", "commit()", "end");
540 }
541 }
542
543 /**
544 * Configure database, subclasses should overload this method, but still call super.configure() so that everything
545 * is configured correctly. This method will be called before any databases are opened or created.
546 *
547 */
548 protected void configure() {
549 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
550 MemoryContainer.logger.entering("MemoryContainer", "configure()", "start");
551 }
552
553 Db4o.configure().allowVersionUpdates(true);
554 Db4o.configure().blockSize(MemoryContainer.blockSize);
555 Db4o.configure().automaticShutDown(false);
556 Db4o.configure().generateUUIDs(ConfigScope.GLOBALLY);
557 Db4o.configure().generateVersionNumbers(ConfigScope.GLOBALLY);
558 Db4o.configure().callbacks(false);
559 Db4o.configure().callConstructors(true);
560 Db4o.configure().objectClass(ClassComparable.class).minimumActivationDepth(3);
561 Db4o.configure().objectClass(MappedField.class).objectField("object").indexed(true);
562 Db4o.configure().objectClass(MappedField.class).cascadeOnActivate(true);
563 Db4o.configure().objectClass(MappedField.class).cascadeOnUpdate(true);
564 Db4o.configure().objectClass(Item.class).objectField("id").cascadeOnActivate(true);
565 Db4o.configure().objectClass(Item.class).objectField("id").cascadeOnUpdate(true);
566 Db4o.configure().objectClass(Item.class).objectField("latest").cascadeOnActivate(true);
567 Db4o.configure().objectClass(Item.class).objectField("latest").cascadeOnUpdate(true);
568 Db4o.configure().objectClass(Item.class).objectField("versions").cascadeOnActivate(false);
569 Db4o.configure().objectClass(Item.class).objectField("versions").cascadeOnUpdate(true);
570 Db4o.configure().objectClass(Item.class).objectField("versions").queryEvaluation(false);
571 Db4o.configure().objectClass(Version.class).objectField("author").cascadeOnActivate(true);
572 Db4o.configure().objectClass(Version.class).objectField("author").cascadeOnUpdate(true);
573 Db4o.configure().objectClass(Version.class).objectField("comment").cascadeOnActivate(true);
574 Db4o.configure().objectClass(Version.class).objectField("comment").cascadeOnUpdate(true);
575 Db4o.configure().objectClass(Version.class).objectField("versionNumber").cascadeOnActivate(true);
576 Db4o.configure().objectClass(Version.class).objectField("versionNumber").cascadeOnUpdate(true);
577 Db4o.configure().objectClass(Version.class).objectField("versionedObject").cascadeOnActivate(true);
578 Db4o.configure().objectClass(Version.class).objectField("versionedObject").cascadeOnUpdate(true);
579
580 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
581 MemoryContainer.logger.exiting("MemoryContainer", "configure()", "end");
582 }
583 }
584
585 /**
586 * Create the map of IDs if it could not be loaded from file.
587 *
588 */
589 private void createMap() {
590 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
591 MemoryContainer.logger.entering("MemoryContainer", "createMap()", "start");
592 }
593
594 if (!Boolean.getBoolean("ov4j.thread.map")) {
595 synchronized (MemoryContainer.classLock) {
596 map();
597 }
598 } else {
599 map();
600 }
601
602 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
603 MemoryContainer.logger.exiting("MemoryContainer", "createMap()", "end");
604 }
605 }
606
607 /**
608 * @see org.ov4j.IContainer#delete(org.ov4j.data.Item)
609 */
610 public void delete(final Item<T, C> it, final boolean keepTrack) {
611 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
612 MemoryContainer.logger.entering("MemoryContainer", "delete(Item<T,C>=" + it + ", boolean=" + keepTrack
613 + ")", "start");
614 }
615
616 final Runnable t = new Runnable() {
617 public void run() {
618 synchronized (saveLock) {
619 unmap(it, keepTrack);
620 db.delete(it);
621 pendingSaves--;
622 }
623 }
624 };
625 synchronized (pendingTasks) {
626 pendingSaves++;
627 pendingTasks.add(t);
628 }
629
630 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
631 MemoryContainer.logger.exiting("MemoryContainer",
632 "delete(Item<T,C>=" + it + ", boolean=" + keepTrack + ")", "end");
633 }
634 }
635
636 /**
637 * @see org.ov4j.IContainer#deletedIds()
638 */
639 public C[] deletedIds() {
640 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
641 MemoryContainer.logger.entering("MemoryContainer", "deletedIds()", "start");
642 }
643
644 final ObjectSet<MappedField> set = unmapDB.get(MappedField.class);
645 final C[] ids = (C[]) new Comparable[set.size()];
646 final ArrayList<Object> invalids = new ArrayList<Object>();
647 for (int i = 0; set.hasNext() && i < ids.length; i++) {
648 final Object next = set.next();
649 if (next instanceof MappedField && ((MappedField) next).getObject() instanceof Comparable) {
650 ids[i] = (C) ((MappedField) next).getObject();
651 } else {
652 invalids.add(next);
653 }
654 }
655 if (invalids.size() > 0) {
656 final ArrayList<?> list = invalids;
657 final Runnable t = new Runnable() {
658 public void run() {
659 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
660 MemoryContainer.logger.entering("MemoryContainer", "deletedIds()$Runnable.run()", "start");
661 }
662
663 synchronized (saveLock) {
664 for (int i = 0; i < list.size(); i++) {
665 unmapDB.delete(list.get(i));
666 }
667 unmapDB.commit();
668 pendingSaves--;
669 }
670
671 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
672 MemoryContainer.logger.exiting("MemoryContainer", "deletedIds()$Runnable.run()", "end");
673 }
674 }
675 };
676 synchronized (pendingTasks) {
677 pendingSaves++;
678 pendingTasks.add(t);
679 }
680 }
681
682 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
683 MemoryContainer.logger.exiting("MemoryContainer", "deletedIds()", "end - return value="
684 + Arrays.toString(ids));
685 }
686 return ids;
687 }
688
689 /**
690 * @see org.ov4j.IContainer#deletedIdsSince(long)
691 */
692 public C[] deletedIdsSince(final long timestamp) {
693 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
694 MemoryContainer.logger.entering("MemoryContainer", "deletedIdsSince(long=" + timestamp + ")", "start");
695 }
696
697 final Query q = unmapDB.query();
698 q.constrain(MappedField.class);
699 q.descend("modificationStamp").constrain(timestamp).greater();
700 final ObjectSet<MappedField> set = q.execute();
701 final C[] ids = (C[]) new Comparable[set.size()];
702 for (int i = 0; set.hasNext() && i < ids.length; i++) {
703 ids[i] = (C) ((MappedField) set.next()).getObject();
704 }
705
706 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
707 MemoryContainer.logger.exiting("MemoryContainer", "deletedIdsSince(long=" + timestamp + ")",
708 "end - return value=" + Arrays.toString(ids));
709 }
710 return ids;
711 }
712
713 /**
714 * Converts the given IContainer into an hessian implementation.
715 *
716 * @param cont
717 * IContainer to convert.
718 * @throws IOException
719 */
720 public void duplicate(final IContainer<T, C> cont) throws IOException {
721 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
722 MemoryContainer.logger.entering("MemoryContainer", "duplicate(IContainer<T,C>=" + cont + ")", "start");
723 }
724
725 final C[] ids = cont.listModifiedSince(-1);
726 if (ids != null) {
727 for (int startIdx = 0; startIdx < ids.length; startIdx += 32) {
728 final C[] buf = (C[]) new Comparable[Math.min(32, (ids.length - startIdx))];
729 System.arraycopy(ids, startIdx, buf, 0, buf.length);
730 batchSave(cont.load(buf, true), false);
731 }
732 }
733 commit();
734
735 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
736 MemoryContainer.logger.exiting("MemoryContainer", "duplicate(IContainer<T,C>=" + cont + ")", "end");
737 }
738 }
739
740 /**
741 * @see org.ov4j.IContainer#inUse(java.lang.Comparable)
742 */
743 public boolean inUse(final C id) {
744 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
745 MemoryContainer.logger.entering("MemoryContainer", "inUse(C=" + id + ")", "start");
746 }
747
748 final MappedField map = new MappedField();
749 map.setObject(id);
750 final ObjectSet<MappedField> set = mapDB.get(map);
751 final boolean res = set.hasNext();
752
753 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
754 MemoryContainer.logger.exiting("MemoryContainer", "inUse(C=" + id + ")", "end - return value=" + res);
755 }
756 return res;
757 }
758
759 /**
760 * @see org.ov4j.IContainer#isClosed()
761 */
762 public boolean isClosed() {
763 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
764 MemoryContainer.logger.entering("MemoryContainer", "isClosed()", "start");
765 }
766
767 final boolean returnboolean = db.isClosed();
768 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
769 MemoryContainer.logger.exiting("MemoryContainer", "isClosed()", "end - return value=" + returnboolean);
770 }
771 return returnboolean;
772 }
773
774 /**
775 * @throws IOException
776 * @see org.ov4j.IContainer#listModifiedSince(long)
777 */
778 public C[] listModifiedSince(final long timestamp) throws IOException {
779 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
780 MemoryContainer.logger.entering("MemoryContainer", "listModifiedSince(long=" + timestamp + ")", "start");
781 }
782
783 final Query q = mapDB.query();
784 q.constrain(MappedField.class);
785 final ObjectSet<MappedField> set = q.execute();
786 final C[] res = (C[]) new Comparable[set.size()];
787 for (int i = 0; set.hasNext() && i < res.length; i++) {
788 res[i] = (C) ((MappedField) set.next()).getObject();
789 }
790
791 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
792 MemoryContainer.logger.exiting("MemoryContainer", "listModifiedSince(long=" + timestamp + ")",
793 "end - return value=" + Arrays.toString(res));
794 }
795 return res;
796 }
797
798 /**
799 * @see org.ov4j.IContainer#load(java.lang.Comparable, boolean)
800 */
801 public Item<T, C> load(final C id, final boolean allVersions) {
802 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
803 MemoryContainer.logger.entering("MemoryContainer", "load(C=" + id + ", boolean=" + allVersions + ")",
804 "start");
805 }
806
807 Item<T, C> res = null;
808 final MappedField map = new MappedField();
809 map.setObject(id);
810 final ObjectSet<MappedField> set = mapDB.get(map);
811 if (set.hasNext()) {
812 final MappedField mf = (MappedField) set.next();
813 res = (Item<T, C>) db.getByID(mf.getId());
814 db.activate(res, 2);
815 if (allVersions && res != null) {
816 db.activate(res.getVersions(), 2);
817 }
818 }
819 if (res != null) {
820 try {
821 res = res.clone();
822 } catch (final CloneNotSupportedException e) {
823 if (MemoryContainer.logger.isLoggable(Level.FINE)) {
824 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "load(C=" + id + ", boolean="
825 + allVersions + ")", "Exception ignored", e);
826 }
827 }
828 }
829
830 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
831 MemoryContainer.logger.exiting("MemoryContainer", "load(C=" + id + ", boolean=" + allVersions + ")",
832 "end - return value=" + res);
833 }
834 return res;
835 }
836
837 /**
838 * @see org.ov4j.IContainer#load(java.lang.Comparable, boolean)
839 */
840 public Item<T, C>[] load(final C[] ids, final boolean allVersions) {
841 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
842 MemoryContainer.logger.entering("MemoryContainer", "load(C[]=" + Arrays.toString(ids) + ", boolean="
843 + allVersions + ")", "start");
844 }
845
846 final Query q = mapDB.query();
847 q.constrain(MappedField.class);
848 q.constrain(new MultipleIDValidator<C>(ids));
849 final ObjectSet<MappedField> set = q.execute();
850 final Item<T, C>[] res = new Item[set.size()];
851 for (int i = 0; set.hasNext() && i < res.length; i++) {
852 res[i] = (Item<T, C>) db.getByID(((MappedField) set.next()).getId());
853 db.activate(res[i], 2);
854 if (res[i] != null) {
855 if (allVersions) {
856 db.activate(res[i].getVersions(), 2);
857 }
858 try {
859 res[i] = res[i].clone();
860 } catch (final CloneNotSupportedException e) {
861 if (MemoryContainer.logger.isLoggable(Level.FINE)) {
862 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "load(C[]=" + Arrays.toString(ids)
863 + ", boolean=" + allVersions + ")", "Exception ignored", e);
864 }
865 }
866 }
867 }
868
869 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
870 MemoryContainer.logger.exiting("MemoryContainer", "load(C[]=" + Arrays.toString(ids) + ", boolean="
871 + allVersions + ")", "end - return value=" + Arrays.toString(res));
872 }
873 return res;
874 }
875
876 private synchronized void map() {
877 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
878 MemoryContainer.logger.entering("MemoryContainer", "map()", "start");
879 }
880
881 final StoredClass cls = db.storedClass(Item.class);
882 if (cls != null) {
883 final long[] ids = cls.getIDs();
884 if (ids != null && ids.length > 0) {
885 for (int i = 0; i < ids.length; i++) {
886 final Item<T, C> it = db.getByID(ids[i]);
887 db.activate(it, 1);
888 final MappedField mf = new MappedField();
889 mf.setObject(it.getId());
890 mf.setId(ids[i]);
891 mf.setModificationStamp(it.getModificationStamp());
892 mapDB.set(mf);
893 }
894 commit();
895 }
896 }
897
898 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
899 MemoryContainer.logger.exiting("MemoryContainer", "map()", "end");
900 }
901 }
902
903 /**
904 * Create an ID map for this item.
905 *
906 * @param it
907 * Item for which a map should be created.
908 */
909 private void map(final Item<T, C> it) {
910 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
911 MemoryContainer.logger.entering("MemoryContainer", "map(Item<T,C>=" + it + ")", "start");
912 }
913
914 final MappedField map = new MappedField();
915 map.setObject(it.getId());
916 final ObjectSet<MappedField> mapSet = mapDB.get(map);
917 MappedField mf = (mapSet == null || !mapSet.hasNext()) ? null : mapSet.next();
918
919 if (mf == null) {
920 long id = db.getID(it);
921 if (id < 1) {
922 final ObjectSet<Item<T, C>> set = db.get(it);
923 id = (set == null || !set.hasNext()) ? null : db.getID(set.next());
924 }
925 if (id > 0) {
926 final ObjectSet<MappedField> unmapSet = unmapDB.get(map);
927 mf = (unmapSet == null || !unmapSet.hasNext()) ? null : unmapSet.next();
928 if (mf != null) {
929 unmapDB.delete(mf);
930 }
931 mf = new MappedField();
932 mf.setObject(it.getId());
933 mf.setId(id);
934 }
935 } else {
936 long id = db.getID(it);
937 if (id < 1) {
938 final ObjectSet<Item<T, C>> set = db.get(it);
939 id = (set == null || !set.hasNext()) ? null : db.getID(set.next());
940 }
941 if (id < 1) {
942 mapDB.delete(mf);
943 mf = null;
944 } else {
945 mf.setId(id);
946 }
947 }
948 if (mf != null) {
949 mf.setModificationStamp(it.getModificationStamp());
950 mapDB.set(mf);
951 }
952
953 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
954 MemoryContainer.logger.exiting("MemoryContainer", "map(Item<T,C>=" + it + ")", "end");
955 }
956 }
957
958 /**
959 * @see org.ov4j.IContainer#modifiedSince(long, boolean)
960 */
961 public Item<T, C>[] modifiedSince(final long timestamp, final boolean allVersions) {
962 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
963 MemoryContainer.logger.entering("MemoryContainer", "modifiedSince(long=" + timestamp + ", boolean="
964 + allVersions + ")", "start");
965 }
966
967 final Query q = mapDB.query();
968 q.constrain(MappedField.class);
969 q.descend("modificationStamp").constrain(timestamp).greater();
970 final ObjectSet<MappedField> set = q.execute();
971 final Item<T, C>[] res = new Item[set.size()];
972 for (int i = 0; set.hasNext() && i < res.length; i++) {
973 final MappedField mf = (MappedField) set.next();
974 res[i] = (Item<T, C>) db.getByID(mf.getId());
975 db.activate(res[i], 2);
976 if (res[i] != null) {
977 if (allVersions) {
978 db.activate(res[i].getVersions(), 2);
979 }
980 try {
981 res[i] = res[i].clone();
982 } catch (final CloneNotSupportedException e) {
983 if (MemoryContainer.logger.isLoggable(Level.FINE)) {
984 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "modifiedSince(long=" + timestamp
985 + ", boolean=" + allVersions + ")", "Exception ignored", e);
986 }
987 }
988 }
989 }
990
991 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
992 MemoryContainer.logger.exiting("MemoryContainer", "modifiedSince(long=" + timestamp + ", boolean="
993 + allVersions + ")", "end - return value=" + Arrays.toString(res));
994 }
995 return res;
996 }
997
998 /**
999 * @see org.ov4j.IContainer#release(java.lang.Object)
1000 */
1001 public void release(final Object obj) {
1002 }
1003
1004 /**
1005 * Replicate the original database to the destination one.
1006 *
1007 * @param original
1008 * Database in which to look for changes.
1009 * @param destination
1010 * Database to which changes will be applied.
1011 */
1012 private synchronized void replicate(final ExtObjectContainer original, final ExtObjectContainer destination) {
1013 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
1014 MemoryContainer.logger.entering("MemoryContainer", "replicate(ExtObjectContainer=" + original
1015 + ", ExtObjectContainer=" + destination + ")", "start");
1016 }
1017
1018 if (original != null && destination != null && !original.isClosed() && !destination.isClosed()) {
1019 final ReplicationProcess replication =
1020 original.replicationBegin(destination, new ReplicationConflictHandler() {
1021 public Object resolveConflict(ReplicationProcess replicationProcess, Object a, Object b) {
1022 return a;
1023 }
1024 });
1025 replication.setDirection(original, destination);
1026 final Query q = original.query();
1027 q.constrain(Item.class);
1028 replication.whereModified(q);
1029 final ObjectSet<Item<T, C>> replicationSet = q.execute();
1030 while (replicationSet.hasNext()) {
1031 replication.replicate(replicationSet.next());
1032 }
1033 replication.commit();
1034 }
1035
1036 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
1037 MemoryContainer.logger.exiting("MemoryContainer", "replicate(ExtObjectContainer=" + original
1038 + ", ExtObjectContainer=" + destination + ")", "end");
1039 }
1040 }
1041
1042 /**
1043 * Always returns object A as replication is always used to backup in peer B.
1044 *
1045 * @see com.db4o.replication.ReplicationConflictHandler#resolveConflict(com.db4o.replication.ReplicationProcess,
1046 * java.lang.Object, java.lang.Object)
1047 */
1048 public Object resolveConflict(final ReplicationProcess proc, final Object objA, final Object objB) {
1049 return objA;
1050 }
1051
1052 /**
1053 * @see org.ov4j.IContainer#rollback()
1054 */
1055 public void rollback() {
1056 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
1057 MemoryContainer.logger.entering("MemoryContainer", "rollback()", "start");
1058 }
1059
1060 final Runnable t = new Runnable() {
1061 public void run() {
1062 synchronized (saveLock) {
1063 db.rollback();
1064 mapDB.rollback();
1065 }
1066 }
1067 };
1068 synchronized (pendingTasks) {
1069 pendingTasks.add(t);
1070 }
1071
1072 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
1073 MemoryContainer.logger.exiting("MemoryContainer", "rollback()", "end");
1074 }
1075 }
1076
1077 /**
1078 * @see org.ov4j.IContainer#save(org.ov4j.data.Item)
1079 */
1080 public void save(final Item<T, C> it) {
1081 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
1082 MemoryContainer.logger.entering("MemoryContainer", "save(Item<T,C>=" + it + ")", "start");
1083 }
1084
1085 final Runnable t = new Runnable() {
1086 public void run() {
1087 synchronized (saveLock) {
1088 db.set(it);
1089 map(it);
1090 pendingSaves--;
1091 }
1092 }
1093 };
1094 synchronized (pendingTasks) {
1095 pendingSaves++;
1096 pendingTasks.add(t);
1097 }
1098
1099 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
1100 MemoryContainer.logger.exiting("MemoryContainer", "save(Item<T,C>=" + it + ")", "end");
1101 }
1102 }
1103
1104 /**
1105 * Returns a string representation of this Container.
1106 *
1107 * @return String representation of the Container.
1108 */
1109 public String toString() {
1110 return "MemoryContainer{" + dbName + "}";
1111 }
1112
1113 /**
1114 * Delete any maps for this item.
1115 *
1116 * @param it
1117 * Item for which maps should be deleted.
1118 * @param keepTrack
1119 * Whether or not to keep ID in history.
1120 */
1121 private void unmap(final Item<T, C> it, final boolean keepTrack) {
1122 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
1123 MemoryContainer.logger.entering("MemoryContainer",
1124 "unmap(Item<T,C>=" + it + ", boolean=" + keepTrack + ")", "start");
1125 }
1126
1127 final MappedField mf = new MappedField();
1128 mf.setId(db.getID(it));
1129 mf.setObject(it.getId());
1130 final ObjectSet<MappedField> set = mapDB.get(mf);
1131 while (set.hasNext()) {
1132 mapDB.delete(set.next());
1133 }
1134 if (keepTrack) {
1135 final MappedField copy = new MappedField();
1136 copy.setObject(it.getId());
1137 copy.setModificationStamp(System.currentTimeMillis());
1138 unmapDB.set(copy);
1139 }
1140
1141 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
1142 MemoryContainer.logger.exiting("MemoryContainer", "unmap(Item<T,C>=" + it + ", boolean=" + keepTrack + ")",
1143 "end");
1144 }
1145 }
1146
1147 public void waitForPendingSaves() {
1148 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
1149 MemoryContainer.logger.entering("MemoryContainer", "waitForPendingSaves()", "start");
1150 }
1151
1152 if (!taskExecutorStarted) {
1153 synchronized (taskExecutor) {
1154 taskExecutorStarted = true;
1155 taskExecutor.start();
1156 }
1157 }
1158
1159 while (pendingSaves > 0) {
1160 try {
1161 Thread.sleep(100);
1162 } catch (final InterruptedException e) {
1163 if (MemoryContainer.logger.isLoggable(Level.FINE)) {
1164 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "waitForPendingSaves()",
1165 "exception ignored", e);
1166 }
1167 }
1168 }
1169
1170 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
1171 MemoryContainer.logger.exiting("MemoryContainer", "waitForPendingSaves()", "end");
1172 }
1173 }
1174
1175 public void waitForPendingTasks() {
1176 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
1177 MemoryContainer.logger.entering("MemoryContainer", "waitForPendingTasks()", "start");
1178 }
1179
1180 if (!taskExecutorStarted) {
1181 synchronized (taskExecutor) {
1182 taskExecutorStarted = true;
1183 taskExecutor.start();
1184 }
1185 }
1186
1187 while (pendingTasks.size() > 0 || runningPendingTasks) {
1188 try {
1189 Thread.sleep(100);
1190 } catch (final InterruptedException e) {
1191 if (MemoryContainer.logger.isLoggable(Level.FINE)) {
1192 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "waitForPendingTasks()",
1193 "exception ignored", e);
1194 }
1195 }
1196 }
1197
1198 if (MemoryContainer.logger.isLoggable(Level.FINER)) {
1199 MemoryContainer.logger.exiting("MemoryContainer", "waitForPendingTasks()", "end");
1200 }
1201 }
1202 }