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 }