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 }