View Javadoc

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 }