001: /**
002: * Copyright Mobixess Inc. 2007
003: */package com.mobixess.jodb.core.transaction;
004:
005: import java.io.IOException;
006: import java.lang.annotation.Annotation;
007: import java.lang.ref.ReferenceQueue;
008: import java.lang.reflect.Constructor;
009: import java.lang.reflect.Field;
010: import java.lang.reflect.Modifier;
011: import java.util.BitSet;
012: import java.util.ConcurrentModificationException;
013: import java.util.Hashtable;
014: import java.util.Iterator;
015: import java.util.Vector;
016: import java.util.WeakHashMap;
017: import java.util.logging.Level;
018: import java.util.logging.Logger;
019:
020: import com.mobixess.jodb.core.IPersistentObjectStatistics;
021: import com.mobixess.jodb.core.IllegalClassTypeException;
022: import com.mobixess.jodb.core.JODBConfig;
023: import com.mobixess.jodb.core.JODBTransient;
024: import com.mobixess.jodb.core.JodbIOException;
025: import com.mobixess.jodb.core.index.JODBIndexingRootAgent;
026: import com.mobixess.jodb.core.io.IOBase;
027: import com.mobixess.jodb.core.io.IOTicket;
028: import com.mobixess.jodb.core.plugin.IObjectInstanceProvider;
029: import com.mobixess.jodb.core.plugin.JODBObjectInstanceFactory;
030: import com.mobixess.jodb.util.PrimitiveJavaTypesUtil;
031: import com.mobixess.jodb.util.Utils;
032:
033: /**
034: * @author Mobixess
035: *
036: */
037: public class JODBSession {
038: private Hashtable<Object, PersistentObjectHandle> _activeObjects = new Hashtable<Object, PersistentObjectHandle>();
039: private Object _activationSynchObject = new Object();
040: private ClassLoader _classLoader;
041:
042: private IOBase _base;
043: private WeakHashMap<Class, ClassDescriptor> _classDescriptors = new WeakHashMap<Class, ClassDescriptor>();//TODO add caching per class loader
044: // private WeakHashMap<ClassDescriptor, int[]> _fieldSubstitutionsIDs = new WeakHashMap<ClassDescriptor, int[]>();
045: private ActiveObjectReferenceCleaner _activeObjectReferenceCleaner = new ActiveObjectReferenceCleaner();
046: private static WeakHashMap<ActiveObjectReferenceCleaner, Object> _cleanersCache = new WeakHashMap<ActiveObjectReferenceCleaner, Object>();
047: private static StaleReferencesCleanerThread _staleReferencesCleanerThread;
048: private Logger _logger = Utils.getLogger(getClass().getName());
049: private static ThreadLocal<SearchKeysHolder> _persistentObjectHandleSearchCache = new ThreadLocal<SearchKeysHolder>() {
050: @Override
051: protected SearchKeysHolder initialValue() {
052: return new SearchKeysHolder();
053: }
054: };
055:
056: /**
057: * @param base
058: */
059: public JODBSession(IOBase base) {
060: super ();
061: _base = base;
062: _classLoader = JODBConfig.getCustomClassLoader();
063: if (JODBConfig.isBackgroundReferenceCleanerEnabled()) {
064: synchronized (_cleanersCache) {//synch with cleaner thread deactivation in StaleReferencesCleanerThread
065: _cleanersCache.put(_activeObjectReferenceCleaner, null);
066: if (_staleReferencesCleanerThread == null) {
067: _staleReferencesCleanerThread = new StaleReferencesCleanerThread();
068: if (JODBConfig.DEBUG) {
069: _logger
070: .info("Background reference cleaner thread start");
071: }
072: _staleReferencesCleanerThread.start();
073: }
074: }
075: }
076: }
077:
078: public void close() {
079: _activeObjects.clear();
080: _cleanersCache.remove(_activeObjectReferenceCleaner);
081: }
082:
083: public PersistentObjectHandle getHandleForActiveObject(Object obj) {
084: _activeObjectReferenceCleaner.removeStaleReferences();
085: ActiveObjectWeakRefHolderSearchKey searchKey = _persistentObjectHandleSearchCache
086: .get()._activeObjectWeakRefHolderSearchKey;
087: searchKey.setSearchKey(obj);
088: PersistentObjectHandle persistentObjectHandle = _activeObjects
089: .get(searchKey);
090: searchKey.setSearchKey(null);//reset hard reference
091: if (persistentObjectHandle != null) {
092: synchronized (persistentObjectHandle) {//just to make sure that object is actually initialized
093: return persistentObjectHandle;
094: }
095: }
096: return null;
097: }
098:
099: public void putObject(Object obj, PersistentObjectHandle handle) {
100: _activeObjectReferenceCleaner.removeStaleReferences();
101: _activeObjects.put(handle.getReference(), handle);
102: _activeObjects.put(handle, handle);
103: }
104:
105: private void removeObject(Object obj) {
106: _activeObjectReferenceCleaner.removeStaleReferences();
107: ActiveObjectWeakRefHolderSearchKey searchKey = _persistentObjectHandleSearchCache
108: .get()._activeObjectWeakRefHolderSearchKey;
109: searchKey.setSearchKey(obj);
110: PersistentObjectHandle persistentObjectHandle = _activeObjects
111: .remove(searchKey);
112: if (persistentObjectHandle != null) {
113: _activeObjects.remove(persistentObjectHandle);
114: }
115: searchKey.setSearchKey(null);//reset hard reference
116: }
117:
118: public PersistentObjectHandle createHandleForObject(Object obj,
119: byte dataMask, long offset) {
120: ActiveObjectWeakRefHolder activeObjectWeakRefHolder = new ActiveObjectWeakRefHolder(
121: obj, _activeObjectReferenceCleaner._referenceQueue);
122: PersistentObjectHandle persistentObjectHandle = new PersistentObjectHandle(
123: activeObjectWeakRefHolder, dataMask, offset);
124: return persistentObjectHandle;
125: }
126:
127: public int getCachedObjectsCount() {
128: _activeObjectReferenceCleaner.removeStaleReferences();
129: return _activeObjects.size();
130: }
131:
132: public JODBQueryList getAllObjects() throws IOException {
133: IOTicket ioTicket = _base.getIOTicket(true, false);
134: ioTicket.lock(false);
135: long[] offsets;
136: try {
137: offsets = _base.getForAllObjects(ioTicket);
138: } finally {
139: ioTicket.unlock();
140: }
141: ioTicket.close();
142: return new JODBQueryList(offsets, this );
143: }
144:
145: public void setClassLoader(ClassLoader classLoader) {
146: _classLoader = classLoader;
147: _classDescriptors.clear();
148: }
149:
150: public Class resolveClassForName(String className)
151: throws ClassNotFoundException {
152: try {
153: ClassLoader classLoader = _classLoader;
154: if (classLoader != null) {
155: return classLoader.loadClass(className);
156: }
157: return Class.forName(className);
158: } catch (ClassNotFoundException e) {
159: Class clazz = PrimitiveJavaTypesUtil
160: .primitiveClassForName(className);
161: if (clazz == null) {
162: throw e;
163: }
164: return clazz;
165: }
166: }
167:
168: public Class resolveClassForID(int classNameID)
169: throws ClassNotFoundException, JodbIOException {
170: String name = _base.getClassTypeForID(classNameID);
171: if (name == null) {
172: throw new ClassNotFoundException("id=" + classNameID);
173: }
174: return resolveClassForName(name);
175: }
176:
177: public ClassDescriptor getDescriptorForClass(int classNameID)
178: throws IllegalClassTypeException, ClassNotFoundException,
179: JodbIOException {
180: Class clazz = resolveClassForID(classNameID);
181: return getDescriptorForClass(clazz);
182: }
183:
184: public IPersistentObjectStatistics getPersistenceStatistics(
185: Object object) throws IOException {
186: PersistentObjectHandle objectHandle = getHandleForActiveObject(object);
187: if (objectHandle == null) {
188: return null;
189: }
190: return _base.getPersistenceStatistics(objectHandle
191: .getObjectEntryOffset(), this );
192: }
193:
194: public void activate(Object obj, int depth) throws IOException {
195: PersistentObjectHandle handle = getHandleForActiveObject(obj);
196: if (handle == null) {
197: return;
198: }
199: IOTicket ioTicket = _base.getIOTicket(true, false);
200: ioTicket.lock(false);
201: try {
202: TransactionUtils.launchObject(this , handle
203: .getObjectEntryOffset(), obj, depth);
204: } catch (Exception e) {
205: ioTicket.unlock();
206: }
207: }
208:
209: public void deactivate(Object obj, int depth) {
210: if (depth <= 0 || obj == null) {
211: return;
212: }
213: PersistentObjectHandle handle = getHandleForActiveObject(obj);
214: if (handle == null) {
215: return;
216: }
217: ClassDescriptor classDescriptor;
218: try {
219: classDescriptor = getDescriptorForClass(obj.getClass());
220: } catch (IllegalClassTypeException e) {
221: return;
222: }
223: FieldAndIDRecord[] fields = classDescriptor.getFields();
224: for (int i = 0; i < fields.length; i++) {
225: Field field = fields[i]._field;
226: try {
227: if (field.getType().isPrimitive()) {
228: Object defaultValue = PrimitiveJavaTypesUtil
229: .getDefaultWrapperInstance(field.getType()
230: .getName());
231: field.set(obj, defaultValue);
232: } else {
233: Object currentValue = field.get(obj);
234: if (currentValue != null) {
235: deactivate(currentValue, depth - 1);
236: }
237: field.set(obj, null);
238: }
239: } catch (Exception e) {
240: _logger.log(Level.SEVERE, "", e);
241: }
242: }
243: removeObject(obj);
244: }
245:
246: public Object getObjectForOffset(long offset) throws IOException {
247: return getObjectForOffset(offset, JODBConfig
248: .getDefaultActivationDepth());
249: }
250:
251: public Object getObjectForOffset(long offset, int activationDepth)
252: throws IOException {
253: Object result = getObjectFromCache(offset);
254: if (result == null) {
255: result = TransactionUtils.launchObject(this , offset, null,
256: JODBConfig.getDefaultActivationDepth());
257: }
258: return result;
259: }
260:
261: public JODBIndexingRootAgent getIndexingRootAgent()
262: throws IOException {
263: long offset = _base.getFirstObjectOffset();
264: return (JODBIndexingRootAgent) getObjectForOffset(offset, 1);
265: }
266:
267: public Object getObjectFromCache(long offset) {
268: PersistentObjectHandle searchHandle = _persistentObjectHandleSearchCache
269: .get()._persistentObjectHandle;
270: searchHandle.setObjectEntryOffset(offset);
271: PersistentObjectHandle handle = _activeObjects
272: .get(searchHandle);
273: Object result = null;
274: if (handle != null) {
275: result = handle.getObject();
276: }
277: if (result != null) {
278: synchronized (handle) {
279: return result;
280: }
281: }
282: return result;
283: }
284:
285: public IOBase getBase() {
286: return _base;
287: }
288:
289: public Object getActivationSynchObject() {
290: return _activationSynchObject;
291: }
292:
293: public ClassDescriptor getDescriptorForClass(Class clazz)
294: throws IllegalClassTypeException {
295: synchronized (_classDescriptors) {
296: ClassDescriptor result = _classDescriptors.get(clazz);
297: if (result == null) {
298: if (clazz.isArray()) {
299: result = getClassDescriptorForArray(clazz);
300: } else {
301: Vector<FieldAndIDRecord> fieldsResult = new Vector<FieldAndIDRecord>();
302: Vector<String> typeResults = new Vector<String>();
303: extractAllFieldsAndTypes(clazz, fieldsResult,
304: typeResults);
305: FieldAndIDRecord[] fields = new FieldAndIDRecord[fieldsResult
306: .size()];
307: fieldsResult.copyInto(fields);
308: String[] types = new String[typeResults.size()];
309: typeResults.copyInto(types);
310: result = new ClassDescriptor(fields, types);
311: result._class = clazz;
312: result._instanceProvider = JODBObjectInstanceFactory
313: .getProvider(clazz);
314: if (result._instanceProvider == null) {
315: testConstructor(clazz, result);
316: }
317: _classDescriptors.put(clazz, result);
318: }
319: }
320: return result;
321: }
322: }
323:
324: private ClassDescriptor getClassDescriptorForArray(Class clazz) {
325: Class type = clazz.getComponentType();
326: if (type == null && clazz == String.class) {
327: type = clazz;
328: }
329: ClassDescriptor result = new ClassDescriptor(type);
330: result._class = clazz;
331: return result;
332: }
333:
334: private void testConstructor(Class clazz,
335: ClassDescriptor classDescriptor)
336: throws IllegalClassTypeException {
337: if (clazz.isPrimitive()) {
338: return;
339: }
340: try {
341: clazz.newInstance();
342: return;
343: } catch (Throwable e) {
344: }
345: Constructor[] constructors = clazz.getConstructors();
346: for (int i = 0; i < constructors.length; i++) {
347: Constructor constructor = constructors[i];
348: constructor.setAccessible(true);
349: Class[] params = constructor.getParameterTypes();
350: Object[] paramValues = new Object[params.length];
351: try {
352: for (int j = 0; j < params.length; j++) {
353: if (!params[j].isPrimitive()) {
354: continue;
355: }
356: paramValues[j] = PrimitiveJavaTypesUtil
357: .getDefaultWrapperInstance(params[j]
358: .getName());
359: }
360: constructor.newInstance(paramValues);
361: classDescriptor._instantiationConstructor = constructor;
362: classDescriptor._instantiationParams = paramValues;
363: return;
364: } catch (Throwable e) {
365: e.printStackTrace();
366: }
367: }
368: throw new IllegalClassTypeException(clazz);
369: }
370:
371: private void extractAllFieldsAndTypes(Class clazz,
372: Vector<FieldAndIDRecord> fieldsResult, Vector<String> types) {
373: if (clazz.isArray()) {
374: return;
375: }
376: if (clazz.isPrimitive()) {
377: types.add(clazz.getName());
378: return;
379: }
380: if (fieldsResult != null) {
381: Field[] fields = clazz.getDeclaredFields();
382: for (int i = 0; i < fields.length; i++) {
383: if ((fields[i].getModifiers() & (Modifier.FINAL | Modifier.STATIC)) != 0) {
384: continue;
385: }
386: Annotation[] annotations = fields[i].getAnnotations();
387: boolean transientField = false;
388: for (int j = 0; j < annotations.length; j++) {
389: if (annotations[j] instanceof JODBTransient) {
390: transientField = true;
391: break;
392: }
393: }
394: if (transientField) {
395: continue;
396: }
397: fieldsResult.add(new FieldAndIDRecord(fields[i]));
398: fields[i].setAccessible(true);
399: }
400: }
401: if (types != null) {
402: types.add(clazz.getCanonicalName());
403: }
404: Class super Class = clazz.getSuperclass();
405: if (super Class != null && super Class != clazz
406: && !super Class.equals(Object.class)) {
407: extractAllFieldsAndTypes(super Class, fieldsResult, types);
408: }
409: }
410:
411: private static class SearchKeysHolder {
412: ActiveObjectWeakRefHolderSearchKey _activeObjectWeakRefHolderSearchKey = new ActiveObjectWeakRefHolderSearchKey();
413: PersistentObjectHandle _persistentObjectHandle = new PersistentObjectHandle();
414: }
415:
416: private class ActiveObjectReferenceCleaner {
417:
418: private ReferenceQueue<Object> _referenceQueue = new ReferenceQueue<Object>();
419:
420: public void removeStaleReferences() {
421: Object r;
422: while ((r = _referenceQueue.poll()) != null) {
423: clearReference(r);
424: }
425: }
426:
427: // public void removeStaleReferencesBlocking() throws InterruptedException{
428: // Object r;
429: // while ((r = _referenceQueue.remove()) != null) {
430: // if(JODBConfig.DEBUG){
431: // _logger.info("clean reference "+r);
432: // }
433: // clearReference(r);
434: // }
435: // }
436:
437: private void clearReference(Object r) {
438: if (!(r instanceof ActiveObjectWeakRefHolder)) {
439: _logger.warning("unexpected reference in queue " + r);
440: return;
441: }
442: if (JODBConfig.DEBUG && false) {
443: _logger.info("clean reference " + r);
444: }
445: ActiveObjectWeakRefHolder activeObjectWeakRefHolder = (ActiveObjectWeakRefHolder) r;
446: synchronized (_activeObjects) {
447: PersistentObjectHandle handle = _activeObjects
448: .remove(activeObjectWeakRefHolder);
449: if (handle != null) {
450: _activeObjects.remove(handle);
451: }
452: }
453: }
454:
455: // public void startCleanerThread(){
456: // Thread thread = new Thread(this,"JODB Reference Cleaner");
457: // thread.setDaemon(true);
458: // thread.setPriority(Thread.MIN_PRIORITY);
459: // thread.start();
460: // }
461:
462: }
463:
464: private static class ActiveObjectWeakRefHolderSearchKey extends
465: ActiveObjectWeakRefHolder {
466:
467: private Object _searchKey;
468:
469: public ActiveObjectWeakRefHolderSearchKey() {
470: super (null);
471: }
472:
473: @Override
474: protected Object getReferencedObject() {
475: return _searchKey;
476: }
477:
478: public void setSearchKey(Object key) {
479: _identityHashCode = System.identityHashCode(key);
480: _searchKey = key;
481: }
482: }
483:
484: public class ClassDescriptor {
485: private Class _class;
486:
487: private FieldAndIDRecord[] _fields;
488:
489: private String[] _types;
490:
491: private Constructor _instantiationConstructor;
492:
493: private Object[] _instantiationParams;
494:
495: private boolean _array;
496:
497: private boolean _isString;
498:
499: private boolean _primitiveArray;
500:
501: private Class _arrayType;
502:
503: private int _primitiveFieldSizeEstimationIncrement = -1;
504:
505: private int _primitiveFieldsStoreSize = -1;
506:
507: private IObjectInstanceProvider _instanceProvider;
508:
509: private FieldAndIDRecord[] _primitiveFields;
510:
511: private FieldAndIDRecord[] _objectFields;
512:
513: private boolean _needFieldsIdsSet = true;
514:
515: //private int[] _substitutionIDs = null;
516:
517: private ClassDescriptor(FieldAndIDRecord[] fields,
518: String[] types) {
519: super ();
520: _fields = fields;
521: _types = types;
522: }
523:
524: public Class getType() {
525: return _class;
526: }
527:
528: private ClassDescriptor(Class arrayType) {
529: _isString = arrayType == String.class;
530: _arrayType = _isString ? char.class : arrayType;
531: _array = true;
532: _types = _isString ? new String[] { String.class.getName(),
533: arrayType.getName() } : new String[] { arrayType
534: .getName() };
535: _primitiveArray = arrayType.isPrimitive();
536: }
537:
538: private synchronized void checkSubstIdsSet() {
539: if (_needFieldsIdsSet) {
540: if (!_array) {
541: for (int i = 0; i < _fields.length; i++) {
542: _fields[i]._id = _base
543: .getOrSetFieldSubstitutionID(_fields[i]._field);
544: }
545: }
546: _needFieldsIdsSet = false;
547: }
548: }
549:
550: // public synchronized int[] getFieldsSubstitutionIDs(){
551: // if(_substitutionIDs!=null){
552: // return _substitutionIDs;
553: // }
554: // _substitutionIDs = new int[_fields.length];
555: // for (int i = 0; i < _substitutionIDs.length; i++) {
556: // _substitutionIDs[i] = _base.getOrSetFieldSubstitutionID(_fields[i]);
557: // }
558: // return _substitutionIDs;
559: // }
560:
561: public int getPrimitiveFieldsTotal() {
562: if (_primitiveFields == null) {
563: calcFieldsCount();
564: }
565: return _primitiveFields.length;
566: }
567:
568: public int getObjectFieldsTotal() {
569: if (_objectFields == null) {
570: calcFieldsCount();
571: }
572: return _objectFields.length;
573: }
574:
575: private void calcFieldsCount() {
576: checkSubstIdsSet();
577: Vector<FieldAndIDRecord> primitiveFields = new Vector<FieldAndIDRecord>();
578: Vector<FieldAndIDRecord> objectFields = new Vector<FieldAndIDRecord>();
579: for (int i = 0; i < _fields.length; i++) {
580: if (_fields[i]._field.getType().isPrimitive()) {
581: primitiveFields.add(_fields[i]);
582: } else {
583: objectFields.add(_fields[i]);
584: }
585: }
586: _primitiveFields = primitiveFields
587: .toArray(new FieldAndIDRecord[primitiveFields
588: .size()]);
589: _objectFields = objectFields
590: .toArray(new FieldAndIDRecord[objectFields.size()]);
591: }
592:
593: public boolean isPrimitive() {
594: return _class.isPrimitive();
595: }
596:
597: public boolean isArray() {
598: return _array;
599: }
600:
601: public Class getArrayType() {
602: return _arrayType;
603: }
604:
605: public FieldAndIDRecord[] getFields() {
606: checkSubstIdsSet();
607: return _fields;
608: }
609:
610: public String[] getTypes() {
611: return _types;
612: }
613:
614: public boolean isPrimitiveArray() {
615: return _primitiveArray;
616: }
617:
618: public Object newInstance() {
619: if (_instanceProvider != null) {
620: return _instanceProvider.newInstance();
621: }
622: try {
623: if (_instantiationConstructor == null) {
624: return _class.newInstance();
625: } else {
626: return _instantiationConstructor
627: .newInstance(_instantiationParams);
628: }
629: } catch (Exception e) {
630: e.printStackTrace();
631: return null;
632: }
633: }
634:
635: public synchronized int getPrimitiveFieldsStorageEstimate(
636: int auxiliaryDataPerFieldSize) throws JodbIOException {
637: if (auxiliaryDataPerFieldSize != _primitiveFieldSizeEstimationIncrement
638: || _primitiveFieldsStoreSize == -1) {
639: _primitiveFieldsStoreSize = 0;
640: for (int i = 0; i < _fields.length; i++) {
641: Field field = _fields[i]._field;
642: if (field.getType().isPrimitive()) {
643: _primitiveFieldsStoreSize += auxiliaryDataPerFieldSize
644: + PrimitiveJavaTypesUtil
645: .getDataOutputWriteLen(field
646: .getType());
647: }
648: }
649: _primitiveFieldSizeEstimationIncrement = auxiliaryDataPerFieldSize;
650: }
651: return _primitiveFieldsStoreSize;
652: }
653:
654: public Field getFieldForName(String name) {
655: for (int i = 0; i < _fields.length; i++) {
656: if (_fields[i]._field.getName().equals(name)) {
657: return _fields[i]._field;
658: }
659: }
660: return null;
661: }
662:
663: public int getFieldIDForName(String name) {
664: for (int i = 0; i < _fields.length; i++) {
665: if (_fields[i]._field.getName().equals(name)) {
666: checkSubstIdsSet();
667: return _fields[i]._id;
668: }
669: }
670: return -1;
671: }
672:
673: public int getFieldIndexForID(int id) {
674: checkSubstIdsSet();
675: for (int i = 0; i < _fields.length; i++) {
676: if (_fields[i]._id == id) {
677: return i;
678: }
679: }
680: return -1;
681: }
682:
683: public Field getFieldForID(int id, BitSet indexes) {
684: checkSubstIdsSet();
685: int index = getFieldIndexForID(id);
686: if (index != -1) {
687: if (indexes != null) {
688: indexes.set(index);
689: }
690: return _fields[index]._field;
691: }
692: return null;
693: }
694: }
695:
696: public static class FieldAndIDRecord implements Comparable {
697:
698: public Field _field;
699: public int _id = -1;
700:
701: public int compareTo(Object o) {
702: FieldAndIDRecord o1 = (FieldAndIDRecord) o;
703: return _id < o1._id ? -1 : (_id == o1._id ? 0 : 1);
704: }
705:
706: /**
707: * @param field
708: */
709: public FieldAndIDRecord(Field field) {
710: super ();
711: _field = field;
712: }
713: }
714:
715: private static class StaleReferencesCleanerThread extends Thread {
716: public StaleReferencesCleanerThread() {
717: setDaemon(true);
718: setPriority(MIN_PRIORITY);
719: setName("JODB Reference Cleaner");
720: }
721:
722: @Override
723: public void run() {
724: while (true) {
725: synchronized (_cleanersCache) {
726: if (_cleanersCache.size() == 0) {
727: _staleReferencesCleanerThread = null;
728: break;
729: }
730: }
731: try {
732: Iterator<ActiveObjectReferenceCleaner> iterator = _cleanersCache
733: .keySet().iterator();
734: while (iterator.hasNext()) {
735: ActiveObjectReferenceCleaner activeObjectReferenceCleaner;
736: try {
737: activeObjectReferenceCleaner = iterator
738: .next();
739: } catch (ConcurrentModificationException e) {
740: //just ignore this exception, will clean on next iteration
741: break;
742: }
743: activeObjectReferenceCleaner
744: .removeStaleReferences();
745: }
746: sleep(JODBConfig.getReferenceCleanerInterval());
747: } catch (Throwable e) {
748: e.printStackTrace();
749: }
750: }
751: }
752: }
753: }
|