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   */
16  package org.ov4j;
17  
18  import java.io.IOException;
19  import java.io.Serializable;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Hashtable;
23  import java.util.TreeMap;
24  import java.util.logging.Level;
25  import java.util.logging.Logger;
26  
27  import org.ov4j.comp.ComparisonResult;
28  import org.ov4j.comp.DefaultComparisonResult;
29  import org.ov4j.data.Item;
30  import org.ov4j.data.Version;
31  
32  /**
33   * Main access to the versioning system.
34   * 
35   * @author smolloy
36   * 
37   */
38  public class Controler<T extends Comparable<? super T> & Cloneable & Serializable, C extends Comparable<? super C>> {
39  	/**
40  	 * Logger for this class
41  	 */
42  	private static final Logger		logger			= Logger.getLogger(Controler.class.getName());
43  
44  	/** The container used for storing data. */
45  	private final IContainer<T, C>	db;
46  
47  	/** The number of versions to keep for historical information. */
48  	private int						versionRollover	= Integer.parseInt(Config.getString("OV4J.DefaultVersionRollover"));
49  
50  	/**
51  	 * Instantiate a Controler using the specified container to store its data.
52  	 * 
53  	 * @param db
54  	 *            Container used for storing data.
55  	 */
56  	public Controler(final IContainer<T, C> db) {
57  		this.db = db;
58  	}
59  
60  	/**
61  	 * Directly add a version.
62  	 * 
63  	 * @param <T>
64  	 *            Type of the object stored in version, must extend VersionableObject.
65  	 * @param id
66  	 *            ID to associate with the version.
67  	 * @param ver
68  	 *            Version to be added.
69  	 * @return True if the version was successfully added, false otherwise.
70  	 * @throws IOException
71  	 */
72  	public boolean addNCommit(final C id, final Version<T> ver) throws IOException {
73  		if (Controler.logger.isLoggable(Level.FINER)) {
74  			Controler.logger.entering("Controler", "addNCommit(C=" + id + ", Version<T>=" + ver + ")", "start");
75  		}
76  
77  		boolean res = false;
78  		if (ver.getVersionNumber() < 1 && !db.inUse(id)) {
79  			final Item<T, C> it = new Item<T, C>();
80  			it.setId(id);
81  			final Version<T>[] versions = new Version[1];
82  			ver.setVersionNumber(1);
83  			it.setModificationStamp(System.currentTimeMillis());
84  			ver.setVersionTimestamp(it.getModificationStamp());
85  			versions[0] = ver;
86  			it.setVersions(versions);
87  			db.save(it);
88  			res = true;
89  		}
90  
91  		if (Controler.logger.isLoggable(Level.FINER)) {
92  			Controler.logger.exiting("Controler", "addNCommit(C=" + id + ", Version<T>=" + ver + ")",
93  				"end - return value=" + res);
94  		}
95  		return res;
96  	}
97  
98  	/**
99  	 * Commit changes to the given objects using the specified class to determine if commit is needed. This method will
100 	 * add new ids if the addNew parameter is set to true, otherwise they will be ignored.
101 	 * 
102 	 * @param ids
103 	 *            Array of IDs associated with the versions to commit, must be in the same order.
104 	 * @param newVersions
105 	 *            Array of versions to be committed, must be in the same order.
106 	 * @param compResultClass
107 	 *            Class to use for comparing objects in the versions.
108 	 * @param addNew
109 	 *            Whether or not to add versions associated with IDs not found in the system.
110 	 * @return True if batch commit was successful.
111 	 * @throws InstantiationException
112 	 * @throws IllegalAccessException
113 	 * @throws IOException
114 	 */
115 	public boolean batchCommit(final C[] ids, final Version<T>[] newVersions,
116 		final Class<? extends ComparisonResult<T>> compResultClass, boolean addNew) throws InstantiationException,
117 		IllegalAccessException, IOException {
118 		if (Controler.logger.isLoggable(Level.FINER)) {
119 			Controler.logger.entering("Controler", "batchCommit(C[]=" + Arrays.toString(ids) + ", Version<T>[]="
120 				+ Arrays.toString(newVersions) + ", Class<? extends ComparisonResult<T>>=" + compResultClass
121 				+ ", boolean=" + addNew + ")", "start");
122 		}
123 
124 		boolean result = false;
125 
126 		final Item<T, C>[] fetched = db.load(ids, true);
127 		final Hashtable<C, Item<T, C>> fetchHash = new Hashtable<C, Item<T, C>>();
128 		final Hashtable<C, Version<T>> verHash = new Hashtable<C, Version<T>>();
129 		for (int i = 0; i < ids.length; i++) {
130 			verHash.put(ids[i], newVersions[i]);
131 		}
132 		if (fetched != null) {
133 			for (int i = 0; result && i < fetched.length; i++) {
134 				final ComparisonResult<T> comp =
135 					compare(verHash.get(fetched[i].getId()), fetched[i].getLatest(), compResultClass, true);
136 				if (comp.getPrecision() < 1.0) {
137 					fetched[i].setModificationStamp(System.currentTimeMillis());
138 					final Version<T> curVer = verHash.get(fetched[i].getId());
139 					curVer.setVersionTimestamp(fetched[i].getModificationStamp());
140 					fetched[i].addVersion(curVer, versionRollover);
141 				}
142 				if (addNew) {
143 					fetchHash.put(fetched[i].getId(), fetched[i]);
144 				}
145 			}
146 			if (!addNew) {
147 				db.batchSave(fetched, true);
148 				result = true;
149 			}
150 		}
151 
152 		if (addNew) {
153 			final Item<T, C>[] toSave = new Item[newVersions.length];
154 			for (int i = 0; i < toSave.length; i++) {
155 				if (fetchHash.containsKey(ids[i])) {
156 					toSave[i] = fetchHash.get(ids[i]);
157 				} else {
158 					toSave[i] = new Item<T, C>();
159 					toSave[i].setId(ids[i]);
160 					toSave[i].setModificationStamp(System.currentTimeMillis());
161 					newVersions[i].setVersionTimestamp(toSave[i].getModificationStamp());
162 					toSave[i].addVersion(newVersions[i], versionRollover);
163 				}
164 			}
165 			db.batchSave(toSave, false);
166 			result = true;
167 		}
168 
169 		if (Controler.logger.isLoggable(Level.FINER)) {
170 			Controler.logger.exiting("Controler", "batchCommit(C[]=" + Arrays.toString(ids) + ", Version<T>[]="
171 				+ Arrays.toString(newVersions) + ", Class<? extends ComparisonResult<T>>=" + compResultClass
172 				+ ", boolean=" + addNew + ")", "end - return value=" + result);
173 		}
174 		return result;
175 	}
176 
177 	/**
178 	 * Removes all versions of the given ids.
179 	 * 
180 	 * @param ids
181 	 *            IDs to be deleted.
182 	 * @return True if IDs were successfully deleted.
183 	 * @throws IOException
184 	 */
185 	public boolean batchDelete(final C[] ids) throws IOException {
186 		if (Controler.logger.isLoggable(Level.FINER)) {
187 			Controler.logger.entering("Controler", "batchDelete(C[]=" + Arrays.toString(ids) + ")", "start");
188 		}
189 
190 		boolean res = false;
191 
192 		final Item<T, C>[] fetched = db.load(ids, false);
193 
194 		if (fetched != null) {
195 			db.batchDelete(fetched, true);
196 			res = true;
197 		}
198 
199 		if (Controler.logger.isLoggable(Level.FINER)) {
200 			Controler.logger.exiting("Controler", "batchDelete(C[]=" + Arrays.toString(ids) + ")",
201 				"end - return value=" + res);
202 		}
203 		return res;
204 	}
205 
206 	/**
207 	 * Remove all version and do not keep IDs in history.
208 	 * 
209 	 * @param ids
210 	 *            IDs to be deleted.
211 	 * @return True if IDs were successfully discarded.
212 	 * @throws IOException
213 	 */
214 	public boolean batchDiscard(final C[] ids) throws IOException {
215 		if (Controler.logger.isLoggable(Level.FINER)) {
216 			Controler.logger.entering("Controler", "batchDiscard(C[]=" + Arrays.toString(ids) + ")", "start");
217 		}
218 
219 		boolean res = false;
220 
221 		final Item<T, C>[] fetched = db.load(ids, false);
222 
223 		if (fetched != null) {
224 			db.batchDelete(fetched, false);
225 			res = true;
226 		}
227 
228 		if (Controler.logger.isLoggable(Level.FINER)) {
229 			Controler.logger.exiting("Controler", "batchDiscard(C[]=" + Arrays.toString(ids) + ")",
230 				"end - return value=" + res);
231 		}
232 		return res;
233 	}
234 
235 	/**
236 	 * Add all versions, will return a BiteSet containing information on which could be added. If abortOnDuplicate is
237 	 * set to true, nothing will be committed unless none of the ids passed were already in use.
238 	 * 
239 	 * @param <T>
240 	 *            Type of the object stored in versions, must extend VersionableObject.
241 	 * @param ids
242 	 *            Array of IDs to associate with the versions, must be in the same order.
243 	 * @param vers
244 	 *            Array of versions to add, must be in the same order.
245 	 * @param abortOnDuplicate
246 	 *            Whether or not to abort import if one of the IDs specified is already in use.
247 	 * @return True if adding was successful.
248 	 * @throws IOException
249 	 */
250 	public boolean batchImport(final C[] ids, final Version<T>[] vers, final boolean abortOnDuplicate)
251 		throws IOException {
252 		if (Controler.logger.isLoggable(Level.FINER)) {
253 			Controler.logger.entering("Controler", "batchImport(C[]=" + Arrays.toString(ids) + ", Version<T>[]="
254 				+ Arrays.toString(vers) + ", boolean=" + abortOnDuplicate + ")", "start");
255 		}
256 
257 		boolean res = false;
258 		if (ids.length == vers.length) {
259 			final Item<T, C>[] items = new Item[ids.length];
260 			for (int i = 0; i < items.length; i++) {
261 				items[i] = new Item<T, C>();
262 				items[i].setId(ids[i]);
263 				final Version<T>[] versions = new Version[1];
264 				items[i].setModificationStamp(System.currentTimeMillis());
265 				vers[i].setVersionTimestamp(items[i].getModificationStamp());
266 				versions[0] = vers[i];
267 				items[i].setVersions(versions);
268 			}
269 			db.batchSave(items, abortOnDuplicate);
270 			res = true;
271 		}
272 
273 		if (Controler.logger.isLoggable(Level.FINER)) {
274 			Controler.logger.exiting("Controler", "batchImport(C[]=" + Arrays.toString(ids) + ", Version<T>[]="
275 				+ Arrays.toString(vers) + ", boolean=" + abortOnDuplicate + ")", "end - return value=" + res);
276 		}
277 		return res;
278 	}
279 
280 	/**
281 	 * Merges all versions of the given ids.
282 	 * 
283 	 * @param ids
284 	 *            IDs to be deleted.
285 	 * @return True if IDs were successfully deleted.
286 	 * @throws IOException
287 	 */
288 	public boolean batchMerge(final C[] ids) throws IOException {
289 		if (Controler.logger.isLoggable(Level.FINER)) {
290 			Controler.logger.entering("Controler", "batchMerge(C[]=" + Arrays.toString(ids) + ")", "start");
291 		}
292 
293 		boolean res = false;
294 
295 		db.batchMerge(ids);
296 		res = true;
297 
298 		if (Controler.logger.isLoggable(Level.FINER)) {
299 			Controler.logger.exiting("Controler", "batchMerge(C[]=" + Arrays.toString(ids) + ")", "end - return value="
300 				+ res);
301 		}
302 		return res;
303 	}
304 
305 	/**
306 	 * Clear all data in the underlying container.
307 	 * 
308 	 */
309 	public void clear() {
310 		if (Controler.logger.isLoggable(Level.FINER)) {
311 			Controler.logger.entering("Controler", "clear()", "start");
312 		}
313 
314 		db.clear();
315 
316 		if (Controler.logger.isLoggable(Level.FINER)) {
317 			Controler.logger.exiting("Controler", "clear()", "end");
318 		}
319 	}
320 
321 	/**
322 	 * Close this controler and its underlying container.
323 	 * 
324 	 */
325 	public synchronized void close() {
326 		if (Controler.logger.isLoggable(Level.FINER)) {
327 			Controler.logger.entering("Controler", "close()", "start");
328 		}
329 
330 		try {
331 			db.close();
332 		} catch (final IOException e) {
333 			if (Controler.logger.isLoggable(Level.FINE)) {
334 				Controler.logger.logp(Level.FINE, "Controler", "close()", "exception ignored", e);
335 			}
336 		}
337 
338 		if (Controler.logger.isLoggable(Level.FINER)) {
339 			Controler.logger.exiting("Controler", "close()", "end");
340 		}
341 	}
342 
343 	/**
344 	 * Commit changes to the given object.
345 	 * 
346 	 * @param id
347 	 *            ID associated with the version to commit.
348 	 * @param newVersion
349 	 *            New version to be committed.
350 	 * @return True if version was committed successfully.
351 	 * @throws IllegalAccessException
352 	 * @throws InstantiationException
353 	 * @throws IOException
354 	 */
355 	public boolean commit(final C id, final Version<T> newVersion) throws InstantiationException,
356 		IllegalAccessException, IOException {
357 		if (Controler.logger.isLoggable(Level.FINER)) {
358 			Controler.logger.entering("Controler", "commit(C=" + id + ", Version<T>=" + newVersion + ")", "start");
359 		}
360 
361 		final boolean returnboolean = commit(id, newVersion, findComparisonResultClass(newVersion), versionRollover);
362 
363 		if (Controler.logger.isLoggable(Level.FINER)) {
364 			Controler.logger.exiting("Controler", "commit(C=" + id + ", Version<T>=" + newVersion + ")",
365 				"end - return value=" + returnboolean);
366 		}
367 		return returnboolean;
368 	}
369 
370 	/**
371 	 * Commit changes to the given object.
372 	 * 
373 	 * @param id
374 	 *            ID associated with the version to commit.
375 	 * @param newVersion
376 	 *            New version to be committed.
377 	 * @param compResultClass
378 	 *            Class to use to compare objects in the version.
379 	 * @return True if version was committed successfully.
380 	 * @throws IllegalAccessException
381 	 * @throws InstantiationException
382 	 * @throws IOException
383 	 */
384 	public boolean commit(final C id, final Version<T> newVersion,
385 		final Class<? extends ComparisonResult<? super T>> compResultClass) throws InstantiationException,
386 		IllegalAccessException, IOException {
387 		if (Controler.logger.isLoggable(Level.FINER)) {
388 			Controler.logger.entering("Controler", "commit(C=" + id + ", Version<T>=" + newVersion
389 				+ ", Class<? extends ComparisonResult<T>>=" + compResultClass + ")", "start");
390 		}
391 
392 		final boolean returnboolean = commit(id, newVersion, compResultClass, versionRollover);
393 
394 		if (Controler.logger.isLoggable(Level.FINER)) {
395 			Controler.logger.exiting("Controler", "commit(C=" + id + ", Version<T>=" + newVersion
396 				+ ", Class<? extends ComparisonResult<T>>=" + compResultClass + ")", "end - return value="
397 				+ returnboolean);
398 		}
399 		return returnboolean;
400 	}
401 
402 	/**
403 	 * Commit changes to the given object using the specified class to determine if commit is needed.
404 	 * 
405 	 * @param id
406 	 *            ID associated with the version to commit.
407 	 * @param newVersion
408 	 *            New version to be committed.
409 	 * @param compResultClass
410 	 *            Class to use to compare objects in the version.
411 	 * @param rollover
412 	 *            Version rollover to use.
413 	 * @return True if version was committed successfully.
414 	 * @throws InstantiationException
415 	 * @throws IllegalAccessException
416 	 * @throws IOException
417 	 */
418 	public boolean commit(final C id, final Version<T> newVersion,
419 		final Class<? extends ComparisonResult<? super T>> compResultClass, final int rollover)
420 		throws InstantiationException, IllegalAccessException, IOException {
421 		if (Controler.logger.isLoggable(Level.FINER)) {
422 			Controler.logger.entering("Controler", "commit(C=" + id + ", Version<T>=" + newVersion
423 				+ ", Class<? extends ComparisonResult<T>>=" + compResultClass + ", int=" + rollover + ")", "start");
424 		}
425 
426 		boolean result = false;
427 
428 		final Item<T, C> fetched = db.load(id, true);
429 		if (fetched != null) {
430 			final ComparisonResult<T> comp = compare(newVersion, fetched.getLatest(), compResultClass, true);
431 			if (comp.getPrecision() < 1.0) {
432 				fetched.setModificationStamp(System.currentTimeMillis());
433 				newVersion.setVersionTimestamp(fetched.getModificationStamp());
434 				result = fetched.addVersion(newVersion, rollover);
435 				if (result) {
436 					db.save(fetched);
437 				}
438 			}
439 		}
440 
441 		if (Controler.logger.isLoggable(Level.FINER)) {
442 			Controler.logger.exiting("Controler", "commit(C=" + id + ", Version<T>=" + newVersion
443 				+ ", Class<? extends ComparisonResult<T>>=" + compResultClass + ", int=" + rollover + ")",
444 				"end - return value=" + result);
445 		}
446 		return result;
447 	}
448 
449 	/**
450 	 * Commit changes to the given object.
451 	 * 
452 	 * @param id
453 	 *            ID associated with the version to commit.
454 	 * @param newVersion
455 	 *            New version to be committed.
456 	 * @param rollover
457 	 *            Version rollover to use.
458 	 * @return True if version was committed successfully.
459 	 * @throws IllegalAccessException
460 	 * @throws InstantiationException
461 	 * @throws IOException
462 	 */
463 	public boolean commit(final C id, final Version<T> newVersion, final int rollover) throws InstantiationException,
464 		IllegalAccessException, IOException {
465 		if (Controler.logger.isLoggable(Level.FINER)) {
466 			Controler.logger.entering("Controler", "commit(C=" + id + ", Version<T>=" + newVersion + ", int="
467 				+ rollover + ")", "start");
468 		}
469 
470 		final boolean returnboolean = commit(id, newVersion, findComparisonResultClass(newVersion), rollover);
471 
472 		if (Controler.logger.isLoggable(Level.FINER)) {
473 			Controler.logger.exiting("Controler", "commit(C=" + id + ", Version<T>=" + newVersion + ", int=" + rollover
474 				+ ")", "end - return value=" + returnboolean);
475 		}
476 		return returnboolean;
477 	}
478 
479 	/**
480 	 * Compare 2 versions after determining which class to use.
481 	 * 
482 	 * @param newVersion
483 	 *            New version to be compared.
484 	 * @param oldVersion
485 	 *            Old version to compare to.
486 	 * @param fast
487 	 *            Whether or not to run in fast mode, only determining if versions are the same or not.
488 	 * @return A ComparisonResult holding comparison information about the 2 versions.
489 	 * @throws InstantiationException
490 	 * @throws IllegalAccessException
491 	 */
492 	public ComparisonResult<T> compare(final Version<T> newVersion, final Version<T> oldVersion, final boolean fast)
493 		throws InstantiationException, IllegalAccessException {
494 		if (Controler.logger.isLoggable(Level.FINER)) {
495 			Controler.logger.entering("Controler", "compare(Version<T>=" + newVersion + ", Version<T>=" + oldVersion
496 				+ ", boolean=" + fast + ")", "start");
497 		}
498 
499 		final ComparisonResult<T> returnComparisonResult =
500 			compare(newVersion, oldVersion, findComparisonResultClass(newVersion), fast);
501 
502 		if (Controler.logger.isLoggable(Level.FINER)) {
503 			Controler.logger.exiting("Controler", "compare(Version<T>=" + newVersion + ", Version<T>=" + oldVersion
504 				+ ", boolean=" + fast + ")", "end - return value=" + returnComparisonResult);
505 		}
506 		return returnComparisonResult;
507 	}
508 
509 	/**
510 	 * Compare 2 versions using the specified class.
511 	 * 
512 	 * @param newVersion
513 	 *            New version to be compared.
514 	 * @param oldVersion
515 	 *            Old version to compare to.
516 	 * @param compResultClass
517 	 *            Class to use to compare objects in versions.
518 	 * @param fast
519 	 *            Whether or not to run in fast mode, only determining if versions are the same or not.
520 	 * @return A ComparisonResult holding comparison information about the 2 versions.
521 	 * @throws IllegalAccessException
522 	 * @throws InstantiationException
523 	 */
524 	public ComparisonResult<T> compare(final Version<T> newVersion, final Version<T> oldVersion,
525 		final Class<? extends ComparisonResult<? super T>> compResultClass, final boolean fast)
526 		throws InstantiationException, IllegalAccessException {
527 		if (Controler.logger.isLoggable(Level.FINER)) {
528 			Controler.logger.entering("Controler", "compare(Version<T>=" + newVersion + ", Version<T>=" + oldVersion
529 				+ ", Class<? extends ComparisonResult<T>>=" + compResultClass + ", boolean=" + fast + ")", "start");
530 		}
531 
532 		final ComparisonResult<T> result = (ComparisonResult<T>) compResultClass.newInstance();
533 		if (oldVersion != null) {
534 			result.setOriginal(oldVersion.getVersionedObject());
535 		}
536 		if (newVersion != null) {
537 			result.setChanged(newVersion.getVersionedObject());
538 		}
539 		if (fast) {
540 			result.fastCompute();
541 		} else {
542 			result.compute();
543 		}
544 
545 		if (Controler.logger.isLoggable(Level.FINER)) {
546 			Controler.logger.exiting("Controler", "compare(Version<T>=" + newVersion + ", Version<T>=" + oldVersion
547 				+ ", Class<? extends ComparisonResult<T>>=" + compResultClass + ", boolean=" + fast + ")",
548 				"end - return value=" + result);
549 		}
550 		return result;
551 	}
552 
553 	/**
554 	 * Removes all versions of the given id.
555 	 * 
556 	 * @param id
557 	 *            ID to be deleted.
558 	 * @return True if ID was successfully deleted.
559 	 * @throws IOException
560 	 */
561 	public boolean delete(final C id) throws IOException {
562 		if (Controler.logger.isLoggable(Level.FINER)) {
563 			Controler.logger.entering("Controler", "delete(C=" + id + ")", "start");
564 		}
565 
566 		boolean res = false;
567 
568 		final Item<T, C> fetched = db.load(id, false);
569 
570 		if (fetched != null) {
571 			db.delete(fetched, true);
572 			res = true;
573 		}
574 
575 		if (Controler.logger.isLoggable(Level.FINER)) {
576 			Controler.logger.exiting("Controler", "delete(C=" + id + ")", "end - return value=" + res);
577 		}
578 		return res;
579 	}
580 
581 	/**
582 	 * Retrieve list of IDs that have been deleted.
583 	 * 
584 	 * @return All IDs deleted.
585 	 * @throws IOException
586 	 */
587 	public C[] deletedIds() throws IOException {
588 		if (Controler.logger.isLoggable(Level.FINER)) {
589 			Controler.logger.entering("Controler", "deletedIds()", "start");
590 		}
591 
592 		final C[] returnComparableArray = db.deletedIds();
593 
594 		if (Controler.logger.isLoggable(Level.FINER)) {
595 			Controler.logger.exiting("Controler", "deletedIds()", "end - return value="
596 				+ Arrays.toString(returnComparableArray));
597 		}
598 		return returnComparableArray;
599 	}
600 
601 	/**
602 	 * Retrieve a list of IDs that have been deleted since the given timestamp.
603 	 * 
604 	 * @param timestamp
605 	 *            Timestamp to use.
606 	 * @return All IDS deleted since the given timestamp.
607 	 * @throws IOException
608 	 */
609 	public C[] deletedIdsSince(final long timestamp) throws IOException {
610 		if (Controler.logger.isLoggable(Level.FINER)) {
611 			Controler.logger.entering("Controler", "deletedIdsSince(long=" + timestamp + ")", "start");
612 		}
613 
614 		final C[] returnComparableArray = db.deletedIdsSince(timestamp);
615 
616 		if (Controler.logger.isLoggable(Level.FINER)) {
617 			Controler.logger.exiting("Controler", "deletedIdsSince(long=" + timestamp + ")", "end - return value="
618 				+ Arrays.toString(returnComparableArray));
619 		}
620 		return returnComparableArray;
621 	}
622 
623 	/**
624 	 * Will perform a diff, trying to determine the class of the comparison result by appending ComparisonResult to the
625 	 * versionedObject classname.
626 	 * 
627 	 * @param id
628 	 *            ID of the item to which the version should be compared.
629 	 * @param newVersion
630 	 *            Version to be compared.
631 	 * @return A ComparisonResult holding information about the comparison.
632 	 * @throws InstantiationException
633 	 * @throws IllegalAccessException
634 	 * @throws IOException
635 	 */
636 	public ComparisonResult<T> diff(final C id, final Version<T> newVersion) throws InstantiationException,
637 		IllegalAccessException, IOException {
638 		if (Controler.logger.isLoggable(Level.FINER)) {
639 			Controler.logger.entering("Controler", "diff(C=" + id + ", Version<T>=" + newVersion + ")", "start");
640 		}
641 
642 		final ComparisonResult<T> returnComparisonResult = diff(id, newVersion, findComparisonResultClass(newVersion));
643 
644 		if (Controler.logger.isLoggable(Level.FINER)) {
645 			Controler.logger.exiting("Controler", "diff(C=" + id + ", Version<T>=" + newVersion + ")",
646 				"end - return value=" + returnComparisonResult);
647 		}
648 		return returnComparisonResult;
649 	}
650 
651 	/**
652 	 * Compares the new version to the latest version of object with the given id. Will store the result in a new
653 	 * instance of the given class, which has to extend ComparisonResult.
654 	 * 
655 	 * @param id
656 	 *            ID of the item to which the version should be compared.
657 	 * @param newVersion
658 	 *            Version to be compared.
659 	 * @param compResultClass
660 	 *            Class to use for comparison.
661 	 * @return A ComparisonResult holding information about the comparison.
662 	 * @throws InstantiationException
663 	 * @throws IllegalAccessException
664 	 * @throws IOException
665 	 */
666 	public ComparisonResult<T> diff(final C id, final Version<T> newVersion,
667 		final Class<? extends ComparisonResult<? super T>> compResultClass) throws InstantiationException,
668 		IllegalAccessException, IOException {
669 		if (Controler.logger.isLoggable(Level.FINER)) {
670 			Controler.logger.entering("Controler", "diff(C=" + id + ", Version<T>=" + newVersion
671 				+ ", Class<? extends ComparisonResult<T>>=" + compResultClass + ")", "start");
672 		}
673 
674 		ComparisonResult<T> result = null;
675 
676 		final Item<T, C> fetched = db.load(id, false);
677 		if (fetched != null) {
678 			final Version<T> latest = fetched.getLatest();
679 			if (latest != null) {
680 				result = compare(newVersion, latest, compResultClass, false);
681 			}
682 		}
683 
684 		if (Controler.logger.isLoggable(Level.FINER)) {
685 			Controler.logger.exiting("Controler", "diff(C=" + id + ", Version<T>=" + newVersion
686 				+ ", Class<? extends ComparisonResult<T>>=" + compResultClass + ")", "end - return value=" + result);
687 		}
688 		return result;
689 	}
690 
691 	/**
692 	 * Remove all version and do not keep ID in history.
693 	 * 
694 	 * @param id
695 	 *            ID to be deleted.
696 	 * @return True if ID was successfully discarded.
697 	 * @throws IOException
698 	 */
699 	public boolean discard(final C id) throws IOException {
700 		if (Controler.logger.isLoggable(Level.FINER)) {
701 			Controler.logger.entering("Controler", "discard(C=" + id + ")", "start");
702 		}
703 
704 		boolean res = false;
705 
706 		Item<T, C> fetched = db.load(id, false);
707 
708 		if (fetched != null) {
709 			db.delete(fetched, false);
710 			fetched = null;
711 			res = true;
712 		}
713 
714 		if (Controler.logger.isLoggable(Level.FINER)) {
715 			Controler.logger.exiting("Controler", "discard(C=" + id + ")", "end - return value=" + res);
716 		}
717 		return res;
718 	}
719 
720 	/**
721 	 * Discard all changes made since the last save.
722 	 * 
723 	 */
724 	public void discardChanges() {
725 		if (Controler.logger.isLoggable(Level.FINER)) {
726 			Controler.logger.entering("Controler", "discardChanges()", "start");
727 		}
728 
729 		db.rollback();
730 
731 		if (Controler.logger.isLoggable(Level.FINER)) {
732 			Controler.logger.exiting("Controler", "discardChanges()", "end");
733 		}
734 	}
735 
736 	/**
737 	 * Fetch the given version of object with the given id.
738 	 * 
739 	 * @param id
740 	 *            ID to retrieve.
741 	 * @param versionNum
742 	 *            Version to retrieve.
743 	 * @return Given version of the item with ID specified.
744 	 * @throws IOException
745 	 */
746 	public Version<T> fetch(final C id, final int versionNum) throws IOException {
747 		if (Controler.logger.isLoggable(Level.FINER)) {
748 			Controler.logger.entering("Controler", "fetch(C=" + id + ", int=" + versionNum + ")", "start");
749 		}
750 
751 		Version<T> result = null;
752 
753 		final Item<T, C> fetched = db.load(id, true);
754 		if (fetched != null) {
755 			final Version<T>[] versions = fetched.getVersions();
756 			if (versions != null && fetched.getLatest().getVersionNumber() >= versionNum
757 				&& versionNum >= versions[0].getVersionNumber()) {
758 				result = versions[versionNum - versions[0].getVersionNumber()];
759 			}
760 		}
761 
762 		if (Controler.logger.isLoggable(Level.FINER)) {
763 			Controler.logger.exiting("Controler", "fetch(C=" + id + ", int=" + versionNum + ")", "end - return value="
764 				+ result);
765 		}
766 		return result;
767 	}
768 
769 	/**
770 	 * Fetch the given versions of object with the given id.
771 	 * 
772 	 * @param id
773 	 *            ID to retrieve.
774 	 * @param versionNums
775 	 *            Versions to retrieve.
776 	 * @return Given versions of the item with ID specified.
777 	 * @throws IOException
778 	 */
779 	public Version<T>[] fetch(final C id, final int[] versionNums) throws IOException {
780 		if (Controler.logger.isLoggable(Level.FINER)) {
781 			Controler.logger.entering("Controler", "fetch(C=" + id + ", int[]=" + versionNums + ")", "start");
782 		}
783 
784 		final ArrayList<Version<T>> result = new ArrayList<Version<T>>();
785 		final TreeMap<Integer, Version<T>> map = new TreeMap<Integer, Version<T>>();
786 
787 		final Item<T, C> fetched = db.load(id, true);
788 		if (fetched != null) {
789 			final Version<T>[] versions = fetched.getVersions();
790 			if (versionNums == null || versionNums.length < 1) {
791 				for (int i = 0; i < versions.length; i++) {
792 					result.add(versions[i]);
793 				}
794 			} else {
795 				for (int i = 0; i < versions.length; i++) {
796 					map.put(versions[i].getVersionNumber(), versions[i]);
797 				}
798 				for (int i = 0; i < versionNums.length; i++) {
799 					final Version<T> ver = map.get(versionNums[i]);
800 					if (ver != null) {
801 						result.add(ver);
802 					}
803 				}
804 			}
805 		}
806 
807 		if (Controler.logger.isLoggable(Level.FINER)) {
808 			Controler.logger.exiting("Controler", "fetch(C=" + id + ", int[]=" + versionNums + ")",
809 				"end - return value=" + result);
810 		}
811 		return result.toArray((Version<T>[]) Version.EMPTY_ARRAY);
812 	}
813 
814 	/**
815 	 * Find the Class to use for comparing this version, will replace "data" by "comp" in the package name and add
816 	 * "ComparisonResult" to the class of the versions's versioned object. If no suitable class is found,
817 	 * DefaultComparisonResult will be returned.
818 	 * 
819 	 * @param ver
820 	 *            Version for which to find a ComparisonResult class.
821 	 * @return The Class to be used to compare the object in this version.
822 	 */
823 	public Class<? extends ComparisonResult<? super T>> findComparisonResultClass(final Version<T> ver) {
824 		if (Controler.logger.isLoggable(Level.FINER)) {
825 			Controler.logger.entering("Controler", "findComparisonResultClass(Version<T>=" + ver + ")", "start");
826 		}
827 
828 		final String dataName = ver.getVersionedObject().getClass().getName();
829 		final String compName = dataName.replace("data", "comp").concat("ComparisonResult");
830 		Class<? extends ComparisonResult<? super T>> returnClass =
831 			(Class<? extends ComparisonResult<? super T>>) DefaultComparisonResult.class;
832 		try {
833 			returnClass = (Class<? extends ComparisonResult<? super T>>) Class.forName(compName);
834 		} catch (final ClassNotFoundException e) {
835 			if (Controler.logger.isLoggable(Level.FINE)) {
836 				Controler.logger.logp(Level.FINE, "Controler", "findComparisonResultClass(Version<T>=" + ver + ")",
837 					"exception ignored", e);
838 			}
839 			returnClass = (Class<? extends ComparisonResult<? super T>>) DefaultComparisonResult.class;
840 		}
841 
842 		if (Controler.logger.isLoggable(Level.FINER)) {
843 			Controler.logger.exiting("Controler", "findComparisonResultClass(Version<T>=" + ver + ")",
844 				"end - return value=" + returnClass);
845 		}
846 		return returnClass;
847 	}
848 
849 	/**
850 	 * Retrieve the number of versions kept for historical purposes.
851 	 * 
852 	 * @return Returns the versionRollover.
853 	 */
854 	public int getVersionRollover() {
855 		return versionRollover;
856 	}
857 
858 	/**
859 	 * Determine if the given id is already in use.
860 	 * 
861 	 * @param id
862 	 *            ID to verify.
863 	 * @return True if ID is in use, false otherwise.
864 	 * @throws IOException
865 	 */
866 	public boolean inUse(final C id) throws IOException {
867 		if (Controler.logger.isLoggable(Level.FINER)) {
868 			Controler.logger.entering("Controler", "inUse(C=" + id + ")", "start");
869 		}
870 
871 		final boolean returnboolean = db.inUse(id);
872 
873 		if (Controler.logger.isLoggable(Level.FINER)) {
874 			Controler.logger.exiting("Controler", "inUse(C=" + id + ")", "end - return value=" + returnboolean);
875 		}
876 		return returnboolean;
877 	}
878 
879 	/**
880 	 * Determine if the underlying container has already been closed.
881 	 * 
882 	 * @return True if the underlying container has been closed.
883 	 */
884 	public boolean isClosed() {
885 		if (Controler.logger.isLoggable(Level.FINER)) {
886 			Controler.logger.entering("Controler", "isClosed()", "start");
887 		}
888 
889 		final boolean returnboolean = db.isClosed();
890 
891 		if (Controler.logger.isLoggable(Level.FINER)) {
892 			Controler.logger.exiting("Controler", "isClosed()", "end - return value=" + returnboolean);
893 		}
894 		return returnboolean;
895 	}
896 
897 	/**
898 	 * Fetch the latest version of the object with given id.
899 	 * 
900 	 * @param id
901 	 *            ID to retrieve.
902 	 * @return Latest version of the item with the given ID.
903 	 * @throws IOException
904 	 */
905 	public Version<T> latest(final C id) throws IOException {
906 		if (Controler.logger.isLoggable(Level.FINER)) {
907 			Controler.logger.entering("Controler", "latest(C=" + id + ")", "start");
908 		}
909 
910 		Version<T> result = null;
911 
912 		final Item<T, C> fetched = db.load(id, false);
913 		if (fetched != null) {
914 			result = fetched.getLatest();
915 		}
916 
917 		if (Controler.logger.isLoggable(Level.FINER)) {
918 			Controler.logger.exiting("Controler", "latest(C=" + id + ")", "end - return value=" + result);
919 		}
920 		return result;
921 	}
922 
923 	/**
924 	 * Fetch the latest version of the object with given id before the given timestamp.
925 	 * 
926 	 * @param id
927 	 *            ID to retrieve.
928 	 * @param tstamp
929 	 *            Timestamp to use.
930 	 * @return Latest version of the item with the given ID before the specified timestamp..
931 	 * @throws IOException
932 	 */
933 	public Version<T> latest(final C id, final int tstamp) throws IOException {
934 		if (Controler.logger.isLoggable(Level.FINER)) {
935 			Controler.logger.entering("Controler", "latest(C=" + id + ", int=" + tstamp + ")", "start");
936 		}
937 
938 		Version<T> result = null;
939 
940 		final Item<T, C> fetched = db.load(id, false);
941 		if (fetched != null) {
942 			result = fetched.getLatestBefore(tstamp);
943 		}
944 
945 		if (Controler.logger.isLoggable(Level.FINER)) {
946 			Controler.logger.exiting("Controler", "latest(C=" + id + ", int=" + tstamp + ")", "end - return value="
947 				+ result);
948 		}
949 		return result;
950 	}
951 
952 	/**
953 	 * Fetch the latest versions of every id specified.
954 	 * 
955 	 * @param ids
956 	 *            Array of IDs to retrieve.
957 	 * @return Array of latest versions for the given IDs, not necessarily in the same order.
958 	 * @throws IOException
959 	 */
960 	public Version<T>[] latests(final C[] ids) throws IOException {
961 		if (Controler.logger.isLoggable(Level.FINER)) {
962 			Controler.logger.entering("Controler", "latests(C[]=" + Arrays.toString(ids) + ")", "start");
963 		}
964 
965 		final Item<T, C>[] items = db.load(ids, false);
966 		final Version<T>[] result = new Version[items == null ? 0 : items.length];
967 		for (int i = 0; i < result.length; i++) {
968 			result[i] = items[i].getLatest();
969 		}
970 
971 		if (Controler.logger.isLoggable(Level.FINER)) {
972 			Controler.logger.exiting("Controler", "latests(C[]=" + Arrays.toString(ids) + ")", "end - return value="
973 				+ Arrays.toString(result));
974 		}
975 		return result;
976 	}
977 
978 	/**
979 	 * Fetch the latest versions of every id specified before the given timestamp.
980 	 * 
981 	 * @param ids
982 	 *            Array of IDs to retrieve.
983 	 * @param tstamp
984 	 *            Timestamp to use.
985 	 * @return Array of latest versions for the given IDs before the specified timestamp, not necessarily in the same
986 	 *         order.
987 	 * @throws IOException
988 	 */
989 	public Version<T>[] latests(final C[] ids, final int tstamp) throws IOException {
990 		if (Controler.logger.isLoggable(Level.FINER)) {
991 			Controler.logger.entering("Controler", "latests(C[]=" + Arrays.toString(ids) + ", int=" + tstamp + ")",
992 				"start");
993 		}
994 
995 		final Item<T, C>[] items = db.load(ids, false);
996 		final Version<T>[] result = new Version[items == null ? 0 : items.length];
997 		for (int i = 0; i < result.length; i++) {
998 			result[i] = items[i].getLatestBefore(tstamp);
999 		}
1000 
1001 		if (Controler.logger.isLoggable(Level.FINER)) {
1002 			Controler.logger.exiting("Controler", "latests(C[]=" + Arrays.toString(ids) + ", int=" + tstamp + ")",
1003 				"end - return value=" + Arrays.toString(result));
1004 		}
1005 		return result;
1006 	}
1007 
1008 	/**
1009 	 * List the latest versions modified since the given timestamp.
1010 	 * 
1011 	 * @param timestamp
1012 	 * @return Array of the latest versions of items modified since specified timestamp.
1013 	 * @throws IOException
1014 	 */
1015 	public C[] listModifiedSince(final long timestamp) throws IOException {
1016 		if (Controler.logger.isLoggable(Level.FINER)) {
1017 			Controler.logger.entering("Controler", "listModifiedSince(long=" + timestamp + ")", "start");
1018 		}
1019 
1020 		C[] ids = null;
1021 		ids = db.listModifiedSince(timestamp);
1022 
1023 		if (Controler.logger.isLoggable(Level.FINER)) {
1024 			Controler.logger.exiting("Controler", "listModifiedSince(long=" + timestamp + ")", "end - return value="
1025 				+ Arrays.toString(ids));
1026 		}
1027 		return ids;
1028 	}
1029 
1030 	/**
1031 	 * Fetch the latest versions modified since the given timestamp.
1032 	 * 
1033 	 * @param timestamp
1034 	 * @return Array of the latest versions of items modified since specified timestamp.
1035 	 * @throws IOException
1036 	 */
1037 	public Version<T>[] modifiedSince(final long timestamp) throws IOException {
1038 		if (Controler.logger.isLoggable(Level.FINER)) {
1039 			Controler.logger.entering("Controler", "modifiedSince(long=" + timestamp + ")", "start");
1040 		}
1041 
1042 		final Item<T, C>[] items = db.modifiedSince(timestamp, false);
1043 		final Version<T>[] result = new Version[items == null ? 0 : items.length];
1044 		for (int i = 0; i < result.length; i++) {
1045 			result[i] = items[i].getLatest();
1046 		}
1047 
1048 		if (Controler.logger.isLoggable(Level.FINER)) {
1049 			Controler.logger.exiting("Controler", "modifiedSince(long=" + timestamp + ")", "end - return value="
1050 				+ Arrays.toString(result));
1051 		}
1052 		return result;
1053 	}
1054 
1055 	/**
1056 	 * Save all changes in the container.
1057 	 * 
1058 	 */
1059 	public void saveChanges() {
1060 		if (Controler.logger.isLoggable(Level.FINER)) {
1061 			Controler.logger.entering("Controler", "saveChanges()", "start");
1062 		}
1063 
1064 		db.commit();
1065 
1066 		if (Controler.logger.isLoggable(Level.FINER)) {
1067 			Controler.logger.exiting("Controler", "saveChanges()", "end");
1068 		}
1069 	}
1070 
1071 	/**
1072 	 * Set the number of versions kept for historical purposes.
1073 	 * 
1074 	 * @param versionRollover
1075 	 *            The versionRollover to set.
1076 	 */
1077 	public void setVersionRollover(final int versionRollover) {
1078 		this.versionRollover = versionRollover;
1079 	}
1080 }