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}