001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tc.objectserver.persistence.sleepycat;
006:
007: import com.sleepycat.je.Cursor;
008: import com.sleepycat.je.CursorConfig;
009: import com.sleepycat.je.Database;
010: import com.sleepycat.je.DatabaseEntry;
011: import com.sleepycat.je.DatabaseException;
012: import com.sleepycat.je.LockMode;
013: import com.sleepycat.je.OperationStatus;
014: import com.tc.object.ObjectID;
015: import com.tc.objectserver.persistence.api.PersistenceTransaction;
016: import com.tc.util.Conversion;
017:
018: import gnu.trove.THashMap;
019:
020: import java.io.IOException;
021: import java.util.Collection;
022: import java.util.Iterator;
023: import java.util.Map;
024: import java.util.Set;
025:
026: public class SleepycatPersistableMap implements Map {
027:
028: private static final Object REMOVED = new Object();
029:
030: /*
031: * This map contains the mappings already in the database
032: */
033: private final Map map = new THashMap(0);
034:
035: /*
036: * This map contains the newly added mappings that are not in the database yet
037: */
038: private final Map delta = new THashMap(0);
039:
040: private final long id;
041: private int removeCount = 0;
042: private boolean clear = false;
043:
044: public SleepycatPersistableMap(ObjectID id) {
045: this .id = id.toLong();
046: }
047:
048: public int size() {
049: return map.size() + delta.size() - removeCount;
050: }
051:
052: public boolean isEmpty() {
053: return size() == 0;
054: }
055:
056: public boolean containsKey(Object key) {
057: Object value;
058: // NOTE:: map cant have mapping to null, it is always mapped to ObjectID.NULL_ID
059: return delta.containsKey(key)
060: || ((value = map.get(key)) != null && value != REMOVED);
061: }
062:
063: public boolean containsValue(Object value) {
064: return delta.containsValue(value) || map.containsValue(value);
065: }
066:
067: public Object get(Object key) {
068: Object value = delta.get(key);
069: if (value == null) {
070: value = map.get(key);
071: if (value == REMOVED)
072: value = null;
073: }
074: return value;
075: }
076:
077: public Object put(Object key, Object value) {
078: Object returnVal = delta.put(key, value);
079: if (returnVal != null) {
080: return returnVal;
081: }
082: if (map.containsKey(key)) {
083: returnVal = map.put(key, REMOVED);
084: if (returnVal == REMOVED) {
085: return null;
086: }
087: removeCount++;
088: }
089: return returnVal;
090: }
091:
092: public Object remove(Object key) {
093: Object returnVal = delta.remove(key);
094: if (returnVal != null) {
095: return returnVal;
096: }
097: if (map.containsKey(key)) {
098: returnVal = map.put(key, REMOVED);
099: if (returnVal == REMOVED) {
100: return null;
101: }
102: removeCount++;
103: }
104: return returnVal;
105: }
106:
107: public void putAll(Map m) {
108: for (Iterator i = m.entrySet().iterator(); i.hasNext();) {
109: Map.Entry entry = (Map.Entry) i.next();
110: put(entry.getKey(), entry.getValue());
111: }
112: }
113:
114: public void clear() {
115: clear = true;
116: // XXX:: May be saving the keys to remove will be faster as sleepycat has to read/fault all records on clear. But
117: // then it is memory to performance trade off.
118: delta.clear();
119: map.clear();
120: removeCount = 0;
121: }
122:
123: public Set keySet() {
124: return new KeyView();
125: }
126:
127: public Collection values() {
128: return new ValuesView();
129: }
130:
131: public Set entrySet() {
132: return new EntryView();
133: }
134:
135: public void commit(SleepycatCollectionsPersistor persistor,
136: PersistenceTransaction tx, Database db) throws IOException,
137: DatabaseException {
138: // long t1 = System.currentTimeMillis();
139: // StringBuffer sb = new StringBuffer("Time to commit : ");
140:
141: // First :: clear the map if necessary
142: if (clear) {
143: basicClear(persistor, tx, db);
144: clear = false;
145: // sb.append(" clear = ").append((System.currentTimeMillis() - t1)).append(" ms : ");
146: // t1 = System.currentTimeMillis();
147: }
148:
149: // Second :: put new or changed objects
150: if (delta.size() > 0) {
151: basicPut(persistor, tx, db);
152: // sb.append(" put(").append(delta.size()).append(") = ").append((System.currentTimeMillis() - t1)).append(" ms :
153: // ");
154: // t1 = System.currentTimeMillis();
155: delta.clear();
156: }
157:
158: // Third :: remove old mappings :: This is slightly inefficient for huge maps. Keeping track of removed records is
159: // again a trade off between memory and performance
160: if (removeCount > 0) {
161: basicRemove(persistor, tx, db);
162: // sb.append(" remove(").append(removeCount).append(") = ").append((System.currentTimeMillis() - t1))
163: // .append(" ms : ");
164: removeCount = 0;
165: }
166: // flakyLogger(sb.toString(), t1);
167: }
168:
169: private void basicRemove(SleepycatCollectionsPersistor persistor,
170: PersistenceTransaction tx, Database db) throws IOException,
171: DatabaseException {
172: for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
173: Map.Entry e = (Entry) i.next();
174: Object k = e.getKey();
175: Object v = e.getValue();
176: if (v == REMOVED) {
177: DatabaseEntry key = new DatabaseEntry();
178: key.setData(persistor.serialize(id, k));
179: OperationStatus status = db.delete(persistor.pt2nt(tx),
180: key);
181: if (!(OperationStatus.NOTFOUND.equals(status) || OperationStatus.SUCCESS
182: .equals(status))) {
183: // make the formatter happy
184: throw new DBException(
185: "Unable to remove Map Entry for object id: "
186: + id + ", status: " + status
187: + ", key: " + key);
188: }
189: i.remove();
190: }
191: }
192:
193: }
194:
195: private void basicPut(SleepycatCollectionsPersistor persistor,
196: PersistenceTransaction tx, Database db) throws IOException,
197: DatabaseException {
198: for (Iterator i = delta.entrySet().iterator(); i.hasNext();) {
199: Map.Entry e = (Entry) i.next();
200: Object k = e.getKey();
201: Object v = e.getValue();
202: DatabaseEntry key = new DatabaseEntry();
203: key.setData(persistor.serialize(id, k));
204: DatabaseEntry value = new DatabaseEntry();
205: value.setData(persistor.serialize(v));
206: OperationStatus status = db.put(persistor.pt2nt(tx), key,
207: value);
208: if (!OperationStatus.SUCCESS.equals(status)) {
209: throw new DBException("Unable to update Map table : "
210: + id + " status : " + status);
211: }
212: map.put(k, v);
213: }
214: }
215:
216: private void basicClear(SleepycatCollectionsPersistor persistor,
217: PersistenceTransaction tx, Database db)
218: throws DatabaseException {
219: // XXX::Sleepycat has the most inefficent way to delete objects. Another way would be to delete all records
220: // explicitly.
221: // XXX:: Since we read in one direction and since we have to read the first record of the next map to break out, we
222: // need READ_COMMITTED to avoid deadlocks between commit thread and GC thread.
223: Cursor c = db.openCursor(persistor.pt2nt(tx),
224: CursorConfig.READ_COMMITTED);
225: byte idb[] = Conversion.long2Bytes(id);
226: DatabaseEntry key = new DatabaseEntry();
227: key.setData(idb);
228: DatabaseEntry value = new DatabaseEntry();
229: value.setPartial(0, 0, true);
230: if (c.getSearchKeyRange(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
231: do {
232: if (partialMatch(idb, key.getData())) {
233: c.delete();
234: } else {
235: break;
236: }
237: } while (c.getNext(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS);
238: }
239: c.close();
240: }
241:
242: // long lastlog;
243: // private void flakyLogger(String message, long start, long end) {
244: // if (lastlog + 1000 < end) {
245: // lastlog = end;
246: // System.err.println(this + " : " + message + " " + (end - start) + " ms");
247: // }
248: // }
249: //
250: // private void flakyLogger(String message, long recent) {
251: // if (lastlog + 1000 < recent) {
252: // System.err.println(this + " : " + message);
253: // }
254: // }
255:
256: private boolean partialMatch(byte[] idbytes, byte[] key) {
257: if (key.length < idbytes.length)
258: return false;
259: for (int i = 0; i < idbytes.length; i++) {
260: if (idbytes[i] != key[i])
261: return false;
262: }
263: return true;
264: }
265:
266: public boolean equals(Object other) {
267: if (!(other instanceof Map)) {
268: return false;
269: }
270: Map that = (Map) other;
271: if (that.size() != this .size()) {
272: return false;
273: }
274: return entrySet().containsAll(that.entrySet());
275: }
276:
277: public int hashCode() {
278: int h = 0;
279: for (Iterator i = entrySet().iterator(); i.hasNext();) {
280: h += i.next().hashCode();
281: }
282: return h;
283: }
284:
285: public String toString() {
286: return "SleepycatPersistableMap(" + id + ")={ Map.size() = "
287: + map.size() + ", delta.size() = " + delta.size()
288: + ", removeCount = " + removeCount + " }";
289: }
290:
291: public void load(SleepycatCollectionsPersistor persistor,
292: PersistenceTransaction tx, Database db) throws IOException,
293: ClassNotFoundException, DatabaseException {
294: // XXX:: Since we read in one direction and since we have to read the first record of the next map to break out, we
295: // need READ_COMMITTED to avoid deadlocks between commit thread and GC thread.
296: Cursor c = db.openCursor(persistor.pt2nt(tx),
297: CursorConfig.READ_COMMITTED);
298: byte idb[] = Conversion.long2Bytes(id);
299: DatabaseEntry key = new DatabaseEntry();
300: key.setData(idb);
301: DatabaseEntry value = new DatabaseEntry();
302: if (c.getSearchKeyRange(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
303: do {
304: if (false)
305: System.err.println("MapDB " + toString(key) + " , "
306: + toString(value));
307: if (partialMatch(idb, key.getData())) {
308: Object mkey = persistor.deserialize(idb.length, key
309: .getData());
310: Object mvalue = persistor.deserialize(value
311: .getData());
312: map.put(mkey, mvalue);
313: // System.err.println("map.put() = " + mkey + " , " + mvalue);
314: } else {
315: break;
316: }
317: } while (c.getNext(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS);
318: }
319: c.close();
320: }
321:
322: private String toString(DatabaseEntry entry) {
323: StringBuffer sb = new StringBuffer();
324: sb.append("<DatabaseEntry ");
325: byte b[] = entry.getData();
326: if (b == null) {
327: sb.append(" NULL Data>");
328: } else if (b.length == 0) {
329: sb.append(" ZERO bytes>");
330: } else {
331: for (int i = 0; i < b.length; i++) {
332: sb.append(b[i]).append(' ');
333: }
334: sb.append(">");
335: }
336: return sb.toString();
337: }
338:
339: private abstract class BaseView implements Set {
340:
341: public int size() {
342: return SleepycatPersistableMap.this .size();
343: }
344:
345: public boolean isEmpty() {
346: return SleepycatPersistableMap.this .isEmpty();
347: }
348:
349: public Object[] toArray() {
350: Object[] result = new Object[size()];
351: Iterator e = iterator();
352: for (int i = 0; e.hasNext(); i++)
353: result[i] = e.next();
354: return result;
355: }
356:
357: public Object[] toArray(Object[] a) {
358: int size = size();
359: if (a.length < size)
360: a = (Object[]) java.lang.reflect.Array.newInstance(a
361: .getClass().getComponentType(), size);
362:
363: Iterator it = iterator();
364: for (int i = 0; i < size; i++) {
365: a[i] = it.next();
366: }
367:
368: if (a.length > size) {
369: a[size] = null;
370: }
371:
372: return a;
373: }
374:
375: public boolean add(Object arg0) {
376: throw new UnsupportedOperationException();
377: }
378:
379: public boolean remove(Object o) {
380: throw new UnsupportedOperationException();
381: }
382:
383: public boolean containsAll(Collection collection) {
384: for (Iterator i = collection.iterator(); i.hasNext();) {
385: if (!contains(i.next())) {
386: return false;
387: }
388: }
389: return true;
390: }
391:
392: public boolean addAll(Collection arg0) {
393: throw new UnsupportedOperationException();
394: }
395:
396: public boolean retainAll(Collection arg0) {
397: throw new UnsupportedOperationException();
398: }
399:
400: public boolean removeAll(Collection arg0) {
401: throw new UnsupportedOperationException();
402: }
403:
404: public void clear() {
405: throw new UnsupportedOperationException();
406: }
407: }
408:
409: private class KeyView extends BaseView {
410:
411: public boolean contains(Object key) {
412: return SleepycatPersistableMap.this .containsKey(key);
413: }
414:
415: public Iterator iterator() {
416: return new KeyIterator();
417: }
418: }
419:
420: private class ValuesView extends BaseView {
421:
422: public boolean contains(Object value) {
423: return SleepycatPersistableMap.this .containsValue(value);
424: }
425:
426: public Iterator iterator() {
427: return new ValuesIterator();
428: }
429: }
430:
431: private class EntryView extends BaseView {
432:
433: public boolean contains(Object o) {
434: Map.Entry entry = (Entry) o;
435: Object val = get(entry.getKey());
436: Object entryValue = entry.getValue();
437: return entryValue == val
438: || (null != val && val.equals(entryValue));
439: }
440:
441: public Iterator iterator() {
442: return new EntryIterator();
443: }
444: }
445:
446: private abstract class BaseIterator implements Iterator {
447:
448: boolean isDelta = false;
449: Iterator current = map.entrySet().iterator();
450: Map.Entry next;
451:
452: BaseIterator() {
453: moveToNext();
454: }
455:
456: private void moveToNext() {
457: while (current.hasNext()) {
458: next = (Entry) current.next();
459: if (next.getValue() != REMOVED) {
460: return;
461: }
462: }
463: if (isDelta) {
464: next = null;
465: } else {
466: current = delta.entrySet().iterator();
467: isDelta = true;
468: moveToNext();
469: }
470: }
471:
472: public boolean hasNext() {
473: return (next != null);
474: }
475:
476: public Object next() {
477: Object key = getNext();
478: moveToNext();
479: return key;
480: }
481:
482: public void remove() {
483: throw new UnsupportedOperationException();
484: }
485:
486: protected abstract Object getNext();
487:
488: }
489:
490: private class KeyIterator extends BaseIterator {
491: protected Object getNext() {
492: return next.getKey();
493: }
494: }
495:
496: private class ValuesIterator extends BaseIterator {
497: protected Object getNext() {
498: return next.getValue();
499: }
500: }
501:
502: private class EntryIterator extends BaseIterator {
503: protected Object getNext() {
504: return next;
505: }
506: }
507: }
|