edu.harvard.med.screensaver.model.libraries.Library.java Source code

Java tutorial

Introduction

Here is the source code for edu.harvard.med.screensaver.model.libraries.Library.java

Source

// $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.libraries;

import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;

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.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Transient;
import javax.persistence.Version;

import com.google.common.base.Function;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.log4j.Logger;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Sort;
import org.hibernate.annotations.SortType;
import org.hibernate.annotations.Type;
import org.joda.time.LocalDate;

import edu.harvard.med.screensaver.db.LibrariesDAO;
import edu.harvard.med.screensaver.model.AbstractEntityVisitor;
import edu.harvard.med.screensaver.model.AuditedAbstractEntity;
import edu.harvard.med.screensaver.model.DuplicateEntityException;
import edu.harvard.med.screensaver.model.activities.AdministrativeActivity;
import edu.harvard.med.screensaver.model.activities.AdministrativeActivityType;
import edu.harvard.med.screensaver.model.annotations.ToMany;
import edu.harvard.med.screensaver.model.annotations.ToOne;
import edu.harvard.med.screensaver.model.meta.Cardinality;
import edu.harvard.med.screensaver.model.meta.PropertyPath;
import edu.harvard.med.screensaver.model.meta.RelationshipPath;
import edu.harvard.med.screensaver.model.screenresults.AssayPlate;
import edu.harvard.med.screensaver.model.screens.ScreenType;
import edu.harvard.med.screensaver.model.users.AdministratorUser;
import edu.harvard.med.screensaver.model.users.ScreeningRoomUser;
import edu.harvard.med.screensaver.util.DevelopmentException;

/**
 * A library represents a set of reagents and their layout into wells across
 * multiple stock plates, and also includes the layout of control and other
 * special purpose wells. The reagents comprising a given library are intended
 * to be a cohesive set that arbitrarily groups the reagents by vendor, species
 * targeted, cellular function, chemical similarity, or any combination thereof.
 * Reagents may belong to multiple libraries.
 * <ul>
 * <li>Screensaver supports libraries for either RNAi and Small Molecule screens. RNAi library wells contain silencing
 * reagents and small molecule library wells contain compounds.</li>
 * <li>96-, 384-, and 1536-well {@link PlateSize plate sizes} are currently supported.</li>
 * <li>A library must be defined for a set of plates that have a sequential plate numbers.</li>
 * </ul>
 * <p>
 * A Library in Screensaver is the <i>definition</i> of a library, and does not imply that the library plates are
 * physically present at the screening facility. The instances of a library being maintained at the screening facility
 * are tracked by library {@link Copy copies}.
 * <p>
 * The domain model allows for a Library to be defined independently of its {@link Well wells} and its well
 * {@link Reagent reagents}. In other words, the domain model permits any of the following states for a Library:
 * <ul>
 * <li>Library is defined with related Wells and Reagents. This is the normal state of a Library in Screensaver.
 * <li>Library is defined with related Wells, but without Reagents ("library contents"). This allows for a library's
 * contents to be unloaded and reloaded without deleting the Library definition itself, which is useful if
 * updated/corrected well contents data becomes available.
 * <li>Library is defined, but without related Wells, and thus without Reagents. It is recommended that Library's
 * {@link Well wells} always be created at the same time a Library is created, even if the Wells are initially all
 * {@link LibraryWellType#EMPTY empty}.
 * </ul>
 * 
 * @author <a mailto="john_sullivan@hms.harvard.edu">John Sullivan</a>
 * @author <a mailto="andrew_tolopko@hms.harvard.edu">Andrew Tolopko</a>
 */
@Entity
@org.hibernate.annotations.Proxy
public class Library extends AuditedAbstractEntity<Integer> {

    // static data

    private static final Logger log = Logger.getLogger(Library.class);
    private static final long serialVersionUID = 0L;

    public static final RelationshipPath<Library> contentsVersions = RelationshipPath.from(Library.class)
            .to("contentsVersions");
    public static final RelationshipPath<Library> latestReleasedContentsVersion = RelationshipPath
            .from(Library.class).to("latestReleasedContentsVersion", Cardinality.TO_ONE);
    public static final RelationshipPath<Library> wells = RelationshipPath.from(Library.class).to("wells");
    public static final RelationshipPath<Library> copies = RelationshipPath.from(Library.class).to("copies");
    public static final PropertyPath<Library> startPlate = RelationshipPath.from(Library.class)
            .toProperty("startPlate");
    public static final PropertyPath<Library> endPlate = RelationshipPath.from(Library.class)
            .toProperty("endPlate");

