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