// $HeadURL$
// $Id$
//
// Copyright © 2006, 2010, 2011, 2012 by the President and Fellows of Harvard College.
//
// Screensaver is an open-source project developed by the ICCB-L and NSRB labs
// at Harvard Medical School. This software is distributed under the terms of
// the GNU General Public License.
package edu.harvard.med.screensaver.model;
import java.io.Serializable;
import java.util.Comparator;
import java.util.SortedSet;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import org.hibernate.annotations.Type;
import org.joda.time.DateTime;
import edu.harvard.med.screensaver.model.activities.Activity;
import edu.harvard.med.screensaver.model.activities.AdministrativeActivity;
import edu.harvard.med.screensaver.model.activities.AdministrativeActivityType;
import edu.harvard.med.screensaver.model.meta.Cardinality;
import edu.harvard.med.screensaver.model.meta.RelationshipPath;
import edu.harvard.med.screensaver.model.users.AdministratorUser;
import edu.harvard.med.screensaver.model.users.ScreensaverUser;
/**
* Parent class of entity types that require auditing of their creation and
* update events. Creation information (who, when) is stored directly as
* properties in the class. Update information (who, when, what) is stored as a
* set of associated AdministrativeActivity objects of type
* {@link AdministrativeActivityType#ENTITY_UPDATE}. (Note that
* AdministrativeActivity is itself an {@link AuditedAbstractEntity}, but entity
* update activities should not themselves every be updated, so we don't have a
* recursion problem!)
* <p/>
* Every subclass will store its entity update activities in it own database
* table. To make this work within Hibernate, the subclass must override the
* {@link #getUpdateActivities()} method for the sole purpose of defining the
* Hibernate mapping annotations there. See existing subclasses for examples of
* the requisite annotations.
* <p/>
* Subclass constructors <i>must</i> call
* {@link #AuditedAbstractEntity(AdministratorUser)} to ensure that a creation
* timestamp is recorded (the {@link #AuditedAbstractEntity()} constructor is
* for Hibernate's use only).
*/
@MappedSuperclass
public abstract class AuditedAbstractEntity<K extends Serializable> extends AbstractEntity<K>
{
private static final long serialVersionUID = 1L;
public static final RelationshipPath<AuditedAbstractEntity> createdBy = RelationshipPath.from(AuditedAbstractEntity.class).to("createdBy", Cardinality.TO_ONE);
public static final RelationshipPath<AuditedAbstractEntity> updateActivities = RelationshipPath.from(AuditedAbstractEntity.class).to("updateActivities");
protected SortedSet<AdministrativeActivity> _updateActivities = Sets.newTreeSet();
private ScreensaverUser _createdBy;
private DateTime _createdTimestamp;
private DateTime _loadedTimestamp;
private DateTime _publiclyAvailableTimestamp;
/**
* @motivation for Hibernate ONLY
*/
protected AuditedAbstractEntity()
{
}
protected AuditedAbstractEntity(AdministratorUser createdBy)
{
_createdBy = createdBy;
_createdTimestamp = new DateTime();
_loadedTimestamp = new DateTime();
_publiclyAvailableTimestamp = new DateTime();
}
protected AuditedAbstractEntity(AdministratorUser createdBy, DateTime loaded, DateTime publiclyAvailable)
{
_createdBy = createdBy;
_createdTimestamp = new DateTime();
_loadedTimestamp = loaded;
_publiclyAvailableTimestamp = publiclyAvailable;
}
//@Immutable
@Column(nullable = false, updatable = false)
@Type(type="org.joda.time.contrib.hibernate.PersistentDateTime")
public DateTime getDateCreated()
{
return _createdTimestamp;
}
private void setDateCreated(DateTime createdTimeStamp)
{
_createdTimestamp = createdTimeStamp;
}
/**
* The date that data is loaded into the database (used by LINCS)
*/
@Column(nullable = true, updatable = false)
@Type(type="org.joda.time.contrib.hibernate.PersistentDateTime")
public DateTime getDateLoaded()
{
return _loadedTimestamp;
}
private void setDateLoaded(DateTime value)
{
_loadedTimestamp = value;
}
/**
* The date that data has been made publicly available (used by LINCS)
*/
@Column(nullable = true, updatable = false)
@Type(type="org.joda.time.contrib.hibernate.PersistentDateTime")
public DateTime getDatePubliclyAvailable()
{
return _publiclyAvailableTimestamp;
}
private void setDatePubliclyAvailable(DateTime value)
{
_publiclyAvailableTimestamp = value;
}
@ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
@JoinColumn(name="createdById", updatable=false)
@org.hibernate.annotations.LazyToOne(value=org.hibernate.annotations.LazyToOneOption.PROXY)
@org.hibernate.annotations.Cascade(value = { org.hibernate.annotations.CascadeType.SAVE_UPDATE })
@edu.harvard.med.screensaver.model.annotations.ToOne(unidirectional=true, hasNonconventionalSetterMethod=true /* nullable, immutable, to-one relationships not supported by domain model testing framework */)
public ScreensaverUser getCreatedBy()
{
return _createdBy;
}
private void setCreatedBy(ScreensaverUser createdBy)
{
_createdBy = createdBy;
}
/**
* To make update persistent, override in subclasses and add necessary Hibernate annotations on the overriding method.
*/
@Transient
public SortedSet<AdministrativeActivity> getUpdateActivities()
{
return _updateActivities;
}
@Transient
public SortedSet<AdministrativeActivity> getUpdateActivitiesOfType(AdministrativeActivityType activityType)
{
return Sets.newTreeSet(Sets.filter(_updateActivities,
AdministrativeActivity.IsOfType(activityType)));
}
/**
* @param activityType
* @return the most recently performed {@link AdministrativeActivity} of the specified type; if no activity of the
* specified type exists, returns a {@link AdministrativeActivity} with all properties set to
* </code>null</code>
*/
@Transient
public AdministrativeActivity getLastUpdateActivityOfType(AdministrativeActivityType activityType)
{
SortedSet<AdministrativeActivity> activities = getUpdateActivitiesOfType(activityType);
return activities.isEmpty() ? AdministrativeActivity.Null : activities.last();
}
private static Ordering<Activity> ActivityRecordedOrdering = Ordering.from(new Comparator<Activity>() {
@Override
public int compare(Activity a1, Activity a2)
{
return a1.getDateCreated().compareTo(a2.getDateCreated());
}
});
@Transient
public AdministrativeActivity getLastRecordedUpdateActivityOfType(AdministrativeActivityType activityType)
{
SortedSet<AdministrativeActivity> activities = getUpdateActivitiesOfType(activityType);
return activities.isEmpty() ? AdministrativeActivity.Null : ActivityRecordedOrdering.max(activities);
}
private void setUpdateActivities(SortedSet<AdministrativeActivity> updateActivities)
{
_updateActivities = updateActivities;
}
public AdministrativeActivity createUpdateActivity(AdministratorUser recordedBy, String comments) {
return createUpdateActivity(AdministrativeActivityType.ENTITY_UPDATE, recordedBy, comments);
}
public AdministrativeActivity createUpdateActivity(AdministrativeActivityType activityType,
AdministratorUser recordedBy,
String comments)
{
AdministrativeActivity updateActivity =
new AdministrativeActivity(recordedBy,
new DateTime().toLocalDate(),
activityType);
updateActivity.setComments(comments);
_updateActivities.add(updateActivity);
return updateActivity;
}
public AdministrativeActivity createComment(AdministratorUser recordedBy,
String comment)
{
return createUpdateActivity(AdministrativeActivityType.COMMENT,
recordedBy,
comment);
}
}