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.BufferedOutputStream;
22  import java.io.File;
23  import java.io.FileNotFoundException;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.PrintStream;
27  import java.io.Serializable;
28  import java.lang.reflect.Modifier;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.Comparator;
33  import java.util.Timer;
34  import java.util.TimerTask;
35  import java.util.TreeSet;
36  import java.util.logging.Level;
37  import java.util.logging.Logger;
38  
39  import org.ov4j.Config;
40  import org.ov4j.IContainer;
41  import org.ov4j.data.ClassComparable;
42  import org.ov4j.data.Item;
43  import org.ov4j.data.Version;
44  
45  import com.db4o.Db4o;
46  import com.db4o.ObjectContainer;
47  import com.db4o.ObjectSet;
48  import com.db4o.config.ConfigScope;
49  import com.db4o.ext.ExtObjectContainer;
50  import com.db4o.ext.StoredClass;
51  import com.db4o.query.Query;
52  import com.db4o.types.SecondClass;
53  
54  /**
55   * IContainer implementation using DB4O database for adta and DB4O extended memory database for mapping IDs.
56   * 
57   * @author smolloy
58   * 
59   */
60  public class Container<T extends Comparable<? super T> & Cloneable & Serializable, C extends Comparable<? super C>>
61  	implements IContainer<T, C> {
62  	/**
63  	 * Logger for this class
64  	 */
65  	private static final Logger	logger		= Logger.getLogger(Container.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 Container.blockSize;
78  	}
79  
80  	/**
81  	 * @param blockSize
82  	 *            the blockSize to set
83  	 */
84  	public static void setBlockSize(final int blockSize) {
85  		Container.blockSize = Math.max(blockSize, 1);
86  	}
87  
88  	/** Database storing data. */
89  	protected ExtObjectContainer		db;
90  
91  	/** Database storing map of IDs. */
92  	protected ExtObjectContainer		mapDB;
93  
94  	/** Database storing map of deleted IDs. */
95  	protected ExtObjectContainer		unmapDB;
96  
97  	/** Name of database. */
98  	private final String				dbName;
99  
100 	/** Timer performing periodic saves. */
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 	/** Where db4o messages will be stored. */
128 	private PrintStream					out					= System.out;
129 
130 	/**
131 	 * Constructor.
132 	 * 
133 	 * @param dbName
134 	 *            Name of the database.
135 	 * @param delay
136 	 *            Delay before the first save.
137 	 * @param saveInterval
138 	 *            Interval (in ms) between periodic replication to backup database.
139 	 */
140 	public Container(final String dbName, final long delay, final long saveInterval, final long defragDelay,
141 		final long defragInterval) {
142 		theTimer = new Timer();
143 		this.dbName = dbName;
144 		configure();
145 		try {
146 			db = Db4o.openFile(dbName).ext();
147 			new File(dbName + "_map").delete();
148 			mapDB = Db4o.openFile(dbName + "_map").ext();
149 			unmapDB = Db4o.openFile(dbName + "_del").ext();
150 		} catch (final Throwable ex) {
151 			if (Container.logger.isLoggable(Level.FINE)) {
152 				Container.logger.logp(Level.FINE, "Container", "Container(String=" + dbName + ", long=" + delay
153 					+ ", long=" + saveInterval + ", long=" + defragDelay + ", long=" + defragInterval + ")",
154 					"exception ignored", ex);
155 			}
156 		}
157 		createMap();
158 		final TimerTask defragTask = new TimerTask() {
159 			public void run() {
160 				defragment();
161 			}
162 		};
163 		final TimerTask backupTask = new TimerTask() {
164 			public void run() {
165 				while (!Config.checkMemory()) {
166 					try {
167 						Thread.sleep(30000);
168 					} catch (InterruptedException e) {
169 						if (Container.logger.isLoggable(Level.FINE)) {
170 							Container.logger.logp(Level.FINE, "Container", "Save$TimerTask.run()", "exception ignored",
171 								e);
172 						}
173 					}
174 				}
175 
176 				backup();
177 			}
178 		};
179 		if (defragDelay > -1 && defragInterval > -1) {
180 			theTimer.scheduleAtFixedRate(defragTask, defragDelay, defragInterval);
181 		}
182 		if (delay > -1 && saveInterval > -1) {
183 			theTimer.scheduleAtFixedRate(backupTask, delay, saveInterval);
184 		}
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 (Container.logger.isLoggable(Level.FINE)) {
204 							Container.logger.logp(Level.FINE, "Container", "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 void backup() {
225 		if (Container.logger.isLoggable(Level.FINER)) {
226 			Container.logger.entering("Container", "backup()", "start");
227 		}
228 
229 		if (db != null && !db.isClosed()) {
230 			try {
231 				commit();
232 				final File f = new File(dbName + ".bak");
233 				if (f.exists()) {
234 					f.delete();
235 				}
236 				db.backup(dbName + ".bak");
237 			} catch (final Throwable ex) {
238 				if (Container.logger.isLoggable(Level.FINE)) {
239 					Container.logger.logp(Level.FINE, "Container", "backup()", "exception ignored", ex);
240 				}
241 			}
242 		}
243 
244 		if (Container.logger.isLoggable(Level.FINER)) {
245 			Container.logger.exiting("Container", "backup()", "end");
246 		}
247 	}
248 
249 	/**
250 	 * @see org.ov4j.IContainer#batchDelete(org.ov4j.data.Item<T>[])
251 	 */
252 	public void batchDelete(final Item<T, C>[] items, final boolean keepTrack) {
253 		if (Container.logger.isLoggable(Level.FINER)) {
254 			Container.logger.entering("Container", "batchDelete(Item<T,C>[]=" + Arrays.toString(items) + ", boolean="
255 				+ keepTrack + ")", "start");
256 		}
257 
258 		for (int i = 0; i < items.length; i++) {
259 			delete(items[i], keepTrack);
260 		}
261 
262 		if (Container.logger.isLoggable(Level.FINER)) {
263 			Container.logger.exiting("Container", "batchDelete(Item<T,C>[]=" + Arrays.toString(items) + ", boolean="
264 				+ keepTrack + ")", "end");
265 		}
266 	}
267 
268 	/**
269 	 * @throws IOException
270 	 * @see org.ov4j.IContainer#batchMerge(java.lang.Comparable[])
271 	 */
272 	public void batchMerge(final C[] ids) throws IOException {
273 		if (Container.logger.isLoggable(Level.FINER)) {
274 			Container.logger.entering("Container", "batchMerge(C[]=" + Arrays.toString(ids) + ")", "start");
275 		}
276 
277 		final Runnable t = new Runnable() {
278 			public void run() {
279 				synchronized (saveLock) {
280 					Query q = db.query();
281 					q.constrain(Item.class);
282 					q.constrain(new MultipleIDValidator<C>(ids));
283 					ObjectSet<Item<T, C>> set = q.execute();
284 					ArrayList<Item<T, C>> res = new ArrayList<Item<T, C>>();
285 					while (set.hasNext()) {
286 						res.add(set.next());
287 					}
288 					Collections.sort(res, new Comparator<Item<T, C>>() {
289 						public int compare(Item<T, C> it1, Item<T, C> it2) {
290 							if (it1 == null) {
291 								return (it2 == null) ? 0 : -1;
292 							}
293 
294 							if (it2 == null) {
295 								return 1;
296 							}
297 
298 							return (it1.getId().compareTo(it2.getId()));
299 						}
300 					});
301 					ArrayList<Item<T, C>> mergedList = new ArrayList<Item<T, C>>();
302 					ArrayList<Item<T, C>> deletedList = new ArrayList<Item<T, C>>();
303 					C lastId = null;
304 					long lastTStamp = -1;
305 					for (int i = 0; i < res.size(); i++) {
306 						Item<T, C> current = res.get(i);
307 						if (lastId == null || lastId.compareTo(current.getId()) != 0) {
308 							mergedList.add(0, current);
309 							lastId = current.getId();
310 							lastTStamp = current.getModificationStamp();
311 						} else {
312 							if (current.getModificationStamp() > lastTStamp) {
313 								deletedList.add(0, mergedList.remove(0));
314 							} else {
315 								deletedList.add(current);
316 							}
317 						}
318 					}
319 					for (int i = 0; i < mergedList.size(); i++) {
320 						Item<T, C> it = mergedList.get(i);
321 						db.set(it);
322 						map(it);
323 					}
324 					for (int i = 0; i < deletedList.size(); i++) {
325 						Item<T, C> it = deletedList.get(i);
326 						db.delete(it);
327 						unmap(it, false);
328 					}
329 				}
330 				pendingSaves--;
331 			}
332 		};
333 		synchronized (pendingTasks) {
334 			pendingSaves++;
335 			pendingTasks.add(t);
336 		}
337 
338 		if (Container.logger.isLoggable(Level.FINER)) {
339 			Container.logger.exiting("Container", "batchMerge(C[]=" + Arrays.toString(ids) + ")", "end");
340 		}
341 	}
342 
343 	/**
344 	 * @throws IOException
345 	 * @see org.ov4j.IContainer#batchSave(org.ov4j.data.Item<T>[], boolean)
346 	 */
347 	public void batchSave(final Item<T, C>[] items, final boolean abortOnDuplicate) throws IOException {
348 		if (Container.logger.isLoggable(Level.FINER)) {
349 			Container.logger.entering("Container", "batchSave(Item<T,C>[]=" + Arrays.toString(items) + ", boolean="
350 				+ abortOnDuplicate + ")", "start");
351 		}
352 
353 		final Runnable t = new Runnable() {
354 			public void run() {
355 				synchronized (saveLock) {
356 					C[] ids = (C[]) new Comparable[items.length];
357 					for (int i = 0; i < items.length; i++) {
358 						ids[i] = items[i].getId();
359 					}
360 					Query q = mapDB.query();
361 					q.constrain(MappedField.class);
362 					q.constrain(new MultipleIDValidator<C>(ids));
363 					ObjectSet<MappedField> set = q.execute();
364 					if (!abortOnDuplicate || set.size() < 1) {
365 						TreeSet<C> idSet = new TreeSet<C>();
366 						while (set.hasNext()) {
367 							idSet.add((C) ((MappedField) set.next()).getObject());
368 						}
369 						for (int i = 0; i < items.length; i++) {
370 							db.set(items[i]);
371 							if (items[i] != null && !idSet.contains(items[i].getId())) {
372 								long id = db.getID(items[i]);
373 								MappedField mf = new MappedField();
374 								mf.setModificationStamp(items[i].getModificationStamp());
375 								mf.setObject(items[i].getId());
376 								mf.setId(id);
377 								mapDB.set(mf);
378 							} else if (items[i] != null) {
379 								idSet.remove(items[i].getId());
380 							}
381 						}
382 					}
383 				}
384 				pendingSaves--;
385 			}
386 		};
387 		synchronized (pendingTasks) {
388 			pendingSaves++;
389 			pendingTasks.add(t);
390 		}
391 
392 		if (Container.logger.isLoggable(Level.FINER)) {
393 			Container.logger.exiting("Container", "batchSave(Item<T,C>[]=" + Arrays.toString(items) + ", boolean="
394 				+ abortOnDuplicate + ")", "end");
395 		}
396 	}
397 
398 	/**
399 	 * Clear the database, will not clear the backups.
400 	 * 
401 	 * WARNING: THIS OPERATION CAN BE VERY HARMFUL AS ALL DATA IN THE DB WILL BE LOST!!!
402 	 * 
403 	 * @see org.ov4j.IContainer#clear()
404 	 */
405 	public void clear() {
406 		if (Container.logger.isLoggable(Level.FINER)) {
407 			Container.logger.entering("Container", "clear()", "start");
408 		}
409 
410 		try {
411 			db.close();
412 			mapDB.close();
413 			unmapDB.close();
414 			final File dbFile = new File(dbName);
415 			dbFile.delete();
416 			final File mapdbFile = new File(dbName + "_map");
417 			mapdbFile.delete();
418 			final File deldbFile = new File(dbName + "_del");
419 			deldbFile.delete();
420 			db = Db4o.openFile(dbName).ext();
421 			mapDB = Db4o.openFile(dbName + "_map").ext();
422 			unmapDB = Db4o.openFile(dbName + "_del").ext();
423 		} catch (final Throwable ex) {
424 			if (Container.logger.isLoggable(Level.FINE)) {
425 				Container.logger.logp(Level.FINE, "Container", "clear()", "exception ignored", ex);
426 			}
427 		}
428 
429 		if (Container.logger.isLoggable(Level.FINER)) {
430 			Container.logger.exiting("Container", "clear()", "end");
431 		}
432 	}
433 
434 	/**
435 	 * @see org.ov4j.IContainer#clearDeletedIDs()
436 	 */
437 	public void clearDeletedIDs() {
438 		if (Container.logger.isLoggable(Level.FINER)) {
439 			Container.logger.entering("Container", "clearDeletedIDs()", "start");
440 		}
441 
442 		unmapDB.close();
443 		final File deldbFile = new File(dbName + "_del");
444 		deldbFile.delete();
445 		unmapDB = Db4o.openFile(dbName + "_del").ext();
446 
447 		if (Container.logger.isLoggable(Level.FINER)) {
448 			Container.logger.exiting("Container", "clearDeletedIDs()", "end");
449 		}
450 	}
451 
452 	/**
453 	 * @see org.ov4j.IContainer#close()
454 	 */
455 	public synchronized void close() {
456 		if (Container.logger.isLoggable(Level.FINER)) {
457 			Container.logger.entering("Container", "close()", "start");
458 		}
459 
460 		close(true);
461 
462 		if (Container.logger.isLoggable(Level.FINER)) {
463 			Container.logger.exiting("Container", "close()", "end");
464 		}
465 	}
466 
467 	/**
468 	 * Close the container, possibly removing shutdown hook.
469 	 * 
470 	 * @param removeHook
471 	 *            Whether or not the shutdown hook should be removed first.
472 	 */
473 	public synchronized void close(final boolean removeHook) {
474 		if (Container.logger.isLoggable(Level.FINER)) {
475 			Container.logger.entering("Container", "close(boolean=" + removeHook + ")", "start");
476 		}
477 
478 		waitForPendingTasks();
479 		closing = true;
480 		if (removeHook) {
481 			Runtime.getRuntime().removeShutdownHook(hookThread);
482 		}
483 		if (pendingTasks != null && pendingTasks.size() > 0) {
484 			runningPendingTasks = true;
485 			Runnable[] tasks = null;
486 			synchronized (pendingTasks) {
487 				tasks = pendingTasks.toArray(new Runnable[0]);
488 				pendingTasks.clear();
489 			}
490 			for (int i = 0; i < tasks.length; i++) {
491 				tasks[i].run();
492 			}
493 			runningPendingTasks = false;
494 		}
495 		if (theTimer != null) {
496 			theTimer.cancel();
497 		}
498 		if (db != null) {
499 			while (!db.close()) {
500 				;
501 			}
502 		}
503 		if (mapDB != null) {
504 			while (!mapDB.close()) {
505 				;
506 			}
507 		}
508 		if (unmapDB != null) {
509 			while (!unmapDB.close()) {
510 				;
511 			}
512 		}
513 		out.close();
514 
515 		if (Container.logger.isLoggable(Level.FINER)) {
516 			Container.logger.exiting("Container", "close(boolean=" + removeHook + ")", "end");
517 		}
518 	}
519 
520 	/**
521 	 * @see org.ov4j.IContainer#commit()
522 	 */
523 	public void commit() {
524 		if (Container.logger.isLoggable(Level.FINER)) {
525 			Container.logger.entering("Container", "commit()", "start");
526 		}
527 
528 		final Runnable t = new Runnable() {
529 			public void run() {
530 				synchronized (saveLock) {
531 					db.commit();
532 					mapDB.commit();
533 				}
534 			}
535 		};
536 		synchronized (pendingTasks) {
537 			pendingTasks.add(t);
538 		}
539 
540 		if (Container.logger.isLoggable(Level.FINER)) {
541 			Container.logger.exiting("Container", "commit()", "end");
542 		}
543 	}
544 
545 	/**
546 	 * Configure database, subclasses should overload this method, but still call super.configure() so that everything
547 	 * is configured correctly. This method will be called before any databases are opened or created.
548 	 * 
549 	 */
550 	protected void configure() {
551 		if (Container.logger.isLoggable(Level.FINER)) {
552 			Container.logger.entering("Container", "configure()", "start");
553 		}
554 
555 		Db4o.configure().allowVersionUpdates(true);
556 		Db4o.configure().blockSize(Container.blockSize);
557 		Db4o.configure().automaticShutDown(false);
558 		Db4o.configure().generateUUIDs(ConfigScope.GLOBALLY);
559 		Db4o.configure().generateVersionNumbers(ConfigScope.GLOBALLY);
560 		Db4o.configure().callbacks(false);
561 		Db4o.configure().callConstructors(true);
562 		Db4o.configure().objectClass(ClassComparable.class).minimumActivationDepth(3);
563 		Db4o.configure().objectClass(MappedField.class).objectField("object").indexed(true);
564 		Db4o.configure().objectClass(MappedField.class).cascadeOnActivate(true);
565 		Db4o.configure().objectClass(MappedField.class).cascadeOnUpdate(true);
566 		Db4o.configure().objectClass(Item.class).objectField("id").cascadeOnActivate(true);
567 		Db4o.configure().objectClass(Item.class).objectField("id").cascadeOnUpdate(true);
568 		Db4o.configure().objectClass(Item.class).objectField("latest").cascadeOnActivate(true);
569 		Db4o.configure().objectClass(Item.class).objectField("latest").cascadeOnUpdate(true);
570 		Db4o.configure().objectClass(Item.class).objectField("versions").cascadeOnActivate(false);
571 		Db4o.configure().objectClass(Item.class).objectField("versions").cascadeOnUpdate(true);
572 		Db4o.configure().objectClass(Item.class).objectField("versions").queryEvaluation(false);
573 		Db4o.configure().objectClass(Version.class).objectField("author").cascadeOnActivate(true);
574 		Db4o.configure().objectClass(Version.class).objectField("author").cascadeOnUpdate(true);
575 		Db4o.configure().objectClass(Version.class).objectField("comment").cascadeOnActivate(true);
576 		Db4o.configure().objectClass(Version.class).objectField("comment").cascadeOnUpdate(true);
577 		Db4o.configure().objectClass(Version.class).objectField("versionNumber").cascadeOnActivate(true);
578 		Db4o.configure().objectClass(Version.class).objectField("versionNumber").cascadeOnUpdate(true);
579 		Db4o.configure().objectClass(Version.class).objectField("versionedObject").cascadeOnActivate(true);
580 		Db4o.configure().objectClass(Version.class).objectField("versionedObject").cascadeOnUpdate(true);
581 		final Level logLevel = Container.logger.getLevel();
582 		if (Level.FINEST.equals(logLevel)) {
583 			Db4o.configure().messageLevel(3);
584 		} else if (Level.FINER.equals(logLevel)) {
585 			Db4o.configure().messageLevel(2);
586 		} else if (Level.FINE.equals(logLevel)) {
587 			Db4o.configure().messageLevel(1);
588 		} else {
589 			Db4o.configure().messageLevel(0);
590 		}
591 		try {
592 			out = new PrintStream(new BufferedOutputStream(new FileOutputStream(dbName + ".log")));
593 		} catch (final FileNotFoundException e) {
594 			if (Container.logger.isLoggable(Level.FINE)) {
595 				Container.logger.logp(Level.FINE, "Container", "configure()", "Exception ignored", e);
596 			}
597 		}
598 		Db4o.configure().setOut(out);
599 
600 		if (Container.logger.isLoggable(Level.FINER)) {
601 			Container.logger.exiting("Container", "configure()", "end");
602 		}
603 	}
604 
605 	/**
606 	 * Create the map of IDs if it could not be loaded from file.
607 	 * 
608 	 */
609 	private void createMap() {
610 		if (Container.logger.isLoggable(Level.FINER)) {
611 			Container.logger.entering("Container", "createMap()", "start");
612 		}
613 
614 		if (!Boolean.getBoolean("ov4j.thread.map")) {
615 			synchronized (Container.classLock) {
616 				map();
617 			}
618 		} else {
619 			map();
620 		}
621 
622 		if (Container.logger.isLoggable(Level.FINER)) {
623 			Container.logger.exiting("Container", "createMap()", "end");
624 		}
625 	}
626 
627 	private synchronized void defrag() {
628 		if (Container.logger.isLoggable(Level.FINER)) {
629 			Container.logger.entering("Container", "defrag()", "start");
630 		}
631 
632 		db.close();
633 		unmapDB.close();
634 		Db4o.configure().activationDepth(0);
635 		Db4o.configure().callbacks(false);
636 		Db4o.configure().classActivationDepthConfigurable(false);
637 		Db4o.configure().weakReferences(false);
638 
639 		db = Db4o.openFile(dbName).ext();
640 		unmapDB = Db4o.openFile(dbName + "_del").ext();
641 		final ObjectContainer writeTo = Db4o.openFile(dbName + ".defrag");
642 		final ObjectContainer writeToUnmap = Db4o.openFile(dbName + "_del.defrag");
643 		writeTo.ext().migrateFrom(db);
644 		writeToUnmap.ext().migrateFrom(unmapDB);
645 		try {
646 			migrate(db, writeTo);
647 			migrate(unmapDB, writeToUnmap);
648 		} catch (final ClassNotFoundException e) {
649 			if (Container.logger.isLoggable(Level.FINE)) {
650 				Container.logger.logp(Level.FINE, "Container", "defrag()", "Exception caught", e);
651 			}
652 
653 			writeTo.close();
654 			new File(dbName + ".defrag").delete();
655 			writeToUnmap.close();
656 			new File(dbName + "_del.defrag").delete();
657 
658 			if (Container.logger.isLoggable(Level.FINER)) {
659 				Container.logger.exiting("Container", "defrag()", "end");
660 			}
661 			return;
662 		}
663 		writeTo.close();
664 		db.close();
665 		writeToUnmap.close();
666 		unmapDB.close();
667 		mapDB.close();
668 
669 		Db4o.configure().classActivationDepthConfigurable(true);
670 		configure();
671 		File bak = new File(dbName + ".defragBAK");
672 		if (bak.exists()) {
673 			bak.delete();
674 		}
675 		new File(dbName).renameTo(bak);
676 		new File(dbName + ".defrag").renameTo(new File(dbName));
677 		db = Db4o.openFile(dbName).ext();
678 		bak = new File(dbName + "_del.defragBAK");
679 		if (bak.exists()) {
680 			bak.delete();
681 		}
682 		new File(dbName + "_del").renameTo(bak);
683 		new File(dbName + "_del.defrag").renameTo(new File(dbName + "_del"));
684 		unmapDB = Db4o.openFile(dbName + "_del").ext();
685 
686 		new File((dbName + "_map")).delete();
687 		mapDB = Db4o.openFile(dbName + "_map").ext();
688 		createMap();
689 
690 		if (Container.logger.isLoggable(Level.FINER)) {
691 			Container.logger.exiting("Container", "defrag()", "end");
692 		}
693 	}
694 
695 	/**
696 	 * Taken from Db4o's Defragment tool, see Db4o licenses for usage.
697 	 * 
698 	 * Defragment the container.
699 	 * 
700 	 * @throws ClassNotFoundException
701 	 */
702 	public void defragment() {
703 		if (Container.logger.isLoggable(Level.FINER)) {
704 			Container.logger.entering("Container", "defragment()", "start");
705 		}
706 
707 		if (!Boolean.getBoolean("ov4j.thread.defrag")) {
708 			synchronized (Container.classLock) {
709 				defrag();
710 			}
711 		} else {
712 			defrag();
713 		}
714 
715 		if (Container.logger.isLoggable(Level.FINER)) {
716 			Container.logger.exiting("Container", "defragment()", "end");
717 		}
718 	}
719 
720 	/**
721 	 * @see org.ov4j.IContainer#delete(org.ov4j.data.Item)
722 	 */
723 	public void delete(final Item<T, C> it, final boolean keepTrack) {
724 		if (Container.logger.isLoggable(Level.FINER)) {
725 			Container.logger.entering("Container", "delete(Item<T,C>=" + it + ", boolean=" + keepTrack + ")", "start");
726 		}
727 
728 		final Runnable t = new Runnable() {
729 			public void run() {
730 				synchronized (saveLock) {
731 					unmap(it, keepTrack);
732 					db.delete(it);
733 					pendingSaves--;
734 				}
735 			}
736 		};
737 		synchronized (pendingTasks) {
738 			pendingSaves++;
739 			pendingTasks.add(t);
740 		}
741 
742 		if (Container.logger.isLoggable(Level.FINER)) {
743 			Container.logger.exiting("Container", "delete(Item<T,C>=" + it + ", boolean=" + keepTrack + ")", "end");
744 		}
745 	}
746 
747 	/**
748 	 * @see org.ov4j.IContainer#deletedIds()
749 	 */
750 	public C[] deletedIds() {
751 		if (Container.logger.isLoggable(Level.FINER)) {
752 			Container.logger.entering("Container", "deletedIds()", "start");
753 		}
754 
755 		final ObjectSet<MappedField> set = unmapDB.get(MappedField.class);
756 		final C[] ids = (C[]) new Comparable[set.size()];
757 		final ArrayList<Object> invalids = new ArrayList<Object>();
758 		for (int i = 0; set.hasNext() && i < ids.length; i++) {
759 			final Object next = set.next();
760 			if (next instanceof MappedField && ((MappedField) next).getObject() instanceof Comparable) {
761 				ids[i] = (C) ((MappedField) next).getObject();
762 			} else {
763 				invalids.add(next);
764 			}
765 		}
766 		if (invalids.size() > 0) {
767 			final ArrayList<?> list = invalids;
768 			final Runnable t = new Runnable() {
769 				public void run() {
770 					synchronized (saveLock) {
771 						for (int i = 0; i < list.size(); i++) {
772 							unmapDB.delete(list.get(i));
773 						}
774 						unmapDB.commit();
775 						pendingSaves--;
776 					}
777 				}
778 			};
779 			synchronized (pendingTasks) {
780 				pendingSaves++;
781 				pendingTasks.add(t);
782 			}
783 		}
784 
785 		if (Container.logger.isLoggable(Level.FINER)) {
786 			Container.logger.exiting("Container", "deletedIds()", "end - return value=" + Arrays.toString(ids));
787 		}
788 		return ids;
789 	}
790 
791 	/**
792 	 * @see org.ov4j.IContainer#deletedIdsSince(long)
793 	 */
794 	public C[] deletedIdsSince(final long timestamp) {
795 		if (Container.logger.isLoggable(Level.FINER)) {
796 			Container.logger.entering("Container", "deletedIdsSince(long=" + timestamp + ")", "start");
797 		}
798 
799 		final Query q = unmapDB.query();
800 		q.constrain(MappedField.class);
801 		q.descend("modificationStamp").constrain(timestamp).greater();
802 		final ObjectSet<MappedField> set = q.execute();
803 		final C[] ids = (C[]) new Comparable[set.size()];
804 		for (int i = 0; set.hasNext() && i < ids.length; i++) {
805 			ids[i] = (C) ((MappedField) set.next()).getObject();
806 		}
807 
808 		if (Container.logger.isLoggable(Level.FINER)) {
809 			Container.logger.exiting("Container", "deletedIdsSince(long=" + timestamp + ")", "end - return value="
810 				+ Arrays.toString(ids));
811 		}
812 		return ids;
813 	}
814 
815 	/**
816 	 * Converts the given IContainer into an hessian implementation.
817 	 * 
818 	 * @param cont
819 	 *            IContainer to convert.
820 	 * @throws IOException
821 	 */
822 	public void duplicate(final IContainer<T, C> cont) throws IOException {
823 		if (Container.logger.isLoggable(Level.FINER)) {
824 			Container.logger.entering("Container", "duplicate(IContainer<T,C>=" + cont + ")", "start");
825 		}
826 
827 		final C[] ids = cont.listModifiedSince(-1);
828 		if (ids != null) {
829 			for (int startIdx = 0; startIdx < ids.length; startIdx += 32) {
830 				final C[] buf = (C[]) new Comparable[Math.min(32, (ids.length - startIdx))];
831 				System.arraycopy(ids, startIdx, buf, 0, buf.length);
832 				batchSave(cont.load(buf, true), false);
833 			}
834 		}
835 		commit();
836 
837 		if (Container.logger.isLoggable(Level.FINER)) {
838 			Container.logger.exiting("Container", "duplicate(IContainer<T,C>=" + cont + ")", "end");
839 		}
840 	}
841 
842 	/**
843 	 * @see org.ov4j.IContainer#inUse(java.lang.Comparable)
844 	 */
845 	public boolean inUse(final C id) {
846 		if (Container.logger.isLoggable(Level.FINER)) {
847 			Container.logger.entering("Container", "inUse(C=" + id + ")", "start");
848 		}
849 
850 		final MappedField map = new MappedField();
851 		map.setObject(id);
852 		final ObjectSet<MappedField> set = mapDB.get(map);
853 		final boolean res = set.hasNext();
854 
855 		if (Container.logger.isLoggable(Level.FINER)) {
856 			Container.logger.exiting("Container", "inUse(C=" + id + ")", "end - return value=" + res);
857 		}
858 		return res;
859 	}
860 
861 	/**
862 	 * @see org.ov4j.IContainer#isClosed()
863 	 */
864 	public boolean isClosed() {
865 		if (Container.logger.isLoggable(Level.FINER)) {
866 			Container.logger.entering("Container", "isClosed()", "start");
867 		}
868 
869 		final boolean returnboolean = db.isClosed();
870 		if (Container.logger.isLoggable(Level.FINER)) {
871 			Container.logger.exiting("Container", "isClosed()", "end - return value=" + returnboolean);
872 		}
873 		return returnboolean;
874 	}
875 
876 	/**
877 	 * @throws IOException
878 	 * @see org.ov4j.IContainer#listModifiedSince(long)
879 	 */
880 	public C[] listModifiedSince(final long timestamp) throws IOException {
881 		if (Container.logger.isLoggable(Level.FINER)) {
882 			Container.logger.entering("Container", "listModifiedSince(long=" + timestamp + ")", "start");
883 		}
884 
885 		final Query q = mapDB.query();
886 		q.constrain(MappedField.class);
887 		final ObjectSet<MappedField> set = q.execute();
888 		final C[] res = (C[]) new Comparable[set.size()];
889 		for (int i = 0; set.hasNext() && i < res.length; i++) {
890 			res[i] = (C) ((MappedField) set.next()).getObject();
891 		}
892 
893 		if (Container.logger.isLoggable(Level.FINER)) {
894 			Container.logger.exiting("Container", "listModifiedSince(long=" + timestamp + ")", "end - return value="
895 				+ Arrays.toString(res));
896 		}
897 		return res;
898 	}
899 
900 	/**
901 	 * @throws IOException
902 	 * @see org.ov4j.IContainer#load(java.lang.Comparable, boolean)
903 	 */
904 	public Item<T, C> load(final C id, final boolean allVersions) throws IOException {
905 		if (Container.logger.isLoggable(Level.FINER)) {
906 			Container.logger.entering("Container", "load(C=" + id + ", boolean=" + allVersions + ")", "start");
907 		}
908 
909 		Item<T, C> res = null;
910 		final MappedField map = new MappedField();
911 		map.setObject(id);
912 		final ObjectSet<MappedField> set = mapDB.get(map);
913 		if (set.hasNext()) {
914 			final MappedField mf = (MappedField) set.next();
915 			res = (Item<T, C>) db.getByID(mf.getId());
916 			db.activate(res, 2);
917 			if (allVersions && res != null) {
918 				db.activate(res.getVersions(), 2);
919 			}
920 		}
921 		if (res != null) {
922 			try {
923 				res = res.clone();
924 			} catch (final CloneNotSupportedException e) {
925 				if (Container.logger.isLoggable(Level.FINE)) {
926 					Container.logger.logp(Level.FINE, "Container", "load(C=" + id + ", boolean=" + allVersions + ")",
927 						"Exception ignored", e);
928 				}
929 			}
930 		}
931 
932 		if (Container.logger.isLoggable(Level.FINER)) {
933 			Container.logger.exiting("Container", "load(C=" + id + ", boolean=" + allVersions + ")",
934 				"end - return value=" + res);
935 		}
936 		return res;
937 	}
938 
939 	/**
940 	 * @throws IOException
941 	 * @see org.ov4j.IContainer#load(java.lang.Comparable, boolean)
942 	 */
943 	public Item<T, C>[] load(final C[] ids, final boolean allVersions) throws IOException {
944 		if (Container.logger.isLoggable(Level.FINER)) {
945 			Container.logger.entering("Container", "load(C[]=" + Arrays.toString(ids) + ", boolean=" + allVersions
946 				+ ")", "start");
947 		}
948 
949 		final Query q = mapDB.query();
950 		q.constrain(MappedField.class);
951 		q.constrain(new MultipleIDValidator<C>(ids));
952 		final ObjectSet<MappedField> set = q.execute();
953 		final Item<T, C>[] res = new Item[set.size()];
954 		for (int i = 0; set.hasNext() && i < res.length; i++) {
955 			res[i] = (Item<T, C>) db.getByID(((MappedField) set.next()).getId());
956 			db.activate(res[i], 2);
957 			if (res[i] != null) {
958 				if (allVersions) {
959 					db.activate(res[i].getVersions(), 2);
960 				}
961 				try {
962 					res[i] = res[i].clone();
963 				} catch (final CloneNotSupportedException e) {
964 					if (Container.logger.isLoggable(Level.FINE)) {
965 						Container.logger.logp(Level.FINE, "Container", "load(C[]=" + Arrays.toString(ids)
966 							+ ", boolean=" + allVersions + ")", "Exception ignored", e);
967 					}
968 				}
969 			}
970 		}
971 
972 		if (Container.logger.isLoggable(Level.FINER)) {
973 			Container.logger.exiting("Container",
974 				"load(C[]=" + Arrays.toString(ids) + ", boolean=" + allVersions + ")", "end - return value="
975 					+ Arrays.toString(res));
976 		}
977 		return res;
978 	}
979 
980 	private synchronized void map() {
981 		if (Container.logger.isLoggable(Level.FINER)) {
982 			Container.logger.entering("Container", "map()", "start");
983 		}
984 
985 		final StoredClass cls = db.storedClass(Item.class);
986 		if (cls != null) {
987 			final long[] ids = cls.getIDs();
988 			if (ids != null && ids.length > 0) {
989 				for (int i = 0; i < ids.length; i++) {
990 					final Item<?, ?> it = db.getByID(ids[i]);
991 					db.activate(it, 1);
992 					final MappedField mf = new MappedField();
993 					mf.setObject(it.getId());
994 					mf.setId(ids[i]);
995 					mf.setModificationStamp(it.getModificationStamp());
996 					mapDB.set(mf);
997 				}
998 				commit();
999 			}
1000 		}
1001 
1002 		if (Container.logger.isLoggable(Level.FINER)) {
1003 			Container.logger.exiting("Container", "map()", "end");
1004 		}
1005 	}
1006 
1007 	/**
1008 	 * Create an ID map for this item.
1009 	 * 
1010 	 * @param it
1011 	 *            Item for which a map should be created.
1012 	 */
1013 	private void map(final Item<T, C> it) {
1014 		if (Container.logger.isLoggable(Level.FINER)) {
1015 			Container.logger.entering("Container", "map(Item<T,C>=" + it + ")", "start");
1016 		}
1017 
1018 		final MappedField map = new MappedField();
1019 		map.setObject(it.getId());
1020 		final ObjectSet<MappedField> mapSet = mapDB.get(map);
1021 		MappedField mf = (mapSet == null || !mapSet.hasNext()) ? null : (MappedField) mapSet.next();
1022 
1023 		if (mf == null) {
1024 			long id = db.getID(it);
1025 			if (id < 1) {
1026 				final ObjectSet<Item<T, C>> set = db.get(it);
1027 				id = (set == null || !set.hasNext()) ? null : db.getID(set.next());
1028 			}
1029 			if (id > 0) {
1030 				final ObjectSet<MappedField> unmapSet = unmapDB.get(map);
1031 				mf = (unmapSet == null || !unmapSet.hasNext()) ? null : (MappedField) unmapSet.next();
1032 				if (mf != null) {
1033 					unmapDB.delete(mf);
1034 				}
1035 				mf = new MappedField();
1036 				mf.setObject(it.getId());
1037 				mf.setId(id);
1038 			}
1039 		} else {
1040 			long id = db.getID(it);
1041 			if (id < 1) {
1042 				final ObjectSet<Item<T, C>> set = db.get(it);
1043 				id = (set == null || !set.hasNext()) ? null : db.getID(set.next());
1044 			}
1045 			if (id < 1) {
1046 				mapDB.delete(mf);
1047 				mf = null;
1048 			} else {
1049 				mf.setId(id);
1050 			}
1051 		}
1052 		if (mf != null) {
1053 			mf.setModificationStamp(it.getModificationStamp());
1054 			mapDB.set(mf);
1055 		}
1056 
1057 		if (Container.logger.isLoggable(Level.FINER)) {
1058 			Container.logger.exiting("Container", "map(Item<T,C>=" + it + ")", "end");
1059 		}
1060 	}
1061 
1062 	/**
1063 	 * Taken from Db4o's Defragment tool, see Db4o licenses for usage.
1064 	 * 
1065 	 * Migrate a container to the other
1066 	 * 
1067 	 * @param origin
1068 	 * @param destination
1069 	 * @throws ClassNotFoundException
1070 	 */
1071 	private void migrate(final ObjectContainer origin, final ObjectContainer destination) throws ClassNotFoundException {
1072 		if (Container.logger.isLoggable(Level.FINER)) {
1073 			Container.logger.entering("Container", "migrate(ObjectContainer=" + origin + ", ObjectContainer="
1074 				+ destination + ")", "start");
1075 		}
1076 
1077 		// get all stored classes
1078 		final StoredClass[] classes = origin.ext().storedClasses();
1079 		removeUnavailableSecondAndAbstractClasses(classes);
1080 		removeSubclasses(classes);
1081 		migrateClasses(origin, destination, classes);
1082 
1083 		if (Container.logger.isLoggable(Level.FINER)) {
1084 			Container.logger.exiting("Container", "migrate(ObjectContainer=" + origin + ", ObjectContainer="
1085 				+ destination + ")", "end");
1086 		}
1087 	}
1088 
1089 	/**
1090 	 * Taken from Db4o's Defragment tool, see Db4o licenses for usage.
1091 	 * 
1092 	 * Migrate classes from on econtainer to the other
1093 	 * 
1094 	 * @param origin
1095 	 * @param destination
1096 	 * @param classes
1097 	 */
1098 	private void migrateClasses(final ObjectContainer origin, final ObjectContainer destination,
1099 		final StoredClass[] classes) {
1100 		if (Container.logger.isLoggable(Level.FINER)) {
1101 			Container.logger.entering("Container", "migrateClasses(ObjectContainer=" + origin + ", ObjectContainer="
1102 				+ destination + ", StoredClass[]=" + Arrays.toString(classes) + ")", "start");
1103 		}
1104 
1105 		for (int i = 0; i < classes.length; i++) {
1106 
1107 			if (classes[i] != null) {
1108 				final long[] ids = classes[i].getIDs();
1109 				origin.ext().purge();
1110 				destination.commit();
1111 				destination.ext().purge();
1112 				for (int j = 0; j < ids.length; j++) {
1113 					final Object obj = origin.ext().getByID(ids[j]);
1114 
1115 					// prevent possible constructor side effects
1116 					origin.activate(obj, 1);
1117 					origin.deactivate(obj, 2);
1118 
1119 					origin.activate(obj, 3);
1120 					destination.set(obj);
1121 
1122 					// Both Containers keep track of state individually,
1123 					// so we need to make sure, both know, the object is deactivated
1124 					origin.deactivate(obj, 1);
1125 					destination.deactivate(obj, 1);
1126 				}
1127 			}
1128 		}
1129 
1130 		if (Container.logger.isLoggable(Level.FINER)) {
1131 			Container.logger.exiting("Container", "migrateClasses(ObjectContainer=" + origin + ", ObjectContainer="
1132 				+ destination + ", StoredClass[]=" + Arrays.toString(classes) + ")", "end");
1133 		}
1134 	}
1135 
1136 	/**
1137 	 * @throws IOException
1138 	 * @see org.ov4j.IContainer#modifiedSince(long, boolean)
1139 	 */
1140 	public Item<T, C>[] modifiedSince(final long timestamp, final boolean allVersions) throws IOException {
1141 		if (Container.logger.isLoggable(Level.FINER)) {
1142 			Container.logger.entering("Container",
1143 				"modifiedSince(long=" + timestamp + ", boolean=" + allVersions + ")", "start");
1144 		}
1145 
1146 		final Query q = mapDB.query();
1147 		q.constrain(MappedField.class);
1148 		q.descend("modificationStamp").constrain(timestamp).greater();
1149 		final ObjectSet<MappedField> set = q.execute();
1150 		final Item<T, C>[] res = new Item[set.size()];
1151 		for (int i = 0; set.hasNext() && i < res.length; i++) {
1152 			final MappedField mf = (MappedField) set.next();
1153 			res[i] = (Item<T, C>) db.getByID(mf.getId());
1154 			db.activate(res[i], 2);
1155 			if (res[i] != null) {
1156 				if (allVersions) {
1157 					db.activate(res[i].getVersions(), 2);
1158 				}
1159 				try {
1160 					res[i] = res[i].clone();
1161 				} catch (final CloneNotSupportedException e) {
1162 					if (Container.logger.isLoggable(Level.FINE)) {
1163 						Container.logger.logp(Level.FINE, "Container", "modifiedSince(long=" + timestamp + ", boolean="
1164 							+ allVersions + ")", "Exception ignored", e);
1165 					}
1166 				}
1167 			}
1168 		}
1169 
1170 		if (Container.logger.isLoggable(Level.FINER)) {
1171 			Container.logger.exiting("Container", "modifiedSince(long=" + timestamp + ", boolean=" + allVersions + ")",
1172 				"end - return value=" + Arrays.toString(res));
1173 		}
1174 		return res;
1175 	}
1176 
1177 	/**
1178 	 * @see org.ov4j.IContainer#release(java.lang.Object)
1179 	 */
1180 	public void release(final Object obj) {
1181 	}
1182 
1183 	/**
1184 	 * Taken from Db4o's Defragment tool, see Db4o licenses for usage.
1185 	 * 
1186 	 * rule out inheritance dependencies
1187 	 * 
1188 	 * @param classes
1189 	 * @throws ClassNotFoundException
1190 	 */
1191 	private void removeSubclasses(final StoredClass[] classes) throws ClassNotFoundException {
1192 		if (Container.logger.isLoggable(Level.FINER)) {
1193 			Container.logger.entering("Container", "removeSubclasses(StoredClass[]=" + Arrays.toString(classes) + ")",
1194 				"start");
1195 		}
1196 
1197 		for (int i = 0; i < classes.length; i++) {
1198 			if (classes[i] != null) {
1199 				final Class<?> javaClass = Class.forName(classes[i].getName());
1200 				for (int j = 0; j < classes.length; j++) {
1201 					if (classes[j] != null && classes[i] != classes[j]) {
1202 						final Class<?> superClass = Class.forName(classes[j].getName());
1203 						if (superClass.isAssignableFrom(javaClass)) {
1204 							classes[i] = null;
1205 							break;
1206 						}
1207 					}
1208 				}
1209 			}
1210 		}
1211 
1212 		if (Container.logger.isLoggable(Level.FINER)) {
1213 			Container.logger.exiting("Container", "removeSubclasses(StoredClass[]=" + Arrays.toString(classes) + ")",
1214 				"end");
1215 		}
1216 	}
1217 
1218 	/**
1219 	 * Taken from Db4o's Defragment tool, see Db4o licenses for usage.
1220 	 * 
1221 	 * Remove classes that are currently not available, abstract classes and all second class objects
1222 	 * 
1223 	 * @param classes
1224 	 */
1225 	private void removeUnavailableSecondAndAbstractClasses(final StoredClass[] classes) {
1226 		if (Container.logger.isLoggable(Level.FINER)) {
1227 			Container.logger.entering("Container", "removeUnavailableSecondAndAbstractClasses(StoredClass[]="
1228 				+ Arrays.toString(classes) + ")", "start");
1229 		}
1230 
1231 		for (int i = 0; i < classes.length; i++) {
1232 			try {
1233 				final Class<?> javaClass = Class.forName(classes[i].getName());
1234 				if (javaClass == null || SecondClass.class.isAssignableFrom(javaClass)
1235 					|| Modifier.isAbstract(javaClass.getModifiers())) {
1236 					classes[i] = null;
1237 				}
1238 			} catch (final Throwable t) {
1239 				if (Container.logger.isLoggable(Level.FINE)) {
1240 					Container.logger.logp(Level.FINE, "Container",
1241 						"removeUnavailableSecondAndAbstractClasses(StoredClass[]=" + Arrays.toString(classes) + ")",
1242 						"Exception caught", t);
1243 				}
1244 
1245 				classes[i] = null;
1246 			}
1247 		}
1248 
1249 		if (Container.logger.isLoggable(Level.FINER)) {
1250 			Container.logger.exiting("Container", "removeUnavailableSecondAndAbstractClasses(StoredClass[]="
1251 				+ Arrays.toString(classes) + ")", "end");
1252 		}
1253 	}
1254 
1255 	/**
1256 	 * @see org.ov4j.IContainer#rollback()
1257 	 */
1258 	public void rollback() {
1259 		if (Container.logger.isLoggable(Level.FINER)) {
1260 			Container.logger.entering("Container", "rollback()", "start");
1261 		}
1262 
1263 		final Runnable t = new Runnable() {
1264 			public void run() {
1265 				synchronized (saveLock) {
1266 					db.rollback();
1267 					mapDB.rollback();
1268 				}
1269 			}
1270 		};
1271 		synchronized (pendingTasks) {
1272 			pendingTasks.add(t);
1273 		}
1274 
1275 		if (Container.logger.isLoggable(Level.FINER)) {
1276 			Container.logger.exiting("Container", "rollback()", "end");
1277 		}
1278 	}
1279 
1280 	/**
1281 	 * @throws IOException
1282 	 * @see org.ov4j.IContainer#save(org.ov4j.data.Item)
1283 	 */
1284 	public void save(final Item<T, C> it) throws IOException {
1285 		if (Container.logger.isLoggable(Level.FINER)) {
1286 			Container.logger.entering("Container", "save(Item<T,C>=" + it + ")", "start");
1287 		}
1288 
1289 		final Runnable t = new Runnable() {
1290 			public void run() {
1291 				synchronized (saveLock) {
1292 					db.set(it);
1293 					map(it);
1294 					pendingSaves--;
1295 				}
1296 			}
1297 		};
1298 		synchronized (pendingTasks) {
1299 			pendingSaves++;
1300 			pendingTasks.add(t);
1301 		}
1302 
1303 		if (Container.logger.isLoggable(Level.FINER)) {
1304 			Container.logger.exiting("Container", "save(Item<T,C>=" + it + ")", "end");
1305 		}
1306 	}
1307 
1308 	/**
1309 	 * Returns a string representation of this Container.
1310 	 * 
1311 	 * @return String representation of the Container.
1312 	 */
1313 	public String toString() {
1314 		return "Container{" + dbName + "}";
1315 	}
1316 
1317 	/**
1318 	 * Delete any maps for this item.
1319 	 * 
1320 	 * @param it
1321 	 *            Item for which maps should be deleted.
1322 	 * @param keepTrack
1323 	 *            Whether or not to keep ID in history.
1324 	 */
1325 	private void unmap(final Item<T, C> it, final boolean keepTrack) {
1326 		if (Container.logger.isLoggable(Level.FINER)) {
1327 			Container.logger.entering("Container", "unmap(Item<T,C>=" + it + ", boolean=" + keepTrack + ")", "start");
1328 		}
1329 
1330 		final MappedField mf = new MappedField();
1331 		mf.setId(db.getID(it));
1332 		mf.setObject(it.getId());
1333 		final ObjectSet<MappedField> set = mapDB.get(mf);
1334 		while (set.hasNext()) {
1335 			mapDB.delete(set.next());
1336 		}
1337 		if (keepTrack) {
1338 			final MappedField copy = new MappedField();
1339 			copy.setObject(it.getId());
1340 			copy.setModificationStamp(System.currentTimeMillis());
1341 			unmapDB.set(copy);
1342 		}
1343 
1344 		if (Container.logger.isLoggable(Level.FINER)) {
1345 			Container.logger.exiting("Container", "unmap(Item<T,C>=" + it + ", boolean=" + keepTrack + ")", "end");
1346 		}
1347 	}
1348 
1349 	public void waitForPendingSaves() {
1350 		if (Container.logger.isLoggable(Level.FINER)) {
1351 			Container.logger.entering("Container", "waitForPendingSaves()", "start");
1352 		}
1353 
1354 		if (!taskExecutorStarted) {
1355 			synchronized (taskExecutor) {
1356 				taskExecutorStarted = true;
1357 				taskExecutor.start();
1358 			}
1359 		}
1360 
1361 		while (pendingSaves > 0) {
1362 			try {
1363 				Thread.sleep(100);
1364 			} catch (final InterruptedException e) {
1365 				if (Container.logger.isLoggable(Level.FINE)) {
1366 					Container.logger.logp(Level.FINE, "Container", "waitForPendingSaves()", "exception ignored", e);
1367 				}
1368 			}
1369 		}
1370 
1371 		if (Container.logger.isLoggable(Level.FINER)) {
1372 			Container.logger.exiting("Container", "waitForPendingSaves()", "end");
1373 		}
1374 	}
1375 
1376 	public void waitForPendingTasks() {
1377 		if (Container.logger.isLoggable(Level.FINER)) {
1378 			Container.logger.entering("Container", "waitForPendingTasks()", "start");
1379 		}
1380 
1381 		if (!taskExecutorStarted) {
1382 			synchronized (taskExecutor) {
1383 				taskExecutorStarted = true;
1384 				taskExecutor.start();
1385 			}
1386 		}
1387 
1388 		while (pendingTasks.size() > 0 || runningPendingTasks) {
1389 			try {
1390 				Thread.sleep(100);
1391 			} catch (final InterruptedException e) {
1392 				if (Container.logger.isLoggable(Level.FINE)) {
1393 					Container.logger.logp(Level.FINE, "Container", "waitForPendingTasks()", "exception ignored", e);
1394 				}
1395 			}
1396 		}
1397 
1398 		if (Container.logger.isLoggable(Level.FINER)) {
1399 			Container.logger.exiting("Container", "waitForPendingTasks()", "end");
1400 		}
1401 	}
1402 }