package edu.ualberta.med.biobank.common.wrappers; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import edu.ualberta.med.biobank.common.exception.BiobankCheckException; import edu.ualberta.med.biobank.common.exception.BiobankException; import edu.ualberta.med.biobank.common.exception.BiobankRuntimeException; import edu.ualberta.med.biobank.common.peer.CapacityPeer; import edu.ualberta.med.biobank.common.peer.ContainerPeer; import edu.ualberta.med.biobank.common.peer.ContainerPositionPeer; import edu.ualberta.med.biobank.common.peer.ContainerTypePeer; import edu.ualberta.med.biobank.common.peer.SitePeer; import edu.ualberta.med.biobank.common.peer.SpecimenPeer; import edu.ualberta.med.biobank.common.peer.SpecimenPositionPeer; import edu.ualberta.med.biobank.common.peer.SpecimenTypePeer; import edu.ualberta.med.biobank.common.util.RowColPos; import edu.ualberta.med.biobank.common.util.StringUtil; import edu.ualberta.med.biobank.common.wrappers.WrapperTransaction.TaskList; import edu.ualberta.med.biobank.common.wrappers.actions.UpdateContainerChildrenAction; import edu.ualberta.med.biobank.common.wrappers.actions.UpdateContainerPathAction; import edu.ualberta.med.biobank.common.wrappers.base.ContainerBaseWrapper; import edu.ualberta.med.biobank.common.wrappers.checks.ContainerPersistChecks; import edu.ualberta.med.biobank.common.wrappers.checks.NotNullPreCheck; import edu.ualberta.med.biobank.common.wrappers.checks.UniqueCheck; import edu.ualberta.med.biobank.common.wrappers.checks.UniquePreCheck; import edu.ualberta.med.biobank.common.wrappers.internal.AbstractPositionWrapper; import edu.ualberta.med.biobank.common.wrappers.internal.ContainerPositionWrapper; import edu.ualberta.med.biobank.common.wrappers.internal.SpecimenPositionWrapper; import edu.ualberta.med.biobank.common.wrappers.tasks.NoActionWrapperQueryTask; import edu.ualberta.med.biobank.model.ActivityStatus; import edu.ualberta.med.biobank.model.Container; import edu.ualberta.med.biobank.model.ContainerType; import edu.ualberta.med.biobank.model.Specimen; import edu.ualberta.med.biobank.server.applicationservice.BiobankApplicationService; import gov.nih.nci.system.applicationservice.ApplicationException; import gov.nih.nci.system.applicationservice.WritableApplicationService; import gov.nih.nci.system.query.SDKQueryResult; import gov.nih.nci.system.query.hibernate.HQLCriteria; public class ContainerWrapper extends ContainerBaseWrapper { public static final String PATH_DELIMITER = "/"; //$NON-NLS-1$ private static final String CHILD_POSITION_CONFLICT_MSG = Messages .getString("ContainerWrapper.child.position.conflict.msg"); //$NON-NLS-1$ private static final String OUT_OF_BOUNDS_POSITION_MSG = Messages .getString("ContainerWrapper.out.of.bounds.position.msg"); //$NON-NLS-1$ private static final String HAS_SPECIMENS_MSG = Messages .getString("ContainerWrapper.has.specimens.msg"); //$NON-NLS-1$ private static final String HAS_CHILD_CONTAINERS_MSG = Messages .getString("ContainerWrapper.has.child.containers.msg"); //$NON-NLS-1$ private static final String CANNOT_HOLD_SPECIMEN_TYPE_MSG = Messages .getString("ContainerWrapper.cannot.hold.specimen.type.msg"); //$NON-NLS-1$ private static final String SAMPLE_EXISTS_AT_POSITION_MSG = Messages .getString("ContainerWrapper.specimen.exists.atposition.msg"); //$NON-NLS-1$ private static final String CONTAINER_AT_POSITION_MSG = Messages .getString("ContainerWrapper.container.atposition.msg"); //$NON-NLS-1$ private static final Collection<Property<?, ? super Container>> UNIQUE_LABEL_PROPS, UNIQUE_BARCODE_PROPS; static { UNIQUE_LABEL_PROPS = new ArrayList<Property<?, ? super Container>>(); UNIQUE_LABEL_PROPS.add(ContainerPeer.SITE.to(SitePeer.ID)); UNIQUE_LABEL_PROPS.add(ContainerPeer.LABEL); UNIQUE_LABEL_PROPS.add(ContainerPeer.CONTAINER_TYPE .to(ContainerTypePeer.ID)); UNIQUE_BARCODE_PROPS = new ArrayList<Property<?, ? super Container>>(); UNIQUE_BARCODE_PROPS.add(ContainerPeer.SITE.to(SitePeer.ID)); UNIQUE_BARCODE_PROPS.add(ContainerPeer.PRODUCT_BARCODE); } private Map<RowColPos, SpecimenWrapper> specimens; private Map<RowColPos, ContainerWrapper> children; private boolean updateChildren = false; public ContainerWrapper(WritableApplicationService appService, Container wrappedObject) { super(appService, wrappedObject); } public ContainerWrapper(WritableApplicationService appService) { super(appService); } @Override protected Container getNewObject() throws Exception { Container newObject = super.getNewObject(); // by default, any newly created Container will have a null parent, so // its top is itself. newObject.setTopContainer(newObject); return newObject; } /** * Return the top {@code Container} of the top loaded {@code Container}. * This will give the correct "in memory" answer of who the top * {@code Container} is (whereas super.getTopContainer() will give the value * from the underlying model). */ @Override public ContainerWrapper getTopContainer() { // if parent is cached, return their top Container, otherwise get and // return mine (from super). if (isPropertyCached(ContainerPeer.POSITION) && getPosition() != null) { if (getPosition().isPropertyCached( ContainerPositionPeer.PARENT_CONTAINER) && getParentContainer() != null) { return getParentContainer().getTopContainer(); } } return super.getTopContainer(); } /** * @return the path, including this {@link Container}'s id. * @throws BiobankRuntimeException if this or any parent is new (does not * have an id) as the path is then undefined. */ @Override public String getPath() { if (isNew()) { throw new BiobankRuntimeException( "container is not in database yet: no ID"); //$NON-NLS-1$ } String parentPath = ""; //$NON-NLS-1$ if (isPropertyCached(ContainerPeer.POSITION) && getPosition() != null) { if (getPosition().isPropertyCached( ContainerPositionPeer.PARENT_CONTAINER) && getParentContainer() != null) { parentPath = getParentContainer().getPath(); } } else { // the persisted path is actually the parent path, although this // method returns the parent path plus its id. parentPath = super.getPath(); if (parentPath == null) { parentPath = ""; //$NON-NLS-1$ } } if (!parentPath.isEmpty()) { parentPath += PATH_DELIMITER; } return parentPath + getId(); } @Override public String getLabel() { if (isPropertyCached(ContainerPeer.POSITION) && getPosition() != null) { if (getPosition().isPropertyCached( ContainerPositionPeer.PARENT_CONTAINER) && getParentContainer() != null) { return getParentContainer().getLabel() + getPositionString(); } } return super.getLabel(); } @Override public void setLabel(String label) { super.setLabel(label); updateChildren = true; } public String getPositionString() { ContainerWrapper parent = getParentContainer(); if (parent != null) { RowColPos pos = getPositionAsRowCol(); if ((pos != null) && (parent.getContainerType() != null)) { return parent.getContainerType().getPositionString(pos); } } return null; } public RowColPos getPositionAsRowCol() { ContainerPositionWrapper pos = getPosition(); return pos == null ? null : pos.getPosition(); } private ContainerPositionWrapper getOrCreatePosition() { ContainerPositionWrapper position = getPosition(); if (position == null) { position = new ContainerPositionWrapper(appService); setPosition(position); } return position; } public ContainerWrapper getParentContainer() { AbstractPositionWrapper<?> pos = getPosition(); return pos == null ? null : pos.getParent(); } @Override @Deprecated public void setPath(String dummy) { throw new BiobankRuntimeException("cannot set path on container"); //$NON-NLS-1$ } /** * This sets the parent bidirectionally. If this is not required then use * setParentInternal(). */ public void setParent(ContainerWrapper container, RowColPos position) throws BiobankCheckException { if (container != null) { container.addChild(position.getRow(), position.getCol(), this); } else { setParentInternal(null, position); } } /** * KLUDGE ALERT: this method is called by ContainerViewForm.openFormFor(). */ public void setParentInternal(ContainerWrapper container, RowColPos position) { if (container == null) { setPosition(null); } else { getOrCreatePosition().setParent(container, position); } ContainerWrapper topContainer = container == null ? this : container .getTopContainer(); setTopContainerInternal(topContainer, true); } @Override @Deprecated public void setTopContainer(ContainerBaseWrapper container) { throw new UnsupportedOperationException( "Not allowed to directly set the top Container. Set the parent Container instead."); //$NON-NLS-1$ } public void setTopContainerInternal(ContainerWrapper container, boolean checkDatabase) { super.setTopContainer(container); // this is overly cautious, assuming that whenever the top Container is // set that it is changed. Could be improved to check if the value has // actually changed, but would probably require lazy loading. if (!isNew() && checkDatabase) { // TODO: actually check the database. Get the current // topSpecimen through an HQL query and compare it against the // one set. updateChildren = true; // TODO: may want to set to false if set back to the original? } else { updateChildren = true; } } public boolean hasParentContainer() { return getParentContainer() != null; } public Integer getRowCapacity() { ContainerTypeWrapper type = getContainerType(); return type == null ? null : type.getRowCapacity(); } public Integer getColCapacity() { ContainerTypeWrapper type = getContainerType(); return type == null ? null : type.getColCapacity(); } /** * 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 */ public RowColPos getPositionFromLabelingScheme(String position) throws Exception { ContainerTypeWrapper type = getContainerType(); RowColPos rcp = type.getRowColFromPositionString(position); if (rcp != null) { if (rcp.getRow() >= type.getRowCapacity() || rcp.getCol() >= type.getColCapacity()) { throw new Exception( MessageFormat .format( Messages .getString("ContainerWrapper.position.error.msg"), //$NON-NLS-1$ position, getFullInfoLabel(), type.getRowCapacity(), type.getColCapacity())); } if (rcp.getRow() < 0 || rcp.getCol() < 0) { throw new Exception( MessageFormat.format( Messages .getString("ContainerWrapper.invalid.position.error.msg"), //$NON-NLS-1$ position, getFullInfoLabel())); } } return rcp; } public Map<RowColPos, SpecimenWrapper> getSpecimens() { if (specimens == null) { Map<RowColPos, SpecimenWrapper> specimens = new TreeMap<RowColPos, SpecimenWrapper>(); List<SpecimenPositionWrapper> positions = getSpecimenPositionCollection(false); for (SpecimenPositionWrapper position : positions) { SpecimenWrapper specimen = position.getSpecimen(); RowColPos rowColPos = new RowColPos(position); specimens.put(rowColPos, specimen); } this.specimens = specimens; } return specimens; } public boolean hasSpecimens() { return !getSpecimens().isEmpty(); } public SpecimenWrapper getSpecimen(Integer row, Integer col) throws BiobankCheckException { RowColPos pos = new RowColPos(row, col); checkPositionValid(pos); return getSpecimens().get(pos); } public void addSpecimen(Integer row, Integer col, SpecimenWrapper specimen) throws Exception { RowColPos rowColPos = new RowColPos(row, col); checkPositionValid(rowColPos); if (!canHoldSpecimenType(specimen)) { String label = getFullInfoLabel(); String specimenType = specimen.getSpecimenType().getName(); String msg = MessageFormat.format(CANNOT_HOLD_SPECIMEN_TYPE_MSG, label, specimenType); throw new BiobankCheckException(msg); } SpecimenWrapper sampleAtPosition = getSpecimen(row, col); if (sampleAtPosition != null) { String label = getFullInfoLabel(); String posString = sampleAtPosition.getPositionString(false, false); String msg = MessageFormat.format(SAMPLE_EXISTS_AT_POSITION_MSG, label, posString, rowColPos); throw new BiobankCheckException(msg); } specimen.setParent(this, rowColPos); getSpecimens().put(rowColPos, specimen); } /** * @return a string with the label of this container + the short name of its * type * */ public String getFullInfoLabel() { if (getContainerType() == null || getContainerType().getNameShort() == null) { return getLabel(); } return getLabel() + " (" + getContainerType().getNameShort() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ } public long getChildCount(boolean fast) throws BiobankException, ApplicationException { return getPropertyCount(ContainerPeer.CHILD_POSITIONS, fast); } public Map<RowColPos, ContainerWrapper> getChildren() { if (children == null) { Map<RowColPos, ContainerWrapper> children = new TreeMap<RowColPos, ContainerWrapper>(); List<ContainerPositionWrapper> positions = getChildPositionCollection(false); for (ContainerPositionWrapper position : positions) { // explicitly set the parent container because (1) skip // lazy-loading later and (2) will put the parentContainer // property in the cache so it is used (see methods getPath(), // getLabel(), and getTopContainer() where recursion is used). // If no wrapper has been loaded, then the model value is used, // which can be inconsistent. // // setWrappedProperty() is used because it does not // bi-directionally set the property. position.setWrappedProperty( ContainerPositionPeer.PARENT_CONTAINER, this); ContainerWrapper container = position.getContainer(); RowColPos rowColPos = new RowColPos(position); ContainerWrapper previous = children.put(rowColPos, container); if (previous != null && !previous.equals(container)) { // this shouldn't ever happen, but just in case String msg = MessageFormat.format( CHILD_POSITION_CONFLICT_MSG, rowColPos, this, previous, container); throw new BiobankRuntimeException(msg); } } this.children = children; } return children; } public boolean hasChildren() { return !getChildren().isEmpty(); } public ContainerWrapper getChild(Integer row, Integer col) { return getChild(new RowColPos(row, col)); } public ContainerWrapper getChild(RowColPos rcp) { return getChildren().get(rcp); } /** * Label can start with parent's label as prefix or without. * * @param label * @return * @throws Exception */ public ContainerWrapper getChildByLabel(String label) throws Exception { ContainerTypeWrapper containerType = getContainerType(); if (containerType == null) { throw new Exception("container type is null"); //$NON-NLS-1$ } if (label.startsWith(getLabel())) { label = label.substring(getLabel().length()); } RowColPos pos = getPositionFromLabelingScheme(label); return getChild(pos); } public void addChild(Integer row, Integer col, ContainerWrapper child) throws BiobankCheckException { RowColPos rowColPos = new RowColPos(row, col); checkPositionValid(rowColPos); ContainerWrapper containerAtPosition = getChild(rowColPos); if (containerAtPosition != null && !containerAtPosition.equals(child)) { String label = getFullInfoLabel(); String existingContainerLabel = containerAtPosition.getLabel(); String msg = MessageFormat.format(CONTAINER_AT_POSITION_MSG, label, existingContainerLabel, rowColPos); throw new BiobankCheckException(msg); } child.setParentInternal(this, rowColPos); getChildren().put(rowColPos, child); } /** * Add a child in this container * * @param positionString position where the child should be added. e.g. AA * or B12 or 15 * @param child * @throws Exception */ public void addChild(String positionString, ContainerWrapper child) throws Exception { RowColPos rowColPos = getPositionFromLabelingScheme(positionString); addChild(rowColPos.getRow(), rowColPos.getCol(), child); } /** * Return true if this container can hold the type of sample * * @throws Exception if the sample type is null. */ public boolean canHoldSpecimenType(SpecimenWrapper specimen) throws Exception { SpecimenTypeWrapper type = specimen.getSpecimenType(); if (type == null) { throw new BiobankCheckException("specimen type is null"); //$NON-NLS-1$ } return getContainerType().getSpecimenTypeCollection(false).contains( type); } @Deprecated public void moveSpecimens(ContainerWrapper destination) throws Exception { Map<RowColPos, SpecimenWrapper> aliquots = getSpecimens(); for (Entry<RowColPos, SpecimenWrapper> e : aliquots.entrySet()) { destination.addSpecimen(e.getKey().getRow(), e.getKey().getCol(), e.getValue()); } destination.persist(); } /** * Get containers with a given label that can hold this type of container * (in this container site) * * @throws BiobankException */ public List<ContainerWrapper> getPossibleParents(String childLabel) throws ApplicationException { return getPossibleParents(appService, childLabel, getSite(), getContainerType()); } private static final String POSSIBLE_PARENTS_BASE_QRY = "select distinct(c) from " //$NON-NLS-1$ + Container.class.getName() + " as c left join c." //$NON-NLS-1$ + Property.concatNames(ContainerPeer.CONTAINER_TYPE, ContainerTypePeer.CHILD_CONTAINER_TYPES) + " as ct where c." //$NON-NLS-1$ + ContainerPeer.SITE.getName() + "=? and c." //$NON-NLS-1$ + ContainerPeer.LABEL.getName() + " in ("; //$NON-NLS-1$ /** * Get containers with a given label that can have a child (container or * specimen) with label 'childLabel'. If child is not null and is a * container, then will check that the parent can contain this type of * container * * @param type if the child is a container, this is its type (if available) * @throws BiobankException */ public static List<ContainerWrapper> getPossibleParents( WritableApplicationService appService, String childLabel, SiteWrapper site, ContainerTypeWrapper type) throws ApplicationException { List<Integer> validLengths = ContainerLabelingSchemeWrapper .getPossibleLabelLength(appService); List<String> validParents = new ArrayList<String>(); for (Integer crop : validLengths) if (crop < childLabel.length()) validParents .add(new StringBuilder("'") //$NON-NLS-1$ .append( childLabel.substring(0, childLabel.length() - crop)) .append("'").toString()); //$NON-NLS-1$ List<ContainerWrapper> filteredWrappers = new ArrayList<ContainerWrapper>(); if (validParents.size() > 0) { List<Object> params = new ArrayList<Object>(); params.add(site.getWrappedObject()); StringBuilder parentQuery = new StringBuilder( POSSIBLE_PARENTS_BASE_QRY).append( StringUtil.join(validParents, ",")).append(")"); //$NON-NLS-1$ //$NON-NLS-2$ if (type != null) { parentQuery.append(" and ct.id=?"); //$NON-NLS-1$ params.add(type.getId()); } HQLCriteria criteria = new HQLCriteria(parentQuery.toString(), params); List<Container> containers = appService.query(criteria); for (Container c : containers) { ContainerTypeWrapper ct = new ContainerTypeWrapper(appService, c.getContainerType()); try { if (ct.getRowColFromPositionString(childLabel.substring(c .getLabel().length())) != null) filteredWrappers .add(new ContainerWrapper(appService, c)); } catch (Exception e) { // can't throw an exception: it means that this label is not // possible in this parent. // Maybe the next one in the list is ok } } } return filteredWrappers; } @SuppressWarnings("nls") private static final String EMPTY_CONTAINERS_HOLDING_SPECIMEN_TYPE_BASE_QRY = "from " + Container.class.getName() + " where " + Property.concatNames(ContainerPeer.SITE, SitePeer.ID) + "=? and " //$NON-NLS-1$ + ContainerPeer.SPECIMEN_POSITIONS.getName() + ".size = 0 and " //$NON-NLS-1$ + Property.concatNames(ContainerPeer.CONTAINER_TYPE, ContainerTypePeer.CAPACITY, CapacityPeer.ROW_CAPACITY) + " >= ? and " + Property.concatNames(ContainerPeer.CONTAINER_TYPE, ContainerTypePeer.CAPACITY, CapacityPeer.COL_CAPACITY) + " >= ? and " + Property.concatNames(ContainerPeer.CONTAINER_TYPE, ContainerTypePeer.ID) + " in (select ct." + ContainerTypePeer.ID.getName() + " from " + ContainerType.class.getName() + " as ct left join ct." //$NON-NLS-1$ + ContainerTypePeer.SPECIMEN_TYPES.getName() + " as sampleType where sampleType." //$NON-NLS-1$ + SpecimenTypePeer.ID.getName() + " in ("; //$NON-NLS-1$ /** * Retrieve a list of empty containers in a specific site. These containers * should be able to hold specimens of type specimen type and should have a * row capacity equals or greater than minRwCapacity and a column capacity * equal or greater than minColCapacity. * * @param appService * @param siteWrapper * @param sampleTypes list of sample types the container should be able to * contain * @param minRowCapacity min row capacity * @param minColCapacity min col capacity */ public static List<ContainerWrapper> getEmptyContainersHoldingSpecimenType( WritableApplicationService appService, SiteWrapper siteWrapper, List<SpecimenTypeWrapper> sampleTypes, Integer minRowCapacity, Integer minColCapacity) throws ApplicationException { List<Integer> typeIds = new ArrayList<Integer>(); for (int i = 0; i < sampleTypes.size(); i++) { SpecimenTypeWrapper st = sampleTypes.get(i); typeIds.add(st.getId()); } String qry = new StringBuilder( EMPTY_CONTAINERS_HOLDING_SPECIMEN_TYPE_BASE_QRY) .append(StringUtil.join(typeIds, ",")).append("))").toString(); //$NON-NLS-1$ //$NON-NLS-2$ HQLCriteria criteria = new HQLCriteria(qry, Arrays.asList(new Object[] { siteWrapper.getId(), minRowCapacity, minColCapacity })); List<Container> containers = appService.query(criteria); return wrapModelCollection(appService, containers, ContainerWrapper.class); } private static final String CONTAINERS_IN_SITE_QRY = "from " //$NON-NLS-1$ + Container.class.getName() + " where " //$NON-NLS-1$ + Property.concatNames(ContainerPeer.SITE, SitePeer.ID) + "=? and " //$NON-NLS-1$ + ContainerPeer.LABEL.getName() + "=?"; //$NON-NLS-1$ /** * Get all containers form a given site with a given label */ public static List<ContainerWrapper> getContainersInSite( WritableApplicationService appService, SiteWrapper siteWrapper, String label) throws ApplicationException { HQLCriteria criteria = new HQLCriteria(CONTAINERS_IN_SITE_QRY, Arrays.asList(new Object[] { siteWrapper.getId(), label })); List<Container> containers = appService.query(criteria); return wrapModelCollection(appService, containers, ContainerWrapper.class); } private static final String CONTAINERS_BY_LABEL = "from " //$NON-NLS-1$ + Container.class.getName() + " where " + ContainerPeer.LABEL.getName() //$NON-NLS-1$ + "=?"; //$NON-NLS-1$ /** * Get all containers with a given label */ public static List<ContainerWrapper> getContainersByLabel( WritableApplicationService appService, String label) throws ApplicationException { HQLCriteria criteria = new HQLCriteria(CONTAINERS_BY_LABEL, Arrays.asList(new Object[] { label })); List<Container> containers = appService.query(criteria); return wrapModelCollection(appService, containers, ContainerWrapper.class); } private static final String CONTAINER_WITH_PRODUCT_BARCODE_IN_SITE_QRY = "from " //$NON-NLS-1$ + Container.class.getName() + " where " //$NON-NLS-1$ + Property.concatNames(ContainerPeer.SITE, SitePeer.ID) + "=? and " //$NON-NLS-1$ + ContainerPeer.PRODUCT_BARCODE.getName() + "=?"; //$NON-NLS-1$ /** * Get the container with the given productBarcode in a site */ public static ContainerWrapper getContainerWithProductBarcodeInSite( WritableApplicationService appService, SiteWrapper siteWrapper, String productBarcode) throws Exception { HQLCriteria criteria = new HQLCriteria( CONTAINER_WITH_PRODUCT_BARCODE_IN_SITE_QRY, Arrays.asList(new Object[] { siteWrapper.getId(), productBarcode })); List<Container> containers = appService.query(criteria); if (containers.size() == 0) { return null; } else if (containers.size() > 1) { throw new Exception( MessageFormat.format( Messages .getString("ContainerWrapper.multiple.containers.product.barcode.error.msg"), //$NON-NLS-1$ productBarcode)); } return new ContainerWrapper(appService, containers.get(0)); } /** * Initialise children at given position with the given type. If the * positions list is null, initialise all the children. <strong>If a * position is already filled then it is skipped and no changes are made to * it</strong>. * * @return true if at least one children has been initialised * @throws BiobankCheckException * @throws WrapperException * @throws ApplicationException */ public void initChildrenWithType(ContainerTypeWrapper type, Set<RowColPos> positions) throws Exception { if (positions == null) { for (int i = 0; i < getContainerType().getRowCapacity().intValue(); i++) { for (int j = 0; j < getContainerType().getColCapacity() .intValue(); j++) { initPositionIfEmpty(type, i, j); } } } else { for (RowColPos rcp : positions) { initPositionIfEmpty(type, rcp.getRow(), rcp.getCol()); } } reload(); } @Deprecated private void initPositionIfEmpty(ContainerTypeWrapper type, int i, int j) throws Exception { if (type == null) { throw new Exception( "Error initializing container. That is not a valid container type."); //$NON-NLS-1$ } Boolean filled = (getChild(i, j) != null); if (!filled) { ContainerWrapper newContainer = new ContainerWrapper(appService); newContainer.setContainerType(type); newContainer.setSite(getSite()); newContainer.setParent(this, new RowColPos(i, j)); newContainer.setActivityStatus(ActivityStatus.ACTIVE); newContainer.persist(); } } /** * Delete the children at positions of this container with the given type * (or all if positions list is null)- If type== null, delete all types. * * @return true if at least one children has been deleted * @throws Exception * @throws BiobankCheckException */ public boolean deleteChildrenWithType(ContainerTypeWrapper type, Set<RowColPos> positions) throws BiobankCheckException, Exception { boolean oneChildrenDeleted = false; if (positions == null) { for (ContainerWrapper child : getChildren().values()) { oneChildrenDeleted = deleteChild(type, child); } } else { for (RowColPos rcp : positions) { ContainerWrapper child = getChild(rcp); if (child != null) { oneChildrenDeleted = deleteChild(type, child); } } } // TODO: instead of reloading, remove children after they're deleted? // TODO: delete as a transaction instead of individually? reload(); return oneChildrenDeleted; } @Deprecated private boolean deleteChild(ContainerTypeWrapper type, ContainerWrapper child) throws Exception { if (type == null || child.getContainerType().equals(type)) { child.delete(); return true; } return false; } @Override public int compareTo(ModelWrapper<Container> wrapper) { if (wrapper instanceof ContainerWrapper) { String c1Label = wrappedObject.getLabel(); String c2Label = wrapper.wrappedObject.getLabel(); return c1Label.compareTo(c2Label); } return 0; } // @Override // public String toString() { // return getLabel() + " (" + getProductBarcode() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ // } @Override protected void resetInternalFields() { specimens = null; children = null; updateChildren = false; } /** * @return true if there is no free position for a new child container * @throws ApplicationException * @throws BiobankCheckException */ public boolean isContainerFull() throws BiobankException, ApplicationException { return (this.getChildCount(true) == this.getContainerType() .getRowCapacity() * this.getContainerType().getColCapacity()); } /** * Search possible parents from the position text. * * @param positionText the position to use for initialisation * @param isContainerPosition if true, the position is a full container * position, if false, it is a full specimen position * @param contType if is a container position, will check the type can be * used * @throws BiobankException */ public static List<ContainerWrapper> getPossibleContainersFromPosition( BiobankApplicationService appService, SiteWrapper site, String positionText, boolean isContainerPosition, ContainerTypeWrapper contType) throws ApplicationException, BiobankException { List<ContainerWrapper> foundContainers; List<ContainerWrapper> possibles = getPossibleParents(appService, positionText, site, contType); if (isContainerPosition) foundContainers = possibles; else { foundContainers = new ArrayList<ContainerWrapper>(); // need to know if can contain specimen if this is a specimen // position for (ContainerWrapper cont : possibles) { if (cont.getContainerType().getSpecimenTypeCollection() != null && cont.getContainerType().getSpecimenTypeCollection() .size() > 0) { foundContainers.add(cont); } } } if (foundContainers.size() == 0) { List<Integer> validLengths = ContainerLabelingSchemeWrapper .getPossibleLabelLength(appService); StringBuffer res = new StringBuffer(); for (int i = 0; i < validLengths.size(); i++) { Integer crop = validLengths.get(i); if (res.length() != 0) res.append(", "); //$NON-NLS-1$ if (crop < positionText.length()) { res.append(positionText.substring(0, positionText.length() - crop)); res.append("("); res.append(positionText.substring(positionText.length() - crop)); res.append(")"); } } String errorMsg; if (contType == null) if (isContainerPosition) errorMsg = MessageFormat .format( Messages .getString("ContainerWrapper.getPossibleContainersFromPosition.error.notfound.msg"), //$NON-NLS-1$ res.toString()); else errorMsg = MessageFormat .format( Messages .getString("ContainerWrapper.getPossibleContainersFromPosition.error.notfoundSpecimenHolder.msg"), //$NON-NLS-1$ res.toString()); else errorMsg = MessageFormat .format( Messages .getString("ContainerWrapper.getPossibleContainersFromPosition.error.notfoundWithType.msg"),//$NON-NLS-1$ contType.getNameShort(), res.toString()); throw new BiobankException(errorMsg); } return foundContainers; } public boolean isPallet96() { return getContainerType().isPallet96(); } private static final String POSITION_FREE_QRY = "from " //$NON-NLS-1$ + Specimen.class.getName() + " where " //$NON-NLS-1$ + SpecimenPeer.SPECIMEN_POSITION.to(SpecimenPositionPeer.ROW).getName() + "=? and " //$NON-NLS-1$ + SpecimenPeer.SPECIMEN_POSITION.to(SpecimenPositionPeer.COL).getName() + "=? and " //$NON-NLS-1$ + SpecimenPeer.SPECIMEN_POSITION.to(SpecimenPositionPeer.CONTAINER) .getName() + "=?"; //$NON-NLS-1$ /** * Method used to check if the current position of this Specimen is * available on the container. Return true if the position is free, false * otherwise */ public boolean isPositionFree(RowColPos position) throws ApplicationException { if (position != null) { if (!isPropertyCached(ContainerPeer.CHILD_POSITIONS)) { HQLCriteria criteria = new HQLCriteria(POSITION_FREE_QRY, Arrays.asList(new Object[] { position.getRow(), position.getCol(), getWrappedObject() })); // TODO: select a count instead? List<Specimen> samples = appService.query(criteria); return samples.isEmpty(); } return !getChildren().containsKey(position); } return true; } @Deprecated @Override protected void addPersistTasks(TaskList tasks) { tasks.add(new NotNullPreCheck<Container>(this, ContainerPeer.LABEL)); tasks.add(new NotNullPreCheck<Container>(this, ContainerPeer.SITE)); tasks.add(new NotNullPreCheck<Container>(this, ContainerPeer.CONTAINER_TYPE)); tasks.add(new UniquePreCheck<Container>(this, UNIQUE_LABEL_PROPS)); tasks.add(new UniquePreCheck<Container>(this, UNIQUE_BARCODE_PROPS)); // TODO: is this next line necessary? Causes error w/ hibernate, so // allow cascade via hibernate? // tasks.deleteRemovedUnchecked(this, ContainerPeer.POSITION); super.addPersistTasks(tasks); tasks.persist(this, ContainerPeer.POSITION); // Need to update the path property after this Container and its // position have been saved to ensure that the path is calculated based // on persistent (non-new) objects. tasks.add(new UpdateContainerPathAction(this)); tasks.persistAdded(this, ContainerPeer.SPECIMEN_POSITIONS); tasks.persistAdded(this, ContainerPeer.CHILD_POSITIONS); addTasksToUpdateChildren(tasks); tasks.add(new UniqueCheck<Container>(this, UNIQUE_LABEL_PROPS)); tasks.add(new UniqueCheck<Container>(this, UNIQUE_BARCODE_PROPS)); tasks.add(new ContainerPersistChecks(this)); } @Deprecated @Override protected void addDeleteTasks(TaskList tasks) { String hasSpecimensMsg = MessageFormat.format(HAS_SPECIMENS_MSG, getLabel()); tasks.add(check().empty(ContainerPeer.SPECIMEN_POSITIONS, hasSpecimensMsg)); String hasChildrenMsg = MessageFormat.format(HAS_CHILD_CONTAINERS_MSG, getLabel()); tasks.add(check().empty(ContainerPeer.CHILD_POSITIONS, hasChildrenMsg)); // Count on Hibernate to delete-cascade this object. We can't because // there's a two-way foreign key constraint. So we could, but it's // really confusing. // tasks.add(cascade().delete(ContainerPeer.POSITION)); super.addDeleteTasks(tasks); } /** * For updating children {@link Container}'s: (1) label, (2) path, and (3) * top {@link Container} whenever the parent or label is changed. * <p> * * @return */ private void addTasksToUpdateChildren(TaskList tasks) { if (updateChildren) { // getLabel() returns the in-memory version, but the underlying // value (e.g. super.getLabel()) needs to be updated for persisting. setLabel(getLabel()); ContainerWrapper topContainer = getTopContainer(); if (isPropertyCached(ContainerPeer.CHILD_POSITIONS)) { // if the children have already been loaded, then update their // top Container so that they update their children, etc. so // that the entire subtree is consistent. List<ContainerPositionWrapper> positions = getChildPositionCollection(false); for (ContainerPositionWrapper position : positions) { ContainerWrapper child = position.getContainer(); child.setTopContainerInternal(topContainer, false); // Save children whether they're are new or not, because the // children's children could be already persistent and need // to be updated (but would then need their parent to be // persisted first). child.addPersistTasks(tasks); } } else { // Use HQL to update all descendants of this Container because // they are not loaded and loading them would be unnecessary. tasks.add(new UpdateContainerChildrenAction(this)); } tasks.add(new ResetUpdateChildrenFlagQueryTask(this)); } } private void checkPositionValid(RowColPos pos) throws BiobankCheckException { int maxRow = getRowCapacity(); int maxCol = getColCapacity(); if (pos.getRow() >= maxRow || pos.getCol() >= maxCol) { String msg = MessageFormat.format(OUT_OF_BOUNDS_POSITION_MSG, pos, maxRow, maxCol); throw new BiobankCheckException(msg); } } private static class ResetUpdateChildrenFlagQueryTask extends NoActionWrapperQueryTask<ContainerWrapper> { public ResetUpdateChildrenFlagQueryTask(ContainerWrapper container) { super(container); } @Override public void afterExecute(SDKQueryResult result) { getWrapper().updateChildren = false; } } }