1 /** 2 * Copyright 2005 Steve Molloy 3 * 4 * This file is part of OV4J. 5 * 6 * OV4J is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as 7 * published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 8 * 9 * OV4J is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 10 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 11 * 12 * You should have received a copy of the GNU General Public License along with OV4J; if not, write to the Free Software 13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 14 * 15 * This file uses DB4O which is also free software which can be redistributed and/or modified under the GNU General 16 * Public License. DB4O can be obtained at http://www.db4o.com 17 * 18 */ 19 package org.ov4j.db4oImpl; 20 21 import java.io.File; 22 import java.io.IOException; 23 import java.io.Serializable; 24 import java.util.ArrayList; 25 import java.util.Arrays; 26 import java.util.Collections; 27 import java.util.Comparator; 28 import java.util.Timer; 29 import java.util.TimerTask; 30 import java.util.TreeSet; 31 import java.util.logging.Level; 32 import java.util.logging.Logger; 33 34 import org.ov4j.Config; 35 import org.ov4j.IContainer; 36 import org.ov4j.data.ClassComparable; 37 import org.ov4j.data.Item; 38 import org.ov4j.data.Version; 39 40 import com.db4o.Db4o; 41 import com.db4o.ObjectSet; 42 import com.db4o.config.ConfigScope; 43 import com.db4o.ext.ExtDb4o; 44 import com.db4o.ext.ExtObjectContainer; 45 import com.db4o.ext.MemoryFile; 46 import com.db4o.ext.StoredClass; 47 import com.db4o.query.Query; 48 import com.db4o.replication.ReplicationConflictHandler; 49 import com.db4o.replication.ReplicationProcess; 50 51 /** 52 * Implementation of IContainer using DB4O memory databases. 53 * 54 * @author smolloy 55 * 56 * @deprecated The MemoryContainer relied on the db4o replication mechanisms which is now external in dRS, the 57 * MemoryContainer will no longer be extended. 58 */ 59 @Deprecated 60 public class MemoryContainer<T extends Comparable<? super T> & Cloneable & Serializable, C extends Comparable<? super C>> 61 implements IContainer<T, C>, ReplicationConflictHandler { 62 /** 63 * Logger for this class 64 */ 65 private static final Logger logger = Logger.getLogger(MemoryContainer.class.getName()); 66 67 /** Lock to avoid concurrent backups or defragmentations of multiple databases. */ 68 private static final Object classLock = new Object(); 69 70 /** Block size for underlying DB, size limit will be roughly blockSize * 2GB. */ 71 private static int blockSize = 1; 72 73 /** 74 * @return the blockSize 75 */ 76 public static int getBlockSize() { 77 return MemoryContainer.blockSize; 78 } 79 80 /** 81 * @param blockSize 82 * the blockSize to set 83 */ 84 public static void setBlockSize(final int blockSize) { 85 MemoryContainer.blockSize = Math.max(blockSize, 1); 86 } 87 88 /** The data storing database. */ 89 protected ExtObjectContainer db; 90 91 /** The ID map database. */ 92 protected ExtObjectContainer mapDB; 93 94 /** Database storing map of deleted IDs. */ 95 protected ExtObjectContainer unmapDB; 96 97 /** The database name. */ 98 private final String dbName; 99 100 /** The timer used to periodically backup to file. */ 101 private final Timer theTimer; 102 103 /** Shutdown hook to make sure memory is saved to file when VM shuts down. */ 104 private final Thread hookThread; 105 106 /** Lock used to avoid concurrent saving */ 107 private final Object saveLock = new Object(); 108 109 /** List of pending tasks to execute. */ 110 private final ArrayList<Runnable> pendingTasks = new ArrayList<Runnable>(); 111 112 /** Flag for closing. */ 113 private boolean closing = false; 114 115 /** Flag for performing pending tasks. */ 116 private boolean runningPendingTasks = false; 117 118 /** Number of pending save or delete operations. */ 119 private int pendingSaves = 0; 120 121 /** Thread executing the background tasks. */ 122 private final Thread taskExecutor; 123 124 /** Whether or not the task executor thread has already been started. */ 125 private boolean taskExecutorStarted = false; 126 127 /** 128 * Constructor. 129 * 130 * @param dbName 131 * Name of the database. 132 * @param delay 133 * Delay before the first save. 134 * @param saveInterval 135 * Interval (in ms) between periodic replication to backup database. 136 */ 137 public MemoryContainer(final String dbName, final long delay, final long saveInterval) { 138 theTimer = new Timer(); 139 this.dbName = dbName; 140 configure(); 141 ExtObjectContainer fileDB = null; 142 ExtObjectContainer unmapFileDB = null; 143 try { 144 fileDB = Db4o.openFile(dbName).ext(); 145 unmapFileDB = Db4o.openFile(dbName + "_del").ext(); 146 } catch (final Throwable ex) { 147 if (MemoryContainer.logger.isLoggable(Level.FINE)) { 148 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "MemoryContainer(String=" + dbName 149 + ", long=" + delay + ", long=" + saveInterval + ")", "exception ignored", ex); 150 } 151 } 152 try { 153 db = ExtDb4o.openMemoryFile(new MemoryFile()).ext(); 154 mapDB = ExtDb4o.openMemoryFile(new MemoryFile()).ext(); 155 unmapDB = ExtDb4o.openMemoryFile(new MemoryFile()).ext(); 156 } catch (final Throwable ex) { 157 if (MemoryContainer.logger.isLoggable(Level.FINE)) { 158 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "MemoryContainer(String=" + dbName 159 + ", long=" + delay + ", long=" + saveInterval + ")", "exception ignored", ex); 160 } 161 } 162 if (db != null && fileDB != null) { 163 replicate(fileDB, db); 164 fileDB.close(); 165 replicate(unmapFileDB, unmapDB); 166 unmapFileDB.close(); 167 createMap(); 168 } 169 final TimerTask saveMemoryTask = new TimerTask() { 170 public void run() { 171 while (!Config.checkMemory()) { 172 try { 173 Thread.sleep(30000); 174 } catch (InterruptedException e) { 175 if (MemoryContainer.logger.isLoggable(Level.FINE)) { 176 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "Save$TimerTask.run()", 177 "exception ignored", e); 178 } 179 } 180 } 181 backup(); 182 } 183 }; 184 theTimer.scheduleAtFixedRate(saveMemoryTask, delay, saveInterval); 185 taskExecutor = new Thread() { 186 public void run() { 187 while (!closing) { 188 if (pendingTasks != null && pendingTasks.size() > 0) { 189 runningPendingTasks = true; 190 Runnable[] tasks = null; 191 synchronized (pendingTasks) { 192 tasks = pendingTasks.toArray(new Runnable[0]); 193 pendingTasks.clear(); 194 } 195 for (int i = 0; i < tasks.length; i++) { 196 tasks[i].run(); 197 } 198 runningPendingTasks = false; 199 } 200 try { 201 Thread.sleep(10); 202 } catch (final InterruptedException e) { 203 if (MemoryContainer.logger.isLoggable(Level.FINE)) { 204 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "TaskExecutor$Thread.run()", 205 "exception ignored", e); 206 } 207 } 208 } 209 } 210 }; 211 final Runnable saveHook = new Runnable() { 212 public void run() { 213 close(false); 214 } 215 }; 216 hookThread = new Thread(saveHook); 217 Runtime.getRuntime().addShutdownHook(hookThread); 218 } 219 220 /** 221 * Backup the data and ID map databases. 222 * 223 */ 224 protected synchronized void backup() { 225 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 226 MemoryContainer.logger.entering("MemoryContainer", "backup()", "start"); 227 } 228 229 if (db != null && !db.isClosed()) { 230 try { 231 commit(); 232 final File backup = new File(dbName); 233 if (backup.exists()) { 234 backup.delete(); 235 } 236 final ExtObjectContainer tmp = Db4o.openFile(dbName).ext(); 237 replicate(db, tmp); 238 if (backup.exists()) { 239 backup.delete(); 240 } 241 final ExtObjectContainer unmaptmp = Db4o.openFile(dbName + "del").ext(); 242 replicate(unmapDB, unmaptmp); 243 unmaptmp.close(); 244 } catch (final Throwable ex) { 245 if (MemoryContainer.logger.isLoggable(Level.FINE)) { 246 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "backup()", "exception ignored", ex); 247 } 248 } 249 } 250 251 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 252 MemoryContainer.logger.exiting("MemoryContainer", "backup()", "end"); 253 } 254 } 255 256 /** 257 * @see org.ov4j.IContainer#batchDelete(org.ov4j.data.Item<T>[]) 258 */ 259 public void batchDelete(final Item<T, C>[] items, final boolean keepTrack) { 260 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 261 MemoryContainer.logger.entering("MemoryContainer", "batchDelete(Item<T,C>[]=" + Arrays.toString(items) 262 + ", boolean=" + keepTrack + ")", "start"); 263 } 264 265 for (int i = 0; i < items.length; i++) { 266 delete(items[i], keepTrack); 267 } 268 269 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 270 MemoryContainer.logger.exiting("MemoryContainer", "batchDelete(Item<T,C>[]=" + Arrays.toString(items) 271 + ", boolean=" + keepTrack + ")", "end"); 272 } 273 } 274 275 /** 276 * @see org.ov4j.IContainer#batchMerge(java.lang.Comparable[]) 277 */ 278 public void batchMerge(final C[] ids) { 279 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 280 MemoryContainer.logger.entering("MemoryContainer", "batchMerge(C[]=" + Arrays.toString(ids) + ")", "start"); 281 } 282 283 final Runnable t = new Runnable() { 284 public void run() { 285 synchronized (saveLock) { 286 Query q = db.query(); 287 q.constrain(Item.class); 288 q.constrain(new MultipleIDValidator<C>(ids)); 289 ObjectSet<Item<T, C>> set = q.execute(); 290 ArrayList<Item<T, C>> res = new ArrayList<Item<T, C>>(); 291 while (set.hasNext()) { 292 res.add((Item<T, C>) set.next()); 293 } 294 Collections.sort(res, new Comparator<Item<T, C>>() { 295 public int compare(Item<T, C> it1, Item<T, C> it2) { 296 if (it1 == null) { 297 return (it2 == null) ? 0 : -1; 298 } 299 300 if (it2 == null) { 301 return 1; 302 } 303 304 return (it1.getId().compareTo(it2.getId())); 305 } 306 }); 307 ArrayList<Item<T, C>> mergedList = new ArrayList<Item<T, C>>(); 308 ArrayList<Item<T, C>> deletedList = new ArrayList<Item<T, C>>(); 309 C lastId = null; 310 long lastTStamp = -1; 311 for (int i = 0; i < res.size(); i++) { 312 Item<T, C> current = res.get(i); 313 if (lastId == null || lastId.compareTo(current.getId()) != 0) { 314 mergedList.add(0, current); 315 lastId = current.getId(); 316 lastTStamp = current.getModificationStamp(); 317 } else { 318 if (current.getModificationStamp() > lastTStamp) { 319 deletedList.add(0, mergedList.remove(0)); 320 } else { 321 deletedList.add(current); 322 } 323 } 324 } 325 for (int i = 0; i < mergedList.size(); i++) { 326 Item<T, C> it = mergedList.get(i); 327 db.set(it); 328 map(it); 329 } 330 for (int i = 0; i < deletedList.size(); i++) { 331 Item<T, C> it = deletedList.get(i); 332 db.delete(it); 333 unmap(it, false); 334 } 335 } 336 pendingSaves--; 337 } 338 }; 339 synchronized (pendingTasks) { 340 pendingSaves++; 341 pendingTasks.add(t); 342 } 343 344 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 345 MemoryContainer.logger.exiting("MemoryContainer", "batchMerge(C[]=" + Arrays.toString(ids) + ")", "end"); 346 } 347 } 348 349 /** 350 * @see org.ov4j.IContainer#batchSave(org.ov4j.data.Item<T>[], boolean) 351 */ 352 public void batchSave(final Item<T, C>[] items, final boolean abortOnDuplicate) throws IOException { 353 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 354 MemoryContainer.logger.entering("MemoryContainer", "batchSave(Item<T,C>[]=" + Arrays.toString(items) 355 + ", boolean=" + abortOnDuplicate + ")", "start"); 356 } 357 358 final Runnable t = new Runnable() { 359 public void run() { 360 synchronized (saveLock) { 361 C[] ids = (C[]) new Comparable[items.length]; 362 for (int i = 0; i < items.length; i++) { 363 ids[i] = items[i].getId(); 364 } 365 Query q = mapDB.query(); 366 q.constrain(MappedField.class); 367 q.constrain(new MultipleIDValidator<C>(ids)); 368 ObjectSet<MappedField> set = q.execute(); 369 if (!abortOnDuplicate || set.size() < 1) { 370 TreeSet<C> idSet = new TreeSet<C>(); 371 while (set.hasNext()) { 372 idSet.add((C) ((MappedField) set.next()).getObject()); 373 } 374 for (int i = 0; i < items.length; i++) { 375 db.set(items[i]); 376 if (items[i] != null && !idSet.contains(items[i].getId())) { 377 long id = db.getID(items[i]); 378 MappedField mf = new MappedField(); 379 mf.setModificationStamp(items[i].getModificationStamp()); 380 mf.setObject(items[i].getId()); 381 mf.setId(id); 382 mapDB.set(mf); 383 } else if (items[i] != null) { 384 idSet.remove(items[i].getId()); 385 } 386 } 387 } 388 } 389 pendingSaves--; 390 } 391 }; 392 synchronized (pendingTasks) { 393 pendingSaves++; 394 pendingTasks.add(t); 395 } 396 397 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 398 MemoryContainer.logger.exiting("MemoryContainer", "batchSave(Item<T,C>[]=" + Arrays.toString(items) 399 + ", boolean=" + abortOnDuplicate + ")", "end"); 400 } 401 } 402 403 /** 404 * Clear the database, will not clear the backups. 405 * 406 * WARNING: THIS OPERATION CAN BE VERY HARMFUL AS ALL DATA IN THE DB WILL BE LOST!!! 407 * 408 * @see org.ov4j.IContainer#clear() 409 */ 410 public void clear() { 411 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 412 MemoryContainer.logger.entering("MemoryContainer", "clear()", "start"); 413 } 414 415 try { 416 db.close(); 417 mapDB.close(); 418 unmapDB.close(); 419 db = ExtDb4o.openMemoryFile(new MemoryFile()).ext(); 420 mapDB = ExtDb4o.openMemoryFile(new MemoryFile()).ext(); 421 unmapDB = ExtDb4o.openMemoryFile(new MemoryFile()).ext(); 422 } catch (final Throwable ex) { 423 if (MemoryContainer.logger.isLoggable(Level.FINE)) { 424 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "clear()", "exception ignored", ex); 425 } 426 } 427 backup(); 428 429 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 430 MemoryContainer.logger.exiting("MemoryContainer", "clear()", "end"); 431 } 432 } 433 434 /** 435 * @see org.ov4j.IContainer#clearDeletedIDs() 436 */ 437 public void clearDeletedIDs() { 438 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 439 MemoryContainer.logger.entering("MemoryContainer", "clearDeletedIDs()", "start"); 440 } 441 442 unmapDB.close(); 443 unmapDB = ExtDb4o.openMemoryFile(new MemoryFile()).ext(); 444 445 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 446 MemoryContainer.logger.exiting("MemoryContainer", "clearDeletedIDs()", "end"); 447 } 448 } 449 450 /** 451 * @see org.ov4j.IContainer#close() 452 */ 453 public synchronized void close() { 454 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 455 MemoryContainer.logger.entering("MemoryContainer", "close()", "start"); 456 } 457 458 close(true); 459 460 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 461 MemoryContainer.logger.exiting("MemoryContainer", "close()", "end"); 462 } 463 } 464 465 /** 466 * Close the container, possibly removing shutdown hook. 467 * 468 * @param removeHook 469 * Whether or not the shutdown hook should be removed first. 470 */ 471 public synchronized void close(final boolean removeHook) { 472 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 473 MemoryContainer.logger.entering("MemoryContainer", "close(boolean=" + removeHook + ")", "start"); 474 } 475 476 waitForPendingTasks(); 477 closing = true; 478 if (removeHook) { 479 Runtime.getRuntime().removeShutdownHook(hookThread); 480 } 481 if (pendingTasks != null && pendingTasks.size() > 0) { 482 runningPendingTasks = true; 483 Runnable[] tasks = null; 484 synchronized (pendingTasks) { 485 tasks = pendingTasks.toArray(new Runnable[0]); 486 pendingTasks.clear(); 487 } 488 for (int i = 0; i < tasks.length; i++) { 489 tasks[i].run(); 490 } 491 runningPendingTasks = false; 492 } 493 backup(); 494 if (theTimer != null) { 495 theTimer.cancel(); 496 } 497 if (db != null) { 498 while (!db.close()) { 499 ; 500 } 501 } 502 if (mapDB != null) { 503 while (!mapDB.close()) { 504 ; 505 } 506 } 507 if (unmapDB != null) { 508 while (!unmapDB.close()) { 509 ; 510 } 511 } 512 513 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 514 MemoryContainer.logger.exiting("MemoryContainer", "close(boolean=" + removeHook + ")", "end"); 515 } 516 } 517 518 /** 519 * @see org.ov4j.IContainer#commit() 520 */ 521 public void commit() { 522 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 523 MemoryContainer.logger.entering("MemoryContainer", "commit()", "start"); 524 } 525 526 final Runnable t = new Runnable() { 527 public void run() { 528 synchronized (saveLock) { 529 db.commit(); 530 mapDB.commit(); 531 } 532 } 533 }; 534 synchronized (pendingTasks) { 535 pendingTasks.add(t); 536 } 537 538 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 539 MemoryContainer.logger.exiting("MemoryContainer", "commit()", "end"); 540 } 541 } 542 543 /** 544 * Configure database, subclasses should overload this method, but still call super.configure() so that everything 545 * is configured correctly. This method will be called before any databases are opened or created. 546 * 547 */ 548 protected void configure() { 549 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 550 MemoryContainer.logger.entering("MemoryContainer", "configure()", "start"); 551 } 552 553 Db4o.configure().allowVersionUpdates(true); 554 Db4o.configure().blockSize(MemoryContainer.blockSize); 555 Db4o.configure().automaticShutDown(false); 556 Db4o.configure().generateUUIDs(ConfigScope.GLOBALLY); 557 Db4o.configure().generateVersionNumbers(ConfigScope.GLOBALLY); 558 Db4o.configure().callbacks(false); 559 Db4o.configure().callConstructors(true); 560 Db4o.configure().objectClass(ClassComparable.class).minimumActivationDepth(3); 561 Db4o.configure().objectClass(MappedField.class).objectField("object").indexed(true); 562 Db4o.configure().objectClass(MappedField.class).cascadeOnActivate(true); 563 Db4o.configure().objectClass(MappedField.class).cascadeOnUpdate(true); 564 Db4o.configure().objectClass(Item.class).objectField("id").cascadeOnActivate(true); 565 Db4o.configure().objectClass(Item.class).objectField("id").cascadeOnUpdate(true); 566 Db4o.configure().objectClass(Item.class).objectField("latest").cascadeOnActivate(true); 567 Db4o.configure().objectClass(Item.class).objectField("latest").cascadeOnUpdate(true); 568 Db4o.configure().objectClass(Item.class).objectField("versions").cascadeOnActivate(false); 569 Db4o.configure().objectClass(Item.class).objectField("versions").cascadeOnUpdate(true); 570 Db4o.configure().objectClass(Item.class).objectField("versions").queryEvaluation(false); 571 Db4o.configure().objectClass(Version.class).objectField("author").cascadeOnActivate(true); 572 Db4o.configure().objectClass(Version.class).objectField("author").cascadeOnUpdate(true); 573 Db4o.configure().objectClass(Version.class).objectField("comment").cascadeOnActivate(true); 574 Db4o.configure().objectClass(Version.class).objectField("comment").cascadeOnUpdate(true); 575 Db4o.configure().objectClass(Version.class).objectField("versionNumber").cascadeOnActivate(true); 576 Db4o.configure().objectClass(Version.class).objectField("versionNumber").cascadeOnUpdate(true); 577 Db4o.configure().objectClass(Version.class).objectField("versionedObject").cascadeOnActivate(true); 578 Db4o.configure().objectClass(Version.class).objectField("versionedObject").cascadeOnUpdate(true); 579 580 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 581 MemoryContainer.logger.exiting("MemoryContainer", "configure()", "end"); 582 } 583 } 584 585 /** 586 * Create the map of IDs if it could not be loaded from file. 587 * 588 */ 589 private void createMap() { 590 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 591 MemoryContainer.logger.entering("MemoryContainer", "createMap()", "start"); 592 } 593 594 if (!Boolean.getBoolean("ov4j.thread.map")) { 595 synchronized (MemoryContainer.classLock) { 596 map(); 597 } 598 } else { 599 map(); 600 } 601 602 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 603 MemoryContainer.logger.exiting("MemoryContainer", "createMap()", "end"); 604 } 605 } 606 607 /** 608 * @see org.ov4j.IContainer#delete(org.ov4j.data.Item) 609 */ 610 public void delete(final Item<T, C> it, final boolean keepTrack) { 611 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 612 MemoryContainer.logger.entering("MemoryContainer", "delete(Item<T,C>=" + it + ", boolean=" + keepTrack 613 + ")", "start"); 614 } 615 616 final Runnable t = new Runnable() { 617 public void run() { 618 synchronized (saveLock) { 619 unmap(it, keepTrack); 620 db.delete(it); 621 pendingSaves--; 622 } 623 } 624 }; 625 synchronized (pendingTasks) { 626 pendingSaves++; 627 pendingTasks.add(t); 628 } 629 630 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 631 MemoryContainer.logger.exiting("MemoryContainer", 632 "delete(Item<T,C>=" + it + ", boolean=" + keepTrack + ")", "end"); 633 } 634 } 635 636 /** 637 * @see org.ov4j.IContainer#deletedIds() 638 */ 639 public C[] deletedIds() { 640 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 641 MemoryContainer.logger.entering("MemoryContainer", "deletedIds()", "start"); 642 } 643 644 final ObjectSet<MappedField> set = unmapDB.get(MappedField.class); 645 final C[] ids = (C[]) new Comparable[set.size()]; 646 final ArrayList<Object> invalids = new ArrayList<Object>(); 647 for (int i = 0; set.hasNext() && i < ids.length; i++) { 648 final Object next = set.next(); 649 if (next instanceof MappedField && ((MappedField) next).getObject() instanceof Comparable) { 650 ids[i] = (C) ((MappedField) next).getObject(); 651 } else { 652 invalids.add(next); 653 } 654 } 655 if (invalids.size() > 0) { 656 final ArrayList<?> list = invalids; 657 final Runnable t = new Runnable() { 658 public void run() { 659 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 660 MemoryContainer.logger.entering("MemoryContainer", "deletedIds()$Runnable.run()", "start"); 661 } 662 663 synchronized (saveLock) { 664 for (int i = 0; i < list.size(); i++) { 665 unmapDB.delete(list.get(i)); 666 } 667 unmapDB.commit(); 668 pendingSaves--; 669 } 670 671 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 672 MemoryContainer.logger.exiting("MemoryContainer", "deletedIds()$Runnable.run()", "end"); 673 } 674 } 675 }; 676 synchronized (pendingTasks) { 677 pendingSaves++; 678 pendingTasks.add(t); 679 } 680 } 681 682 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 683 MemoryContainer.logger.exiting("MemoryContainer", "deletedIds()", "end - return value=" 684 + Arrays.toString(ids)); 685 } 686 return ids; 687 } 688 689 /** 690 * @see org.ov4j.IContainer#deletedIdsSince(long) 691 */ 692 public C[] deletedIdsSince(final long timestamp) { 693 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 694 MemoryContainer.logger.entering("MemoryContainer", "deletedIdsSince(long=" + timestamp + ")", "start"); 695 } 696 697 final Query q = unmapDB.query(); 698 q.constrain(MappedField.class); 699 q.descend("modificationStamp").constrain(timestamp).greater(); 700 final ObjectSet<MappedField> set = q.execute(); 701 final C[] ids = (C[]) new Comparable[set.size()]; 702 for (int i = 0; set.hasNext() && i < ids.length; i++) { 703 ids[i] = (C) ((MappedField) set.next()).getObject(); 704 } 705 706 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 707 MemoryContainer.logger.exiting("MemoryContainer", "deletedIdsSince(long=" + timestamp + ")", 708 "end - return value=" + Arrays.toString(ids)); 709 } 710 return ids; 711 } 712 713 /** 714 * Converts the given IContainer into an hessian implementation. 715 * 716 * @param cont 717 * IContainer to convert. 718 * @throws IOException 719 */ 720 public void duplicate(final IContainer<T, C> cont) throws IOException { 721 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 722 MemoryContainer.logger.entering("MemoryContainer", "duplicate(IContainer<T,C>=" + cont + ")", "start"); 723 } 724 725 final C[] ids = cont.listModifiedSince(-1); 726 if (ids != null) { 727 for (int startIdx = 0; startIdx < ids.length; startIdx += 32) { 728 final C[] buf = (C[]) new Comparable[Math.min(32, (ids.length - startIdx))]; 729 System.arraycopy(ids, startIdx, buf, 0, buf.length); 730 batchSave(cont.load(buf, true), false); 731 } 732 } 733 commit(); 734 735 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 736 MemoryContainer.logger.exiting("MemoryContainer", "duplicate(IContainer<T,C>=" + cont + ")", "end"); 737 } 738 } 739 740 /** 741 * @see org.ov4j.IContainer#inUse(java.lang.Comparable) 742 */ 743 public boolean inUse(final C id) { 744 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 745 MemoryContainer.logger.entering("MemoryContainer", "inUse(C=" + id + ")", "start"); 746 } 747 748 final MappedField map = new MappedField(); 749 map.setObject(id); 750 final ObjectSet<MappedField> set = mapDB.get(map); 751 final boolean res = set.hasNext(); 752 753 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 754 MemoryContainer.logger.exiting("MemoryContainer", "inUse(C=" + id + ")", "end - return value=" + res); 755 } 756 return res; 757 } 758 759 /** 760 * @see org.ov4j.IContainer#isClosed() 761 */ 762 public boolean isClosed() { 763 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 764 MemoryContainer.logger.entering("MemoryContainer", "isClosed()", "start"); 765 } 766 767 final boolean returnboolean = db.isClosed(); 768 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 769 MemoryContainer.logger.exiting("MemoryContainer", "isClosed()", "end - return value=" + returnboolean); 770 } 771 return returnboolean; 772 } 773 774 /** 775 * @throws IOException 776 * @see org.ov4j.IContainer#listModifiedSince(long) 777 */ 778 public C[] listModifiedSince(final long timestamp) throws IOException { 779 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 780 MemoryContainer.logger.entering("MemoryContainer", "listModifiedSince(long=" + timestamp + ")", "start"); 781 } 782 783 final Query q = mapDB.query(); 784 q.constrain(MappedField.class); 785 final ObjectSet<MappedField> set = q.execute(); 786 final C[] res = (C[]) new Comparable[set.size()]; 787 for (int i = 0; set.hasNext() && i < res.length; i++) { 788 res[i] = (C) ((MappedField) set.next()).getObject(); 789 } 790 791 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 792 MemoryContainer.logger.exiting("MemoryContainer", "listModifiedSince(long=" + timestamp + ")", 793 "end - return value=" + Arrays.toString(res)); 794 } 795 return res; 796 } 797 798 /** 799 * @see org.ov4j.IContainer#load(java.lang.Comparable, boolean) 800 */ 801 public Item<T, C> load(final C id, final boolean allVersions) { 802 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 803 MemoryContainer.logger.entering("MemoryContainer", "load(C=" + id + ", boolean=" + allVersions + ")", 804 "start"); 805 } 806 807 Item<T, C> res = null; 808 final MappedField map = new MappedField(); 809 map.setObject(id); 810 final ObjectSet<MappedField> set = mapDB.get(map); 811 if (set.hasNext()) { 812 final MappedField mf = (MappedField) set.next(); 813 res = (Item<T, C>) db.getByID(mf.getId()); 814 db.activate(res, 2); 815 if (allVersions && res != null) { 816 db.activate(res.getVersions(), 2); 817 } 818 } 819 if (res != null) { 820 try { 821 res = res.clone(); 822 } catch (final CloneNotSupportedException e) { 823 if (MemoryContainer.logger.isLoggable(Level.FINE)) { 824 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "load(C=" + id + ", boolean=" 825 + allVersions + ")", "Exception ignored", e); 826 } 827 } 828 } 829 830 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 831 MemoryContainer.logger.exiting("MemoryContainer", "load(C=" + id + ", boolean=" + allVersions + ")", 832 "end - return value=" + res); 833 } 834 return res; 835 } 836 837 /** 838 * @see org.ov4j.IContainer#load(java.lang.Comparable, boolean) 839 */ 840 public Item<T, C>[] load(final C[] ids, final boolean allVersions) { 841 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 842 MemoryContainer.logger.entering("MemoryContainer", "load(C[]=" + Arrays.toString(ids) + ", boolean=" 843 + allVersions + ")", "start"); 844 } 845 846 final Query q = mapDB.query(); 847 q.constrain(MappedField.class); 848 q.constrain(new MultipleIDValidator<C>(ids)); 849 final ObjectSet<MappedField> set = q.execute(); 850 final Item<T, C>[] res = new Item[set.size()]; 851 for (int i = 0; set.hasNext() && i < res.length; i++) { 852 res[i] = (Item<T, C>) db.getByID(((MappedField) set.next()).getId()); 853 db.activate(res[i], 2); 854 if (res[i] != null) { 855 if (allVersions) { 856 db.activate(res[i].getVersions(), 2); 857 } 858 try { 859 res[i] = res[i].clone(); 860 } catch (final CloneNotSupportedException e) { 861 if (MemoryContainer.logger.isLoggable(Level.FINE)) { 862 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "load(C[]=" + Arrays.toString(ids) 863 + ", boolean=" + allVersions + ")", "Exception ignored", e); 864 } 865 } 866 } 867 } 868 869 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 870 MemoryContainer.logger.exiting("MemoryContainer", "load(C[]=" + Arrays.toString(ids) + ", boolean=" 871 + allVersions + ")", "end - return value=" + Arrays.toString(res)); 872 } 873 return res; 874 } 875 876 private synchronized void map() { 877 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 878 MemoryContainer.logger.entering("MemoryContainer", "map()", "start"); 879 } 880 881 final StoredClass cls = db.storedClass(Item.class); 882 if (cls != null) { 883 final long[] ids = cls.getIDs(); 884 if (ids != null && ids.length > 0) { 885 for (int i = 0; i < ids.length; i++) { 886 final Item<T, C> it = db.getByID(ids[i]); 887 db.activate(it, 1); 888 final MappedField mf = new MappedField(); 889 mf.setObject(it.getId()); 890 mf.setId(ids[i]); 891 mf.setModificationStamp(it.getModificationStamp()); 892 mapDB.set(mf); 893 } 894 commit(); 895 } 896 } 897 898 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 899 MemoryContainer.logger.exiting("MemoryContainer", "map()", "end"); 900 } 901 } 902 903 /** 904 * Create an ID map for this item. 905 * 906 * @param it 907 * Item for which a map should be created. 908 */ 909 private void map(final Item<T, C> it) { 910 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 911 MemoryContainer.logger.entering("MemoryContainer", "map(Item<T,C>=" + it + ")", "start"); 912 } 913 914 final MappedField map = new MappedField(); 915 map.setObject(it.getId()); 916 final ObjectSet<MappedField> mapSet = mapDB.get(map); 917 MappedField mf = (mapSet == null || !mapSet.hasNext()) ? null : mapSet.next(); 918 919 if (mf == null) { 920 long id = db.getID(it); 921 if (id < 1) { 922 final ObjectSet<Item<T, C>> set = db.get(it); 923 id = (set == null || !set.hasNext()) ? null : db.getID(set.next()); 924 } 925 if (id > 0) { 926 final ObjectSet<MappedField> unmapSet = unmapDB.get(map); 927 mf = (unmapSet == null || !unmapSet.hasNext()) ? null : unmapSet.next(); 928 if (mf != null) { 929 unmapDB.delete(mf); 930 } 931 mf = new MappedField(); 932 mf.setObject(it.getId()); 933 mf.setId(id); 934 } 935 } else { 936 long id = db.getID(it); 937 if (id < 1) { 938 final ObjectSet<Item<T, C>> set = db.get(it); 939 id = (set == null || !set.hasNext()) ? null : db.getID(set.next()); 940 } 941 if (id < 1) { 942 mapDB.delete(mf); 943 mf = null; 944 } else { 945 mf.setId(id); 946 } 947 } 948 if (mf != null) { 949 mf.setModificationStamp(it.getModificationStamp()); 950 mapDB.set(mf); 951 } 952 953 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 954 MemoryContainer.logger.exiting("MemoryContainer", "map(Item<T,C>=" + it + ")", "end"); 955 } 956 } 957 958 /** 959 * @see org.ov4j.IContainer#modifiedSince(long, boolean) 960 */ 961 public Item<T, C>[] modifiedSince(final long timestamp, final boolean allVersions) { 962 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 963 MemoryContainer.logger.entering("MemoryContainer", "modifiedSince(long=" + timestamp + ", boolean=" 964 + allVersions + ")", "start"); 965 } 966 967 final Query q = mapDB.query(); 968 q.constrain(MappedField.class); 969 q.descend("modificationStamp").constrain(timestamp).greater(); 970 final ObjectSet<MappedField> set = q.execute(); 971 final Item<T, C>[] res = new Item[set.size()]; 972 for (int i = 0; set.hasNext() && i < res.length; i++) { 973 final MappedField mf = (MappedField) set.next(); 974 res[i] = (Item<T, C>) db.getByID(mf.getId()); 975 db.activate(res[i], 2); 976 if (res[i] != null) { 977 if (allVersions) { 978 db.activate(res[i].getVersions(), 2); 979 } 980 try { 981 res[i] = res[i].clone(); 982 } catch (final CloneNotSupportedException e) { 983 if (MemoryContainer.logger.isLoggable(Level.FINE)) { 984 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "modifiedSince(long=" + timestamp 985 + ", boolean=" + allVersions + ")", "Exception ignored", e); 986 } 987 } 988 } 989 } 990 991 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 992 MemoryContainer.logger.exiting("MemoryContainer", "modifiedSince(long=" + timestamp + ", boolean=" 993 + allVersions + ")", "end - return value=" + Arrays.toString(res)); 994 } 995 return res; 996 } 997 998 /** 999 * @see org.ov4j.IContainer#release(java.lang.Object) 1000 */ 1001 public void release(final Object obj) { 1002 } 1003 1004 /** 1005 * Replicate the original database to the destination one. 1006 * 1007 * @param original 1008 * Database in which to look for changes. 1009 * @param destination 1010 * Database to which changes will be applied. 1011 */ 1012 private synchronized void replicate(final ExtObjectContainer original, final ExtObjectContainer destination) { 1013 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 1014 MemoryContainer.logger.entering("MemoryContainer", "replicate(ExtObjectContainer=" + original 1015 + ", ExtObjectContainer=" + destination + ")", "start"); 1016 } 1017 1018 if (original != null && destination != null && !original.isClosed() && !destination.isClosed()) { 1019 final ReplicationProcess replication = 1020 original.replicationBegin(destination, new ReplicationConflictHandler() { 1021 public Object resolveConflict(ReplicationProcess replicationProcess, Object a, Object b) { 1022 return a; 1023 } 1024 }); 1025 replication.setDirection(original, destination); 1026 final Query q = original.query(); 1027 q.constrain(Item.class); 1028 replication.whereModified(q); 1029 final ObjectSet<Item<T, C>> replicationSet = q.execute(); 1030 while (replicationSet.hasNext()) { 1031 replication.replicate(replicationSet.next()); 1032 } 1033 replication.commit(); 1034 } 1035 1036 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 1037 MemoryContainer.logger.exiting("MemoryContainer", "replicate(ExtObjectContainer=" + original 1038 + ", ExtObjectContainer=" + destination + ")", "end"); 1039 } 1040 } 1041 1042 /** 1043 * Always returns object A as replication is always used to backup in peer B. 1044 * 1045 * @see com.db4o.replication.ReplicationConflictHandler#resolveConflict(com.db4o.replication.ReplicationProcess, 1046 * java.lang.Object, java.lang.Object) 1047 */ 1048 public Object resolveConflict(final ReplicationProcess proc, final Object objA, final Object objB) { 1049 return objA; 1050 } 1051 1052 /** 1053 * @see org.ov4j.IContainer#rollback() 1054 */ 1055 public void rollback() { 1056 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 1057 MemoryContainer.logger.entering("MemoryContainer", "rollback()", "start"); 1058 } 1059 1060 final Runnable t = new Runnable() { 1061 public void run() { 1062 synchronized (saveLock) { 1063 db.rollback(); 1064 mapDB.rollback(); 1065 } 1066 } 1067 }; 1068 synchronized (pendingTasks) { 1069 pendingTasks.add(t); 1070 } 1071 1072 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 1073 MemoryContainer.logger.exiting("MemoryContainer", "rollback()", "end"); 1074 } 1075 } 1076 1077 /** 1078 * @see org.ov4j.IContainer#save(org.ov4j.data.Item) 1079 */ 1080 public void save(final Item<T, C> it) { 1081 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 1082 MemoryContainer.logger.entering("MemoryContainer", "save(Item<T,C>=" + it + ")", "start"); 1083 } 1084 1085 final Runnable t = new Runnable() { 1086 public void run() { 1087 synchronized (saveLock) { 1088 db.set(it); 1089 map(it); 1090 pendingSaves--; 1091 } 1092 } 1093 }; 1094 synchronized (pendingTasks) { 1095 pendingSaves++; 1096 pendingTasks.add(t); 1097 } 1098 1099 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 1100 MemoryContainer.logger.exiting("MemoryContainer", "save(Item<T,C>=" + it + ")", "end"); 1101 } 1102 } 1103 1104 /** 1105 * Returns a string representation of this Container. 1106 * 1107 * @return String representation of the Container. 1108 */ 1109 public String toString() { 1110 return "MemoryContainer{" + dbName + "}"; 1111 } 1112 1113 /** 1114 * Delete any maps for this item. 1115 * 1116 * @param it 1117 * Item for which maps should be deleted. 1118 * @param keepTrack 1119 * Whether or not to keep ID in history. 1120 */ 1121 private void unmap(final Item<T, C> it, final boolean keepTrack) { 1122 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 1123 MemoryContainer.logger.entering("MemoryContainer", 1124 "unmap(Item<T,C>=" + it + ", boolean=" + keepTrack + ")", "start"); 1125 } 1126 1127 final MappedField mf = new MappedField(); 1128 mf.setId(db.getID(it)); 1129 mf.setObject(it.getId()); 1130 final ObjectSet<MappedField> set = mapDB.get(mf); 1131 while (set.hasNext()) { 1132 mapDB.delete(set.next()); 1133 } 1134 if (keepTrack) { 1135 final MappedField copy = new MappedField(); 1136 copy.setObject(it.getId()); 1137 copy.setModificationStamp(System.currentTimeMillis()); 1138 unmapDB.set(copy); 1139 } 1140 1141 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 1142 MemoryContainer.logger.exiting("MemoryContainer", "unmap(Item<T,C>=" + it + ", boolean=" + keepTrack + ")", 1143 "end"); 1144 } 1145 } 1146 1147 public void waitForPendingSaves() { 1148 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 1149 MemoryContainer.logger.entering("MemoryContainer", "waitForPendingSaves()", "start"); 1150 } 1151 1152 if (!taskExecutorStarted) { 1153 synchronized (taskExecutor) { 1154 taskExecutorStarted = true; 1155 taskExecutor.start(); 1156 } 1157 } 1158 1159 while (pendingSaves > 0) { 1160 try { 1161 Thread.sleep(100); 1162 } catch (final InterruptedException e) { 1163 if (MemoryContainer.logger.isLoggable(Level.FINE)) { 1164 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "waitForPendingSaves()", 1165 "exception ignored", e); 1166 } 1167 } 1168 } 1169 1170 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 1171 MemoryContainer.logger.exiting("MemoryContainer", "waitForPendingSaves()", "end"); 1172 } 1173 } 1174 1175 public void waitForPendingTasks() { 1176 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 1177 MemoryContainer.logger.entering("MemoryContainer", "waitForPendingTasks()", "start"); 1178 } 1179 1180 if (!taskExecutorStarted) { 1181 synchronized (taskExecutor) { 1182 taskExecutorStarted = true; 1183 taskExecutor.start(); 1184 } 1185 } 1186 1187 while (pendingTasks.size() > 0 || runningPendingTasks) { 1188 try { 1189 Thread.sleep(100); 1190 } catch (final InterruptedException e) { 1191 if (MemoryContainer.logger.isLoggable(Level.FINE)) { 1192 MemoryContainer.logger.logp(Level.FINE, "MemoryContainer", "waitForPendingTasks()", 1193 "exception ignored", e); 1194 } 1195 } 1196 } 1197 1198 if (MemoryContainer.logger.isLoggable(Level.FINER)) { 1199 MemoryContainer.logger.exiting("MemoryContainer", "waitForPendingTasks()", "end"); 1200 } 1201 } 1202 }