package edu.ualberta.med.biobank.model; import java.text.MessageFormat; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; 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.Table; import javax.persistence.Transient; import javax.persistence.UniqueConstraint; import javax.validation.constraints.NotNull; import org.hibernate.annotations.ForeignKey; import org.hibernate.annotations.Type; import org.hibernate.validator.constraints.NotEmpty; import edu.ualberta.med.biobank.common.util.RowColPos; import edu.ualberta.med.biobank.validator.constraint.Empty; import edu.ualberta.med.biobank.validator.constraint.Unique; import edu.ualberta.med.biobank.validator.constraint.model.ValidContainer; import edu.ualberta.med.biobank.validator.group.PreDelete; import edu.ualberta.med.biobank.validator.group.PrePersist; /** * A specifically built physical unit that can hold child containers, or can be * contained in a parent container. * */ @Entity @Table(name = "CONTAINER", uniqueConstraints = { @UniqueConstraint(columnNames = { "SITE_ID", "CONTAINER_TYPE_ID", "LABEL" }), @UniqueConstraint(columnNames = { "SITE_ID", "PRODUCT_BARCODE" }) }) // TODO: consider pulling @UniqueConstraint into this @Unique annotation, // because this is a total repeating of constraints. Would then need to figure // out how to add DDL constraints from our annotations and how to get a bean's // value of a specific column. @Unique.List({ @Unique(properties = { "site", "containerType", "label" }, groups = PrePersist.class), @Unique(properties = { "site", "productBarcode" }, groups = PrePersist.class) }) @Empty.List({ @Empty(property = "specimenPositions", groups = PreDelete.class), @Empty(property = "childPositions", groups = PreDelete.class) }) @ValidContainer(groups = PrePersist.class) public class Container extends AbstractBiobankModel { private static final long serialVersionUID = 1L; private String productBarcode; private String label; private Double temperature; private String path; private Set<Comment> comments = new HashSet<Comment>(0); private Set<ContainerPosition> childPositions = new HashSet<ContainerPosition>(0); private Container topContainer; private Set<SpecimenPosition> specimenPositions = new HashSet<SpecimenPosition>(0); private ContainerPosition position; private Site site; private ActivityStatus activityStatus = ActivityStatus.ACTIVE; private ContainerType containerType; @Column(name = "PRODUCT_BARCODE") public String getProductBarcode() { return this.productBarcode; } public void setProductBarcode(String productBarcode) { this.productBarcode = productBarcode; } @NotEmpty(message = "{edu.ualberta.med.biobank.model.Container.label.NotEmpty}") @Column(name = "LABEL", nullable = false) public String getLabel() { return this.label; } public void setLabel(String label) { this.label = label; } // TODO: should be decimal? @Column(name = "TEMPERATURE") public Double getTemperature() { return this.temperature; } public void setTemperature(Double temperature) { this.temperature = temperature; } @Column(name = "PATH") public String getPath() { return this.path; } public void setPath(String path) { this.path = path; } @ManyToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY) @JoinTable(name = "CONTAINER_COMMENT", joinColumns = { @JoinColumn(name = "CONTAINER_ID", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "COMMENT_ID", unique = true, nullable = false, updatable = false) }) public Set<Comment> getComments() { return this.comments; } public void setComments(Set<Comment> comments) { this.comments = comments; } @OneToMany(fetch = FetchType.LAZY, mappedBy = "parentContainer") public Set<ContainerPosition> getChildPositions() { return this.childPositions; } public void setChildPositions(Set<ContainerPosition> childPositions) { this.childPositions = childPositions; } @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "TOP_CONTAINER_ID") public Container getTopContainer() { return this.topContainer; } public void setTopContainer(Container topContainer) { this.topContainer = topContainer; } @OneToMany(fetch = FetchType.LAZY, mappedBy = "container") public Set<SpecimenPosition> getSpecimenPositions() { return this.specimenPositions; } public void setSpecimenPositions(Set<SpecimenPosition> specimenPositions) { this.specimenPositions = specimenPositions; } @NotNull(message = "{edu.ualberta.med.biobank.model.Container.containerType.NotNull}") @ManyToOne @JoinColumn(name = "CONTAINER_TYPE_ID") @ForeignKey(name = "FK_Container_containerType") public ContainerType getContainerType() { return containerType; } public void setContainerType(ContainerType containerType) { this.containerType = containerType; } @OneToOne(cascade = CascadeType.ALL, mappedBy = "container", orphanRemoval = true) public ContainerPosition getPosition() { return this.position; } public void setPosition(ContainerPosition position) { this.position = position; } @NotNull(message = "{edu.ualberta.med.biobank.model.Container.site.NotNull}") @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "SITE_ID", nullable = false) public Site getSite() { return this.site; } public void setSite(Site site) { this.site = site; } @NotNull(message = "{edu.ualberta.med.biobank.model.Container.activityStatus.NotNull}") @Column(name = "ACTIVITY_STATUS_ID", nullable = false) @Type(type = "activityStatus") public ActivityStatus getActivityStatus() { return this.activityStatus; } public void setActivityStatus(ActivityStatus activityStatus) { this.activityStatus = activityStatus; } @Transient public RowColPos getPositionAsRowCol() { return getPosition() == null ? null : getPosition().getPosition(); } @Transient public Container getParentContainer() { return getPosition() == null ? null : getPosition() .getParentContainer(); } @Transient public String getPositionString() { Container parent = getParentContainer(); if (parent != null) { RowColPos pos = getPositionAsRowCol(); if (pos != null) { return parent.getContainerType().getPositionString(pos); } } return null; } public boolean isPositionFree(RowColPos requestedPosition) { if (getChildPositions().size() > 0) { for (ContainerPosition pos : getChildPositions()) { RowColPos rcp = new RowColPos(pos.getRow(), pos.getCol()); if (requestedPosition.equals(rcp)) { return false; } } } // else assume this container has specimens for (SpecimenPosition pos : getSpecimenPositions()) { RowColPos rcp = new RowColPos(pos.getRow(), pos.getCol()); if (requestedPosition.equals(rcp)) { return false; } } return true; } @Transient public Container getChild(RowColPos requestedPosition) throws Exception { if (getChildPositions().size() == 0) { throw new Exception("container does not have children"); } for (ContainerPosition pos : childPositions) { RowColPos rcp = new RowColPos(pos.getRow(), pos.getCol()); if (requestedPosition.equals(rcp)) { return pos.getContainer(); } } return null; } /** * Label can start with parent's label as prefix or without. * * @param label * @return * @throws Exception */ @Transient public Container getChildByLabel(String childLabel) throws Exception { ContainerType containerType = getContainerType(); if (containerType == null) { throw new Exception("container type is null"); } // remove parent label from child label if (childLabel.startsWith(getLabel())) { childLabel = childLabel.substring(getLabel().length()); } RowColPos pos = getPositionFromLabelingScheme(getLabel()); return getChild(pos); } /** * position is 2 letters, or 2 number or 1 letter and 1 number... this * position string is used to get the correct row and column index the given * position String. * * @throws Exception */ @Transient public RowColPos getPositionFromLabelingScheme(String position) throws Exception { ContainerType containerType = getContainerType(); RowColPos rcp = containerType.getRowColFromPositionString(position); if (rcp != null) { if (rcp.getRow() >= containerType.getRowCapacity() || rcp.getCol() >= containerType.getColCapacity()) { throw new Exception( MessageFormat .format( "Can''t use position {0} in container {1}. Reason: capacity = {2}*{3}", position, getFullInfoLabel(), containerType.getRowCapacity(), containerType.getColCapacity())); } if (rcp.getRow() < 0 || rcp.getCol() < 0) { throw new Exception( MessageFormat.format( "Position ''{0}'' is invalid for this container {1}", position, getFullInfoLabel())); } } return rcp; } /** * @return a string with the label of this container + the short name of its * type * */ @Transient public String getFullInfoLabel() { ContainerType containerType = getContainerType(); if ((containerType == null) || (containerType.getNameShort() == null)) { return getLabel(); } return getLabel() + " (" + containerType.getNameShort() + ")"; } public boolean hasSpecimens() { return (getSpecimenPositions().size() > 0); } }