001package co.codewizards.cloudstore.local.persistence; 002 003import static java.util.Objects.*; 004 005import java.util.Date; 006 007import javax.jdo.JDOHelper; 008import javax.jdo.annotations.IdGeneratorStrategy; 009import javax.jdo.annotations.IdentityType; 010import javax.jdo.annotations.Inheritance; 011import javax.jdo.annotations.InheritanceStrategy; 012import javax.jdo.annotations.NotPersistent; 013import javax.jdo.annotations.NullValue; 014import javax.jdo.annotations.PersistenceCapable; 015import javax.jdo.annotations.Persistent; 016import javax.jdo.annotations.PrimaryKey; 017 018/** 019 * Base class of all persistence-capable (a.k.a. entity) classes. 020 * @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co 021 */ 022@PersistenceCapable(identityType=IdentityType.APPLICATION) 023@Inheritance(strategy=InheritanceStrategy.SUBCLASS_TABLE) 024public abstract class Entity implements AutoTrackChanged 025{ 026 @PrimaryKey 027 @Persistent(valueStrategy=IdGeneratorStrategy.NATIVE) 028 private long id = Long.MIN_VALUE; 029 030 // We always initialise this, though the value might be overwritten when DataNucleus loads 031 // the object's data from the DB. There's no need to defer the Date instantiation. 032 // Creating 1 million instances of Date costs less than 68 ms (I tested creating them and 033 // putting them into a LinkedList (preventing optimizer short-cuts), so the LinkedList 034 // overhead is included in this time). 035 @Persistent(nullValue=NullValue.EXCEPTION) 036 private Date created = new Date(); 037 038 @Persistent(nullValue=NullValue.EXCEPTION) 039 private Date changed = new Date(); 040 041 @NotPersistent 042 private transient int hashCode; 043 044 /** 045 * Get the unique identifier of this object. 046 * <p> 047 * This identifier is unique per entity type (the first sub-class of this class 048 * having an own table - which is usually the direct sub-class of {@link Entity}). 049 * <p> 050 * This identifier is assigned when the object is persisted into the DB. 051 * @return the unique identifier of this object. 052 */ 053 public long getId() { 054 return id; 055 } 056 057 @Override 058 public boolean equals(final Object obj) { 059 if (this == obj) { 060 return true; 061 } 062 if (obj == null) { 063 return false; 064 } 065 066 final Object thisOid = JDOHelper.getObjectId(this); 067 if (thisOid == null) { 068 return false; 069 } 070 071 final Object otherOid = JDOHelper.getObjectId(obj); 072 return thisOid.equals(otherOid); 073 } 074 075 @Override 076 public int hashCode() { 077 if (hashCode == 0) { 078 // Freeze the hashCode. 079 // 080 // We make sure the hashCode does not change after it was once initialised, because the object might 081 // have been added to a HashSet (or Map) before being persisted. During persistence, the object's id is 082 // assigned and without freezing the hashCode, the object is thus not found in the Map/Set, anymore. 083 // 084 // This new strategy seems to be working well; messages like this do not occur anymore: 085 // 086 // Aug 22, 2014 9:44:23 AM org.datanucleus.store.rdbms.mapping.java.PersistableMapping postInsert 087 // INFO: Object "co.codewizards.cloudstore.local.persistence.FileChunk@36dccbc7" has field "co.codewizards.cloudstore.local.persistence.FileChunk.normalFile" with an N-1 bidirectional relation set to relate to "co.codewizards.cloudstore.local.persistence.NormalFile@63323bb" but the collection at "co.codewizards.cloudstore.local.persistence.NormalFile.fileChunks" doesnt contain this object. 088 089 final Object thisOid = JDOHelper.getObjectId(this); 090 if (thisOid == null) 091 hashCode = super.hashCode(); 092 else 093 hashCode = thisOid.hashCode(); 094 095 if (hashCode == 0) // very unlikely, but we want our code to be 100% robust. 096 hashCode = 1; 097 } 098 return hashCode; 099 } 100 101 @Override 102 public String toString() { 103 return getClass().getSimpleName() + '[' + toString_getProperties() + ']'; 104 } 105 106 protected String toString_getProperties() { 107 return "id=" + JDOHelper.getObjectId(this); 108 } 109 110 /** 111 * Gets the timestamp of the creation of this entity. 112 * @return the timestamp of the creation of this entity. Never <code>null</code>. 113 */ 114 public Date getCreated() { 115 return created; 116 } 117 /** 118 * Sets the timestamp of the creation of this entity. 119 * <p> 120 * <b>Important: You should normally never invoke this method!</b> The {@code created} property 121 * is supposed to be read-only (assigned once during object creation and never again). 122 * This setter merely exists for extraordinary, unforeseen use cases as well as tests. 123 * @param created the timestamp of the creation of this entity. Must not be <code>null</code>. 124 */ 125 protected void setCreated(final Date created) { 126 requireNonNull(created, "created"); 127 this.created = created; 128 } 129 130 @Override 131 public Date getChanged() { 132 return changed; 133 } 134 @Override 135 public void setChanged(final Date changed) { 136 requireNonNull(created, "created"); 137 this.changed = changed; 138 } 139 140}