001: /**
002: * Copyright (C) 2006 NetMind Consulting Bt.
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 3 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */package hu.netmind.persistence;
018:
019: import java.util.*;
020: import java.lang.ref.WeakReference;
021: import org.apache.log4j.Logger;
022:
023: /**
024: * This class tracks objects' ids and values for different transactions.
025: * Basically this tracker can answer questions about the state of a
026: * previously registered object.<br>
027: * @author Brautigam Robert
028: * @version Revision: $Revision$
029: */
030: public class ObjectTracker implements TransactionListener,
031: WeakMapListener {
032: private static Logger logger = Logger
033: .getLogger(ObjectTracker.class);
034: private static ProfileLogger profLogger = ProfileLogger.getLogger();
035:
036: private StoreContext context;
037: private Random random;
038: private WeakMap objectData;
039: private HashMap sharedData;
040:
041: ObjectTracker(StoreContext context) {
042: this .context = context;
043: random = new Random();
044: sharedData = new HashMap();
045: objectData = new WeakMap();
046: objectData.setListener(this );
047: ensureTrackTableExists();
048: context.getTransactionTracker().addListener(this );
049: }
050:
051: /**
052: * Get the data structure for an object.
053: */
054: public ObjectData getObjectData(Object obj) {
055: // Only the objectData needs to be synchronized, because only one
056: // transaction can write these objects at a given time. So many
057: // readers may be using the objectdata at a time, but only
058: // one can write it.
059: synchronized (objectData) {
060: return (ObjectData) objectData.get(obj);
061: }
062: }
063:
064: /**
065: * Return whether a given attribute of a given object changed.
066: */
067: public boolean hasChanged(ClassInfo info, Object obj,
068: String attributeName, Map dbAttributes) {
069: // If object does no exists, then all attributes are changed. If
070: // object exists, then the dbAttributes contains the current
071: // attributes, as found in the database.
072: if (!exists(obj))
073: return true;
074: // Get values
075: Object value = info.getAttributeValue(obj, attributeName);
076: Object oldValue = getAttributeValue(obj, attributeName);
077: Object dbValue = dbAttributes.get(attributeName);
078: // Check nulls
079: if ((dbValue == null) && (value == null)) {
080: logger.debug("both values were null, not changed.");
081: return false;
082: }
083: if (((dbValue != null) && (value == null))
084: || ((dbValue == null) && (value != null))) {
085: logger
086: .debug("one value was null, and the other non-null, so changed.");
087: return true;
088: }
089: // Check handled types
090: Class type = info.getAttributeType(attributeName);
091: if (context.getTypeHandlerTracker().isHandled(type)) {
092: TypeHandler handler = context.getTypeHandlerTracker()
093: .getHandler(type);
094: boolean result = handler.hasChanged(info, obj,
095: attributeName, dbAttributes);
096: logger.debug("value was a tracked object, result will be: "
097: + result);
098: return result;
099: }
100: // Check byte array
101: if (dbValue instanceof byte[]) {
102: boolean result = !Arrays.equals((byte[]) dbValue,
103: (byte[]) value);
104: logger.debug("values were byte arrays, changed: " + result);
105: return result;
106: }
107: // Check custom objects, in which case the dbValue is a persistence_id,
108: // and the 'value' is an object.
109: if (context.getClassTracker().getType(value.getClass()) == ClassTracker.TYPE_OBJECT) {
110: boolean result = !new Long(getIdentifier(value))
111: .equals(dbValue);
112: logger
113: .debug("value was a custom object, it's persistence id changed: "
114: + result);
115: return result;
116:
117: }
118: // Fallback to equals()
119: boolean result = !value.equals(dbValue);
120: logger.debug("comparing values with equals(), changed: "
121: + result + " (" + value.getClass().getName()
122: + " vs. db " + dbValue.getClass().getName() + ")");
123: return result;
124: }
125:
126: /**
127: * Determines, whether two objects are of the same database instance.
128: * Two objects are the same, if their ids equal. Note however, that
129: * they do not need to contain the same values, or be of same version!
130: */
131: public boolean equals(Object o1, Object o2) {
132: long id1 = getIdentifier(o1);
133: long id2 = getIdentifier(o2);
134: return (o1 == o2) || ((id1 != 0) && (id2 != 0) && (id1 == id2));
135: }
136:
137: /**
138: * Return the attribute's last known value. The last known value is
139: * determined as follows: If the value changed during current transaction,
140: * the new value is returned, or else the last known saved value is
141: * returned. That means, if a transaction currently changes this
142: * attribute, another transaction will see the old value until the
143: * first transaction commits.
144: */
145: public Object getAttributeValue(Object obj, String attributeName) {
146: ObjectData data = getObjectData(obj);
147: // Following block does not need synchronizing because only one thread
148: // can write OR read the data.
149: //
150: // Get attribute from transactional attributes first (Note: only
151: // one transaction can modify the object).
152: Map txAttributes = (Map) data.getTransactionAttributes();
153: if (txAttributes != null) {
154: Object value = txAttributes.get(attributeName);
155: if (value != null)
156: return value;
157: }
158: // No transactional attributes, so return global ones
159: return data.getAttributes().get(attributeName);
160: }
161:
162: /**
163: * Update a single attribute. This method simply calls <code>updateObject()</code>.
164: */
165: public void updateAttribute(Object obj, String identifier,
166: Object value) {
167: HashMap changes = new HashMap();
168: changes.put(identifier, value);
169: updateObject(obj, changes);
170: }
171:
172: /**
173: * Update the attribute's known values with those given. Note, that
174: * this method will first make the changes only in current transaction,
175: * and only publish them when the current transaction commits.
176: */
177: public void updateObject(Object obj, Map changes) {
178: // Warning: do not print changes here, because toString() might
179: // screw up lazy list loading.
180: logger.debug("updating object.");
181: // Update object's tracked attributes
182: ObjectData data = getObjectData(obj);
183: // Does not need synchronizing
184: data.getTransactionAttributes().putAll(changes);
185: }
186:
187: /**
188: * Determine whether an object exists.
189: * @return True, if object exists in store. This means that searches
190: * will return the object. If this is false, the object cannot be
191: * found yet.
192: */
193: public boolean exists(Object obj) {
194: ObjectData data = getObjectData(obj);
195: if (data == null)
196: return false;
197: return data.currentlyExists();
198: }
199:
200: /**
201: * Mark object as existent. If this is inside a transaction,
202: * the existence will only be permanent, if object is saved inside
203: * the transaction.
204: */
205: public void makeExist(Object obj) {
206: ObjectData data = getObjectData(obj);
207: if (data != null)
208: data.setCurrentlyExists(true);
209: }
210:
211: /**
212: * Mark an object as non-existent. This happens, if the object gets
213: * deleted, but there are some object instances that are used.
214: */
215: public void makeUnexist(Object obj) {
216: ObjectData data = getObjectData(obj);
217: if (data != null) {
218: data.setExists(false);
219: data.setCurrentlyExists(false);
220: }
221: }
222:
223: /**
224: * Register an object into the tracker. This means, the object will
225: * get an id, and associated tracking data structure will be allocated.
226: * @param obj The object to register.
227: * @param id The id of object. If this is given (not 0), the object is
228: * assumed to exist.
229: */
230: public void registerObject(Object obj, long id) {
231: ObjectData data = getObjectData(obj);
232: if (data != null)
233: return;
234: // Create object data
235: data = new ObjectData();
236: if (id == 0) {
237: data.setId(nextId(obj));
238: data.setExists(false);
239: data.setCurrentlyExists(false);
240: } else {
241: data.setId(id);
242: data.setExists(true);
243: data.setCurrentlyExists(true);
244: }
245: PersistenceMetaData metaData = new PersistenceMetaData();
246: data.setMetaData(metaData);
247: metaData.setPersistenceId(new Long(data.getId()));
248: metaData.setObjectClass(obj.getClass());
249: synchronized (objectData) {
250: objectData.put(obj, data, new Long(data.getId()));
251: // Create shared state, if not present
252: SharedData sData = data.getSharedData();
253: if (sData == null)
254: sData = new SharedData();
255: else
256: sData.setRefCounter(sData.getRefCounter() + 1);
257: sharedData.put(new Long(data.getId()), sData);
258: }
259: // Profile
260: profLogger.profile("sharedmap", "Shared data size is: "
261: + sharedData.size());
262: }
263:
264: /**
265: * Called when an object leaves the weak map. Note: This is thread-safe
266: * because it is called from WeakMap, which is handled safely in this
267: * class.
268: */
269: public void notifyValueLeave(Object id) {
270: // Object is leaving weakmap, so remove shared state too if nescessary.
271: SharedData data = (SharedData) sharedData.get(id);
272: // Decrease count, remove if no object reference it
273: data.setRefCounter(data.getRefCounter() - 1);
274: if (data.getRefCounter() <= 0)
275: sharedData.remove(id);
276: }
277:
278: /**
279: * Get the identifier for a given object.
280: * @param obj The object to identify.
281: * @return The identifier, or 0 if the object is not registered.
282: */
283: public long getIdentifier(Object obj) {
284: ObjectData data = getObjectData(obj);
285: if (data == null)
286: return 0;
287: return data.getId();
288: }
289:
290: /**
291: * Get the meta data associated with the object.
292: */
293: public PersistenceMetaData getMetaData(Object obj) {
294: // Register to be sure
295: registerObject(obj, 0);
296: // Get the metadata
297: return getObjectData(obj).getMetaData();
298: }
299:
300: /**
301: * Ensure that the given table exists.
302: */
303: private void ensureTrackTableExists() {
304: Transaction transaction = context.getTransactionTracker()
305: .getTransaction(TransactionTracker.TX_NEW);
306: transaction.begin();
307: try {
308: // Create table schema
309: Map tableSchema = new HashMap();
310: tableSchema.put("persistence_id", Long.class);
311: tableSchema.put("object_table", String.class);
312: Vector keySchema = new Vector();
313: keySchema.add("persistence_id");
314: // Ensure it exists
315: context.getDatabase().ensureTable(transaction,
316: "persistence_object_ids", tableSchema, keySchema,
317: true);
318: } catch (StoreException e) {
319: transaction.markRollbackOnly();
320: throw e;
321: } catch (Throwable e) {
322: transaction.markRollbackOnly();
323: throw new StoreException("unexpected exception.", e);
324: } finally {
325: transaction.commit();
326: }
327: }
328:
329: /**
330: * Generates the next unique id, also it inserts record to
331: * database, that the given id has been used.
332: * <i>Implementation note:</i>This methods tries to find a unique
333: * id until it finds one, possibly looking forever. Method could
334: * contain a lookup limit, but there is no resonable limit. A chance
335: * of catching a used id in a 64 bit space with a billion records
336: * is still one-to-billion.<br>
337: * <i>Note:</i>This method is thread-safe, it uses the database
338: * for determining the id is correct.
339: * @param obj The object to get the id for.
340: */
341: private long nextId(Object obj) {
342: long result = 0;
343: while (result == 0) {
344: Transaction transaction = context.getTransactionTracker()
345: .getTransaction(TransactionTracker.TX_NEW);
346: transaction.begin();
347: try {
348: ClassInfo classInfo = context.getClassTracker()
349: .getClassInfo(obj.getClass(), obj);
350: // Generate random number, and record
351: result = random.nextLong() + 1;
352: result = Math.abs(result);
353: Map entry = new HashMap();
354: entry.put("persistence_id", new Long(result));
355: entry.put("object_table", classInfo
356: .getTableName(classInfo.getSourceEntry()));
357: // Insert record
358: context.getDatabase().insert(transaction,
359: "persistence_object_ids", entry);
360: } catch (AlreadyExistsException e) {
361: // If already exists
362: result = 0;
363: transaction.markRollbackOnly();
364: } catch (StoreException e) {
365: transaction.markRollbackOnly();
366: throw e;
367: } catch (Throwable e) {
368: transaction.markRollbackOnly();
369: throw new StoreException("unexpected error.", e);
370: } finally {
371: transaction.commit();
372: }
373: }
374: return result;
375: }
376:
377: /**
378: * Called, when transaction tracker commits a transaction successfully.
379: * This tracker uses it to set object to exists and not exists state.
380: * @param transaction The transaction which committed.
381: */
382: public void transactionCommited(Transaction transaction) {
383: logger.debug("transaction commited");
384: // All saved objects will be set to 'exists' state.
385: List savedObjects = transaction.getSavedObjects();
386: for (int i = 0; i < savedObjects.size(); i++) {
387: // If object was saved, it exists, so set exists flag
388: Object obj = ((ObjectWrapper) savedObjects.get(i))
389: .getObject();
390: ObjectData data = (ObjectData) getObjectData(obj);
391: if (data == null)
392: logger
393: .warn("something may be wrong, an object commited which was not tracked, object was: "
394: + obj);
395: else {
396: // Object now exists
397: data.setExists(true);
398: data.setCurrentlyExists(true);
399: // Now check, whether object had transactional attributes.
400: // If it had, copy them to global ones
401: // Note: following block does not need synchronizing
402: Map txAttributes = (Map) data
403: .getTransactionAttributes();
404: if (txAttributes != null) {
405: data.setTransactionAttributes(new HashMap());
406: data.getAttributes().putAll(txAttributes);
407: }
408: }
409: }
410: // All removed objects will be set to 'not exists' state.
411: List removedObjects = transaction.getRemovedObjects();
412: for (int i = 0; i < removedObjects.size(); i++) {
413: Object obj = ((ObjectWrapper) removedObjects.get(i))
414: .getObject();
415: ObjectData data = (ObjectData) getObjectData(obj);
416: if (data == null)
417: logger
418: .warn("something may be wrong, an object commited (for remove) which was not tracked, object was: "
419: + obj);
420: else {
421: // Clear all data of object. Set exists to 'false'.
422: data.setExists(false);
423: data.setCurrentlyExists(false);
424: data.setAttributes(new HashMap());
425: data.setTransactionAttributes(new HashMap());
426: }
427: }
428: }
429:
430: /**
431: * Convert a value of a type to a given target type.
432: */
433: public static Object getTypeValue(Object value, Class attrClass) {
434: Class valueClass = null;
435: // Check if value is null, note that primitive types will
436: // have a sane default if confronted with null
437: if (value == null) {
438: if (attrClass.equals(byte.class))
439: return new Byte((byte) 0);
440: else if (attrClass.equals(double.class))
441: return new Double(0);
442: else if (attrClass.equals(float.class))
443: return new Float(0);
444: else if (attrClass.equals(int.class))
445: return new Integer(0);
446: else if (attrClass.equals(long.class))
447: return new Long(0);
448: else if (attrClass.equals(short.class))
449: return new Short((short) 0);
450: else if (attrClass.equals(boolean.class))
451: return new Boolean(false);
452: else if (attrClass.equals(char.class))
453: return new Character('\u0000');
454: return null;
455: }
456: valueClass = value.getClass();
457: // Handle primitive types with exceptional circumstances
458: if ((attrClass.equals(Character.class))
459: || (attrClass.equals(char.class))
460: && (valueClass.equals(String.class))) {
461: if (((String) value).length() > 0)
462: return new Character(((String) value).charAt(0));
463: }
464: // Byte array
465: else if (attrClass.equals(byte[].class))
466: return value;
467: // Boolean
468: else if ((attrClass.equals(Boolean.class) || attrClass
469: .equals(boolean.class))
470: && ((valueClass.equals(Integer.class)) || (valueClass
471: .equals(Long.class)))) {
472: return new Boolean(((Number) value).intValue() > 0);
473: }
474: // Easy primitive classes
475: else if (valueClass.equals(String.class)
476: || valueClass.equals(Date.class)
477: || valueClass.equals(Boolean.class))
478: return value;
479: // Container classes
480: else if ((value instanceof Map) || (value instanceof List))
481: return value;
482: // Not easy number classes
483: else if (value instanceof Number) {
484: Number number = (Number) value;
485: if (attrClass.equals(Byte.class)
486: || attrClass.equals(byte.class))
487: return new Byte(number.byteValue());
488: if (attrClass.equals(Double.class)
489: || attrClass.equals(double.class))
490: return new Double(number.doubleValue());
491: if (attrClass.equals(Float.class)
492: || attrClass.equals(float.class))
493: return new Float(number.floatValue());
494: if (attrClass.equals(Integer.class)
495: || attrClass.equals(int.class))
496: return new Integer(number.intValue());
497: if (attrClass.equals(Long.class)
498: || attrClass.equals(long.class))
499: return new Long(number.longValue());
500: if (attrClass.equals(Short.class)
501: || attrClass.equals(short.class))
502: return new Short(number.shortValue());
503: }
504: // All other objects
505: return value;
506: }
507:
508: /**
509: * Called, when transaction tracker rolls back a transaction successfully.
510: * @param transaction The transaction which rolled back.
511: */
512: public void transactionRolledback(Transaction transaction) {
513: logger.debug("transaction rolled back");
514: // We must discard all transaction references
515: List savedObjects = transaction.getSavedObjects();
516: for (int i = 0; i < savedObjects.size(); i++) {
517: Object obj = ((ObjectWrapper) savedObjects.get(i))
518: .getObject();
519: ObjectData data = (ObjectData) getObjectData(obj);
520: if (data == null)
521: logger
522: .warn("something may be wrong, an object rolled back which was not tracked, object was: "
523: + obj);
524: else {
525: // Set current exists flag to stable exists flag
526: data.setCurrentlyExists(data.exists());
527: // Does not need synchronizing
528: data.setTransactionAttributes(new HashMap());
529: }
530: }
531: // All removed objects
532: List removedObjects = transaction.getRemovedObjects();
533: for (int i = 0; i < removedObjects.size(); i++) {
534: Object obj = ((ObjectWrapper) removedObjects.get(i))
535: .getObject();
536: ObjectData data = (ObjectData) getObjectData(obj);
537: if (data == null)
538: logger
539: .warn("something may be wrong, an object rolled back (for remove) which was not tracked, object was: "
540: + obj);
541: else {
542: // Set current exists flag to stable exists flag
543: data.setCurrentlyExists(data.exists());
544: // Does not need synchronizing
545: data.setTransactionAttributes(new HashMap());
546: }
547: }
548: }
549:
550: /**
551: * Get an object wrapper for an object which disregards object's
552: * own equals() and hashCode() methods.
553: */
554: public ObjectWrapper getWrapper(Object obj) {
555: return new ObjectWrapper(obj);
556: }
557:
558: /**
559: * Search for an item in a list based on id.
560: */
561: /**
562: * This is the structure tracked for an object.
563: */
564: public class ObjectData implements Comparable {
565: private long id;
566: private boolean exists;
567: private boolean currentlyExists; // Inside transaction it may differ from 'exists'.
568: private Map transactionAttributes;
569: private Map attributes;
570: private PersistenceMetaData metaData;
571:
572: public ObjectData() {
573: transactionAttributes = new HashMap();
574: attributes = new HashMap();
575: }
576:
577: public PersistenceMetaData getMetaData() {
578: return metaData;
579: }
580:
581: public void setMetaData(PersistenceMetaData metaData) {
582: this .metaData = metaData;
583: }
584:
585: public int compareTo(Object obj) {
586: return new Long(id).compareTo(new Long(
587: ((ObjectData) obj).id));
588: }
589:
590: public SharedData getSharedData() {
591: return (SharedData) sharedData.get(new Long(id));
592: }
593:
594: public long getId() {
595: return id;
596: }
597:
598: private void setId(long id) {
599: this .id = id;
600: }
601:
602: private Map getTransactionAttributes() {
603: return transactionAttributes;
604: }
605:
606: private void setTransactionAttributes(Map transactionAttributes) {
607: this .transactionAttributes = transactionAttributes;
608: }
609:
610: public boolean exists() {
611: return exists;
612: }
613:
614: private void setExists(boolean exists) {
615: this .exists = exists;
616: }
617:
618: private boolean currentlyExists() {
619: return currentlyExists;
620: }
621:
622: public void setCurrentlyExists(boolean currentlyExists) {
623: this .currentlyExists = currentlyExists;
624: }
625:
626: private Map getAttributes() {
627: return attributes;
628: }
629:
630: private void setAttributes(Map attributes) {
631: this .attributes = attributes;
632: }
633: }
634:
635: /**
636: * This is a structure which is shared for objects of the same id.
637: */
638: public class SharedData {
639: private int refCounter;
640:
641: public SharedData() {
642: refCounter = 1;
643: }
644:
645: public int getRefCounter() {
646: return refCounter;
647: }
648:
649: public void setRefCounter(int refCounter) {
650: this .refCounter = refCounter;
651: }
652:
653: }
654:
655: public class ObjectWrapper {
656: private Object obj;
657: private long id;
658:
659: public ObjectWrapper(Object obj) {
660: this .obj = obj;
661: registerObject(obj, 0);
662: this .id = ObjectTracker.this .getIdentifier(obj);
663: }
664:
665: public long getIdentifier() {
666: return id;
667: }
668:
669: public Object getObject() {
670: return obj;
671: }
672:
673: public int hashCode() {
674: return (int) (id >> 32);
675: }
676:
677: public boolean equals(Object rhs) {
678: if (!(rhs instanceof ObjectWrapper))
679: return false;
680: return id == ((ObjectWrapper) rhs).id;
681: }
682: }
683: }
|