    public static final Function<Library, String> ToShortName = new Function<Library, String>() {
        @Override
        public String apply(Library l) {
            return l.getShortName();
        }
    };

    // private instance data

    private Integer _version;
    private SortedSet<Well> _wells = Sets.newTreeSet();
    private SortedSet<Copy> _copies = Sets.newTreeSet();
    private String _libraryName;
    private String _shortName;
    private String _description;
    private String _provider;
    private ScreenType _screenType;
    private LibraryType _libraryType;
    private Solvent _solvent;
    private boolean _isPool;
    private Integer _startPlate;
    private Integer _endPlate;
    private LibraryScreeningStatus _screeningStatus;
    private LocalDate _dateReceived;
    private LocalDate _dateScreenable;
    private PlateSize _plateSize;
    private ScreeningRoomUser _owner;
    private Integer _experimentalWellCount = new Integer(0);
    private SortedSet<LibraryContentsVersion> _contentsVersions = Sets.newTreeSet();
    private LibraryContentsVersion _latestReleasedContentsVersion;

    // public constructor

    /**
     * @motivation for hibernate and proxy/concrete subclass constructors
     */
    protected Library() {
    }

    /**
     * 
     * Construct a new, unitialized Library
     * @motivation for new Library creation via user interface, where even required
     *             fields are allowed to be uninitialized, initially
     * @param createdBy
     */
    public Library(AdministratorUser createdBy) {
        super(createdBy);
    }

    /**
     * Construct an initialized <code>Library</code> object.
     *
     * @param libraryName the library name
     * @param shortName the short name
     * @param screenType the screen type (RNAi or Small Molecule)
     * @param libraryType the library type
     * @param startPlate the start plate
     * @param endPlate the end plate
     */
    public Library(AdministratorUser createdBy, String libraryName, String shortName, ScreenType screenType,
            LibraryType libraryType, Integer startPlate, Integer endPlate, PlateSize plateSize) {
        super(createdBy);
        _libraryName = libraryName;
        _shortName = shortName;
        _screenType = screenType;
        _solvent = Solvent.getDefaultSolventType(_screenType);
        _libraryType = libraryType;
        _startPlate = startPlate;
        _endPlate = endPlate;
        _plateSize = plateSize;
        setScreeningStatus(LibraryScreeningStatus.ALLOWED);
    }

    @Override
    public Object acceptVisitor(AbstractEntityVisitor visitor) {
        return visitor.visit(this);
    }

