package co.codewizards.cloudstore.local.persistence;
import java.util.Date;
import javax.jdo.JDOHelper;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.Inheritance;
import javax.jdo.annotations.InheritanceStrategy;
import javax.jdo.annotations.NotPersistent;
import javax.jdo.annotations.NullValue;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import static co.codewizards.cloudstore.core.util.AssertUtil.*;
/**
* Base class of all persistence-capable (a.k.a. entity) classes.
* @author Marco หงุ่ยตระกูล-Schulze - marco at codewizards dot co
*/
@PersistenceCapable(identityType=IdentityType.APPLICATION)
@Inheritance(strategy=InheritanceStrategy.SUBCLASS_TABLE)
public abstract class Entity implements AutoTrackChanged
{
@PrimaryKey
@Persistent(valueStrategy=IdGeneratorStrategy.NATIVE)
private long id = -1;
// We always initialise this, though the value might be overwritten when DataNucleus loads
// the object's data from the DB. There's no need to defer the Date instantiation.
// Creating 1 million instances of Date costs less than 68 ms (I tested creating them and
// putting them into a LinkedList (preventing optimizer short-cuts), so the LinkedList
// overhead is included in this time).
@Persistent(nullValue=NullValue.EXCEPTION)
private Date created = new Date();
@Persistent(nullValue=NullValue.EXCEPTION)
private Date changed = new Date();
@NotPersistent
private transient int hashCode;
/**
* Get the unique identifier of this object.
* <p>
* This identifier is unique per entity type (the first sub-class of this class
* having an own table - which is usually the direct sub-class of {@link Entity}).
* <p>
* This identifier is assigned when the object is persisted into the DB.
* @return the unique identifier of this object.
*/
public long getId() {
return id;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
final Object thisOid = JDOHelper.getObjectId(this);
if (thisOid == null) {
return false;
}
final Object otherOid = JDOHelper.getObjectId(obj);
return thisOid.equals(otherOid);
}
@Override
public int hashCode() {
if (hashCode == 0) {
// Freeze the hashCode.
//
// We make sure the hashCode does not change after it was once initialised, because the object might
// have been added to a HashSet (or Map) before being persisted. During persistence, the object's id is
// assigned and without freezing the hashCode, the object is thus not found in the Map/Set, anymore.
//
// This new strategy seems to be working well; messages like this do not occur anymore:
//
// Aug 22, 2014 9:44:23 AM org.datanucleus.store.rdbms.mapping.java.PersistableMapping postInsert
// 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.
final Object thisOid = JDOHelper.getObjectId(this);
if (thisOid == null)
hashCode = super.hashCode();
else
hashCode = thisOid.hashCode();
if (hashCode == 0) // very unlikely, but we want our code to be 100% robust.
hashCode = 1;
}
return hashCode;
}
@Override
public String toString() {
return String.format("%s[%s]", getClass().getSimpleName(), JDOHelper.getObjectId(this));
}
/**
* Gets the timestamp of the creation of this entity.
* @return the timestamp of the creation of this entity. Never <code>null</code>.
*/
public Date getCreated() {
return created;
}
/**
* Sets the timestamp of the creation of this entity.
* <p>
* <b>Important: You should normally never invoke this method!</b> The {@code created} property
* is supposed to be read-only (assigned once during object creation and never again).
* This setter merely exists for extraordinary, unforeseen use cases as well as tests.
* @param created the timestamp of the creation of this entity. Must not be <code>null</code>.
*/
protected void setCreated(final Date created) {
assertNotNull(created, "created");
this.created = created;
}
@Override
public Date getChanged() {
return changed;
}
@Override
public void setChanged(final Date changed) {
assertNotNull(created, "created");
this.changed = changed;
}
}