// $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.screenresults;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapKey;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.persistence.Version;
import com.google.common.base.Function;
import org.apache.log4j.Logger;
import org.hibernate.annotations.OptimisticLock;
import edu.harvard.med.screensaver.db.ScreenDAO;
import edu.harvard.med.screensaver.model.AbstractEntity;
import edu.harvard.med.screensaver.model.AbstractEntityVisitor;
import edu.harvard.med.screensaver.model.annotations.ToMany;
import edu.harvard.med.screensaver.model.libraries.Reagent;
import edu.harvard.med.screensaver.model.meta.Cardinality;
import edu.harvard.med.screensaver.model.meta.RelationshipPath;
import edu.harvard.med.screensaver.model.screens.Screen;
import edu.harvard.med.screensaver.model.screens.Study;
/**
* Annotation type on a reagent.
*
* @author <a mailto="andrew_tolopko@hms.harvard.edu">Andrew Tolopko</a>
* @author <a mailto="john_sullivan@hms.harvard.edu">John Sullivan</a>
*/
@Entity
@Table(uniqueConstraints={ @UniqueConstraint(columnNames={ "studyId", "name" }) })
@org.hibernate.annotations.Proxy
@edu.harvard.med.screensaver.model.annotations.ContainedEntity(containingEntityClass=Screen.class)
public class AnnotationType extends AbstractEntity<Integer> implements MetaDataType, Comparable<AnnotationType>
{
// private static data
private static final long serialVersionUID = 1L;
private static Logger log = Logger.getLogger(AnnotationType.class);
public static final RelationshipPath<AnnotationType> study = RelationshipPath.from(AnnotationType.class).to("study", Cardinality.TO_ONE);
public static final RelationshipPath<AnnotationType> annotationValues = RelationshipPath.from(AnnotationType.class).to("annotationValues");
public static final Function<AnnotationType,String> ToName = new Function<AnnotationType,String>() {
public String apply(AnnotationType entity)
{
return entity.getName();
}
};
// private instance data
private Integer _version;
private Screen _study;
private String _name;
private String _description;
private Integer _ordinal;
private boolean _isNumeric;
private Map<Reagent,AnnotationValue> _values = new HashMap<Reagent,AnnotationValue>();
// constructors
/**
* Construct an initialized <code>AnnotationType</code>. Intended only for use by {@link
* Screen}.
* @param study the study
* @param name the name of the annotation type
* @param description the description for the annotation type
* @param ordinal the ordinal position of this <code>AnnotationType</code> within its
* parent {@link Screen}.
* @param isNumeric true iff this annotation type contains numeric result values
*/
public AnnotationType(Screen study,
String name,
String description,
Integer ordinal,
boolean isNumeric)
{
_study = study;
_name = name;
_description = description;
_ordinal = ordinal;
_isNumeric = isNumeric;
//TODO: may have to lazily make this connection, re: large studies, [#2268] -sde4
_study.getAnnotationTypes().add(this);
}
// public instance methods
@Override
public Object acceptVisitor(AbstractEntityVisitor visitor)
{
return visitor.visit(this);
}
/**
* Defines natural ordering of <code>AnnotationType</code> objects, based
* upon their ordinal field value. Note that natural ordering is only defined
* between <code>AnnotationType</code> objects that share the same parent
* {@link Screen}.
*/
public int compareTo(AnnotationType that)
{
return getOrdinal().compareTo(that.getOrdinal());
}
/**
* Get the id for the annotation.
* @return the id for the annotation type
*/
@Id
@org.hibernate.annotations.GenericGenerator(
name="annotation_type_id_seq",
strategy="sequence",
parameters = {
@org.hibernate.annotations.Parameter(name="sequence", value="annotation_type_id_seq")
}
)
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="annotation_type_id_seq")
public Integer getAnnotationTypeId()
{
return getEntityId();
}
/**
* Get the study.
* @return the study
*/
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="studyId", nullable=false, updatable=false)
@org.hibernate.annotations.ForeignKey(name="fk_annotation_type_to_screen")
@org.hibernate.annotations.LazyToOne(value=org.hibernate.annotations.LazyToOneOption.PROXY)
public Screen getStudy()
{
return _study;
}
/**
* Get the set of annotation values for this annotation type
* @return the set of annotation values for this annotation type
*/
@OneToMany(fetch=FetchType.LAZY,
cascade={ CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE },
mappedBy="annotationType")
@ToMany(hasNonconventionalMutation=true /* model unit tests don't handle Maps yet, tested in AnnotationTypeTest#testAnnotationValues */)
@MapKey(name="reagent")
@OptimisticLock(excluded=true)
@org.hibernate.annotations.Cascade(value={org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE})
// removing, as this inconveniently forces us to access all reagents before looking for them in the map collection @org.hibernate.annotations.LazyCollection(LazyCollectionOption.EXTRA)
public Map<Reagent,AnnotationValue> getAnnotationValues()
{
return _values;
}
public AnnotationValue createAnnotationValue(
Reagent reagent,
String value)
{
return createAnnotationValue(reagent, value, true);
}
/**
* Create and return an annotation value for the annotation type.
*
* @param reagent the reagent
* @param value the value
* @param populateStudyReagentLink if set to false, the annotation value will be created without populating
* the {@link Study#getReagents()} and the {@link Reagent#getStudies() } links.
* if this is done, then {@ ScreenDAO#populateStudyReagentLinkTable(int) } must be called after creating the study.
* @return the new annotation value
*/
public AnnotationValue createAnnotationValue(
Reagent reagent,
String value, boolean populateStudyReagentLink )
{
if (_values.containsKey(reagent)) {
AnnotationValue extantAnnotValue = _values.get(reagent);
if (extantAnnotValue.getValue().equals(value)) {
log.warn("duplicate annotation for " + reagent + " (ignoring duplicate)");
}
else {
log.error("conflicting annotations exist for " + reagent + " (fix this!)");
}
return null;
}
AnnotationValue annotationValue = new AnnotationValue(
this,
reagent,
value,
_isNumeric && value != null ? new Double(value) : null);
// lazily make this connection, re: large studies, [#2268] -sde4
if(populateStudyReagentLink) reagent.getAnnotationValues().put(this, annotationValue);
// lazily make this connection, re: large studies, [#2268] -sde4
if(populateStudyReagentLink) getStudy().addReagent(reagent);
_values.put(reagent, annotationValue);
return annotationValue;
}
/**
* Get the name of the annotation type.
* @return the name of the annotation type
*/
@org.hibernate.annotations.Type(type="text")
public String getName()
{
return _name;
}
/**
* Set the name of the annotation type.
* @param name the new name of the annotation type
*/
public void setName(String name)
{
_name = name;
}
/**
* Get the description
* @return the description
*/
@org.hibernate.annotations.Type(type="text")
public String getDescription()
{
return _description;
}
/**
* Set the description
* @param description the new description
*/
public void setDescription(String description)
{
_description = description;
}
/**
* Get the 0-based ordinal position of this <code>AnnotationType</code> within its
* parent {@link Screen}.
* @return the 0-based ordinal position
*/
@Column(nullable=false, updatable=false)
public Integer getOrdinal()
{
return _ordinal;
}
/**
* Return true iff this annotation type contains numeric result values.
* @return true iff this annotation type contains numeric result values
*/
@Column(nullable=false, updatable=false, name="isNumeric")
public boolean isNumeric()
{
return _isNumeric;
}
// protected constructor
/**
* Construct an uninitialized <code>AnnotationType</code>.
* @motivation for hibernate and proxy/concrete subclass constructors
*/
protected AnnotationType() {}
// private instance methods
/**
* Set the id for the annotation type.
* @param annotationTypeId the new id for the annotation type
* @motivation for hibernate
*/
private void setAnnotationTypeId(Integer annotationTypeId)
{
setEntityId(annotationTypeId);
}
/**
* Get the version for the annotation type.
* @return the version for the annotation type
* @motivation for hibernate
*/
@Column(nullable=false)
@Version
private Integer getVersion()
{
return _version;
}
/**
* Set the version for the annotation type.
* @param version the new version for the annotation type
* @motivation for hibernate
*/
private void setVersion(Integer version)
{
_version = version;
}
/**
* Set the study.
* @param study the new study
* @motivation for hibernate
*/
private void setStudy(Screen study)
{
_study = study;
}
/**
* Set the annotation values.
* @param values the new set of annotation values
*/
private void setAnnotationValues(Map<Reagent,AnnotationValue> values)
{
_values = values;
}
/**
* Set the ordinal position of this <code>AnnotationType</code> within its
* parent {@link Screen}.
* @param ordinal the ordinal position of this <code>AnnotationType</code>
* within its parent {@link ScreenResult}
* @motivation for Hibernate
*/
private void setOrdinal(Integer ordinal)
{
_ordinal = ordinal;
}
/**
* Set the numericalness of this annotation type.
* @param isNumeric the new numericalness of this annotation type
*/
private void setNumeric(boolean isNumeric)
{
_isNumeric = isNumeric;
}
public void clearAnnotationValues()
{
_values.clear();
}
}