    /**
     * Get the id for the screening library.
     *
     * @return the id for the screening library
     */
    @Id
    @org.hibernate.annotations.GenericGenerator(name = "library_id_seq", strategy = "sequence", parameters = {
            @Parameter(name = "sequence", value = "library_id_seq") })
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "library_id_seq")
    public Integer getLibraryId() {
        return getEntityId();
    }

    @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
    @JoinTable(name = "libraryUpdateActivity", joinColumns = @JoinColumn(name = "libraryId", nullable = false, updatable = false), inverseJoinColumns = @JoinColumn(name = "updateActivityId", nullable = false, updatable = false))
    @Sort(type = SortType.NATURAL)
    @ToMany(singularPropertyName = "updateActivity", hasNonconventionalMutation = true /* model testing framework doesn't understand this is a containment relationship, and so requires addUpdateActivity() method*/)
    @Override
    public SortedSet<AdministrativeActivity> getUpdateActivities() {
        return _updateActivities;
    }

    /**
     * Get the set of wells.
     *
     * @return the wells
     */
    @OneToMany(mappedBy = "library", cascade = { CascadeType.ALL })
    @Sort(type = SortType.NATURAL)
    public SortedSet<Well> getWells() {
        return _wells;
    }

    /**
     * Get the number of wells.
     *
     * @return the number of wells
     * @motivation {@link #getWells} forces loading of all wells, just to get the
     *             size; Hibernate can optimize a collection size request, if we
     *             get directly from an underyling extra-lazy persistent
     *             collection.
     */
    @Transient
    public int getNumWells() {
        return _wells.size();
    }

    /**
     * Create and return a new well for the library.
     *
     * @param wellKey the well key for the new well
     * @param wellType the well type for the new well
     * @return the new well
     */
    public Well createWell(WellKey wellKey, LibraryWellType wellType) {
        Well well = new Well(this, wellKey, wellType);
        if (!_wells.add(well)) {
            throw new DuplicateEntityException(this, well);
        }
        return well;
    }

    /**
     * Get the copies.
     *
     * @return the copies
     */
    @OneToMany(mappedBy = "library", cascade = { CascadeType.ALL })
    @Sort(type = SortType.NATURAL)
    public SortedSet<Copy> getCopies() {
        return _copies;
    }

    /**
     * Get the copy with the given copy name
     *
     * @param copyName the copy name of the copy to get
     * @return the copy with the given copy name
     */
    @Transient
    public Copy getCopy(final String copyName) {
        return (Copy) CollectionUtils.find(_copies, new Predicate() {
            public boolean evaluate(Object e) {
                return ((Copy) e).getName().equals(copyName);
            };
        });
    }

    /**
     * Create a new copy for the library.
     *
     * @param usageType the copy usage type
     * @param name the copy name
     */
    public Copy createCopy(AdministratorUser createdBy, CopyUsageType usageType, String name) {
        Copy copy = new Copy(createdBy, this, usageType, name);
        addCopy(copy);
        return copy;
    }

    public void addCopy(Copy copy) {
        if (!_copies.add(copy)) {
            throw new DuplicateEntityException(this, copy);
        }
    }

    /**
     * Get the library name.
     *
     * @return the library name
     */
    @Column(unique = true, nullable = false)
    @org.hibernate.annotations.Type(type = "text")
    public String getLibraryName() {
        return _libraryName;
    }

    /**
     * Set the library name.
     *
     * @param libraryName the new library name
     */
    public void setLibraryName(String libraryName) {
        _libraryName = libraryName;
    }

    /**
     * Get the short name.
     *
     * @return the short name
     */
    @Column(unique = true, nullable = false)
    @org.hibernate.annotations.Type(type = "text")
    public String getShortName() {
        return _shortName;
    }

    /**
     * Set the short name.
     *
     * @param shortName the new short name
     */
    public void setShortName(String shortName) {
        _shortName = shortName;
    }

    /**
     * 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 provider of the library. The provider may be a commercial vendor, an academic lab, etc. Note that a library
     * may be comprised of reagents from multiple {@link Reagent#getVendorId() vendors}, and these vendor(s) are not
     * necessarily the same as the library's provider.
     */
    @org.hibernate.annotations.Type(type = "text")
    public String getProvider() {
        return _provider;
    }

    public void setProvider(String provider) {
        _provider = provider;
    }

    /**
     * Get the screen type.
     *
     * @return the screen type
     */
    @Column(nullable = false)
    @org.hibernate.annotations.Type(type = "edu.harvard.med.screensaver.model.screens.ScreenType$UserType")
    public ScreenType getScreenType() {
        return _screenType;
    }

    /**
     * Set the screen type.
     *
     * @param screenType the new screen type
     */
    public void setScreenType(ScreenType screenType) {
        _screenType = screenType;
    }

    /**
     * Get the library type.
     *
     * @return the library type
     */
    @Column(nullable = false)
    @org.hibernate.annotations.Type(type = "edu.harvard.med.screensaver.model.libraries.LibraryType$UserType")
    public LibraryType getLibraryType() {
        return _libraryType;
    }

    /**
     * Set the library type.
     *
     * @param libraryType the new library type
     */
    public void setLibraryType(LibraryType libraryType) {
        _libraryType = libraryType;
    }

    @Column(nullable = false)
    @org.hibernate.annotations.Type(type = "edu.harvard.med.screensaver.model.libraries.Solvent$UserType")
    public Solvent getSolvent() {
        return _solvent;
    }

    public void setSolvent(Solvent solvent) {
        _solvent = solvent;
    }

    /**
     * Determines whether this library's well contains pools of reagents. Intended
     * for use with RNAi libraries (in particular, Dharmacon siGENOME libraries),
     * but can be commandeered for any applicable library.
     * 
     * @return true if this library's wells contains pools of reagents, otherwise
     *         may return false or null.
     */
    @Column(name = "isPool", nullable = false)
    public boolean isPool() {
        return _isPool;
    }

    public void setPool(boolean isPool) {
        _isPool = isPool;
    }

    @Transient
    public Class<? extends Reagent> getReagentType() {
        if (_screenType == ScreenType.SMALL_MOLECULE) {
            if (_libraryType == LibraryType.NATURAL_PRODUCTS) {
                return NaturalProductReagent.class;
            }
            return SmallMoleculeReagent.class;
        } else if (_screenType == ScreenType.RNAI) {
            return SilencingReagent.class;
        }
        throw new DevelopmentException("unhandled screen/library type");
    }

    /**
     * Get the start plate.
     *
     * @return the start plate
     */
    @Column(unique = true, nullable = false)
    public Integer getStartPlate() {
        return _startPlate;
    }

    /**
     * Set the start plate.
     *
     * @param startPlate the new start plate
     */
    public void setStartPlate(Integer startPlate) {
        _startPlate = startPlate;
    }

    /**
     * Get the end plate.
     *
     * @return the end plate
     */
    @Column(unique = true, nullable = false)
    public Integer getEndPlate() {
        return _endPlate;
    }

    /**
     * Set the end plate.
     *
     * @param endPlate the new end plate
     */
    public void setEndPlate(Integer endPlate) {
        _endPlate = endPlate;
    }

    /**
     * Return true iff this library contains the specified plate.
     *
     * @param plateNumber
     * @return true iff this library contains the specified plate
     */
    public boolean containsPlate(Integer plateNumber) {
        return plateNumber != null && plateNumber >= getStartPlate() && plateNumber <= getEndPlate();
    }

    /**
     * Get the screening status of this library, indicating whether this library
     * is available for screening.
     *
     * @return the screening status
     */
    @org.hibernate.annotations.Type(type = "edu.harvard.med.screensaver.model.libraries.LibraryScreeningStatus$UserType")
    @Column(nullable = false)
    public LibraryScreeningStatus getScreeningStatus() {
        return _screeningStatus;
    }

    /**
     * Set the screening status
     *
     * @param screeningStatus the new screening status
     */
    public void setScreeningStatus(LibraryScreeningStatus screeningStatus) {
        _screeningStatus = screeningStatus;
    }

    /**
     * Get the date received.
     *
     * @return the date received
     */
    @Type(type = "edu.harvard.med.screensaver.db.usertypes.LocalDateType")
    public LocalDate getDateReceived() {
        return _dateReceived;
    }

    /**
     * Set the date received.
     *
     * @param dateReceived the new date received
     */
    public void setDateReceived(LocalDate dateReceived) {
        _dateReceived = dateReceived;
    }

    /**
     * Get the date screenable.
     *
     * @return the date screenable
     */
    @Type(type = "edu.harvard.med.screensaver.db.usertypes.LocalDateType")
    public LocalDate getDateScreenable() {
        return _dateScreenable;
    }

    /**
     * Set the date screenable.
     *
     * @param dateScreenable the new date screenable
     */
    public void setDateScreenable(LocalDate dateScreenable) {
        _dateScreenable = dateScreenable;
    }

    // private instance methods

    /**
     * Set the id for the screening library.
     *
     * @param libraryId the new id for the screening library
     * @motivation for hibernate
     */
    private void setLibraryId(Integer libraryId) {
        setEntityId(libraryId);
    }

    /**
     * Get the version for the screening library.
     *
     * @return the version for the screening library
     * @motivation for hibernate
     */
    @Version
    @Column(nullable = false)
    private Integer getVersion() {
        return _version;
    }

    /**
     * Set the version for the screening library.
     *
     * @param version the new version for the screening library
     * @motivation for hibernate
     */
    private void setVersion(Integer version) {
        _version = version;
    }

    /**
     * Set the set of wells.
     *
     * @param wells the new set of wells
     * @motivation for hibernate
     */
    private void setWells(SortedSet<Well> wells) {
        _wells = wells;
    }

    /**
     * Set the set of copies.
     *
     * @param copies the new set of copies
     * @motivation for hibernate
     */
    private void setCopies(SortedSet<Copy> copies) {
        _copies = copies;
    }

    @Column(nullable = false/*, updatable=false*/)
    //@org.hibernate.annotations.Immutable
    @org.hibernate.annotations.Type(type = "edu.harvard.med.screensaver.model.libraries.PlateSize$UserType")
    public PlateSize getPlateSize() {
        return _plateSize;
    }

    /**
     * Note: if plateSize is changed, it is the responsibility of the caller to
     * also add/remove wells, as necessary, to match the new plate size.
     *
     * @param plateSize the new PlateSize
     */
    public void setPlateSize(PlateSize plateSize) {
        //    if (!isHibernateCaller() && getEntityId() != null && plateSize != _plateSize) {
        //      throw new DataModelViolationException("cannot change plate size after library is created");
        //    }
        _plateSize = plateSize;
    }

    @edu.harvard.med.screensaver.model.annotations.Column(hasNonconventionalSetterMethod = true)
    public Integer getExperimentalWellCount() {
        return _experimentalWellCount;
    }

    private void setExperimentalWellCount(Integer experimentalWellCount) {
        this._experimentalWellCount = experimentalWellCount;
    }

    void incExperimentalWellCount() {
        _experimentalWellCount = _experimentalWellCount + 1;
    }

    void decExperimentalWellCount() {
        assert _experimentalWellCount > 0;
        _experimentalWellCount = Math.max(0, _experimentalWellCount - 1);
    }

    @OneToMany(mappedBy = "library", cascade = { CascadeType.ALL }, orphanRemoval = true)
    @org.hibernate.annotations.Sort(type = org.hibernate.annotations.SortType.NATURAL)
    public SortedSet<LibraryContentsVersion> getContentsVersions() {
        return _contentsVersions;
    }

    private void setContentsVersions(SortedSet<LibraryContentsVersion> contentsVersions) {
        _contentsVersions = contentsVersions;
    }

    /**
     * Get the most recently released {@link LibraryContentsVersion}, which
     * represents the latest contents version that is available for viewing by
     * {@link ScreeningRoomUser}s. Note that there may exist a newer
     * contents versions that has not yet been released for viewing, and so is
     * only available to {@link AdministratorUser}s.
     */
    @OneToOne
    @JoinColumn(name = "latest_released_contents_version_id")
    @ToOne(hasNonconventionalSetterMethod = true) /* the released contents versions must be one of the library's contents versions */
    public LibraryContentsVersion getLatestReleasedContentsVersion() {
        return _latestReleasedContentsVersion;
    }

    /*package*/ void setLatestReleasedContentsVersion(LibraryContentsVersion latestReleasedContentsVersion) {
        _latestReleasedContentsVersion = latestReleasedContentsVersion;
    }

    /**
     * Get the most recently created {@link LibraryContentsVersion}, which may be
     * newer than the latest <i>released</i> contents version, and so may only be
     * available to {@link AdministratorUser}s.
     */
    @Transient
    public LibraryContentsVersion getLatestContentsVersion() {
        if (_contentsVersions.isEmpty()) {
            return null;
        }
        return _contentsVersions.last();
    }

    /**
     * Create a new {@link LibraryContentsVersion}. This contents version will not
     * be available for viewing by {@link ScreeningRoomUser}s until its has been
     * released by calling
     * {@link #setLatestReleasedContentsVersion(LibraryContentsVersion)}.
     *
     * @return the new {@link LibraryContentsVersion}
     */
    public LibraryContentsVersion createContentsVersion(AdministratorUser recordedBy) {
        AdministrativeActivity loadingAdminActivity = new AdministrativeActivity(recordedBy, new LocalDate(),
                AdministrativeActivityType.LIBRARY_CONTENTS_LOADING);
        LibraryContentsVersion libraryContentsVersion = new LibraryContentsVersion(this,
                _contentsVersions.isEmpty() ? LibraryContentsVersion.FIRST_VERSION_NUMBER
                        : _contentsVersions.last().getVersionNumber() + 1,
                loadingAdminActivity);
        _contentsVersions.add(libraryContentsVersion);
        return libraryContentsVersion;
    }

    //Set the FetchType to EAGER otherwise when browsing libraries: org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    @ManyToOne(fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    @JoinColumn(name = "ownerScreenerId", nullable = true)
    @org.hibernate.annotations.ForeignKey(name = "fk_library_to_owner")
    //  @org.hibernate.annotations.LazyToOne(value=org.hibernate.annotations.LazyToOneOption.PROXY)
    @org.hibernate.annotations.Cascade(value = { org.hibernate.annotations.CascadeType.SAVE_UPDATE })

    public ScreeningRoomUser getOwner() {
        return _owner;
    }

    public void setOwner(ScreeningRoomUser owner) {
        _owner = owner;
    }

    @Transient
    public SortedSet<LibraryPlate> getLibraryPlates() {
        SetMultimap<Integer, AssayPlate> index = HashMultimap.create();
        for (Copy copy : getCopies()) {
            for (Map.Entry<Integer, Plate> entry : copy.getPlates().entrySet()) {
                index.putAll(entry.getKey(), entry.getValue().getAssayPlates());
            }
        }
        SortedSet<LibraryPlate> libraryPlates = Sets.newTreeSet();
        Set<AssayPlate> assayPlates;
        for (int p = getStartPlate(); p <= getEndPlate(); ++p) {
            if (index.containsKey(p)) {
                assayPlates = index.get(p);
            } else {
                assayPlates = Collections.emptySet();
            }
            libraryPlates.add(new LibraryPlate(p, this, assayPlates));
        }
        return libraryPlates;
    }

    // [#3439] Old well values are not nulled out before reloading new library content versions
    // TODO: the well contents are not held back for the LCV release, it appears, since the LCV release only affects the latest released reagent -sde4
    public void resetContents() {
        for (Well well : getWells()) {
            well.resetLibraryContents();
        }
    }
}