// $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(); } }