diff --git a/api/src/org/labkey/api/assay/AbstractAssayProvider.java b/api/src/org/labkey/api/assay/AbstractAssayProvider.java index d4ac4acfae8..68c37827b90 100644 --- a/api/src/org/labkey/api/assay/AbstractAssayProvider.java +++ b/api/src/org/labkey/api/assay/AbstractAssayProvider.java @@ -24,7 +24,6 @@ import org.labkey.api.assay.actions.UploadWizardAction; import org.labkey.api.assay.pipeline.AssayRunAsyncContext; import org.labkey.api.assay.plate.AssayPlateMetadataService; -import org.labkey.api.assay.plate.PlateMetadataDataHandler; import org.labkey.api.assay.security.DesignAssayPermission; import org.labkey.api.audit.AuditLogService; import org.labkey.api.data.ActionButton; @@ -143,10 +142,6 @@ import static org.labkey.api.data.CompareType.IN; import static org.labkey.api.util.PageFlowUtil.jsString; -/** - * User: jeckels - * Date: Sep 14, 2007 - */ public abstract class AbstractAssayProvider implements AssayProvider { public static final String ASSAY_NAME_SUBSTITUTION = "${AssayName}"; @@ -911,15 +906,13 @@ public List>> getDomains(ExpProtocol pr sortDomainList(domains); // see if there is a plate metadata domain associated with this protocol - if (AssayPlateMetadataService.getService(PlateMetadataDataHandler.DATA_TYPE) != null) + Domain plateDomain = AssayPlateMetadataService.get().getPlateDataDomain(protocol); + if (plateDomain != null) { - Domain plateDomain = AssayPlateMetadataService.getService(PlateMetadataDataHandler.DATA_TYPE).getPlateDataDomain(protocol); - if (plateDomain != null) - { - Map values = DefaultValueService.get().getDefaultValues(plateDomain.getContainer(), plateDomain); - domains.add(new Pair<>(plateDomain, values)); - } + Map values = DefaultValueService.get().getDefaultValues(plateDomain.getContainer(), plateDomain); + domains.add(new Pair<>(plateDomain, values)); } + return domains; } diff --git a/api/src/org/labkey/api/assay/AbstractAssayTsvDataHandler.java b/api/src/org/labkey/api/assay/AbstractAssayTsvDataHandler.java index e4f547147de..cca0728077a 100644 --- a/api/src/org/labkey/api/assay/AbstractAssayTsvDataHandler.java +++ b/api/src/org/labkey/api/assay/AbstractAssayTsvDataHandler.java @@ -97,10 +97,6 @@ import static java.util.stream.Collectors.toList; import static org.labkey.api.gwt.client.ui.PropertyType.SAMPLE_CONCEPT_URI; -/** - * User: jeckels - * Date: Jan 3, 2008 - */ public abstract class AbstractAssayTsvDataHandler extends AbstractExperimentDataHandler implements ValidationDataHandler { protected static final Object ERROR_VALUE = new Object() { @@ -210,18 +206,14 @@ public Map>> getValidationDataMap(ExpData dat List> dataRows = loader.load(); boolean skipFirstRowAdjustment = dataRows.isEmpty(); - if (plateMetadataEnabled) + if (plateMetadataEnabled && AssayPlateMetadataService.isExperimentalAppPlateEnabled()) { - AssayPlateMetadataService svc = AssayPlateMetadataService.getService(PlateMetadataDataHandler.DATA_TYPE); - if (AssayPlateMetadataService.isExperimentalAppPlateEnabled() && svc != null) - { - Pair plateIdAdded = new Pair<>("PlateIdAdded", false); - Integer plateSetId = getPlateSetValueFromRunProps(context, provider, protocol); - dataRows = svc.parsePlateData(context.getContainer(), context.getUser(), provider, protocol, plateSetId, dataFile, dataRows, plateIdAdded); + Pair plateIdAdded = new Pair<>("PlateIdAdded", false); + Integer plateSetId = getPlateSetValueFromRunProps(context, provider, protocol); + dataRows = AssayPlateMetadataService.get().parsePlateData(context.getContainer(), context.getUser(), provider, protocol, plateSetId, dataFile, dataRows, plateIdAdded); - // need to do this otherwise the added plateID may get stripped - skipFirstRowAdjustment = plateIdAdded.second; - } + // need to do this otherwise the added plateID may get stripped + skipFirstRowAdjustment = plateIdAdded.second; } // loader did not parse any rows @@ -249,29 +241,27 @@ private List> mergePlateMetadata(XarContext context, AssayPr Map rawPlateMetadata, File plateMetadataFile) throws ExperimentException { - AssayPlateMetadataService svc = AssayPlateMetadataService.getService(PlateMetadataDataHandler.DATA_TYPE); - if (svc != null) + Map plateMetadata = null; + if (plateMetadataFile != null || rawPlateMetadata != null) { - Map plateMetadata = null; - if (plateMetadataFile != null || rawPlateMetadata != null) - { - plateMetadata = plateMetadataFile != null - ? svc.parsePlateMetadata(plateMetadataFile) - : rawPlateMetadata; - } + if (plateMetadataFile != null) + plateMetadata = AssayPlateMetadataService.get().parsePlateMetadata(plateMetadataFile); + else + plateMetadata = rawPlateMetadata; + } - Domain runDomain = provider.getRunDomain(protocol); - DomainProperty propertyPlateTemplate = runDomain.getPropertyByName(AssayPlateMetadataService.PLATE_TEMPLATE_COLUMN_NAME); - DomainProperty propertyPlateSet = runDomain.getPropertyByName(AssayPlateMetadataService.PLATE_SET_COLUMN_NAME); - if (propertyPlateTemplate != null || propertyPlateSet != null) - { - Map runProps = ((AssayUploadXarContext)context).getContext().getRunProperties(); - Object lsid = runProps.getOrDefault(propertyPlateTemplate, null); - Lsid templateLsid = lsid != null && !StringUtils.isEmpty(String.valueOf(lsid)) ? Lsid.parse(String.valueOf(lsid)) : null; - Integer plateSetId = getPlateSetValueFromRunProps(context, provider, protocol); - return svc.mergePlateMetadata(context.getContainer(), context.getUser(), templateLsid, plateSetId, dataRows, plateMetadata, provider, protocol); - } + Domain runDomain = provider.getRunDomain(protocol); + DomainProperty propertyPlateTemplate = runDomain.getPropertyByName(AssayPlateMetadataService.PLATE_TEMPLATE_COLUMN_NAME); + DomainProperty propertyPlateSet = runDomain.getPropertyByName(AssayPlateMetadataService.PLATE_SET_COLUMN_NAME); + if (propertyPlateTemplate != null || propertyPlateSet != null) + { + Map runProps = ((AssayUploadXarContext)context).getContext().getRunProperties(); + Object lsid = runProps.getOrDefault(propertyPlateTemplate, null); + Lsid templateLsid = lsid != null && !StringUtils.isEmpty(String.valueOf(lsid)) ? Lsid.parse(String.valueOf(lsid)) : null; + Integer plateSetId = getPlateSetValueFromRunProps(context, provider, protocol); + return AssayPlateMetadataService.get().mergePlateMetadata(context.getContainer(), context.getUser(), templateLsid, plateSetId, dataRows, plateMetadata, provider, protocol); } + return dataRows; } @@ -293,8 +283,6 @@ private Integer getPlateSetValueFromRunProps(XarContext context, AssayProvider p /** * Creates a DataLoader that can handle missing value indicators if the columns on the domain * are configured to support it. - * - * @throws ExperimentException */ public static DataLoader createLoaderForImport(File dataFile, @Nullable Domain dataDomain, DataLoaderSettings settings, boolean shouldInferTypes) throws ExperimentException { @@ -310,7 +298,7 @@ public static DataLoader createLoaderForImport(File dataFile, @Nullable Domain d { if (col.isMvEnabled()) { - // Check for all of the possible names for the column in the incoming data when deciding if we should + // Check for all possible names for the column in the incoming data when deciding if we should // check it for missing values Set columnAliases = ImportAliasable.Helper.createImportMap(Collections.singletonList(col), false).keySet(); mvEnabledColumns.addAll(columnAliases); @@ -689,22 +677,16 @@ private void addAssayPlateMetadata( if (datas.size() == 1) { ExpData plateData = datas.get(0); - AssayPlateMetadataService svc = AssayPlateMetadataService.getService((AssayDataType)plateData.getDataType()); - if (svc != null) - { - Map plateMetadata; - - if (plateData.getFile() != null) - plateMetadata = svc.parsePlateMetadata(plateData.getFile()); - else if (getRawPlateMetadata() != null) - plateMetadata = getRawPlateMetadata(); - else - throw new ExperimentException("There was no plate metadata JSON available for this run"); + Map plateMetadata; - svc.addAssayPlateMetadata(resultData, plateMetadata, container, user, run, provider, protocol, inserted, rowIdToLsidMap); - } + if (plateData.getFile() != null) + plateMetadata = AssayPlateMetadataService.get().parsePlateMetadata(plateData.getFile()); + else if (getRawPlateMetadata() != null) + plateMetadata = getRawPlateMetadata(); else - throw new ExperimentException("No PlateMetadataService registered for data type : " + plateData.getDataType().toString()); + throw new ExperimentException("There was no plate metadata JSON available for this run"); + + AssayPlateMetadataService.get().addAssayPlateMetadata(resultData, plateMetadata, container, user, run, provider, protocol, inserted, rowIdToLsidMap); } else { @@ -720,32 +702,31 @@ protected ParticipantVisitResolver createResolver(User user, ExpRun run, ExpProt return AssayService.get().createResolver(user, run, protocol, provider, null); } - /** Insert the data into the database. Transaction is active. */ - protected List> insertRowData(ExpData data, User user, Container container, ExpRun run, ExpProtocol protocol, AssayProvider provider,Domain dataDomain, List> fileData, TableInfo tableInfo, boolean autoFillDefaultColumns) - throws SQLException, ValidationException, ExperimentException + /** Insert the data into the database. Transaction is active. */ + protected List> insertRowData( + ExpData data, + User user, + Container container, + ExpRun run, + ExpProtocol protocol, + AssayProvider provider, + Domain dataDomain, + List> fileData, + TableInfo tableInfo, + boolean autoFillDefaultColumns + ) throws SQLException, ValidationException, ExperimentException { OntologyManager.UpdateableTableImportHelper importHelper = new SimpleAssayDataImportHelper(data); if (provider.isPlateMetadataEnabled(protocol)) - { - AssayPlateMetadataService svc = AssayPlateMetadataService.getService(PlateMetadataDataHandler.DATA_TYPE); - if (svc != null) - { - importHelper = svc.getImportHelper(container, user, run, data, protocol, provider); - } - } + importHelper = AssayPlateMetadataService.get().getImportHelper(container, user, run, data, protocol, provider); if (tableInfo instanceof UpdateableTableInfo) - { return OntologyManager.insertTabDelimited(tableInfo, container, user, importHelper, fileData, autoFillDefaultColumns, LOG); - } - else - { - Integer id = OntologyManager.ensureObject(container, data.getLSID()); - List lsids = OntologyManager.insertTabDelimited(container, user, id, - importHelper, dataDomain, fileData, false); - // TODO: Add LSID values into return value rows - return fileData; - } + + Integer id = OntologyManager.ensureObject(container, data.getLSID()); + OntologyManager.insertTabDelimited(container, user, id, importHelper, dataDomain, fileData, false); + + return fileData; } protected abstract boolean shouldAddInputMaterials(); diff --git a/api/src/org/labkey/api/assay/DefaultAssayRunCreator.java b/api/src/org/labkey/api/assay/DefaultAssayRunCreator.java index c7cc27f94ce..4f0042d0c27 100644 --- a/api/src/org/labkey/api/assay/DefaultAssayRunCreator.java +++ b/api/src/org/labkey/api/assay/DefaultAssayRunCreator.java @@ -99,10 +99,6 @@ import static java.util.Collections.unmodifiableCollection; -/** - * User: jeckels - * Date: Oct 12, 2011 - */ public class DefaultAssayRunCreator implements AssayRunCreator { private static final Logger LOG = LogManager.getLogger(DefaultAssayRunCreator.class); @@ -164,7 +160,7 @@ public Pair saveExperimentRun( exp = saveExperimentRun(context, exp, run, false); - // re-fetch the run after is has been fully constructed + // re-fetch the run after it has been fully constructed run = ExperimentService.get().getExpRun(run.getRowId()); context.uploadComplete(run); diff --git a/api/src/org/labkey/api/assay/SimpleAssayDataImportHelper.java b/api/src/org/labkey/api/assay/SimpleAssayDataImportHelper.java index b1219032bc4..21203afa376 100644 --- a/api/src/org/labkey/api/assay/SimpleAssayDataImportHelper.java +++ b/api/src/org/labkey/api/assay/SimpleAssayDataImportHelper.java @@ -16,7 +16,6 @@ package org.labkey.api.assay; -import org.labkey.api.data.Parameter; import org.labkey.api.data.ParameterMapStatement; import org.labkey.api.exp.OntologyManager; import org.labkey.api.exp.api.ExpData; @@ -24,10 +23,6 @@ import java.util.Map; -/** - * User: jeckels - * Date: Jul 23, 2007 - */ public class SimpleAssayDataImportHelper implements OntologyManager.ImportHelper, OntologyManager.UpdateableTableImportHelper { private int _id = 0; diff --git a/api/src/org/labkey/api/assay/plate/AssayPlateMetadataService.java b/api/src/org/labkey/api/assay/plate/AssayPlateMetadataService.java index b6e8283fd87..1ed0ce5dd09 100644 --- a/api/src/org/labkey/api/assay/plate/AssayPlateMetadataService.java +++ b/api/src/org/labkey/api/assay/plate/AssayPlateMetadataService.java @@ -3,7 +3,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.json.JSONObject; -import org.labkey.api.assay.AssayDataType; import org.labkey.api.assay.AssayProvider; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; @@ -15,11 +14,11 @@ import org.labkey.api.exp.api.ExpRun; import org.labkey.api.exp.property.Domain; import org.labkey.api.security.User; +import org.labkey.api.services.ServiceRegistry; import org.labkey.api.settings.ExperimentalFeatureService; import org.labkey.api.util.Pair; import java.io.File; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -27,17 +26,11 @@ public interface AssayPlateMetadataService { String PLATE_TEMPLATE_COLUMN_NAME = "PlateTemplate"; String PLATE_SET_COLUMN_NAME = "PlateSet"; - Map _handlers = new HashMap<>(); String EXPERIMENTAL_APP_PLATE_SUPPORT = "experimental-app-plate-support"; - static void registerService(AssayDataType dataType, AssayPlateMetadataService handler) + static void setInstance(AssayPlateMetadataService serviceImpl) { - if (dataType != null) - { - _handlers.put(dataType, handler); - } - else - throw new RuntimeException("The specified assay data type is null"); + ServiceRegistry.get().registerService(AssayPlateMetadataService.class, serviceImpl); } static boolean isExperimentalAppPlateEnabled() @@ -53,10 +46,9 @@ static boolean isBiologicsFolder(Container container) return false; } - @Nullable - static AssayPlateMetadataService getService(AssayDataType dataType) + static AssayPlateMetadataService get() { - return _handlers.get(dataType); + return ServiceRegistry.get().getService(AssayPlateMetadataService.class); } /** @@ -75,17 +67,33 @@ static AssayPlateMetadataService getService(AssayDataType dataType) * @param inserted the inserted result data * @param rowIdToLsidMap a map of result data rowIds to result data lsids */ - void addAssayPlateMetadata(ExpData resultData, Map plateMetadata, Container container, User user, ExpRun run, AssayProvider provider, - ExpProtocol protocol, List> inserted, Map rowIdToLsidMap) throws ExperimentException; + void addAssayPlateMetadata( + ExpData resultData, + Map plateMetadata, + Container container, + User user, + ExpRun run, + AssayProvider provider, + ExpProtocol protocol, + List> inserted, + Map rowIdToLsidMap + ) throws ExperimentException; /** * Merges the results data with the plate metadata to produce a single row map * * @return the merged rows */ - List> mergePlateMetadata(Container container, User user, Lsid plateLsid, Integer plateSetId, - List> rows, @Nullable Map plateMetadata, - AssayProvider provider, ExpProtocol protocol) throws ExperimentException; + List> mergePlateMetadata( + Container container, + User user, + Lsid plateLsid, + Integer plateSetId, + List> rows, + @Nullable Map plateMetadata, + AssayProvider provider, + ExpProtocol protocol + ) throws ExperimentException; /** * Methods to create the metadata model from either a JSON object or a file object @@ -98,14 +106,14 @@ List> mergePlateMetadata(Container container, User user, Lsi * well as cases where plate identifiers have not been supplied. */ List> parsePlateData( - Container container, - User user, - AssayProvider provider, - ExpProtocol protocol, - Integer plateSetId, - File dataFile, - List> data, - Pair plateIdAdded + Container container, + User user, + AssayProvider provider, + ExpProtocol protocol, + Integer plateSetId, + File dataFile, + List> data, + Pair plateIdAdded ) throws ExperimentException; /** @@ -113,7 +121,14 @@ List> parsePlateData( * with the plate used in the assay run import */ @NotNull - OntologyManager.UpdateableTableImportHelper getImportHelper(Container container, User user, ExpRun run, ExpData data, ExpProtocol protocol, AssayProvider provider) throws ExperimentException; + OntologyManager.UpdateableTableImportHelper getImportHelper( + Container container, + User user, + ExpRun run, + ExpData data, + ExpProtocol protocol, + AssayProvider provider + ) throws ExperimentException; interface MetadataLayer { diff --git a/api/src/org/labkey/api/qc/TsvDataExchangeHandler.java b/api/src/org/labkey/api/qc/TsvDataExchangeHandler.java index 14d72c13f2a..14160b40525 100644 --- a/api/src/org/labkey/api/qc/TsvDataExchangeHandler.java +++ b/api/src/org/labkey/api/qc/TsvDataExchangeHandler.java @@ -31,7 +31,6 @@ import org.labkey.api.assay.actions.AssayRunUploadForm; import org.labkey.api.assay.actions.ProtocolIdForm; import org.labkey.api.assay.plate.AssayPlateMetadataService; -import org.labkey.api.assay.plate.PlateMetadataDataHandler; import org.labkey.api.data.Container; import org.labkey.api.data.TSVWriter; import org.labkey.api.exp.ExperimentDataHandler; @@ -307,7 +306,7 @@ protected Set _writeRunData(AssayRunUploadContext context, ExpRun run, Fil File runData = new File(scriptDir, RUN_DATA_FILE); result.add(runData); - AssayPlateMetadataService svc = AssayPlateMetadataService.getService(PlateMetadataDataHandler.DATA_TYPE); + AssayPlateMetadataService svc = AssayPlateMetadataService.get(); if (svc != null) { ExpProtocol protocol = run.getProtocol(); diff --git a/assay/api-src/org/labkey/api/assay/DefaultAssaySaveHandler.java b/assay/api-src/org/labkey/api/assay/DefaultAssaySaveHandler.java index 0cfc96e6061..f7e0a936375 100644 --- a/assay/api-src/org/labkey/api/assay/DefaultAssaySaveHandler.java +++ b/assay/api-src/org/labkey/api/assay/DefaultAssaySaveHandler.java @@ -298,17 +298,13 @@ protected AssayRunUploadContext createRunUploadContext(ViewContext context, ExpP if (rawPlateMetadata != null) { - AssayPlateMetadataService svc = AssayPlateMetadataService.getService(PlateMetadataDataHandler.DATA_TYPE); - if (svc != null) + try { - try - { - factory.setRawPlateMetadata(svc.parsePlateMetadata(rawPlateMetadata)); - } - catch(ExperimentException e) - { - throw new RuntimeException(e); - } + factory.setRawPlateMetadata(AssayPlateMetadataService.get().parsePlateMetadata(rawPlateMetadata)); + } + catch (ExperimentException e) + { + throw new RuntimeException(e); } } factory.setInputDatas(inputData); diff --git a/assay/api-src/org/labkey/api/assay/plate/AbstractPlateLayoutHandler.java b/assay/api-src/org/labkey/api/assay/plate/AbstractPlateLayoutHandler.java index 77a19b68355..599d76350a6 100644 --- a/assay/api-src/org/labkey/api/assay/plate/AbstractPlateLayoutHandler.java +++ b/assay/api-src/org/labkey/api/assay/plate/AbstractPlateLayoutHandler.java @@ -26,16 +26,12 @@ import java.util.Map; import java.util.stream.Collectors; -/** - * User: klum - * Date: Jun 13, 2012 - */ public abstract class AbstractPlateLayoutHandler implements PlateLayoutHandler { Map, PlateType> _plateTypeMap; @Override - public void validateTemplate(Container container, User user, Plate template) throws ValidationException + public void validatePlate(Container container, User user, Plate plate) throws ValidationException { } diff --git a/assay/api-src/org/labkey/api/assay/plate/Plate.java b/assay/api-src/org/labkey/api/assay/plate/Plate.java index 38157f4ad43..7157c20dad2 100644 --- a/assay/api-src/org/labkey/api/assay/plate/Plate.java +++ b/assay/api-src/org/labkey/api/assay/plate/Plate.java @@ -38,6 +38,8 @@ public interface Plate extends PropertySet, Identifiable int getPlateNumber(); + boolean isArchived(); + boolean isTemplate(); @NotNull PlateType getPlateType(); @@ -72,7 +74,7 @@ public interface Plate extends PropertySet, Identifiable @NotNull WellGroup addWellGroup(String name, WellGroup.Type type, List positions); - @Nullable Integer getRowId(); + Integer getRowId(); @NotNull Position getPosition(int row, int col); @@ -100,4 +102,6 @@ public interface Plate extends PropertySet, Identifiable @Nullable Integer getRunCount(); boolean isIdentifierMatch(String id); + + boolean isNew(); } diff --git a/assay/api-src/org/labkey/api/assay/plate/PlateLayoutHandler.java b/assay/api-src/org/labkey/api/assay/plate/PlateLayoutHandler.java index 1f3055f7f23..8a4c1e0ee1f 100644 --- a/assay/api-src/org/labkey/api/assay/plate/PlateLayoutHandler.java +++ b/assay/api-src/org/labkey/api/assay/plate/PlateLayoutHandler.java @@ -31,16 +31,15 @@ */ public interface PlateLayoutHandler { - @NotNull - String getAssayType(); + @NotNull String getAssayType(); @NotNull List getLayoutTypes(PlateType plateType); /** - * createTemplate will be given a null value for templateTypeName when it is creating a new template which is a + * createPlate will be given a null value for plateName when it is creating a new plate which is a * default for that assay type. */ - Plate createTemplate(@Nullable String templateTypeName, Container container, @NotNull PlateType plateType) throws SQLException; + Plate createPlate(@Nullable String plateName, Container container, @NotNull PlateType plateType) throws SQLException; List getSupportedPlateTypes(); @@ -52,9 +51,9 @@ public interface PlateLayoutHandler boolean canCreateNewGroups(WellGroup.Type type); /** - * Validate a new or edited plate template for handler specific errors. + * Validate a new or edited plate for handler specific errors. */ - void validateTemplate(Container container, User user, Plate template) throws ValidationException; + void validatePlate(Container container, User user, Plate plate) throws ValidationException; Map> getDefaultGroupsForTypes(); diff --git a/assay/api-src/org/labkey/api/assay/plate/PlateService.java b/assay/api-src/org/labkey/api/assay/plate/PlateService.java index 05d58e058ce..d8fe38bc8a5 100644 --- a/assay/api-src/org/labkey/api/assay/plate/PlateService.java +++ b/assay/api-src/org/labkey/api/assay/plate/PlateService.java @@ -80,7 +80,8 @@ static PlateService get() @Nullable Plate createPlate(Plate plate, double[][] wellValues, boolean[][] excludedWells); /** - * Creates a new plate + * Instantiates a new plate instance. + * This plate is not persisted to the database. * @param container The template's container. * @param templateType The type of plate, if associated with a particular assay. * @param plateType Specifies the overall shape of the plate @@ -89,16 +90,6 @@ static PlateService get() */ @NotNull Plate createPlate(Container container, String templateType, @NotNull PlateType plateType); - /** - * Creates a new plate template. - * @param container The template's container. - * @param templateType The type of plate template, if associated with a particular assay. - * @param plateType Specifies the overall shape of the plate - * @return A newly created plate template instance. - * @throws IllegalArgumentException Thrown if a template of the specified name already exists in the container. - */ - @NotNull Plate createPlateTemplate(Container container, String templateType, @NotNull PlateType plateType); - /** * Adds a new well group to the plate * @param plate A plate instance object. @@ -165,14 +156,7 @@ static PlateService get() */ @Nullable Plate getPlate(ContainerFilter cf, Integer plateSetId, Object plateIdentifier); - /** - * Gets all plate templates for the specified container. Plate templates are Plate instances - * which have their template field set to TRUE. - * - * @return An array of all plates that are configured as templates from the specified container. - */ - @NotNull - List getPlateTemplates(Container container); + @NotNull List getPlates(Container container); /** * Gets the plate set by ID @@ -231,8 +215,8 @@ static PlateService get() Position createPosition(Container container, int row, int column); /** - * Deletes all plate and template data from the specified container. Typically used - * only when a container is deleted. + * Deletes all plate and template data from the specified container. + * Typically used only when a container is deleted. * @param container The container from which to delete all plate data. */ void deleteAllPlateData(Container container); @@ -250,17 +234,6 @@ static PlateService get() */ void registerDetailsLinkResolver(PlateDetailsResolver resolver); - /** - * Copies a plate from one container to another. - * @param source The source plate template - * @param user The user performing the copy - * @param destination The destination container - * @return The copied plate template - * @throws SQLException Thrown in the event of a database failure. - * @throws NameConflictException Thrown if the destination container already contains a template by the same name. - */ - Plate copyPlate(Plate source, User user, Container destination) throws Exception; - /** * Registers a handler for a particular type of plate */ diff --git a/assay/api-src/org/labkey/api/assay/plate/PlateSet.java b/assay/api-src/org/labkey/api/assay/plate/PlateSet.java index e00e8ce9ab2..1c5a45d0f59 100644 --- a/assay/api-src/org/labkey/api/assay/plate/PlateSet.java +++ b/assay/api-src/org/labkey/api/assay/plate/PlateSet.java @@ -19,6 +19,8 @@ public interface PlateSet boolean isArchived(); + boolean isTemplate(); + List getPlates(User user); PlateSetType getType(); diff --git a/assay/api-src/org/labkey/api/assay/plate/PlateUtils.java b/assay/api-src/org/labkey/api/assay/plate/PlateUtils.java index ea275f15209..a35cb165df6 100644 --- a/assay/api-src/org/labkey/api/assay/plate/PlateUtils.java +++ b/assay/api-src/org/labkey/api/assay/plate/PlateUtils.java @@ -389,8 +389,8 @@ public static Location parseLocation(String description) public static class Location { - private int _row; - private int _col; + private final int _row; + private final int _col; public Location(int row, int col) { diff --git a/assay/resources/schemas/assay.xml b/assay/resources/schemas/assay.xml index beeff84f7d6..f3e64424b9b 100644 --- a/assay/resources/schemas/assay.xml +++ b/assay/resources/schemas/assay.xml @@ -90,6 +90,8 @@ Boolean indicating whether each plate is a template versus an uploaded instance of a plate template. + true + false false @@ -99,6 +101,10 @@ A text label of the plate assay type ("NAb", for example). + + true + false + @@ -210,6 +216,10 @@ true + + true + false +
diff --git a/assay/resources/schemas/dbscripts/postgresql/assay-24.006-24.007.sql b/assay/resources/schemas/dbscripts/postgresql/assay-24.006-24.007.sql new file mode 100644 index 00000000000..2c1f6337fc3 --- /dev/null +++ b/assay/resources/schemas/dbscripts/postgresql/assay-24.006-24.007.sql @@ -0,0 +1,7 @@ +ALTER TABLE assay.PlateSet ADD COLUMN Template BOOLEAN NOT NULL DEFAULT FALSE; +UPDATE assay.Plate SET Template = False WHERE Template = True; + +ALTER TABLE assay.Plate ADD COLUMN Archived BOOLEAN NOT NULL DEFAULT FALSE; + +UPDATE assay.Plate SET AssayType = 'Standard' WHERE AssayType IS NULL; +ALTER TABLE assay.Plate ALTER COLUMN AssayType SET NOT NULL; diff --git a/assay/resources/schemas/dbscripts/sqlserver/assay-24.006-24.007.sql b/assay/resources/schemas/dbscripts/sqlserver/assay-24.006-24.007.sql new file mode 100644 index 00000000000..0e2d1c99d14 --- /dev/null +++ b/assay/resources/schemas/dbscripts/sqlserver/assay-24.006-24.007.sql @@ -0,0 +1,7 @@ +ALTER TABLE assay.PlateSet ADD Template BIT NOT NULL DEFAULT 0; +UPDATE assay.Plate SET Template = 0 WHERE Template = 1; + +ALTER TABLE assay.Plate ADD Archived BIT NOT NULL DEFAULT 0; + +UPDATE assay.Plate SET AssayType = 'Standard' WHERE AssayType IS NULL; +ALTER TABLE assay.Plate ALTER COLUMN AssayType NVARCHAR(200) NOT NULL; diff --git a/assay/src/org/labkey/assay/AssayDomainServiceImpl.java b/assay/src/org/labkey/assay/AssayDomainServiceImpl.java index b068da721f6..a73a6216fe3 100644 --- a/assay/src/org/labkey/assay/AssayDomainServiceImpl.java +++ b/assay/src/org/labkey/assay/AssayDomainServiceImpl.java @@ -85,11 +85,6 @@ import java.util.Map; import java.util.Set; -/** - * User: brittp - * Date: Jun 22, 2007 - * Time: 10:01:10 AM - */ public class AssayDomainServiceImpl extends BaseRemoteService implements AssayDomainService { public static final Logger LOG = LogManager.getLogger(AssayDomainServiceImpl.class); @@ -332,7 +327,7 @@ private void setPlateTemplateList(AssayProvider provider, GWTProtocol protocol) if (provider instanceof PlateBasedAssayProvider) { List plateTemplates = new ArrayList<>(); - for (Plate template : PlateService.get().getPlateTemplates(getContainer())) + for (Plate template : PlateService.get().getPlates(getContainer())) plateTemplates.add(template.getName()); protocol.setAvailablePlateTemplates(plateTemplates); } @@ -378,232 +373,225 @@ public GWTProtocol saveChanges(GWTProtocol assay, boolean replaceIfExisting) thr // time, which will lead to a SQLException on the UNIQUE constraint on protocol LSIDs synchronized (AssayDomainServiceImpl.class) { - if (replaceIfExisting) + if (!replaceIfExisting) + throw new AssayException("Only replaceIfExisting == true is supported."); + + DbSchema schema = AssayDbSchema.getInstance().getSchema(); + try (DbScope.Transaction transaction = schema.getScope().ensureTransaction()) { - DbSchema schema = AssayDbSchema.getInstance().getSchema(); - try (DbScope.Transaction transaction = schema.getScope().ensureTransaction()) + if (assay.getAutoLinkCategory() != null && assay.getAutoLinkCategory().length() > 200) + throw new AssayException("Linked Dataset Category name must be shorter than 200 characters."); + + ExpProtocol protocol; + boolean isNew = assay.getProtocolId() == null; + boolean hasNameChange = false; + AssayProvider assayProvider = AssayService.get().getProvider(assay.getProviderName()); + String oldAssayName = null; + if (isNew) { - if (assay.getAutoLinkCategory() != null && assay.getAutoLinkCategory().length() > 200) - throw new AssayException("Linked Dataset Category name must be shorter than 200 characters."); - - ExpProtocol protocol; - boolean isNew = assay.getProtocolId() == null; - boolean hasNameChange = false; - AssayProvider assayProvider = AssayService.get().getProvider(assay.getProviderName()); - String oldAssayName = null; - if (isNew) - { - // check for existing assay protocol with the given name before creating - if (AssayManager.get().getAssayProtocolByName(getContainer(), assay.getName()) != null) - throw new AssayException("Assay protocol already exists for this name."); - - XarContext context = new XarContext("Domains", getContainer(), getUser()); - context.addSubstitution("AssayName", PageFlowUtil.encode(assay.getName())); + // check for existing assay protocol with the given name before creating + if (AssayManager.get().getAssayProtocolByName(getContainer(), assay.getName()) != null) + throw new AssayException("Assay protocol already exists for this name."); - protocol = AssayManager.get().createAssayDefinition(getUser(), getContainer(), assay, context); - assay.setProtocolId(protocol.getRowId()); + XarContext context = new XarContext("Domains", getContainer(), getUser()); + context.addSubstitution("AssayName", PageFlowUtil.encode(assay.getName())); - Set domainURIs = new HashSet<>(); - for (GWTDomain domain : assay.getDomains()) - { - domain.setDomainURI(LsidUtils.resolveLsidFromTemplate(domain.getDomainURI(), context)); - domain.setName(assay.getName() + " " + domain.getName()); - GWTDomain gwtDomain = DomainUtil.getDomainDescriptor(getUser(), domain.getDomainURI(), getContainer()); - if (gwtDomain == null) - { - Domain newDomain = DomainUtil.createDomain(PropertyService.get().getDomainKind(domain.getDomainURI()).getKindName(), domain, null, getContainer(), getUser(), domain.getName(), null); - domainURIs.add(newDomain.getTypeURI()); - } - else - { - ValidationException domainErrors = updateDomainDescriptor(domain, protocol, assayProvider, false); - if (domainErrors.hasErrors()) - { - throw domainErrors; - } - domainURIs.add(domain.getDomainURI()); - } + protocol = AssayManager.get().createAssayDefinition(getUser(), getContainer(), assay, context); + assay.setProtocolId(protocol.getRowId()); - } - setPropertyDomainURIs(protocol, domainURIs, assayProvider); - } - else + Set domainURIs = new HashSet<>(); + for (GWTDomain domain : assay.getDomains()) { - protocol = ExperimentService.get().getExpProtocol(assay.getProtocolId().intValue()); - - if (protocol == null) + domain.setDomainURI(LsidUtils.resolveLsidFromTemplate(domain.getDomainURI(), context)); + domain.setName(assay.getName() + " " + domain.getName()); + GWTDomain gwtDomain = DomainUtil.getDomainDescriptor(getUser(), domain.getDomainURI(), getContainer()); + if (gwtDomain == null) { - throw new AssayException("Assay design has been deleted"); + Domain newDomain = DomainUtil.createDomain(PropertyService.get().getDomainKind(domain.getDomainURI()).getKindName(), domain, null, getContainer(), getUser(), domain.getName(), null); + domainURIs.add(newDomain.getTypeURI()); } - - //ensure that the user has edit perms in this container - if (!canUpdateProtocols()) - throw new AssayException("You do not have sufficient permissions to update this Assay"); - - if (!protocol.getContainer().equals(getContainer())) - throw new AssayException("Assays can only be edited in the folder where they were created. " + - "This assay was created in folder " + protocol.getContainer().getPath()); - oldAssayName = protocol.getName(); - hasNameChange = !assay.getName().equals(oldAssayName); - protocol.setName(assay.getName()); - protocol.setProtocolDescription(assay.getDescription()); - if (assay.getStatus() != null) - protocol.setStatus(ExpProtocol.Status.valueOf(assay.getStatus())); - } - - Map newParams = new HashMap<>(protocol.getProtocolParameters()); - if (assay.getProtocolParameters() != null) - { - for (Map.Entry entry : assay.getProtocolParameters().entrySet()) + else { - ProtocolParameter param = new ProtocolParameter(); - String uri = entry.getKey(); - param.setOntologyEntryURI(uri); - param.setValue(SimpleTypeNames.STRING, entry.getValue()); - if (hasNameChange && assayProvider.canRename() && XarConstants.APPLICATION_NAME_TEMPLATE_URI.equals(uri) && entry.getValue() != null) + ValidationException domainErrors = updateDomainDescriptor(domain, protocol, assayProvider, false); + if (domainErrors.hasErrors()) { - String updatedName = entry.getValue().replace(oldAssayName, assay.getName()); - param.setValue(SimpleTypeNames.STRING, updatedName); + throw domainErrors; } - param.setName(uri.contains("#") ? uri.substring(uri.indexOf("#") + 1) : uri); - newParams.put(uri, param); + domainURIs.add(domain.getDomainURI()); } - } - protocol.setProtocolParameters(newParams.values()); - if (hasNameChange) - { - if (AssayManager.get().getAssayProtocolByName(getContainer(), assay.getName()) != null) - throw new ValidationException("Another assay protocol already exists for this name."); - ExperimentService.get().handleAssayNameChange(assay.getName(), oldAssayName, assayProvider, protocol,getUser(), getContainer()); } + setPropertyDomainURIs(protocol, domainURIs, assayProvider); + } + else + { + protocol = ExperimentService.get().getExpProtocol(assay.getProtocolId().intValue()); - AssayProvider provider = AssayService.get().getProvider(protocol); - if (provider instanceof PlateBasedAssayProvider && assay.getSelectedPlateTemplate() != null) + if (protocol == null) { - PlateBasedAssayProvider plateProvider = (PlateBasedAssayProvider)provider; - Plate plate = PlateManager.get().getPlateByName(getContainer(), assay.getSelectedPlateTemplate()); - if (plate != null) - plateProvider.setPlate(getContainer(), protocol, plate); - else - throw new AssayException("The selected plate could not be found. Perhaps it was deleted by another user?"); - - String selectedFormat = assay.getSelectedMetadataInputFormat(); - SampleMetadataInputFormat inputFormat = SampleMetadataInputFormat.valueOf(selectedFormat); - if (inputFormat != null) - ((PlateBasedAssayProvider)provider).setMetadataInputFormat(protocol, inputFormat); + throw new AssayException("Assay design has been deleted"); } - // data transform scripts - List transformScripts = new ArrayList<>(); - List submittedScripts = assay.getProtocolTransformScripts(); - if (!submittedScripts.isEmpty() && !canUpdateTransformationScript()) - throw new AssayException("You must be a platform developer or site admin to configure assay transformation scripts."); - for (String script : assay.getProtocolTransformScripts()) + //ensure that the user has edit perms in this container + if (!canUpdateProtocols()) + throw new AssayException("You do not have sufficient permissions to update this Assay"); + + if (!protocol.getContainer().equals(getContainer())) + throw new AssayException("Assays can only be edited in the folder where they were created. " + + "This assay was created in folder " + protocol.getContainer().getPath()); + oldAssayName = protocol.getName(); + hasNameChange = !assay.getName().equals(oldAssayName); + protocol.setName(assay.getName()); + protocol.setProtocolDescription(assay.getDescription()); + if (assay.getStatus() != null) + protocol.setStatus(ExpProtocol.Status.valueOf(assay.getStatus())); + } + + Map newParams = new HashMap<>(protocol.getProtocolParameters()); + if (assay.getProtocolParameters() != null) + { + for (Map.Entry entry : assay.getProtocolParameters().entrySet()) { - if (!StringUtils.isBlank(script)) + ProtocolParameter param = new ProtocolParameter(); + String uri = entry.getKey(); + param.setOntologyEntryURI(uri); + param.setValue(SimpleTypeNames.STRING, entry.getValue()); + if (hasNameChange && assayProvider.canRename() && XarConstants.APPLICATION_NAME_TEMPLATE_URI.equals(uri) && entry.getValue() != null) { - transformScripts.add(new File(script)); + String updatedName = entry.getValue().replace(oldAssayName, assay.getName()); + param.setValue(SimpleTypeNames.STRING, updatedName); } + param.setName(uri.contains("#") ? uri.substring(uri.indexOf("#") + 1) : uri); + newParams.put(uri, param); } + } + protocol.setProtocolParameters(newParams.values()); - if (provider instanceof DetectionMethodAssayProvider && assay.getSelectedDetectionMethod() != null) - { - DetectionMethodAssayProvider dmProvider = (DetectionMethodAssayProvider)provider; - String detectionMethod = assay.getSelectedDetectionMethod(); - if (detectionMethod != null) - dmProvider.setSelectedDetectionMethod(getContainer(), protocol, detectionMethod); - else - throw new AssayException("The selected detection method could not be found."); - } + if (hasNameChange) + { + if (AssayManager.get().getAssayProtocolByName(getContainer(), assay.getName()) != null) + throw new ValidationException("Another assay protocol already exists for this name."); + ExperimentService.get().handleAssayNameChange(assay.getName(), oldAssayName, assayProvider, protocol,getUser(), getContainer()); + } - ValidationException scriptValidation = provider.setValidationAndAnalysisScripts(protocol, transformScripts); - if (scriptValidation.hasErrors()) - { - for (var error : scriptValidation.getErrors()) - { - if (error.getSeverity() == ValidationException.SEVERITY.ERROR) - throw scriptValidation; + AssayProvider provider = AssayService.get().getProvider(protocol); + if (provider instanceof PlateBasedAssayProvider plateProvider && assay.getSelectedPlateTemplate() != null) + { + Plate plate = PlateManager.get().getPlateByName(getContainer(), assay.getSelectedPlateTemplate()); + if (plate != null) + plateProvider.setPlate(getContainer(), protocol, plate); + else + throw new AssayException("The selected plate could not be found. Perhaps it was deleted by another user?"); - // TODO: return warnings back to client - HelpTopic help = error.getHelp(); - LOG.log(error.getSeverity().getLevel(), error.getMessage() - + (help != null ? "\n For more information: " + help.getHelpTopicHref() : "")); - } - } -// -// provider.setDetectionMethods(protocol, assay.getAvailableDetectionMethods()); - - provider.setSaveScriptFiles(protocol, assay.isSaveScriptFiles()); - provider.setEditableResults(protocol, assay.isEditableResults()); - provider.setEditableRuns(protocol, assay.isEditableRuns()); - provider.setBackgroundUpload(protocol, assay.isBackgroundUpload()); - provider.setQCEnabled(protocol, assay.isQcEnabled()); - provider.setPlateMetadataEnabled(protocol, assay.isPlateMetadata()); - - Map props = new HashMap<>(protocol.getObjectProperties()); - // get the autoLinkTargetContainer from either the id on the assay object entityId - String autoLinkTargetContainerId = assay.getAutoCopyTargetContainer() != null ? assay.getAutoCopyTargetContainer().getEntityId() : assay.getAutoCopyTargetContainerId(); - // verify that the autoLinkTargetContainerId is valid - if (autoLinkTargetContainerId != null && ContainerManager.getForId(autoLinkTargetContainerId) == null) - { - throw new AssayException("No such auto-link target container id: " + autoLinkTargetContainerId); - } + String selectedFormat = assay.getSelectedMetadataInputFormat(); + SampleMetadataInputFormat inputFormat = SampleMetadataInputFormat.valueOf(selectedFormat); + if (inputFormat != null) + plateProvider.setMetadataInputFormat(protocol, inputFormat); + } - if (autoLinkTargetContainerId != null) - { - props.put(StudyPublishService.AUTO_LINK_TARGET_PROPERTY_URI, new ObjectProperty(protocol.getLSID(), protocol.getContainer(), StudyPublishService.AUTO_LINK_TARGET_PROPERTY_URI, autoLinkTargetContainerId)); - } - else + // data transform scripts + List transformScripts = new ArrayList<>(); + List submittedScripts = assay.getProtocolTransformScripts(); + if (!submittedScripts.isEmpty() && !canUpdateTransformationScript()) + throw new AssayException("You must be a platform developer or site admin to configure assay transformation scripts."); + for (String script : assay.getProtocolTransformScripts()) + { + if (!StringUtils.isBlank(script)) { - props.remove(StudyPublishService.AUTO_LINK_TARGET_PROPERTY_URI); + transformScripts.add(new File(script)); } + } - String autoLinkCategory = assay.getAutoLinkCategory(); - if (autoLinkCategory != null) - { - props.put(StudyPublishService.AUTO_LINK_CATEGORY_PROPERTY_URI, new ObjectProperty(protocol.getLSID(), protocol.getContainer(), StudyPublishService.AUTO_LINK_CATEGORY_PROPERTY_URI, autoLinkCategory)); - } + if (provider instanceof DetectionMethodAssayProvider dmProvider && assay.getSelectedDetectionMethod() != null) + { + String detectionMethod = assay.getSelectedDetectionMethod(); + if (detectionMethod != null) + dmProvider.setSelectedDetectionMethod(getContainer(), protocol, detectionMethod); else - { - props.remove(StudyPublishService.AUTO_LINK_CATEGORY_PROPERTY_URI); - } - - protocol.setObjectProperties(props); - - protocol.save(getUser()); + throw new AssayException("The selected detection method could not be found."); + } - StringBuilder errors = new StringBuilder(); - for (GWTDomain domain : assay.getDomains()) + ValidationException scriptValidation = provider.setValidationAndAnalysisScripts(protocol, transformScripts); + if (scriptValidation.hasErrors()) + { + for (var error : scriptValidation.getErrors()) { - ValidationException domainErrors = updateDomainDescriptor(domain, protocol, provider, hasNameChange); + if (error.getSeverity() == ValidationException.SEVERITY.ERROR) + throw scriptValidation; - // Need to bail out inside of the loop because some errors may have left the DB connection in - // an unusable state. - if (domainErrors.hasErrors()) - throw domainErrors; + // TODO: return warnings back to client + HelpTopic help = error.getHelp(); + LOG.log(error.getSeverity().getLevel(), error.getMessage() + + (help != null ? "\n For more information: " + help.getHelpTopicHref() : "")); } + } + + provider.setSaveScriptFiles(protocol, assay.isSaveScriptFiles()); + provider.setEditableResults(protocol, assay.isEditableResults()); + provider.setEditableRuns(protocol, assay.isEditableRuns()); + provider.setBackgroundUpload(protocol, assay.isBackgroundUpload()); + provider.setQCEnabled(protocol, assay.isQcEnabled()); + provider.setPlateMetadataEnabled(protocol, assay.isPlateMetadata()); + + Map props = new HashMap<>(protocol.getObjectProperties()); + // get the autoLinkTargetContainer from either the id on the assay object entityId + String autoLinkTargetContainerId = assay.getAutoCopyTargetContainer() != null ? assay.getAutoCopyTargetContainer().getEntityId() : assay.getAutoCopyTargetContainerId(); + // verify that the autoLinkTargetContainerId is valid + if (autoLinkTargetContainerId != null && ContainerManager.getForId(autoLinkTargetContainerId) == null) + { + throw new AssayException("No such auto-link target container id: " + autoLinkTargetContainerId); + } - if (assay.getExcludedContainerIds() != null && (!isNew || !assay.getExcludedContainerIds().isEmpty())) - ExperimentService.get().ensureDataTypeContainerExclusions(ExperimentService.DataTypeForExclusion.AssayDesign, assay.getExcludedContainerIds(), protocol.getRowId(), getUser()); + if (autoLinkTargetContainerId != null) + { + props.put(StudyPublishService.AUTO_LINK_TARGET_PROPERTY_URI, new ObjectProperty(protocol.getLSID(), protocol.getContainer(), StudyPublishService.AUTO_LINK_TARGET_PROPERTY_URI, autoLinkTargetContainerId)); + } + else + { + props.remove(StudyPublishService.AUTO_LINK_TARGET_PROPERTY_URI); + } - QueryService.get().updateLastModified(); - transaction.commit(); - AssayManager.get().clearProtocolCache(); - return getAssayDefinition(assay.getProtocolId(), false); + String autoLinkCategory = assay.getAutoLinkCategory(); + if (autoLinkCategory != null) + { + props.put(StudyPublishService.AUTO_LINK_CATEGORY_PROPERTY_URI, new ObjectProperty(protocol.getLSID(), protocol.getContainer(), StudyPublishService.AUTO_LINK_CATEGORY_PROPERTY_URI, autoLinkCategory)); } - catch (UnexpectedException e) + else { - Throwable cause = e.getCause(); - throw new ValidationException(cause.getMessage()); + props.remove(StudyPublishService.AUTO_LINK_CATEGORY_PROPERTY_URI); } - catch (ExperimentException e) + + protocol.setObjectProperties(props); + + protocol.save(getUser()); + + for (GWTDomain domain : assay.getDomains()) { - throw new ValidationException(e.getMessage()); + ValidationException domainErrors = updateDomainDescriptor(domain, protocol, provider, hasNameChange); + + // Need to bail out inside of the loop because some errors may have left the DB connection in + // an unusable state. + if (domainErrors.hasErrors()) + throw domainErrors; } + + if (assay.getExcludedContainerIds() != null && (!isNew || !assay.getExcludedContainerIds().isEmpty())) + ExperimentService.get().ensureDataTypeContainerExclusions(ExperimentService.DataTypeForExclusion.AssayDesign, assay.getExcludedContainerIds(), protocol.getRowId(), getUser()); + + QueryService.get().updateLastModified(); + transaction.commit(); + AssayManager.get().clearProtocolCache(); + return getAssayDefinition(assay.getProtocolId(), false); + } + catch (UnexpectedException e) + { + Throwable cause = e.getCause(); + throw new ValidationException(cause.getMessage()); + } + catch (ExperimentException e) + { + throw new ValidationException(e.getMessage()); } - else - throw new AssayException("Only replaceIfExisting == true is supported."); } } diff --git a/assay/src/org/labkey/assay/AssayListQueryView.java b/assay/src/org/labkey/assay/AssayListQueryView.java index e37e9572f53..9f143bb169b 100644 --- a/assay/src/org/labkey/assay/AssayListQueryView.java +++ b/assay/src/org/labkey/assay/AssayListQueryView.java @@ -86,9 +86,9 @@ protected void populateButtonBar(DataView view, ButtonBar bar) if (getContainer().hasPermission(getUser(), DesignAssayPermission.class)) { - ActionURL plateURL = PageFlowUtil.urlProvider(PlateUrls.class).getPlateTemplateListURL(getContainer()); + ActionURL plateURL = PageFlowUtil.urlProvider(PlateUrls.class).getPlateListURL(getContainer()); plateURL.addReturnURL(getViewContext().getActionURL()); - ActionButton insert = new ActionButton("Configure Plate Templates", plateURL); + ActionButton insert = new ActionButton("Configure Plates", plateURL); insert.setActionType(ActionButton.Action.LINK); insert.setDisplayPermission(DesignAssayPermission.class); bar.add(insert); diff --git a/assay/src/org/labkey/assay/AssayModule.java b/assay/src/org/labkey/assay/AssayModule.java index 144337c18cf..7e4dfd71e7f 100644 --- a/assay/src/org/labkey/assay/AssayModule.java +++ b/assay/src/org/labkey/assay/AssayModule.java @@ -77,6 +77,7 @@ import org.labkey.assay.plate.PlateDocumentProvider; import org.labkey.assay.plate.PlateImpl; import org.labkey.assay.plate.PlateManager; +import org.labkey.assay.plate.PlateManagerTest; import org.labkey.assay.plate.PlateMetadataDomainKind; import org.labkey.assay.plate.PlateMetricsProvider; import org.labkey.assay.plate.TsvPlateLayoutHandler; @@ -115,7 +116,7 @@ public String getName() @Override public Double getSchemaVersion() { - return 24.006; + return 24.007; } @Override @@ -144,6 +145,7 @@ protected void init() { AssayService.setInstance(new AssayManager()); PlateService.setInstance(new PlateManager()); + AssayPlateMetadataService.setInstance(new AssayPlateMetadataServiceImpl()); addController("assay", AssayController.class); addController("plate", PlateController.class); PlateSchema.register(this); @@ -155,7 +157,6 @@ protected void init() ExperimentService.get().registerExperimentDataHandler(new TsvDataHandler()); ExperimentService.get().registerExperimentDataHandler(new FileBasedModuleDataHandler()); ExperimentService.get().registerExperimentDataHandler(new PlateMetadataDataHandler()); - AssayPlateMetadataService.registerService(PlateMetadataDataHandler.DATA_TYPE, new AssayPlateMetadataServiceImpl()); PropertyService.get().registerDomainKind(new AssayPlateDataDomainKind()); PlateService.get().registerPlateLayoutHandler(new TsvPlateLayoutHandler()); @@ -331,7 +332,7 @@ public Set getProvisionedSchemaNames() TsvAssayProvider.TestCase.class, AssaySchemaImpl.TestCase.class, AssayProviderSchema.TestCase.class, - PlateManager.TestCase.class, + PlateManagerTest.class, PositionImpl.TestCase.class, PlateImpl.TestCase.class, PlateUtils.TestCase.class, diff --git a/assay/src/org/labkey/assay/AssayUpgradeCode.java b/assay/src/org/labkey/assay/AssayUpgradeCode.java index f75935e8f4b..ae89add25dc 100644 --- a/assay/src/org/labkey/assay/AssayUpgradeCode.java +++ b/assay/src/org/labkey/assay/AssayUpgradeCode.java @@ -38,6 +38,8 @@ import org.labkey.api.exp.api.ExperimentService; import org.labkey.api.exp.api.StorageProvisioner; import org.labkey.api.exp.property.Domain; +import org.labkey.api.exp.property.DomainKind; +import org.labkey.api.exp.property.PropertyService; import org.labkey.api.module.ModuleContext; import org.labkey.api.security.LimitedUser; import org.labkey.api.security.User; @@ -150,7 +152,6 @@ private static void _addAssayResultColumns(ExpProtocol protocol, Domain resultsD private static void _ensureColumn(String colName, Domain domain, ExpProtocol protocol, SchemaTableInfo provisionedTable, AssayResultDomainKind kind) { - ColumnInfo col = provisionedTable.getColumn(colName); if (col != null) _log.error("Column '" + colName + "' is already defined in result table for '" + protocol.getName() + "'."); @@ -158,7 +159,6 @@ private static void _ensureColumn(String colName, Domain domain, ExpProtocol pro PropertyStorageSpec colProp = kind.getBaseProperties(domain).stream().filter(p -> colName.equalsIgnoreCase(p.getName())).findFirst().orElseThrow(); StorageProvisioner.get().addStorageProperties(domain, Arrays.asList(colProp), true); _log.info("Added '" + colName + "' column to '" + protocol.getName() + " provisioned result table."); - } /** @@ -167,7 +167,8 @@ private static void _ensureColumn(String colName, Domain domain, ExpProtocol pro * Switch from storing the protocol plate template by name to the plate lsid. */ @DeferredUpgrade - public static void updateProtocolPlateTemplate(ModuleContext ctx) throws Exception + @SuppressWarnings({"UnusedDeclaration"}) + public static void updateProtocolPlateTemplate(ModuleContext ctx) { if (ctx.isNewInstall()) return; @@ -219,6 +220,7 @@ private static Plate getPlate(ExpProtocol protocol) * The referenced upgrade script creates a new plate set for every plate in the system. We now * want to iterate over each plate set to set the name using the configured name expression. */ + @SuppressWarnings({"UnusedDeclaration"}) public static void updatePlateSetNames(ModuleContext ctx) throws Exception { if (ctx.isNewInstall()) @@ -267,6 +269,7 @@ public static void updatePlateSetNames(ModuleContext ctx) throws Exception * Iterate over each plate and plate set to generate a Plate ID and PlateSet ID based on the * configured name expression for each. */ + @SuppressWarnings({"UnusedDeclaration"}) public static void initializePlateAndPlateSetIDs(ModuleContext ctx) throws Exception { if (ctx.isNewInstall()) @@ -341,11 +344,28 @@ public static void initializePlateAndPlateSetIDs(ModuleContext ctx) throws Excep } } + /** + * Well metadata has transitioned to a provisioned architecture. + */ + private static @Nullable Domain getPlateMetadataVocabDomain(Container container, User user) + { + DomainKind vocabDomainKind = PropertyService.get().getDomainKindByName("Vocabulary"); + + if (vocabDomainKind == null) + return null; + + // the domain is scoped at the project level (project and subfolder scoping) + Container domainContainer = PlateManager.get().getPlateMetadataDomainContainer(container); + String domainURI = vocabDomainKind.generateDomainURI(null, "PlateMetadataDomain", domainContainer, user); + return PropertyService.get().getDomain(container, domainURI); + } + /** * Called from assay-24.002-24.003.sql to delete the vocabulary domains associated with * plate metadata. This upgrade transitions to using a provisioned table approach. Since the plate features are * still under an experimental flag we won't worry about upgrading the domains. */ + @SuppressWarnings({"UnusedDeclaration"}) public static void deletePlateVocabDomains(ModuleContext ctx) throws Exception { if (ctx.isNewInstall()) @@ -362,7 +382,7 @@ public static void deletePlateVocabDomains(ModuleContext ctx) throws Exception { if (container != null) { - Domain domain = PlateManager.get().getPlateMetadataVocabDomain(container, User.getAdminServiceUser()); + Domain domain = getPlateMetadataVocabDomain(container, User.getAdminServiceUser()); if (domain != null) { // delete the plate metadata values @@ -407,6 +427,7 @@ public static void deletePlateVocabDomains(ModuleContext ctx) throws Exception /** * Called from assay-24.005-24.006.sql */ + @SuppressWarnings({"UnusedDeclaration"}) public static void populatePlateSetPaths(ModuleContext ctx) throws Exception { if (ctx.isNewInstall()) diff --git a/assay/src/org/labkey/assay/PlateController.java b/assay/src/org/labkey/assay/PlateController.java index e5033d355e5..b922fba8080 100644 --- a/assay/src/org/labkey/assay/PlateController.java +++ b/assay/src/org/labkey/assay/PlateController.java @@ -48,6 +48,7 @@ import org.labkey.api.query.FieldKey; import org.labkey.api.query.ValidationException; import org.labkey.api.reader.ColumnDescriptor; +import org.labkey.api.security.ActionNames; import org.labkey.api.security.RequiresAnyOf; import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.User; @@ -101,9 +102,9 @@ public PlateController() public static class PlateUrlsImpl implements PlateUrls { @Override - public ActionURL getPlateTemplateListURL(Container c) + public ActionURL getPlateListURL(Container c) { - return new ActionURL(PlateTemplateListAction.class, c); + return new ActionURL(PlateListAction.class, c); } @Override @@ -119,7 +120,7 @@ public static class BeginAction extends SimpleViewAction @Override public ModelAndView getView(Object o, BindException errors) { - return HttpView.redirect(new ActionURL(PlateTemplateListAction.class, getContainer())); + return HttpView.redirect(new ActionURL(PlateListAction.class, getContainer())); } @Override @@ -128,10 +129,10 @@ public void addNavTrail(NavTree root) } } - public static class PlateTemplateListBean { - private List _templates; + private final List _templates; + public PlateTemplateListBean(List templates) { _templates = templates; @@ -144,21 +145,25 @@ public List getTemplates() } @RequiresPermission(ReadPermission.class) - public static class PlateTemplateListAction extends SimpleViewAction + @ActionNames("plateList, plateTemplateList") + public static class PlateListAction extends SimpleViewAction { @Override - public ModelAndView getView(ReturnUrlForm plateTemplateListForm, BindException errors) + public ModelAndView getView(ReturnUrlForm form, BindException errors) { setHelpTopic("editPlateTemplate"); - List plateTemplates = PlateService.get().getPlateTemplates(getContainer()); - return new JspView<>("/org/labkey/assay/plate/view/plateTemplateList.jsp", + List plateTemplates = PlateService.get().getPlates(getContainer()) + .stream() + .filter(p -> !TsvPlateLayoutHandler.TYPE.equalsIgnoreCase(p.getAssayType())) + .toList(); + return new JspView<>("/org/labkey/assay/plate/view/plateList.jsp", new PlateTemplateListBean(plateTemplates)); } @Override public void addNavTrail(NavTree root) { - root.addChild("Plate Templates"); + root.addChild("Plates"); } } @@ -240,11 +245,11 @@ public ModelAndView getView(DesignerForm form, BindException errors) properties.put("templateRowCount", "" + form.getRowCount()); properties.put("templateColumnCount", "" + form.getColCount()); - List templates = PlateService.get().getPlateTemplates(getContainer()); - for (int i = 0; i < templates.size(); i++) + List plates = PlateService.get().getPlates(getContainer()); + for (int i = 0; i < plates.size(); i++) { - Plate template = templates.get(i); - properties.put("templateName[" + i + "]", template.getName()); + Plate plate = plates.get(i); + properties.put("templateName[" + i + "]", plate.getName()); } return new AssayGWTView(gwt.client.org.labkey.plate.designer.client.TemplateDesigner.class, properties); } @@ -253,7 +258,7 @@ public ModelAndView getView(DesignerForm form, BindException errors) public void addNavTrail(NavTree root) { setHelpTopic("editPlateTemplate"); - root.addChild("Plate Template Editor"); + root.addChild("Plate Editor"); } } @@ -324,7 +329,7 @@ protected void renderCellContents(StringBuilder html, Container c, ActionURL url Container dest = ContainerManager.getForPath(_selectedDestination); if (dest != null) { - _destinationTemplates = PlateService.get().getPlateTemplates(dest); + _destinationTemplates = PlateService.get().getPlates(dest); } } @@ -410,29 +415,29 @@ public void validateCommand(CopyForm form, Errors errors) if (_plate == null) errors.reject(ERROR_REQUIRED, "Unable to retrieve source plate with ID : " + form.getPlateId()); - if (PlateManager.get().isDuplicatePlate(_destination, getUser(), _plate.getName(), null)) + if (PlateManager.get().isDuplicatePlateName(_destination, getUser(), _plate.getName(), null)) errors.reject("copyForm", "A plate template with the same name already exists in the destination folder."); } @Override public boolean handlePost(CopyForm form, BindException errors) throws Exception { - PlateService.get().copyPlate(_plate, getUser(), _destination); + PlateManager.get().copyPlateDeprecated(_plate, getUser(), _destination); return true; } @Override public ActionURL getSuccessURL(CopyForm copyForm) { - return new ActionURL(PlateTemplateListAction.class, getContainer()); + return new ActionURL(PlateListAction.class, getContainer()); } } private String getUniqueName(Container container, String originalName) { Set existing = new HashSet<>(); - for (Plate template : PlateService.get().getPlateTemplates(container)) - existing.add(template.getName()); + for (Plate plate : PlateService.get().getPlates(container)) + existing.add(plate.getName()); String baseUniqueName; if (!originalName.startsWith("Copy of ")) baseUniqueName = "Copy of " + originalName; @@ -558,11 +563,18 @@ public void setPlateId(Integer plateId) public static class CreatePlateForm implements ApiJsonForm { + private String _assayType = TsvPlateLayoutHandler.TYPE; + private final List> _data = new ArrayList<>(); + private String _description; private String _name; - private Integer _plateType; private Integer _plateSetId; - private List> _data = new ArrayList<>(); - private String _assayType = TsvPlateLayoutHandler.TYPE; + private Integer _plateType; + private boolean _template; + + public String getDescription() + { + return _description; + } public String getName() { @@ -589,6 +601,11 @@ public String getAssayType() return _assayType; } + public Boolean isTemplate() + { + return _template; + } + @Override public void bindJson(JSONObject json) { @@ -604,6 +621,12 @@ public void bindJson(JSONObject json) if (json.has("assayType")) _assayType = json.getString("assayType"); + if (json.has("description")) + _description = json.getString("description"); + + if (json.has("template")) + _template = json.getBoolean("template"); + if (json.has("data")) { RowMapFactory factory = new RowMapFactory<>(); @@ -644,8 +667,13 @@ public Object execute(CreatePlateForm form, BindException errors) throws Excepti { try { - Plate plate = PlateManager.get().createAndSavePlate(getContainer(), getUser(), _plateType, form.getName(), form.getPlateSetId(), form.getAssayType(), form.getData()); - return success(plate); + PlateImpl plate = new PlateImpl(getContainer(), form.getName(), form.getAssayType(), _plateType); + if (form.isTemplate() != null) + plate.setTemplate(form.isTemplate()); + plate.setDescription(form.getDescription()); + + Plate newPlate = PlateManager.get().createAndSavePlate(getContainer(), getUser(), plate, form.getPlateSetId(), form.getData()); + return success(newPlate); } catch (Exception e) { @@ -885,8 +913,9 @@ public static class CreatePlateSetForm private String _name; private List _plates = new ArrayList<>(); private Integer _parentPlateSetId; - private PlateSetType _type; private String _selectionKey; + private Boolean _template; + private PlateSetType _type; public String getDescription() { @@ -973,6 +1002,15 @@ public boolean isDefaultCase() return !_plates.isEmpty() && _selectionKey == null; } + public Boolean getTemplate() + { + return _template; + } + + public void setTemplate(Boolean template) + { + _template = template; + } } @RequiresPermission(InsertPermission.class) @@ -994,6 +1032,8 @@ public Object execute(CreatePlateSetForm form, BindException errors) throws Exce plateSet.setDescription(form.getDescription()); plateSet.setName(form.getName()); plateSet.setType(form.getType()); + if (form.getTemplate() != null) + plateSet.setTemplate(form.getTemplate()); List plates = new ArrayList<>(); if (form.isStandaloneAssayPlateCase() || form.isRearrayCase()) @@ -1026,9 +1066,20 @@ else if (form.isDefaultCase()) public static class ArchiveForm { + private List _plateIds; private List _plateSetIds; private boolean _restore; + public List getPlateIds() + { + return _plateIds; + } + + public void setPlateIds(List plateIds) + { + _plateIds = plateIds; + } + public List getPlateSetIds() { return _plateSetIds; @@ -1052,13 +1103,13 @@ public void setRestore(boolean restore) @Marshal(Marshaller.JSONObject) @RequiresPermission(UpdatePermission.class) - public static class ArchivePlateSetsAction extends MutatingApiAction + public static class ArchiveAction extends MutatingApiAction { @Override public void validateForm(ArchiveForm form, Errors errors) { - if (form.getPlateSetIds() == null) - errors.reject(ERROR_GENERIC, "\"plateSetIds\" is a required field."); + if (form.getPlateIds() == null && form.getPlateSetIds() == null) + errors.reject(ERROR_GENERIC, "Either \"plateIds\" or \"plateSetIds\" must be specified."); } @Override @@ -1066,7 +1117,7 @@ public Object execute(ArchiveForm form, BindException errors) throws Exception { try { - PlateManager.get().archivePlateSets(getContainer(), getUser(), form.getPlateSetIds(), !form.isRestore()); + PlateManager.get().archive(getContainer(), getUser(), form.getPlateSetIds(), form.getPlateIds(), !form.isRestore()); return success(); } catch (Exception e) @@ -1205,7 +1256,6 @@ public Object execute(HitForm form, BindException errors) throws Exception public static class PlateSetAssaysForm { private ContainerFilter.Type _containerFilter; - private int _plateSetId; public ContainerFilter.Type getContainerFilter() @@ -1362,4 +1412,70 @@ public Object execute(InstrumentInstructionForm form, BindException errors) thro return null; } } + + public static class CopyPlateForm + { + private boolean _copyAsTemplate; + private String _description; + private String _name; + private Integer _sourcePlateRowId; + + public boolean isCopyAsTemplate() + { + return _copyAsTemplate; + } + + public void setCopyAsTemplate(boolean copyAsTemplate) + { + _copyAsTemplate = copyAsTemplate; + } + + public String getDescription() + { + return _description; + } + + public void setDescription(String description) + { + _description = description; + } + + public String getName() + { + return _name; + } + + public void setName(String name) + { + _name = name; + } + + public Integer getSourcePlateRowId() + { + return _sourcePlateRowId; + } + + public void setSourcePlateRowId(Integer sourcePlateRowId) + { + _sourcePlateRowId = sourcePlateRowId; + } + } + + @RequiresPermission(InsertPermission.class) + public static class CopyPlateAction extends MutatingApiAction + { + @Override + public void validateForm(CopyPlateForm form, Errors errors) + { + if (form.getSourcePlateRowId() == null) + errors.reject(ERROR_REQUIRED, "Specifying \"sourcePlateRowId\" is required."); + } + + @Override + public Object execute(CopyPlateForm form, BindException errors) throws Exception + { + Plate plate = PlateManager.get().copyPlate(getContainer(), getUser(), form.getSourcePlateRowId(), form.isCopyAsTemplate(), form.getName(), form.getDescription()); + return success(plate); + } + } } diff --git a/assay/src/org/labkey/assay/TSVProtocolSchema.java b/assay/src/org/labkey/assay/TSVProtocolSchema.java index e9aa8341635..79c28e6b949 100644 --- a/assay/src/org/labkey/assay/TSVProtocolSchema.java +++ b/assay/src/org/labkey/assay/TSVProtocolSchema.java @@ -23,7 +23,6 @@ import org.labkey.api.assay.AssayUrls; import org.labkey.api.assay.AssayWellExclusionService; import org.labkey.api.assay.plate.AssayPlateMetadataService; -import org.labkey.api.assay.plate.PlateMetadataDataHandler; import org.labkey.api.data.BaseColumnInfo; import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.Container; @@ -159,10 +158,9 @@ private class _AssayResultTable extends AssayResultTable if (getProvider().isPlateMetadataEnabled(getProtocol())) { // legacy standard assay plate metadata support - Domain plateDataDomain = AssayPlateMetadataService.getService(PlateMetadataDataHandler.DATA_TYPE).getPlateDataDomain(getProtocol()); + Domain plateDataDomain = AssayPlateMetadataService.get().getPlateDataDomain(getProtocol()); if (plateDataDomain != null) { - List plateDefaultColumns = new ArrayList<>(); ColumnInfo lsidCol = getColumn("Lsid"); if (lsidCol != null) { @@ -175,6 +173,7 @@ private class _AssayResultTable extends AssayResultTable col.setCalculated(true); addColumn(col); + List plateDefaultColumns = new ArrayList<>(); for (DomainProperty prop : plateDataDomain.getProperties()) { plateDefaultColumns.add(FieldKey.fromParts("PlateData", prop.getName())); @@ -234,15 +233,9 @@ else if (o2.toString().toLowerCase().endsWith(PLATE_DATA_LAYER_SUFFIX)) @Nullable private TableInfo createPlateDataTable(ContainerFilter cf) { - AssayPlateMetadataService svc = AssayPlateMetadataService.getService(PlateMetadataDataHandler.DATA_TYPE); - if (svc != null) - { - Domain domain = svc.getPlateDataDomain(getProtocol()); - if (domain != null) - { - return new _AssayPlateDataTable(domain, this, cf); - } - } + Domain domain = AssayPlateMetadataService.get().getPlateDataDomain(getProtocol()); + if (domain != null) + return new _AssayPlateDataTable(domain, this, cf); return null; } diff --git a/assay/src/org/labkey/assay/actions/ImportRunApiAction.java b/assay/src/org/labkey/assay/actions/ImportRunApiAction.java index 03a7780cbbc..e8ba5e38e5c 100644 --- a/assay/src/org/labkey/assay/actions/ImportRunApiAction.java +++ b/assay/src/org/labkey/assay/actions/ImportRunApiAction.java @@ -78,10 +78,6 @@ import static org.labkey.api.assay.AssayDataCollector.PRIMARY_FILE; import static org.labkey.api.assay.AssayFileWriter.createFile; -/** - * User: kevink - * Date: 8/26/12 - */ @ActionNames("importRun") @RequiresPermission(InsertPermission.class) @ApiVersion(12.3) @@ -193,10 +189,10 @@ public ApiResponse execute(ImportRunApiForm form, BindException errors) throws E // Import the file at runFilePath if it is available, otherwise AssayRunUploadContextImpl.getUploadedData() will use the multi-part form POSTed file File file = null; - if (runFilePath != null && runFilePath.length() > 0) + if (runFilePath != null && !runFilePath.isEmpty()) { // Resolve file under module resources - if (moduleName != null && moduleName.length() > 0) + if (moduleName != null && !moduleName.isEmpty()) { Module m = ModuleLoader.getInstance().getModule(moduleName); if (m == null) @@ -223,7 +219,7 @@ public ApiResponse execute(ImportRunApiForm form, BindException errors) throws E if (!root.isUnderRoot(f)) f = root.resolvePath(runFilePath); - if (f == null || !NetworkDrive.exists(f) || !root.isUnderRoot(f)) + if (!NetworkDrive.exists(f) || !root.isUnderRoot(f)) throw new NotFoundException("File not found: " + runFilePath); file = f; @@ -291,15 +287,10 @@ else if (rawData != null && !rawData.isEmpty()) if (form.getPlateMetadata() != null) { - AssayPlateMetadataService svc = AssayPlateMetadataService.getService(PlateMetadataDataHandler.DATA_TYPE); - if (svc != null) - { - ExpData plateData = DefaultAssayRunCreator.createData(getContainer(), "Plate Metadata", PlateMetadataDataHandler.DATA_TYPE, null); - plateData.save(getUser()); - outputData.put(plateData, ExpDataRunInput.DEFAULT_ROLE); - - factory.setRawPlateMetadata(svc.parsePlateMetadata(form.getPlateMetadata())); - } + ExpData plateData = DefaultAssayRunCreator.createData(getContainer(), "Plate Metadata", PlateMetadataDataHandler.DATA_TYPE, null); + plateData.save(getUser()); + outputData.put(plateData, ExpDataRunInput.DEFAULT_ROLE); + factory.setRawPlateMetadata(AssayPlateMetadataService.get().parsePlateMetadata(form.getPlateMetadata())); } factory.setInputDatas(inputData) diff --git a/assay/src/org/labkey/assay/pipeline/AssayImportRunTask.java b/assay/src/org/labkey/assay/pipeline/AssayImportRunTask.java index d8c1c1ba100..0b5e0aa3f81 100644 --- a/assay/src/org/labkey/assay/pipeline/AssayImportRunTask.java +++ b/assay/src/org/labkey/assay/pipeline/AssayImportRunTask.java @@ -378,35 +378,32 @@ else if ("zip".equalsIgnoreCase(FileUtil.getExtension(dataFile))) { File dataFile = getDataFile(job); - AssayPlateMetadataService svc = AssayPlateMetadataService.getService(PlateMetadataDataHandler.DATA_TYPE); - if (svc != null) + try { - try + // plate metadata is only supported for zip archives because on JSON formats are currently supported + if ("zip".equalsIgnoreCase(FileUtil.getExtension(dataFile))) { - // plate metadata is only supported for zip archives because on JSON formats are currently supported - if ("zip".equalsIgnoreCase(FileUtil.getExtension(dataFile))) + ensureExplodedZip(job, dataFile); + File dir = getExplodedZipDir(job, dataFile); + File[] results = dir.listFiles((dir1, name) -> PLATE_METADATA_NAME.equalsIgnoreCase(FileUtil.getBaseName(name))); + if (results != null && results.length == 1) { - ensureExplodedZip(job, dataFile); - File dir = getExplodedZipDir(job, dataFile); - File[] results = dir.listFiles((dir1, name) -> PLATE_METADATA_NAME.equalsIgnoreCase(FileUtil.getBaseName(name))); - if (results != null && results.length == 1) - { - File metadataFile = results[0]; - job.getLogger().info("Found plate metadata file named : " + metadataFile + ", attempting to parse JSON metadata."); - return svc.parsePlateMetadata(metadataFile); - } + File metadataFile = results[0]; + job.getLogger().info("Found plate metadata file named : " + metadataFile + ", attempting to parse JSON metadata."); + return AssayPlateMetadataService.get().parsePlateMetadata(metadataFile); } } - catch (ExperimentException e) - { - throw new PipelineJobException(e); - } } + catch (ExperimentException e) + { + throw new PipelineJobException(e); + } + return null; } @Override - void cleanUp(PipelineJob job) throws PipelineJobException + void cleanUp(PipelineJob job) { File dataFile = getDataFile(job); if ("zip".equalsIgnoreCase(FileUtil.getExtension(dataFile))) diff --git a/assay/src/org/labkey/assay/plate/AssayPlateMetadataServiceImpl.java b/assay/src/org/labkey/assay/plate/AssayPlateMetadataServiceImpl.java index de0a4cbaf75..aa965a77f32 100644 --- a/assay/src/org/labkey/assay/plate/AssayPlateMetadataServiceImpl.java +++ b/assay/src/org/labkey/assay/plate/AssayPlateMetadataServiceImpl.java @@ -78,12 +78,13 @@ import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.labkey.api.assay.AssayResultDomainKind.WELL_LSID_COLUMN_NAME; public class AssayPlateMetadataServiceImpl implements AssayPlateMetadataService { private boolean _domainDirty; - private Map> _propValues = new HashMap<>(); + private final Map> _propValues = new HashMap<>(); @Override public void addAssayPlateMetadata( @@ -116,7 +117,6 @@ public void addAssayPlateMetadata( Map descriptorMap = new CaseInsensitiveHashMap<>(); domain.getProperties().forEach(dp -> descriptorMap.put(dp.getName(), dp.getPropertyDescriptor())); List> jsonData = new ArrayList<>(); - Set propsToInsert = new HashSet<>(); // merge the plate data with the uploaded result data for (Map row : inserted) @@ -139,10 +139,7 @@ public void addAssayPlateMetadata( if (descriptorMap.containsKey(k)) { if (v != null) - { jsonRow.put(descriptorMap.get(k).getURI(), v); - propsToInsert.add(descriptorMap.get(k)); - } } }); jsonRow.put("Lsid", rowIdToLsidMap.get(rowId)); @@ -182,12 +179,12 @@ public void addAssayPlateMetadata( * to metadata properties. */ private Map> prepareMergedPlateData( - Container container, - User user, - Lsid plateLsid, - Map plateMetadata, - ExpProtocol protocol, - boolean ensurePlateDomain // true to create the plate domain and properties if they don't exist + Container container, + User user, + Lsid plateLsid, + Map plateMetadata, + ExpProtocol protocol, + boolean ensurePlateDomain // true to create the plate domain and properties if they don't exist ) throws ExperimentException { _domainDirty = false; @@ -278,14 +275,15 @@ private Map> prepareMergedPlateData( @Override public List> mergePlateMetadata( - Container container, - User user, - Lsid plateLsid, - Integer plateSetId, - List> rows, - @Nullable Map plateMetadata, - AssayProvider provider, - ExpProtocol protocol) throws ExperimentException + Container container, + User user, + Lsid plateLsid, + Integer plateSetId, + List> rows, + @Nullable Map plateMetadata, + AssayProvider provider, + ExpProtocol protocol + ) throws ExperimentException { Domain resultDomain = provider.getResultsDomain(protocol); DomainProperty plateProperty = resultDomain.getPropertyByName(AssayResultDomainKind.PLATE_COLUMN_NAME); @@ -492,7 +490,7 @@ public Map parsePlateMetadata(File jsonData) throws Exper } } - private Map _parsePlateMetadata(JsonNode rootNode) throws ExperimentException + private Map _parsePlateMetadata(JsonNode rootNode) { Map layers = new CaseInsensitiveHashMap<>(); @@ -529,14 +527,14 @@ else if (propEntry.getValue().isBoolean()) @Override public List> parsePlateData( - Container container, - User user, - AssayProvider provider, - ExpProtocol protocol, - Integer plateSetId, - File dataFile, - List> data, - Pair plateIdAdded + Container container, + User user, + AssayProvider provider, + ExpProtocol protocol, + Integer plateSetId, + File dataFile, + List> data, + Pair plateIdAdded ) throws ExperimentException { // get the ordered list of plates for the plate set @@ -544,6 +542,9 @@ public List> parsePlateData( PlateSet plateSet = PlateManager.get().getPlateSet(cf, plateSetId); if (plateSet == null) throw new ExperimentException("Plate set " + plateSetId + " not found."); + if (plateSet.isTemplate()) + throw new ExperimentException(String.format("Plate set \"%s\" is a template plate set. Template plate sets do not support associating assay data.", plateSet.getName())); + List plates = PlateManager.get().getPlatesForPlateSet(plateSet); if (plates.isEmpty()) throw new ExperimentException("No plates were found for the plate set (" + plateSetId + ")."); @@ -556,10 +557,8 @@ public List> parsePlateData( // best attempt at returning something we can import return (gridRows.isEmpty() && !data.isEmpty()) ? data : gridRows; } - else - { - return parsePlateRows(container, user, provider, protocol, plates, data, plateIdAdded); - } + + return parsePlateRows(provider, protocol, plates, data, plateIdAdded); } private boolean isGridFormat(List> data) @@ -573,13 +572,11 @@ private boolean isGridFormat(List> data) } private List> parsePlateRows( - Container container, - User user, - AssayProvider provider, - ExpProtocol protocol, - List plates, - List> data, - Pair plateIdAdded + AssayProvider provider, + ExpProtocol protocol, + List plates, + List> data, + Pair plateIdAdded ) throws ExperimentException { DomainProperty plateProp = provider.getResultsDomain(protocol).getPropertyByName(AssayResultDomainKind.PLATE_COLUMN_NAME); @@ -590,57 +587,57 @@ private List> parsePlateRows( String plateIdField = data.get(0).keySet().stream().filter(importAliases::contains).findFirst().orElse(null); boolean hasPlateIdentifiers = plateIdField != null && (data.stream().filter(row -> row.get(plateIdField) != null).findFirst().orElse(null) != null); - if (!hasPlateIdentifiers) + if (hasPlateIdentifiers) + return data; + + final String ERROR_MESSAGE = "Unable to automatically assign plate identifiers to the data rows because %s. Please include plate identifiers for the data rows."; + plateIdAdded.second = true; + + // verify all plates in the set have the same shape + Set types = plates.stream().map(Plate::getPlateType).collect(Collectors.toSet()); + if (types.size() > 1) + throw new ExperimentException(String.format(ERROR_MESSAGE, "the plate set contains different plate types")); + + PlateType type = types.stream().toList().get(0); + int plateSize = type.getRows() * type.getColumns(); + if ((data.size() % plateSize) != 0) + throw new ExperimentException(String.format(ERROR_MESSAGE, "the number of rows in the data (" + data.size() + ") does not fit evenly and would result in a plate with partial wells filled")); + + if (data.size() > (plates.size() * plateSize)) + throw new ExperimentException(String.format(ERROR_MESSAGE, "the number of rows in the data (" + data.size() + ") exceeds the total number of wells available in the plate set (" + (plates.size() * plateSize) + ")")); + + // attempt to add the plate identifier into the data rows in the order that they appear in the plate set + List> newData = new ArrayList<>(); + int rowCount = 0; + int curPlate = 0; + Set positions = new HashSet<>(); + String plateFieldName = plateIdField != null ? plateIdField : AssayResultDomainKind.PLATE_COLUMN_NAME; + for (Map row : data) { - final String ERROR_MESSAGE = "Unable to automatically assign plate identifiers to the data rows because %s. Please include plate identifiers for the data rows."; - plateIdAdded.second = true; - - // verify all plates in the set have the same shape - Set types = plates.stream().map(Plate::getPlateType).collect(Collectors.toSet()); - if (types.size() > 1) - throw new ExperimentException(String.format(ERROR_MESSAGE, "the plate set contains different plate types")); - - PlateType type = types.stream().toList().get(0); - int plateSize = type.getRows() * type.getColumns(); - if ((data.size() % plateSize) != 0) - throw new ExperimentException(String.format(ERROR_MESSAGE, "the number of rows in the data (" + data.size() + ") does not fit evenly and would result in a plate with partial wells filled")); - - if (data.size() > (plates.size() * plateSize)) - throw new ExperimentException(String.format(ERROR_MESSAGE, "the number of rows in the data (" + data.size() + ") exceeds the total number of wells available in the plate set (" + (plates.size() * plateSize) + ")")); - - // attempt to add the plate identifier into the data rows in the order that they appear in the plate set - List> newData = new ArrayList<>(); - int rowCount = 0; - int curPlate = 0; - Set positions = new HashSet<>(); - String plateFieldName = plateIdField != null ? plateIdField : AssayResultDomainKind.PLATE_COLUMN_NAME; - for (Map row : data) - { - // well location field is required, return if not provided or it will fail downstream - String well = String.valueOf(row.get(AssayResultDomainKind.WELL_LOCATION_COLUMN_NAME)); - if (well == null) - return data; + // well location field is required, return if not provided or it will fail downstream + String well = String.valueOf(row.get(AssayResultDomainKind.WELL_LOCATION_COLUMN_NAME)); + if (well == null) + return data; - Position position = new PositionImpl(null, well); - if (positions.contains(position)) - throw new ExperimentException(String.format(ERROR_MESSAGE, "there is more than one well referencing the same position in the plate " + position)); + Position position = new PositionImpl(null, well); + if (positions.contains(position)) + throw new ExperimentException(String.format(ERROR_MESSAGE, "there is more than one well referencing the same position in the plate " + position)); - positions.add(position); - Map newRow = new HashMap<>(row); - newRow.put(plateFieldName, plates.get(curPlate).getPlateId()); - newData.add(newRow); + positions.add(position); + Map newRow = new HashMap<>(row); + newRow.put(plateFieldName, plates.get(curPlate).getPlateId()); + newData.add(newRow); - if (++rowCount >= plateSize) - { - // move to the next plate in the set - rowCount = 0; - curPlate++; - positions.clear(); - } + if (++rowCount >= plateSize) + { + // move to the next plate in the set + rowCount = 0; + curPlate++; + positions.clear(); } - return newData; } - return data; + + return newData; } /** @@ -679,7 +676,7 @@ public PlateGridInfo(PlateUtils.GridInfo info, PlateSet plateSet) throws Experim } } - private @Nullable Plate getPlateForId(String annotation, List platesetPlates) throws ExperimentException + private @NotNull Plate getPlateForId(String annotation, List platesetPlates) throws ExperimentException { Plate plate = platesetPlates.stream().filter(p -> p.isIdentifierMatch(annotation)).findFirst().orElse(null); if (plate == null) @@ -713,13 +710,13 @@ public PlateGridInfo(PlateUtils.GridInfo info, PlateSet plateSet) throws Experim } private List> parsePlateGrids( - Container container, - User user, - AssayProvider provider, - ExpProtocol protocol, - PlateSet plateSet, - List plates, - File dataFile + Container container, + User user, + AssayProvider provider, + ExpProtocol protocol, + PlateSet plateSet, + List plates, + File dataFile ) throws ExperimentException { // parse the data file for each distinct plate type found in the set of plates for the plateSetId @@ -734,8 +731,8 @@ private List> parsePlateGrids( { if (!plateTypeGrids.containsKey(plate.getPlateType())) { - Plate template = PlateService.get().createPlateTemplate(container, TsvPlateLayoutHandler.TYPE, plate.getPlateType()); - for (PlateUtils.GridInfo gridInfo : plateReader.loadMultiGridFile(template, dataFile)) + Plate p = PlateService.get().createPlate(container, TsvPlateLayoutHandler.TYPE, plate.getPlateType()); + for (PlateUtils.GridInfo gridInfo : plateReader.loadMultiGridFile(p, dataFile)) { PlateGridInfo plateInfo = new PlateGridInfo(gridInfo, plateSet); plateTypeGrids.put(plate.getPlateType(), plateInfo); @@ -853,12 +850,13 @@ private Map getDataRowFromWell(String plateId, Well well, String @Override @NotNull public OntologyManager.UpdateableTableImportHelper getImportHelper( - Container container, - User user, - ExpRun run, - ExpData data, - ExpProtocol protocol, - AssayProvider provider) throws ExperimentException + Container container, + User user, + ExpRun run, + ExpData data, + ExpProtocol protocol, + AssayProvider provider + ) { return new PlateMetadataImportHelper(data, container, user, run, protocol, provider); } @@ -947,8 +945,8 @@ else if (templateProperty != null) private static class MetadataLayerImpl implements MetadataLayer { - private String _name; - private Map _wellGroupMap = new CaseInsensitiveHashMap<>(); + private final String _name; + private final Map _wellGroupMap = new CaseInsensitiveHashMap<>(); public MetadataLayerImpl(String name) { @@ -975,8 +973,8 @@ public void addWellGroup(MetadataWellGroup wellGroup) private static class MetadataWellGroupImpl implements MetadataWellGroup { - private String _name; - private Map _properties = new CaseInsensitiveHashMap<>(); + private final String _name; + private final Map _properties = new CaseInsensitiveHashMap<>(); public MetadataWellGroupImpl(String name) { @@ -1007,7 +1005,7 @@ public static final class TestCase private static User user; @BeforeClass - public static void setupTest() throws Exception + public static void setupTest() { container = JunitUtil.getTestContainer(); user = TestContext.get().getUser(); @@ -1016,7 +1014,7 @@ public static void setupTest() throws Exception } @After - public void cleanupTest() throws Exception + public void cleanupTest() { PlateManager.get().deleteAllPlateData(container); } @@ -1024,7 +1022,7 @@ public void cleanupTest() throws Exception @Test public void testGridAnnotations() throws Exception { - // create a plateset + // create a plate set PlateSetImpl plateSet = new PlateSetImpl(); PlateType plateType = PlateManager.get().getPlateType(8, 12); List plates = List.of( @@ -1053,14 +1051,14 @@ public void testGridAnnotations() throws Exception gridInfo = new PlateGridInfo( new PlateUtils.GridInfo(new double[8][12], List.of(platesetPlates.get(0).getPlateId(), "Density")), plateSet); - assertEquals("Expected plate to not resolve on annotation without a prefix", null, gridInfo.getPlate()); - assertEquals("Expected measure to not resolve on annotation without a prefix", null, gridInfo.getMeasureName()); + assertNull("Expected plate to not resolve on annotation without a prefix", gridInfo.getPlate()); + assertNull("Expected measure to not resolve on annotation without a prefix", gridInfo.getMeasureName()); gridInfo = new PlateGridInfo( new PlateUtils.GridInfo(new double[8][12], List.of("PLATE:" + platesetPlates.get(0).getPlateId(), "Density")), plateSet); assertEquals("Expected plate to resolve on annotation with a prefix", platesetPlates.get(0).getRowId(), gridInfo.getPlate().getRowId()); - assertEquals("Expected measure to not resolve on annotation without a prefix", null, gridInfo.getMeasureName()); + assertNull("Expected measure to not resolve on annotation without a prefix", gridInfo.getMeasureName()); gridInfo = new PlateGridInfo( new PlateUtils.GridInfo(new double[8][12], List.of("plate:" + platesetPlates.get(0).getPlateId(), "MEASURE : Density")), @@ -1071,7 +1069,7 @@ public void testGridAnnotations() throws Exception gridInfo = new PlateGridInfo( new PlateUtils.GridInfo(new double[8][12], List.of(platesetPlates.get(0).getPlateId(), "measure : Density")), plateSet); - assertEquals("Expected plate to not resolve on annotation without a prefix", null, gridInfo.getPlate()); + assertNull("Expected plate to not resolve on annotation without a prefix", gridInfo.getPlate()); assertEquals("Expected measure to resolve on annotation with a prefix", "Density", gridInfo.getMeasureName()); } } diff --git a/assay/src/org/labkey/assay/plate/PlateCache.java b/assay/src/org/labkey/assay/plate/PlateCache.java index c7469b80653..b45926a9f38 100644 --- a/assay/src/org/labkey/assay/plate/PlateCache.java +++ b/assay/src/org/labkey/assay/plate/PlateCache.java @@ -21,7 +21,6 @@ import org.labkey.assay.query.AssayDbSchema; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -132,16 +131,18 @@ private void addCacheKeys(PlateCacheKey cacheKey, Plate plate) return plate != null ? plate.copy() : null; } - public static @NotNull Collection getPlates(Container c) + public static @NotNull List getPlates(Container c) { List plates = new ArrayList<>(); - List ids = new TableSelector(AssayDbSchema.getInstance().getTableInfoPlate(), - Collections.singleton("RowId"), - SimpleFilter.createContainerFilter(c), null).getArrayList(Integer.class); + List ids = new TableSelector( + AssayDbSchema.getInstance().getTableInfoPlate(), + Collections.singleton("RowId"), + SimpleFilter.createContainerFilter(c), + new Sort("RowId") + ).getArrayList(Integer.class); + for (Integer id : ids) - { plates.add(PLATE_CACHE.get(PlateCacheKey.getCacheKey(c, id))); - } return plates; } diff --git a/assay/src/org/labkey/assay/plate/PlateDataServiceImpl.java b/assay/src/org/labkey/assay/plate/PlateDataServiceImpl.java index 4778045bc91..fdd22c2406a 100644 --- a/assay/src/org/labkey/assay/plate/PlateDataServiceImpl.java +++ b/assay/src/org/labkey/assay/plate/PlateDataServiceImpl.java @@ -84,7 +84,7 @@ public GWTPlate getTemplateDefinition(String templateName, int plateId, String a PlateType plateType = PlateService.get().getPlateType(rowCount, columnCount); if (plateType == null) throw new Exception("The plate type : (" + rowCount + " x " + columnCount + ") does not exist"); - template = handler.createTemplate(templateTypeName, getContainer(), plateType); + template = handler.createPlate(templateTypeName, getContainer(), plateType); } // Translate PlateTemplate to GWTPlate @@ -153,21 +153,21 @@ public int saveChanges(GWTPlate gwtPlate, boolean replaceIfExisting) throws Exce try { boolean updateExisting = false; - Plate template; + Plate plate; if (gwtPlate.getRowId() > 0) { - template = PlateManager.get().getPlate(getContainer(), gwtPlate.getRowId()); - if (template == null) + plate = PlateManager.get().getPlate(getContainer(), gwtPlate.getRowId()); + if (plate == null) throw new Exception("Plate template not found: " + gwtPlate.getRowId()); // check another plate of the same name doesn't already exist - if (PlateManager.get().isDuplicatePlate(getContainer(), getUser(), gwtPlate.getName(), null) && !replaceIfExisting) + if (PlateManager.get().isDuplicatePlateName(getContainer(), getUser(), gwtPlate.getName(), null) && !replaceIfExisting) throw new Exception("A plate template with name '" + gwtPlate.getName() + "' already exists."); - if (!template.getAssayType().equals(gwtPlate.getType())) - throw new Exception("Plate template type '" + template.getAssayType() + "' cannot be changed for '" + gwtPlate.getName() + "'"); + if (!plate.getAssayType().equals(gwtPlate.getType())) + throw new Exception("Plate template type '" + plate.getAssayType() + "' cannot be changed for '" + gwtPlate.getName() + "'"); - if (template.getRows() != gwtPlate.getRows() || template.getColumns() != gwtPlate.getCols()) + if (plate.getRows() != gwtPlate.getRows() || plate.getColumns() != gwtPlate.getCols()) throw new Exception("Plate template dimensions cannot be changed for '" + gwtPlate.getName() + "'"); // TODO: Use a version column to avoid concurrent updates @@ -190,19 +190,19 @@ public int saveChanges(GWTPlate gwtPlate, boolean replaceIfExisting) throws Exce PlateType plateType = PlateService.get().getPlateType(gwtPlate.getRows(), gwtPlate.getCols()); if (plateType == null) throw new Exception("The plate type : (" + gwtPlate.getRows() + " x " + gwtPlate.getCols() + ") does not exist"); - template = PlateManager.get().createPlateTemplate(getContainer(), gwtPlate.getType(), plateType); + plate = PlateManager.get().createPlate(getContainer(), gwtPlate.getType(), plateType); } - template.setName(gwtPlate.getName()); - template.setProperties(gwtPlate.getPlateProperties()); + plate.setName(gwtPlate.getName()); + plate.setProperties(gwtPlate.getPlateProperties()); // first, mark well groups not submitted for saving as deleted Set groups = gwtPlate.getGroups(); - List existingWellGroups = template.getWellGroups(); + List existingWellGroups = plate.getWellGroups(); for (WellGroup existingWellGroup : existingWellGroups) { if (groups.stream().noneMatch(g-> g.getRowId() == existingWellGroup.getRowId())) - ((PlateImpl)template).markWellGroupForDeletion(existingWellGroup); + ((PlateImpl)plate).markWellGroupForDeletion(existingWellGroup); } // next, update positions on existing well groups or create new well groups @@ -211,7 +211,7 @@ public int saveChanges(GWTPlate gwtPlate, boolean replaceIfExisting) throws Exce WellGroup.Type groupType = WellGroup.Type.valueOf(gwtGroup.getType()); List positions = new ArrayList<>(); for (GWTPosition gwtPosition : gwtGroup.getPositions()) - positions.add(template.getPosition(gwtPosition.getRow(), gwtPosition.getCol())); + positions.add(plate.getPosition(gwtPosition.getRow(), gwtPosition.getCol())); WellGroupImpl group; if (updateExisting && gwtGroup.getRowId() > 0) @@ -225,19 +225,19 @@ public int saveChanges(GWTPlate gwtPlate, boolean replaceIfExisting) throws Exce group.setName(gwtGroup.getName()); group.setPositions(positions); - ((PlateImpl)template).storeWellGroup(group); + ((PlateImpl)plate).storeWellGroup(group); } else { assert gwtGroup.getRowId() <= 0 : "Updating existing well group on a new template"; - group = (WellGroupImpl) template.addWellGroup(gwtGroup.getName(), groupType, positions); + group = (WellGroupImpl) plate.addWellGroup(gwtGroup.getName(), groupType, positions); } group.setProperties(gwtGroup.getProperties()); } - PlateManager.get().getPlateLayoutHandler(template.getAssayType()).validateTemplate(getContainer(), getUser(), template); - return PlateService.get().save(getContainer(), getUser(), template); + PlateManager.get().getPlateLayoutHandler(plate.getAssayType()).validatePlate(getContainer(), getUser(), plate); + return PlateService.get().save(getContainer(), getUser(), plate); } catch (BatchValidationException | ValidationException e) { diff --git a/assay/src/org/labkey/assay/plate/PlateImpl.java b/assay/src/org/labkey/assay/plate/PlateImpl.java index 3fb87bcb9f3..4b929be9e98 100644 --- a/assay/src/org/labkey/assay/plate/PlateImpl.java +++ b/assay/src/org/labkey/assay/plate/PlateImpl.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.Test; @@ -34,12 +35,16 @@ import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; import org.labkey.api.data.Transient; +import org.labkey.api.query.FieldKey; import org.labkey.api.query.QueryRowReference; +import org.labkey.api.query.SchemaKey; import org.labkey.api.util.GUID; import org.labkey.api.util.UnexpectedException; import org.labkey.api.view.ActionURL; import org.labkey.assay.PlateController; import org.labkey.assay.plate.model.PlateBean; +import org.labkey.assay.plate.query.PlateSchema; +import org.labkey.assay.plate.query.PlateTable; import java.util.ArrayList; import java.util.Arrays; @@ -56,52 +61,50 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class PlateImpl extends PropertySetImpl implements Plate, Cloneable { - private String _name; - private Integer _rowId; - private int _createdBy; + private boolean _archived; + private String _assayType; private long _created; - private int _modifiedBy; - private long _modified; + private int _createdBy; + private List _customFields = Collections.emptyList(); private String _dataFileId; - private String _assayType; - private boolean _isTemplate; + private List _deletedGroups; private String _description; + private Map> _groups; + private long _modified; + private int _modifiedBy; + private String _name; private String _plateId; - private PlateType _plateType; + private int _plateNumber = 1; private PlateSet _plateSet; - - private Map> _groups; - private List _deletedGroups; - - private WellImpl[][] _wells; + private PlateType _plateType; + private Integer _rowId; + private Integer _runCount; + private int _runId = PlateService.NO_RUNID; // NO_RUNID means no run yet, well data comes from file, dilution data must be calculated + private boolean _template; + private WellImpl[][] _wells = null; private Map _wellMap; - - private int _runId; // NO_RUNID means no run yet, well data comes from file, dilution data must be calculated - private int _plateNumber; - private List _customFields = Collections.emptyList(); private Integer _metadataDomainId; - private Integer _runCount; - + // no-param constructor for reflection public PlateImpl() { - // no-param constructor for reflection - _wells = null; - _runId = PlateService.NO_RUNID; - _plateNumber = 1; } public PlateImpl(Container container, String name, String assayType, @NotNull PlateType plateType) { super(container); - _name = name; + _name = StringUtils.trimToNull(name); _assayType = assayType; - _container = container; _dataFileId = GUID.makeGUID(); _plateType = plateType; } - public PlateImpl(PlateImpl plate, double[][] wellValues, boolean[][] excluded, int runId, int plateNumber) + public PlateImpl(Container container, String name, @NotNull PlateType plateType) + { + this(container, name, TsvPlateLayoutHandler.TYPE, plateType); + } + + public PlateImpl(@NotNull PlateImpl plate, double[][] wellValues, boolean[][] excluded, int runId, int plateNumber) { this(plate.getContainer(), plate.getName(), plate.getAssayType(), plate.getPlateType()); @@ -126,8 +129,6 @@ else if (wellValues.length != plate.getRows() && wellValues[0].length != plate.g for (WellGroup group : plate.getWellGroups()) addWellGroup(new WellGroupImpl(this, (WellGroupImpl) group)); - - setContainer(plate.getContainer()); } public static PlateImpl from(PlateBean bean) @@ -142,6 +143,7 @@ public static PlateImpl from(PlateBean bean) plate.setDataFileId(bean.getDataFileId()); plate.setAssayType(bean.getAssayType()); plate.setPlateId(bean.getPlateId()); + plate.setArchived(bean.getArchived()); plate.setDescription(bean.getDescription()); // entity fields @@ -179,7 +181,9 @@ public static PlateImpl from(PlateBean bean) @Override public @Nullable QueryRowReference getQueryRowReference() { - return null; + if (isNew()) + return null; + return new QueryRowReference(getContainer(), SchemaKey.fromParts(PlateSchema.SCHEMA_NAME), PlateTable.NAME, FieldKey.fromParts("rowId"), getRowId()); } @JsonIgnore @@ -191,7 +195,7 @@ public String getLSIDNamespacePrefix() } @Override - public WellGroup addWellGroup(String name, WellGroup.Type type, Position upperLeft, Position lowerRight) + public @NotNull WellGroup addWellGroup(String name, WellGroup.Type type, Position upperLeft, Position lowerRight) { int regionWidth = lowerRight.getColumn() - upperLeft.getColumn() + 1; int regionHeight = lowerRight.getRow() - upperLeft.getRow(); @@ -206,7 +210,7 @@ public WellGroup addWellGroup(String name, WellGroup.Type type, Position upperLe @JsonIgnore @Override - public WellGroupImpl addWellGroup(String name, WellGroup.Type type, List positions) + public @NotNull WellGroupImpl addWellGroup(String name, WellGroup.Type type, List positions) { return storeWellGroup(createWellGroup(name, type, positions)); } @@ -261,27 +265,33 @@ private boolean wellGroupsInOrder(Map groups) @JsonIgnore @Override - public List getWellGroups(Position position) + public @NotNull List getWellGroups(Position position) { + List wellGroups = getWellGroups(); + if (wellGroups.isEmpty()) + return Collections.emptyList(); + List groups = new ArrayList<>(); - for (WellGroup group : getWellGroups()) + for (WellGroup group : wellGroups) { if (group.contains(position)) groups.add(group); } + return groups; } @JsonIgnore @Override - public List getWellGroups() + public @NotNull List getWellGroups() { + if (_groups == null) + return Collections.emptyList(); + List allGroups = new ArrayList<>(); - if (_groups != null) - { - for (Map groups : _groups.values()) - allGroups.addAll(groups.values()); - } + for (Map groups : _groups.values()) + allGroups.addAll(groups.values()); + return allGroups; } @@ -298,34 +308,47 @@ public List getWellGroups() @JsonIgnore @Override - public List getWellGroups(WellGroup.Type type) + public @NotNull List getWellGroups(WellGroup.Type type) { + if (_groups == null) + return Collections.emptyList(); + List allGroups = new ArrayList<>(); - if (_groups != null) - { - var typedGroups = _groups.get(type); - if (typedGroups != null && !typedGroups.isEmpty()) - allGroups.addAll(typedGroups.values()); - } + var typedGroups = _groups.get(type); + if (typedGroups != null && !typedGroups.isEmpty()) + allGroups.addAll(typedGroups.values()); + return allGroups; } @JsonIgnore @Override - public Map> getWellGroupMap() + public @NotNull Map> getWellGroupMap() { + if (_groups == null) + return Collections.emptyMap(); + Map> wellgroupTypeMap = new HashMap<>(); - if (_groups != null) + for (Map.Entry> groupEntry : _groups.entrySet()) { - for (Map.Entry> groupEntry : _groups.entrySet()) - { - Map groupMap = new HashMap<>(groupEntry.getValue()); - wellgroupTypeMap.put(groupEntry.getKey(), groupMap); - } + Map groupMap = new HashMap<>(groupEntry.getValue()); + wellgroupTypeMap.put(groupEntry.getKey(), groupMap); } + return wellgroupTypeMap; } + @Override + public boolean isArchived() + { + return _archived; + } + + public void setArchived(boolean archived) + { + _archived = archived; + } + @Override @JsonIgnore public int getColumns() @@ -351,7 +374,8 @@ public int getRows() } @JsonIgnore - public PositionImpl getPosition(int row, int col) + @Override + public @NotNull PositionImpl getPosition(int row, int col) { return new PositionImpl(_container, row, col); } @@ -501,15 +525,13 @@ public List getDeletedWellGroups() @JsonIgnore @Override - public WellImpl getWell(int row, int col) + public @NotNull WellImpl getWell(int row, int col) { if (_wells != null) return _wells[row][col]; - else - { - // there is no data associated with this plate, return a well will no data. - return new WellImpl(this, row, col, null, false); - } + + // there is no data associated with this plate, return a well will no data. + return new WellImpl(this, row, col, null, false); } @Override @@ -554,7 +576,7 @@ public void setWells(WellImpl[][] wells) @JsonIgnore @Override - public List getWells() + public @NotNull List getWells() { if (_wellMap != null) return _wellMap.values().stream().toList(); @@ -566,19 +588,19 @@ else if (_wells != null) wells.add(getWell(row, col)); return wells; } - else - return Collections.emptyList(); + + return Collections.emptyList(); } @Override public boolean isTemplate() { - return _isTemplate; + return _template; } public void setTemplate(boolean template) { - _isTemplate = template; + _template = template; } @Override @@ -649,11 +671,12 @@ public void setCustomFields(List customFields) _customFields = customFields; } + @Override public PlateImpl copy() { try { - return (PlateImpl)super.clone(); + return (PlateImpl) super.clone(); } catch (CloneNotSupportedException e) { @@ -703,6 +726,12 @@ public boolean isIdentifierMatch(String id) return id != null && !id.isEmpty() && (id.equals(getRowId() + "") || id.equalsIgnoreCase(getPlateId()) || id.equalsIgnoreCase(getName())); } + @Override + public boolean isNew() + { + return _rowId == null || _rowId <= 0; + } + public static final class TestCase { @Test diff --git a/assay/src/org/labkey/assay/plate/PlateManager.java b/assay/src/org/labkey/assay/plate/PlateManager.java index b600135cdc4..fb8f52d9e0d 100644 --- a/assay/src/org/labkey/assay/plate/PlateManager.java +++ b/assay/src/org/labkey/assay/plate/PlateManager.java @@ -21,16 +21,13 @@ import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; import org.labkey.api.assay.AssayListener; import org.labkey.api.assay.AssayProtocolSchema; import org.labkey.api.assay.AssayProvider; import org.labkey.api.assay.AssayService; import org.labkey.api.assay.dilution.DilutionCurve; import org.labkey.api.assay.plate.AbstractPlateLayoutHandler; +import org.labkey.api.assay.plate.AssayPlateMetadataService; import org.labkey.api.assay.plate.Plate; import org.labkey.api.assay.plate.PlateCustomField; import org.labkey.api.assay.plate.PlateLayoutHandler; @@ -76,14 +73,11 @@ import org.labkey.api.exp.OntologyObject; import org.labkey.api.exp.PropertyDescriptor; import org.labkey.api.exp.PropertyType; -import org.labkey.api.exp.api.ExpMaterial; import org.labkey.api.exp.api.ExpObject; import org.labkey.api.exp.api.ExpProtocol; import org.labkey.api.exp.api.ExpRun; -import org.labkey.api.exp.api.ExpSampleType; import org.labkey.api.exp.api.ExperimentListener; import org.labkey.api.exp.api.ExperimentService; -import org.labkey.api.exp.api.SampleTypeService; import org.labkey.api.exp.api.StorageProvisioner; import org.labkey.api.exp.property.Domain; import org.labkey.api.exp.property.DomainKind; @@ -105,10 +99,8 @@ import org.labkey.api.security.permissions.ReadPermission; import org.labkey.api.security.permissions.UpdatePermission; import org.labkey.api.util.GUID; -import org.labkey.api.util.JunitUtil; import org.labkey.api.util.PageFlowUtil; import org.labkey.api.util.Pair; -import org.labkey.api.util.TestContext; import org.labkey.api.util.UnexpectedException; import org.labkey.api.view.ActionURL; import org.labkey.api.view.HttpView; @@ -122,7 +114,6 @@ import org.labkey.assay.plate.model.PlateSetAssays; import org.labkey.assay.plate.model.PlateSetLineage; import org.labkey.assay.plate.model.PlateTypeBean; -import org.labkey.assay.plate.model.WellBean; import org.labkey.assay.plate.model.WellGroupBean; import org.labkey.assay.plate.query.PlateSchema; import org.labkey.assay.plate.query.PlateSetTable; @@ -150,13 +141,7 @@ import java.util.stream.Collectors; import static java.util.Collections.emptyList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.labkey.api.assay.plate.PlateSet.MAX_PLATES; -import static org.labkey.api.util.JunitUtil.deleteTestContainer; public class PlateManager implements PlateService, AssayListener, ExperimentListener { @@ -189,13 +174,14 @@ public PlateManager() registerPlateLayoutHandler(new AbstractPlateLayoutHandler() { @Override - public Plate createTemplate(@Nullable String templateTypeName, Container container, @NotNull PlateType plateType) + public Plate createPlate(@Nullable String plateName, Container container, @NotNull PlateType plateType) { validatePlateType(plateType); - return PlateService.get().createPlateTemplate(container, getAssayType(), plateType); + return PlateManager.get().createPlate(container, getAssayType(), plateType); } @Override + @NotNull public String getAssayType() { return "blank"; @@ -241,96 +227,55 @@ public List getWellGroupTypes() throw new IllegalArgumentException("Only plates retrieved from the plate service can be used to create plate instances."); } + @Override + public @NotNull Plate createPlate(Container container, String assayType, @NotNull PlateType plateType) + { + return new PlateImpl(container, null, assayType, plateType); + } + public @NotNull Plate createAndSavePlate( @NotNull Container container, @NotNull User user, - @NotNull PlateType plateType, - @Nullable String plateName, + @NotNull Plate plate, @Nullable Integer plateSetId, - @Nullable String assayType, @Nullable List> data ) throws Exception { - Plate plate = null; + if (!plate.isNew()) + throw new ValidationException(String.format("Failed to create plate. The provided plate already exists with rowId (%d).", plate.getRowId())); + + if (plate.isTemplate() && isDuplicatePlateTemplateName(container, plate.getName())) + throw new ValidationException(String.format("Failed to create plate template. A plate template already exists with the name \"%s\".", plate.getName())); + try (DbScope.Transaction tx = ensureTransaction()) { - Plate plateTemplate = PlateService.get().createPlateTemplate(container, assayType, plateType); - - plate = createPlate(plateTemplate, null, null); if (plateSetId != null) { PlateSet plateSet = getPlateSet(container, plateSetId); if (plateSet == null) - throw new IllegalArgumentException("Failed to create plate. Plate set with rowId (" + plateSetId + ") is not available in " + container.getPath()); + throw new ValidationException(String.format("Failed to create plate. Plate set with rowId (%d) is not available in %s.", plateSetId, container.getPath())); + if (plate.isTemplate() && !plateSet.isTemplate()) + throw new ValidationException(String.format("Failed to create plate. Plate set \"%s\" is not a template plate set.", plateSet.getName())); + if (!plate.isTemplate() && plateSet.isTemplate()) + throw new ValidationException(String.format("Failed to create plate. Plate set \"%s\" is a template plate set.", plateSet.getName())); ((PlateImpl) plate).setPlateSet(plateSet); } - if (StringUtils.trimToNull(plateName) != null) - plate.setName(plateName.trim()); - int plateRowId = save(container, user, plate); plate = getPlate(container, plateRowId); if (plate == null) - throw new IllegalStateException("Unexpected failure. Failed to retrieve plate after save."); + throw new IllegalStateException("Unexpected failure. Failed to retrieve plate after save (pre-commit)."); - // if well data was specified, save that to the well table - if (data != null && !data.isEmpty()) - { - QueryUpdateService qus = getWellUpdateService(container, user); - TableInfo wellTable = getWellTable(container, user); - BatchValidationException errors = new BatchValidationException(); - Set customFields = new HashSet<>(); - - TableInfo metadataTable = getPlateMetadataTable(container, user); - Set metadataFields = Collections.emptySet(); - if (metadataTable != null) - metadataFields = metadataTable.getColumns().stream().map(ColumnInfo::getFieldKey).collect(Collectors.toSet()); - - // resolve columns and set any custom fields associated with the plate - List> rows = new ArrayList<>(); - for (Map dataRow : data) - { - if (dataRow.containsKey("wellLocation")) - { - PlateUtils.Location loc = PlateUtils.parseLocation(String.valueOf(dataRow.get("wellLocation"))); - Well well = plate.getWell(loc.getRow(), loc.getCol()); - if (well != null) - { - Map row = new CaseInsensitiveHashMap<>(dataRow); - row.put("rowId", well.getRowId()); - rows.add(row); - - for (String colName : dataRow.keySet()) - { - ColumnInfo col = wellTable.getColumn(FieldKey.fromParts(colName)); - if (col != null && metadataFields.contains(col.getFieldKey())) - { - PlateCustomField customField = new PlateCustomField(col.getPropertyURI()); - customFields.add(customField); - } - } - } - else - LOG.error("There is no corresponding well at : " + dataRow.get("wellLocation") + " for the plate : " + plate.getName()); - } - else - { - // should we fail or just log? - LOG.error("Unable to add well data for plate: " + plate.getName() + " each data row must contain a wellLocation field."); - } - } + saveWellData(container, user, plate, data); - // update the well table - qus.updateRows(user, container, rows, null, errors, null, null); - if (errors.hasErrors()) - throw errors; - - // add custom fields to the plate - if (!customFields.isEmpty()) - addFields(container, user, plate.getRowId(), customFields.stream().toList()); - } tx.commit(); - return getPlate(container, plate.getRowId()); + + // re-fetch the plate to get updated well data + plate = getPlate(container, plateRowId); + if (plate == null) + throw new IllegalStateException("Unexpected failure. Failed to retrieve plate after save (post-commit)."); + + return plate; } catch (Exception e) { @@ -341,6 +286,67 @@ public List getWellGroupTypes() } } + private void saveWellData( + @NotNull Container container, + @NotNull User user, + @NotNull Plate plate, + List> data + ) throws Exception + { + requireActiveTransaction(); + + if (data == null || data.isEmpty()) + return; + + QueryUpdateService qus = getWellUpdateService(container, user); + TableInfo wellTable = getWellTable(container, user); + BatchValidationException errors = new BatchValidationException(); + Set customFields = new HashSet<>(); + + TableInfo metadataTable = getPlateMetadataTable(container, user); + Set metadataFields = Collections.emptySet(); + if (metadataTable != null) + metadataFields = metadataTable.getColumns().stream().map(ColumnInfo::getFieldKey).collect(Collectors.toSet()); + + // resolve columns and set any custom fields associated with the plate + List> rows = new ArrayList<>(); + for (Map dataRow : data) + { + if (dataRow.containsKey("wellLocation")) + { + PlateUtils.Location loc = PlateUtils.parseLocation(String.valueOf(dataRow.get("wellLocation"))); + Well well = plate.getWell(loc.getRow(), loc.getCol()); + Map row = new CaseInsensitiveHashMap<>(dataRow); + row.put("rowId", well.getRowId()); + rows.add(row); + + for (String colName : dataRow.keySet()) + { + ColumnInfo col = wellTable.getColumn(FieldKey.fromParts(colName)); + if (col != null && metadataFields.contains(col.getFieldKey())) + { + PlateCustomField customField = new PlateCustomField(col.getPropertyURI()); + customFields.add(customField); + } + } + } + else + { + // should we fail or just log? + LOG.error("Unable to add well data for plate: " + plate.getName() + " each data row must contain a wellLocation field."); + } + } + + // update the well table + qus.updateRows(user, container, rows, null, errors, null, null); + if (errors.hasErrors()) + throw errors; + + // add custom fields to the plate + if (!customFields.isEmpty()) + addFields(container, user, plate.getRowId(), customFields.stream().toList()); + } + @Override public WellGroup createWellGroup(Plate plate, String name, WellGroup.Type type, List positions) { @@ -370,7 +376,6 @@ public Position createPosition(Container container, int row, int column) return null; } - @Override @NotNull public List getPlateTemplates(Container container) { @@ -425,7 +430,7 @@ public int getRunCountUsingPlate(@NotNull Container c, @NotNull User user, @NotN List protocols = AssayService.get().getAssayProtocols(c, provider) .stream().filter(provider::isPlateMetadataEnabled).toList(); - // get the runIds for each protocol, query against its assayresults table + // get the runIds for each protocol, query against its assay results table List runIds = new ArrayList<>(); for (ExpProtocol protocol : protocols) { @@ -495,18 +500,9 @@ public int getRunCountUsingPlate(@NotNull Container c, @NotNull User user, @NotN return new SqlSelector(ExperimentService.get().getSchema(), sql); } - @Override - @NotNull - public Plate createPlate(Container container, String templateType, @NotNull PlateType plateType) + @NotNull Plate createPlateTemplate(Container container, String assayType, @NotNull PlateType plateType) { - return new PlateImpl(container, null, templateType, plateType); - } - - @Override - @NotNull - public Plate createPlateTemplate(Container container, String templateType, @NotNull PlateType plateType) - { - Plate plate = createPlate(container, templateType, plateType); + Plate plate = createPlate(container, assayType, plateType); ((PlateImpl)plate).setTemplate(true); return plate; @@ -645,13 +641,12 @@ public ContainerFilter getPlateContainerFilter(@Nullable ExpProtocol protocol, C * Issue 49665 : Checks to see if there is a plate with the same name in the folder, or for * Biologics folders if there is a duplicate plate name in the plate set. */ - public boolean isDuplicatePlate(Container c, User user, String name, @Nullable PlateSet plateSet) + public boolean isDuplicatePlateName(Container c, User user, String name, @Nullable PlateSet plateSet) { // Identifying the "Biologics" folder type as the logic we pivot this behavior on is not intended to be // a long-term solution. We will be looking to introduce plating as a ProductFeature which we can then // leverage instead. - boolean isBiologicsProject = c.getProject() != null && "Biologics".equals(ContainerManager.getFolderTypeName(c.getProject())); - if (isBiologicsProject && plateSet != null) + if (plateSet != null && AssayPlateMetadataService.isBiologicsFolder(c)) { for (Plate plate : plateSet.getPlates(user)) { @@ -660,14 +655,29 @@ public boolean isDuplicatePlate(Container c, User user, String name, @Nullable P } return false; } - else - { - Plate plate = getPlateByName(c, name); - return plate != null && plate.getName().equals(name); - } + + Plate plate = getPlateByName(c, name); + return plate != null && plate.getName().equals(name); } - private Collection getPlates(Container c) + public boolean isDuplicatePlateTemplateName(Container container, String name) + { + if (StringUtils.trimToNull(name) == null) + return false; + + SimpleFilter filter = new SimpleFilter(FieldKey.fromParts("Name"), name); + filter.addCondition(FieldKey.fromParts("Template"), true); + + ContainerFilter cf = QueryService.get().getContainerFilterForLookups(container, User.getAdminServiceUser()); + if (cf == null) + cf = ContainerFilter.current(container); + filter.addCondition(cf.createFilterClause(AssayDbSchema.getInstance().getSchema(), FieldKey.fromParts("Container"))); + + return new TableSelector(AssayDbSchema.getInstance().getTableInfoPlate(), Set.of("RowId"), filter, null).exists(); + } + + @Override + public @NotNull List getPlates(Container c) { return PlateCache.getPlates(c); } @@ -866,8 +876,12 @@ private int savePlateImpl(Container container, User user, PlateImpl plate) throw if (!updateExisting && plate.getPlateSet() == null) { // ensure a plate set for each new plate - plate.setPlateSet(createPlateSet(container, user, new PlateSetImpl(), null, null)); + PlateSetImpl plateSet = new PlateSetImpl(); + plateSet.setTemplate(plate.isTemplate()); + + plate.setPlateSet(createPlateSet(container, user, plateSet, null, null)); } + Map plateRow = ObjectFactory.Registry.getFactory(PlateBean.class).toMap(PlateBean.from(plate), new ArrayListMap<>()); QueryUpdateService qus = getPlateUpdateService(container, user); BatchValidationException errors = new BatchValidationException(); @@ -987,6 +1001,9 @@ private int savePlateImpl(Container container, User user, PlateImpl plate) throw Table.batchExecute(AssayDbSchema.getInstance().getSchema(), insertSql, wellGroupPositions); } + if (!updateExisting && !plate.getCustomFields().isEmpty()) + setFields(container, user, plate.getRowId(), plate.getCustomFields()); + final Integer plateRowId = plateId; transaction.addCommitTask(() -> { clearCache(container, plate); @@ -1441,25 +1458,190 @@ public void registerLsidHandlers() _lsidHandlersRegistered = true; } - @Override - public Plate copyPlate(Plate source, User user, Container destContainer) - throws Exception + private void copyProperties(@NotNull Plate source, @NotNull Plate copy) { - if (isDuplicatePlate(destContainer, user, source.getName(), null)) - throw new PlateService.NameConflictException(source.getName()); - Plate newPlate = createPlateTemplate(destContainer, source.getAssayType(), source.getPlateType()); - newPlate.setName(source.getName()); for (String property : source.getPropertyNames()) - newPlate.setProperty(property, source.getProperty(property)); + copy.setProperty(property, source.getProperty(property)); + } + + private void copyWellData(User user, @NotNull Plate source, @NotNull Plate copy, boolean copySample) throws Exception + { + requireActiveTransaction(); + + var container = source.getContainer(); + var wellTable = getWellTable(container, user); + + var sourceWellData = new TableSelector(wellTable, Set.of("RowId", "LSID", "SampleId"), new SimpleFilter(FieldKey.fromParts("PlateId"), source.getRowId()), new Sort("RowId")).getMapArray(); + var copyWellData = new TableSelector(wellTable, Set.of("RowId", "LSID"), new SimpleFilter(FieldKey.fromParts("PlateId"), copy.getRowId()), new Sort("RowId")).getMapArray(); + + if (sourceWellData.length != copyWellData.length) + { + String msg = "Failed to copy well data. Source plate \"%s\" contains %d rows of well data which does not match %d in copied plate."; + throw new ValidationException(String.format(msg, source.getName(), sourceWellData.length, copyWellData.length)); + } + + var sourceWellLSIDS = Arrays.stream(sourceWellData).map(data -> data.get("LSID")).toList(); + var sourceFilter = new SimpleFilter(FieldKey.fromParts("LSID"), sourceWellLSIDS, CompareType.IN); + + final Set wellMetadataFields; + final Map> sourceMetaData; + + var metadataTable = getPlateMetadataTable(container, user); + if (metadataTable != null) + { + wellMetadataFields = metadataTable.getColumns().stream().map(ColumnInfo::getFieldKey).collect(Collectors.toSet()); + wellMetadataFields.remove(FieldKey.fromParts("LSID")); + + var metaDataRows = new TableSelector(metadataTable, sourceFilter, null).getMapCollection(); + sourceMetaData = new CaseInsensitiveHashMap<>(); + for (var row : metaDataRows) + sourceMetaData.put((String) row.get("LSID"), row); + } + else + { + wellMetadataFields = Collections.emptySet(); + sourceMetaData = Collections.emptyMap(); + } + + List> newWellData = new ArrayList<>(); + + for (int i = 0; i < sourceWellData.length; i++) + { + var sourceRow = sourceWellData[i]; + String sourceWellLSID = (String) sourceRow.get("LSID"); + var copyRow = copyWellData[i]; + + var updateCopyRow = new CaseInsensitiveHashMap<>(); + updateCopyRow.put("RowId", copyRow.get("RowId")); + if (copySample) + updateCopyRow.put("SampleId", sourceRow.get("SampleId")); + + if (sourceMetaData.containsKey(sourceWellLSID)) + { + var sourceMetaDataRow = (Map) sourceMetaData.get(sourceWellLSID); + + for (var field : wellMetadataFields) + { + var value = sourceMetaDataRow.get(field.toString()); + if (value != null) + updateCopyRow.put(FieldKey.fromParts("properties", field.toString()).toString(), value); + } + } + + newWellData.add(updateCopyRow); + } + + var errors = new BatchValidationException(); + getWellUpdateService(container, user).updateRows(user, container, newWellData, null, errors, null, null); + if (errors.hasErrors()) + throw errors; + } + + private void copyWellGroups(@NotNull Plate source, @NotNull Plate copy) + { for (WellGroup originalGroup : source.getWellGroups()) { List positions = new ArrayList<>(); for (Position position : originalGroup.getPositions()) - positions.add(newPlate.getPosition(position.getRow(), position.getColumn())); - WellGroup copyGroup = newPlate.addWellGroup(originalGroup.getName(), originalGroup.getType(), positions); + positions.add(copy.getPosition(position.getRow(), position.getColumn())); + WellGroup copyGroup = copy.addWellGroup(originalGroup.getName(), originalGroup.getType(), positions); for (String property : originalGroup.getPropertyNames()) copyGroup.setProperty(property, originalGroup.getProperty(property)); } + } + + public Plate copyPlate( + Container container, + User user, + Integer sourcePlateRowId, + boolean copyAsTemplate, + @Nullable String name, + @Nullable String description + ) throws Exception + { + if (!container.hasPermission(user, InsertPermission.class)) + throw new UnauthorizedException("Failed to copy plate. Insufficient permissions."); + + ContainerFilter cf = QueryService.get().getContainerFilterForLookups(container, user); + if (cf == null) + cf = ContainerFilter.current(container); + + PlateImpl sourcePlate = (PlateImpl) getPlate(cf, sourcePlateRowId); + if (sourcePlate == null) + throw new ValidationException(String.format("Failed to copy plate. Unable to resolve source plate with RowId (%d).", sourcePlateRowId)); + + PlateSet sourcePlateSet = sourcePlate.getPlateSet(); + if (sourcePlateSet == null) + throw new ValidationException(String.format("Failed to copy plate. Unable to resolve source plate set for plate \"%s\".", sourcePlate.getName())); + + if (!container.equals(sourcePlateSet.getContainer())) + throw new ValidationException(String.format("Failed to copy plate. The destination folder \"%s\" does not match the plate set folder \"%s\".", container.getPath(), sourcePlateSet.getContainer().getPath())); + + boolean isTemplate = copyAsTemplate || sourcePlate.isTemplate(); + boolean hasName = StringUtils.trimToNull(name) != null; + + if (isTemplate && !hasName) + throw new ValidationException("Failed to copy plate template. A \"name\" is required."); + + if (!isTemplate && ((PlateSetImpl) sourcePlateSet).isFull()) + throw new ValidationException(String.format("Failed to copy plate. The plate set \"%s\" is full.", sourcePlateSet.getName())); + + if (hasName) + { + if (isTemplate) + { + if (isDuplicatePlateTemplateName(container, name)) + throw new ValidationException(String.format("Failed to copy plate template. A plate template already exists with the name \"%s\".", name)); + } + else if (isDuplicatePlateName(container, user, name, sourcePlateSet)) + throw new ValidationException(String.format("Failed to copy plate. A plate already exists with the name \"%s\".", name)); + } + + try (DbScope.Transaction tx = ExperimentService.get().ensureTransaction()) + { + // Copy the plate + PlateImpl newPlate = new PlateImpl(container, name, sourcePlate.getAssayType(), sourcePlate.getPlateType()); + newPlate.setCustomFields(sourcePlate.getCustomFields()); + newPlate.setDescription(description); + + if (isTemplate) + newPlate.setTemplate(true); + else + newPlate.setPlateSet(sourcePlateSet); + + copyProperties(sourcePlate, newPlate); + copyWellGroups(sourcePlate, newPlate); + + // Save the plate + int plateId = save(container, user, newPlate); + newPlate = (PlateImpl) getPlate(container, plateId); + if (newPlate == null) + throw new IllegalStateException("Unexpected failure. Failed to retrieve plate after save (pre-commit)."); + + // Copy plate metadata + copyWellData(user, sourcePlate, newPlate, !newPlate.isTemplate()); + + tx.commit(); + + return newPlate; + } + } + + /** + * @deprecated Use {@link #copyPlate(Container, User, Integer, boolean, String, String)} + */ + @Deprecated + public Plate copyPlateDeprecated(Plate source, User user, Container destContainer) + throws Exception + { + if (isDuplicatePlateName(destContainer, user, source.getName(), null)) + throw new PlateService.NameConflictException(source.getName()); + Plate newPlate = createPlate(destContainer, source.getAssayType(), source.getPlateType()); + newPlate.setName(source.getName()); + + copyProperties(source, newPlate); + copyWellGroups(source, newPlate); + int plateId = save(destContainer, user, newPlate); return getPlate(destContainer, plateId); } @@ -1479,11 +1661,44 @@ public void clearCache(Container c, Plate plate) PlateCache.uncache(c, plate); } - public void clearCache(Container c) + private void clearCache(Container c) { PlateCache.uncache(c); } + /** + * Clear the plate cache for an arbitrary collection of plates where only the rowIds are known. + */ + private void clearCache(Collection plateRowIds) + { + var table = AssayDbSchema.getInstance().getTableInfoPlate(); + SQLFragment sql = new SQLFragment("SELECT rowId, container FROM ").append(table, "") + .append("WHERE rowId ").appendInClause(plateRowIds, table.getSqlDialect()); + Collection> plateData = new SqlSelector(table.getSchema(), sql).getMapCollection(); + + for (Map data : plateData) + { + Integer rowId = (Integer) data.get("rowId"); + String containerId = (String) data.get("container"); + if (StringUtils.trimToNull(containerId) == null) + { + LOG.warn(String.format("clearCache: failed to resolve containerId for plate with rowId %d", rowId)); + continue; + } + + Container c = ContainerManager.getForId(containerId); + if (c == null) + { + LOG.warn(String.format("clearCache: failed to resolve container for plate with rowId %d with containerId %s.", rowId, containerId)); + continue; + } + + Plate plate = PlateCache.getPlate(c, rowId); + if (plate != null) + PlateCache.uncache(c, plate); + } + } + @Override public DilutionCurve getDilutionCurve(List wellGroups, boolean assumeDecreasing, DilutionCurve.PercentCalculator percentCalculator, StatsService.CurveFitType type) throws FitFailedException { @@ -1515,6 +1730,9 @@ public List getPlateLayouts() List layouts = new ArrayList<>(); for (PlateLayoutHandler handler : getPlateLayoutHandlers()) { + if (TsvPlateLayoutHandler.TYPE.equalsIgnoreCase(handler.getAssayType())) + continue; + for (PlateType type : handler.getSupportedPlateTypes()) { int wellCount = type.getRows() * type.getColumns(); @@ -1644,33 +1862,15 @@ public void indexPlates(SearchService.IndexTask task, Container c, @Nullable Dat return PropertyService.get().getDomain(container, domainURI); } - /** - * Well metadata has transitioned to a provisioned architecture. - */ - @Deprecated - public @Nullable Domain getPlateMetadataVocabDomain(Container container, User user) - { - DomainKind vocabDomainKind = PropertyService.get().getDomainKindByName("Vocabulary"); - - if (vocabDomainKind == null) - return null; - - // the domain is scoped at the project level (project and subfolder scoping) - String domainURI = vocabDomainKind.generateDomainURI(null, "PlateMetadataDomain", getPlateMetadataDomainContainer(container), user); - return PropertyService.get().getDomain(container, domainURI); - } - public @Nullable TableInfo getPlateMetadataTable(Container container, User user) { Domain domain = getPlateMetadataDomain(container, user); if (domain != null) - { return StorageProvisioner.createTableInfo(domain); - } return null; } - private Container getPlateMetadataDomainContainer(Container container) + public Container getPlateMetadataDomainContainer(Container container) { // scope the metadata container to the project if (container.isRoot()) @@ -1876,32 +2076,31 @@ private List _getFields(Container container, User user, Integer throw new IllegalArgumentException("Failed to get well custom fields. Well id \"" + wellId + "\" not found."); List fields = _getFields(plate.getContainer(), user, plate.getRowId()).stream().map(WellCustomField::new).toList(); + if (fields.isEmpty()) + return Collections.emptyList(); // merge in any well metadata values - if (!fields.isEmpty()) - { - Map customFieldMap = new HashMap<>(); - for (WellCustomField customField : fields) - customFieldMap.put(FieldKey.fromParts("properties", customField.getName()), customField); - SimpleFilter filter = new SimpleFilter(FieldKey.fromParts("rowId"), wellId); + Map customFieldMap = new HashMap<>(); + for (WellCustomField customField : fields) + customFieldMap.put(FieldKey.fromParts("properties", customField.getName()), customField); + SimpleFilter filter = new SimpleFilter(FieldKey.fromParts("rowId"), wellId); - TableInfo wellTable = getWellTable(plate.getContainer(), user); - Map columnMap = QueryService.get().getColumns(wellTable, customFieldMap.keySet()); - try (Results r = QueryService.get().select(wellTable, columnMap.values(), filter, null)) + TableInfo wellTable = getWellTable(plate.getContainer(), user); + Map columnMap = QueryService.get().getColumns(wellTable, customFieldMap.keySet()); + try (Results r = QueryService.get().select(wellTable, columnMap.values(), filter, null)) + { + while (r.next()) { - while (r.next()) + for (Map.Entry rowEntry : r.getFieldKeyRowMap().entrySet()) { - for (Map.Entry rowEntry : r.getFieldKeyRowMap().entrySet()) - { - if (customFieldMap.containsKey(rowEntry.getKey())) - customFieldMap.get(rowEntry.getKey()).setValue(rowEntry.getValue()); - } + if (customFieldMap.containsKey(rowEntry.getKey())) + customFieldMap.get(rowEntry.getKey()).setValue(rowEntry.getValue()); } } - catch (SQLException e) - { - throw UnexpectedException.wrap(e); - } + } + catch (SQLException e) + { + throw UnexpectedException.wrap(e); } return fields.stream().sorted(Comparator.comparing(PlateCustomField::getName)).toList(); @@ -2032,9 +2231,13 @@ public PlateSetImpl createPlateSet( PlateSetImpl parentPlateSet = null; if (parentPlateSetId != null) { + if (plateSet.isTemplate()) + throw new ValidationException("Failed to create plate set. Template plate sets do not support specifying a parent plate set."); parentPlateSet = (PlateSetImpl) getPlateSet(container, parentPlateSetId); if (parentPlateSet == null) throw new ValidationException(String.format("Failed to create plate set. Parent plate set with rowId (%d) is not available.", parentPlateSetId)); + if (parentPlateSet.isTemplate()) + throw new ValidationException(String.format("Failed to create plate set. Parent plate set with \"%s\" is a template plate set. Template plate sets are not supported as a parent plate set.", parentPlateSet.getName())); if (parentPlateSet.getRootPlateSetId() == null) throw new ValidationException(String.format("Failed to create plate set. Parent plate set with rowId (%d) does not have a root plate set specified.", parentPlateSetId)); } @@ -2064,8 +2267,11 @@ public PlateSetImpl createPlateSet( if (plateType == null) throw new ValidationException("Failed to create plate set. Plate Type (" + plate.plateType + ") is invalid."); + var plateImpl = new PlateImpl(container, plate.name, plateType); + plateImpl.setTemplate(plateSet.isTemplate()); + // TODO: Write a cheaper plate create/save for multiple plates - createAndSavePlate(container, user, plateType, plate.name, plateSetId, TsvPlateLayoutHandler.TYPE, plate.data); + createAndSavePlate(container, user, plateImpl, plateSetId, plate.data); } } @@ -2114,41 +2320,66 @@ else if (PlateSetType.assay.equals(parentPlateSet.getType())) } } - public void archivePlateSets(Container container, User user, List plateSetIds, boolean archive) throws Exception + private String getArchiveAction(boolean archive) + { + return archive ? "archive" : "restore"; + } + + public void archive(Container container, User user, @Nullable List plateSetIds, @Nullable List plateIds, boolean archive) throws Exception + { + boolean archivingPlates = plateIds != null && !plateIds.isEmpty(); + boolean archivingPlateSets = plateSetIds != null && !plateSetIds.isEmpty(); + + if (!archivingPlates && !archivingPlateSets) + throw new ValidationException(String.format("Failed to %s. Neither plates nor plate sets were specified.", getArchiveAction(archive))); + + try (DbScope.Transaction tx = ensureTransaction()) + { + if (archivingPlates) + { + archive(container, user, AssayDbSchema.getInstance().getTableInfoPlate(), "plates", plateIds, archive); + tx.addCommitTask(() -> clearCache(plateIds), DbScope.CommitTaskOption.POSTCOMMIT); + } + + if (archivingPlateSets) + archive(container, user, AssayDbSchema.getInstance().getTableInfoPlateSet(), "plate sets", plateSetIds, archive); + + tx.commit(); + } + } + + private void archive(Container container, User user, @NotNull TableInfo table, String type, @NotNull List rowIds, boolean archive) throws Exception { - String action = archive ? "archive" : "restore"; Class perm = UpdatePermission.class; if (!container.hasPermission(user, perm)) - throw new UnauthorizedException("Failed to " + action + " plate sets. Insufficient permissions."); + throw new UnauthorizedException(String.format("Failed to %s %s. Insufficient permissions.", getArchiveAction(archive), type)); - if (plateSetIds.isEmpty()) - throw new ValidationException("Failed to " + action + " plate sets. No plate sets specified."); + if (rowIds.isEmpty()) + throw new ValidationException(String.format("Failed to %s %s. No %s specified.", getArchiveAction(archive), type, type)); try (DbScope.Transaction tx = ensureTransaction()) { - TableInfo plateSetTable = AssayDbSchema.getInstance().getTableInfoPlateSet(); - // Ensure user has permission in all containers { SQLFragment sql = new SQLFragment("SELECT DISTINCT container FROM ") - .append(plateSetTable, "PS") + .append(table, "") .append(" WHERE rowId ") - .appendInClause(plateSetIds, plateSetTable.getSqlDialect()); + .appendInClause(rowIds, table.getSqlDialect()); - for (String containerId : new SqlSelector(plateSetTable.getSchema(), sql).getCollection(String.class)) + for (String containerId : new SqlSelector(table.getSchema(), sql).getCollection(String.class)) { Container c = ContainerManager.getForId(containerId); if (c != null && !c.hasPermission(user, perm)) - throw new UnauthorizedException("Failed to " + action + " plate sets. Insufficient permissions in " + c.getPath()); + throw new UnauthorizedException(String.format("Failed to %s %s. Insufficient permissions in %s.", getArchiveAction(archive), type, c.getPath())); } } - SQLFragment sql = new SQLFragment("UPDATE ").append(plateSetTable) + SQLFragment sql = new SQLFragment("UPDATE ").append(table) .append(" SET archived = ?").add(archive) - .append(" WHERE rowId ").appendInClause(plateSetIds, plateSetTable.getSqlDialect()); + .append(" WHERE rowId ").appendInClause(rowIds, table.getSqlDialect()); - new SqlExecutor(plateSetTable.getSchema()).execute(sql); + new SqlExecutor(table.getSchema()).execute(sql); tx.commit(); } @@ -2502,7 +2733,7 @@ private void requireActiveTransaction() throw new IllegalStateException("This method must be called from within a transaction"); } - private Pair>> getWellSampleData(int[] sampleIdsSorted, Integer rowCount, Integer columnCount, int sampleIdsCounter, Container c) + Pair>> getWellSampleData(int[] sampleIdsSorted, Integer rowCount, Integer columnCount, int sampleIdsCounter, Container c) { if (sampleIdsSorted.length == 0) throw new IllegalArgumentException("No samples are in the current selection."); @@ -2517,7 +2748,7 @@ private Pair>> getWellSampleData(int[] sampleI Map wellData = Map.of( "sampleId", sampleIdsSorted[sampleIdsCounter], - "wellLocation", PlateManager.get().createPosition(c, rowIdx, colIdx).getDescription() + "wellLocation", createPosition(c, rowIdx, colIdx).getDescription() ); wellSampleDataForPlate.add(wellData); sampleIdsCounter++; @@ -2551,7 +2782,7 @@ public List getPlateData(ViewContext viewConte } else { - plateType = PlateManager.get().getPlateType(plateTypeId); + plateType = getPlateType(plateTypeId); if (plateType == null) throw new IllegalArgumentException(String.format("The plate type id (%d) is invalid.", plateTypeId)); plateTypesHash.put(plateTypeId, plateType); @@ -2574,7 +2805,7 @@ public List getPlates(Integer plateSetId, Cont { List plates = new ArrayList<>(); - PlateSet parentPlateSet = PlateManager.get().getPlateSet(c, plateSetId); + PlateSet parentPlateSet = getPlateSet(c, plateSetId); if (parentPlateSet == null) throw new IllegalArgumentException(String.format("Failed to get plate set. Plate set with rowId (%d) is not available.", plateSetId)); @@ -2591,7 +2822,7 @@ public List getPlates(Integer plateSetId, Cont for (Map row : ts.getMapArray()) { Map dataEntry = new HashMap<>(); - dataEntry.put("wellLocation", PlateManager.get().createPosition(c, (Integer) row.get("row"),(Integer) row.get("col")).getDescription()); + dataEntry.put("wellLocation", createPosition(c, (Integer) row.get("row"),(Integer) row.get("col")).getDescription()); dataEntry.put("sampleId", row.get("sampleid")); data.add(dataEntry); } @@ -2603,12 +2834,12 @@ public List getPlates(Integer plateSetId, Cont } public List getWorklist( - int sourcePlateSetId, - int destinationPlateSetId, - Set sourceIncludedMetadataCols, - Set destinationIncludedMetadataCols, - Container c, - User u + int sourcePlateSetId, + int destinationPlateSetId, + Set sourceIncludedMetadataCols, + Set destinationIncludedMetadataCols, + Container c, + User u ) throws RuntimeSQLException { TableInfo wellTable = getWellTable(c, u); @@ -2620,593 +2851,4 @@ public List getInstrumentInstructions(int plateSetId, Set in TableInfo wellTable = getWellTable(c, u); return new PlateSetExport().getInstrumentInstructions(wellTable, plateSetId, includedMetadataCols); } - - public static final class TestCase - { - private static Container container; - private static User user; - private static ExpSampleType sampleType; - - @BeforeClass - public static void setupTest() throws Exception - { - container = JunitUtil.getTestContainer(); - user = TestContext.get().getUser(); - - PlateService.get().deleteAllPlateData(container); - Domain domain = PlateManager.get().getPlateMetadataDomain(container ,user); - if (domain != null) - domain.delete(user); - - // create custom properties - List customFields = List.of( - new GWTPropertyDescriptor("barcode", "http://www.w3.org/2001/XMLSchema#string"), - new GWTPropertyDescriptor("concentration", "http://www.w3.org/2001/XMLSchema#double"), - new GWTPropertyDescriptor("negativeControl", "http://www.w3.org/2001/XMLSchema#double")); - - PlateManager.get().createPlateMetadataFields(container, user, customFields); - - // create sample type - List props = new ArrayList<>(); - props.add(new GWTPropertyDescriptor("col1", "string")); - props.add(new GWTPropertyDescriptor("col2", "string")); - sampleType = SampleTypeService.get().createSampleType(container, user, "SampleType1", null, props, emptyList(), 0, -1, -1, -1, null, null); - } - - @After - public void cleanupTest() - { - PlateManager.get().deleteAllPlateData(container); - } - - @AfterClass - public static void onComplete() - { - deleteTestContainer(); - container = null; - user = null; - } - - @Test - public void createPlateTemplate() throws Exception - { - // - // INSERT - // - - PlateLayoutHandler handler = PlateManager.get().getPlateLayoutHandler(TsvPlateLayoutHandler.TYPE); - PlateType plateType = PlateManager.get().getPlateType(8, 12); - assertNotNull("96 well plate type was not found", plateType); - - Plate template = handler.createTemplate("UNUSED", container, plateType); - template.setName("bob"); - template.setProperty("friendly", "yes"); - assertNull(template.getRowId()); - assertNull(template.getLSID()); - - WellGroup wg1 = template.addWellGroup("wg1", WellGroup.Type.SAMPLE, - PlateService.get().createPosition(container, 0, 0), - PlateService.get().createPosition(container, 0, 11)); - wg1.setProperty("score", "100"); - assertNull(wg1.getRowId()); - assertNull(wg1.getLSID()); - - int plateId = PlateService.get().save(container, user, template); - - // - // VERIFY INSERT - // - - assertEquals(1, PlateManager.get().getPlateTemplates(container).size()); - - Plate savedTemplate = PlateManager.get().getPlateByName(container, "bob"); - assertEquals(plateId, savedTemplate.getRowId().intValue()); - assertEquals("bob", savedTemplate.getName()); - assertEquals("yes", savedTemplate.getProperty("friendly")); assertNotNull(savedTemplate.getLSID()); - assertEquals(plateType.getRowId(), savedTemplate.getPlateType().getRowId()); - - List wellGroups = savedTemplate.getWellGroups(); - assertEquals(3, wellGroups.size()); - - // TsvPlateTypeHandler creates two CONTROL well groups "Positive" and "Negative" - List controlWellGroups = savedTemplate.getWellGroups(WellGroup.Type.CONTROL); - assertEquals(2, controlWellGroups.size()); - - List sampleWellGroups = savedTemplate.getWellGroups(WellGroup.Type.SAMPLE); - assertEquals(1, sampleWellGroups.size()); - WellGroup savedWg1 = sampleWellGroups.get(0); - assertEquals("wg1", savedWg1.getName()); - assertEquals("100", savedWg1.getProperty("score")); - - List savedWg1Positions = savedWg1.getPositions(); - assertEquals(12, savedWg1Positions.size()); - - // - // UPDATE - // - - // rename plate - savedTemplate.setName("sally"); - - // add well group - WellGroup wg2 = savedTemplate.addWellGroup("wg2", WellGroup.Type.SAMPLE, - PlateService.get().createPosition(container, 1, 0), - PlateService.get().createPosition(container, 1, 11)); - - // rename existing well group - ((WellGroupImpl)savedWg1).setName("wg1_renamed"); - - // add positions - controlWellGroups.get(0).setPositions(List.of( - PlateService.get().createPosition(container, 0, 0), - PlateService.get().createPosition(container, 0, 1))); - - // delete well group - ((PlateImpl)savedTemplate).markWellGroupForDeletion(controlWellGroups.get(1)); - - int newPlateId = PlateService.get().save(container, user, savedTemplate); - assertEquals(savedTemplate.getRowId().intValue(), newPlateId); - - // - // VERIFY UPDATE - // - - // verify plate - Plate updatedTemplate = PlateService.get().getPlate(container, plateId); - assertEquals("sally", updatedTemplate.getName()); - assertEquals(savedTemplate.getLSID(), updatedTemplate.getLSID()); - - // verify well group rename - WellGroup updatedWg1 = updatedTemplate.getWellGroup(savedWg1.getRowId()); - assertNotNull(updatedWg1); - assertEquals(savedWg1.getLSID(), updatedWg1.getLSID()); - assertEquals("wg1_renamed", updatedWg1.getName()); - - // verify added well group - WellGroup updatedWg2 = updatedTemplate.getWellGroup(wg2.getRowId()); - assertNotNull(updatedWg2); - - // verify deleted well group - List updatedControlWellGroups = updatedTemplate.getWellGroups(WellGroup.Type.CONTROL); - assertEquals(1, updatedControlWellGroups.size()); - - // verify added positions - assertEquals(2, updatedControlWellGroups.get(0).getPositions().size()); - - // verify plate type information - assertEquals(plateType.getRows().intValue(), updatedTemplate.getRows()); - assertEquals(plateType.getColumns().intValue(), updatedTemplate.getColumns()); - - // - // DELETE - // - - PlateService.get().deletePlate(container, user, updatedTemplate.getRowId()); - - assertNull(PlateService.get().getPlate(container, updatedTemplate.getRowId())); - assertEquals(0, PlateManager.get().getPlateTemplates(container).size()); - } - - @Test - public void testCreateAndSavePlate() throws Exception - { - // Arrange - PlateType plateType = PlateManager.get().getPlateType(8, 12); - assertNotNull("96 well plate type was not found", plateType); - - // Act - Plate plate = PlateManager.get().createAndSavePlate(container, user, plateType, "testCreateAndSavePlate plate", null, null, null); - - // Assert - assertTrue("Expected plate to have been persisted and provided with a rowId", plate.getRowId() > 0); - assertTrue("Expected plate to have been persisted and provided with a plateId", plate.getPlateId() != null); - - // verify access via plate ID - Plate savedPlate = PlateService.get().getPlate(container, plate.getPlateId()); - assertTrue("Expected plate to be accessible via it's plate ID", savedPlate != null); - assertTrue("Plate retrieved by plate ID doesn't match the original plate.", savedPlate.getRowId().equals(plate.getRowId())); - - // verify container filter access - savedPlate = PlateService.get().getPlate(ContainerManager.getSharedContainer(), plate.getRowId()); - assertTrue("Saved plate should not exist in the shared container", savedPlate == null); - - savedPlate = PlateService.get().getPlate(ContainerFilter.Type.CurrentAndSubfolders.create(ContainerManager.getSharedContainer(), user), plate.getRowId()); - assertTrue("Expected plate to be accessible via a container filter", plate.getRowId().equals(savedPlate.getRowId())); - } - - @Test - public void testAccessPlateByIdentifiers() throws Exception - { - // Arrange - PlateType plateType = PlateManager.get().getPlateType(8, 12); - assertNotNull("96 well plate type was not found", plateType); - PlateSetImpl plateSetImpl = new PlateSetImpl(); - plateSetImpl.setName("testAccessPlateByIdentifiersPlateSet"); - ContainerFilter cf = ContainerFilter.Type.CurrentAndSubfolders.create(ContainerManager.getSharedContainer(), user); - - // Act - PlateSet plateSet = PlateManager.get().createPlateSet(container, user, plateSetImpl, List.of( - new CreatePlateSetPlate("testAccessPlateByIdentifiersFirst", plateType.getRowId(), null), - new CreatePlateSetPlate("testAccessPlateByIdentifiersSecond", plateType.getRowId(), null), - new CreatePlateSetPlate("testAccessPlateByIdentifiersThird", plateType.getRowId(), null) - ), null); - - // Assert - assertTrue("Expected plateSet to have been persisted and provided with a rowId", plateSet.getRowId() > 0); - List plates = plateSet.getPlates(user); - assertEquals("Expected plateSet to have 3 plates", 3, plates.size()); - - // verify access via plate rowId - assertNotNull("Expected plate to be accessible via it's rowId", PlateService.get().getPlate(cf, plateSet.getRowId(), plates.get(0).getRowId())); - assertNotNull("Expected plate to be accessible via it's rowId", PlateService.get().getPlate(cf, plateSet.getRowId(), plates.get(1).getRowId())); - assertNotNull("Expected plate to be accessible via it's rowId", PlateService.get().getPlate(cf, plateSet.getRowId(), plates.get(2).getRowId())); - - // verify access via plate ID - assertNotNull("Expected plate to be accessible via it's plate ID", PlateService.get().getPlate(cf, plateSet.getRowId(), plates.get(0).getPlateId())); - assertNotNull("Expected plate to be accessible via it's plate ID", PlateService.get().getPlate(cf, plateSet.getRowId(), plates.get(1).getPlateId())); - assertNotNull("Expected plate to be accessible via it's plate ID", PlateService.get().getPlate(cf, plateSet.getRowId(), plates.get(2).getPlateId())); - - // verify access via plate name - assertNotNull("Expected plate to be accessible via it's name", PlateService.get().getPlate(cf, plateSet.getRowId(), "testAccessPlateByIdentifiersFirst")); - // verify error when trying to access non-existing plate name - try - { - PlateService.get().getPlate(cf, plateSet.getRowId(), "testAccessPlateByIdentifiersBogus"); - fail("Expected a validation error when accessing plates by non-existing name"); - } - catch (IllegalArgumentException e) - { - assertEquals("Expected validation exception", "The plate identifier \"testAccessPlateByIdentifiersBogus\" does not match any plate in the plate set \"testAccessPlateByIdentifiersPlateSet\".", e.getMessage()); - } - } - - @Test - public void testCreatePlateTemplates() throws Exception - { - // Verify plate service assumptions about plate templates - PlateType plateType = PlateManager.get().getPlateType(16, 24); - assertNotNull("384 well plate type was not found", plateType); - Plate plate = PlateService.get().createPlateTemplate(container, TsvPlateLayoutHandler.TYPE, plateType); - plate.setName("my plate template"); - int plateId = PlateService.get().save(container, user, plate); - - // Assert - assertTrue("Expected saved plateId to be returned", plateId != 0); - assertTrue("Expected saved plate to have the template field set to true", PlateService.get().getPlate(container, plateId).isTemplate()); - - // Verify only plate templates are returned - plateType = PlateManager.get().getPlateType(8, 12); - assertNotNull("96 well plate type was not found", plateType); - - plate = PlateService.get().createPlate(container, TsvPlateLayoutHandler.TYPE, plateType); - plate.setName("non plate template"); - PlateService.get().save(container, user, plate); - - List plates = PlateService.get().getPlateTemplates(container); - assertEquals("Expected only a single plate to be returned", 1, plates.size()); - for (Plate template : plates) - { - assertTrue("Expected saved plate to have the template field set to true", template.isTemplate()); - } - } - - @Test - public void testCreatePlateMetadata() throws Exception - { - PlateType plateType = PlateManager.get().getPlateType(16, 24); - assertNotNull("384 well plate type was not found", plateType); - - Plate plate = PlateService.get().createPlateTemplate(container, TsvPlateLayoutHandler.TYPE, plateType); - plate.setName("new plate with metadata"); - int plateId = PlateService.get().save(container, user, plate); - - // Assert - assertTrue("Expected saved plateId to be returned", plateId != 0); - - List fields = PlateManager.get().getPlateMetadataFields(container, user); - - // Verify returned sorted by name - assertEquals("Expected plate custom fields", 3, fields.size()); - assertEquals("Expected barcode custom field", "barcode", fields.get(0).getName()); - assertEquals("Expected concentration custom field", "concentration", fields.get(1).getName()); - assertEquals("Expected negativeControl custom field", "negativeControl", fields.get(2).getName()); - - // assign custom fields to the plate - assertEquals("Expected custom fields to be added to the plate", 3, PlateManager.get().addFields(container, user, plateId, fields).size()); - - // verification when adding custom fields to the plate - try - { - PlateManager.get().addFields(container, user, plateId, fields); - fail("Expected a validation error when adding existing fields"); - } - catch (IllegalArgumentException e) - { - assertEquals("Expected validation exception", "Failed to add plate custom fields. Custom field \"barcode\" already is associated with this plate.", e.getMessage()); - } - - // remove a plate custom field - fields = PlateManager.get().removeFields(container, user, plateId, List.of(fields.get(0))); - assertEquals("Expected 2 plate custom fields", 2, fields.size()); - assertEquals("Expected concentration custom field", "concentration", fields.get(0).getName()); - assertEquals("Expected negativeControl custom field", "negativeControl", fields.get(1).getName()); - - // select wells - SimpleFilter filter = SimpleFilter.createContainerFilter(container); - filter.addCondition(FieldKey.fromParts("PlateId"), plateId); - filter.addCondition(FieldKey.fromParts("Row"), 0); - List wells = new TableSelector(AssayDbSchema.getInstance().getTableInfoWell(), filter, new Sort("Col")).getArrayList(WellBean.class); - - assertEquals("Expected 24 wells to be returned", 24, wells.size()); - - // update - TableInfo wellTable = QueryService.get().getUserSchema(user, container, PlateSchema.SCHEMA_NAME).getTable(WellTable.NAME); - QueryUpdateService qus = wellTable.getUpdateService(); - assertNotNull(qus); - BatchValidationException errors = new BatchValidationException(); - - // add metadata to 2 rows - WellBean well = wells.get(0); - List> rows = List.of(CaseInsensitiveHashMap.of( - "rowid", well.getRowId(), - "properties/concentration", 1.25, - "properties/negativeControl", 5.25 - )); - - qus.updateRows(user, container, rows, null, errors, null, null); - if (errors.hasErrors()) - fail(errors.getMessage()); - - well = wells.get(1); - rows = List.of(CaseInsensitiveHashMap.of( - "rowid", well.getRowId(), - "properties/concentration", 2.25, - "properties/negativeControl", 6.25 - )); - - qus.updateRows(user, container, rows, null, errors, null, null); - if (errors.hasErrors()) - fail(errors.getMessage()); - - FieldKey fkConcentration = FieldKey.fromParts("properties", "concentration"); - FieldKey fkNegativeControl = FieldKey.fromParts("properties", "negativeControl"); - Map columns = QueryService.get().getColumns(wellTable, List.of(fkConcentration, fkNegativeControl)); - - // verify plate metadata property updates - try (Results r = QueryService.get().select(wellTable, columns.values(), filter, new Sort("Col"))) - { - int row = 0; - while (r.next()) - { - if (row == 0) - { - assertEquals(1.25, r.getDouble(fkConcentration), 0); - assertEquals(5.25, r.getDouble(fkNegativeControl), 0); - } - else if (row == 1) - { - assertEquals(2.25, r.getDouble(fkConcentration), 0); - assertEquals(6.25, r.getDouble(fkNegativeControl), 0); - } - else - { - // the remainder should be null - assertEquals(0, r.getDouble(fkConcentration), 0); - assertEquals(0, r.getDouble(fkNegativeControl), 0); - } - row++; - } - } - } - - @Test - public void testCreateAndSavePlateWithData() throws Exception - { - // Arrange - PlateType plateType = PlateManager.get().getPlateType(8, 12); - assertNotNull("96 well plate type was not found", plateType); - - // Act - List> rows = List.of( - CaseInsensitiveHashMap.of( - "wellLocation", "A1", - "properties/concentration", 2.25, - "properties/barcode", "B1234") - , - CaseInsensitiveHashMap.of( - "wellLocation", "A2", - "properties/concentration", 1.25, - "properties/barcode", "B5678" - ) - ); - Plate plate = PlateManager.get().createAndSavePlate(container, user, plateType, "hit selection plate", null, null, rows); - assertEquals("Expected 2 plate custom fields", 2, plate.getCustomFields().size()); - - TableInfo wellTable = QueryService.get().getUserSchema(user, container, PlateSchema.SCHEMA_NAME).getTable(WellTable.NAME); - FieldKey fkConcentration = FieldKey.fromParts("properties", "concentration"); - FieldKey fkBarcode = FieldKey.fromParts("properties", "barcode"); - Map columns = QueryService.get().getColumns(wellTable, List.of(fkConcentration, fkBarcode)); - - // verify that well data was added - SimpleFilter filter = SimpleFilter.createContainerFilter(container); - filter.addCondition(FieldKey.fromParts("PlateId"), plate.getRowId()); - filter.addCondition(FieldKey.fromParts("Row"), 0); - try (Results r = QueryService.get().select(wellTable, columns.values(), filter, new Sort("Col"))) - { - int row = 0; - while (r.next()) - { - if (row == 0) - { - assertEquals(2.25, r.getDouble(fkConcentration), 0); - assertEquals("B1234", r.getString(fkBarcode)); - } - else if (row == 1) - { - assertEquals(1.25, r.getDouble(fkConcentration), 0); - assertEquals("B5678", r.getString(fkBarcode)); - } - else - { - // the remainder should be null - assertEquals(0, r.getDouble(fkConcentration), 0); - assertNull(r.getString(fkBarcode)); - } - row++; - } - } - } - - @Test - public void getWellSampleData() - { - // Act - int[] sampleIdsSorted = new int[]{0, 3, 5, 8, 10, 11, 12, 13, 15, 17, 19}; - Pair>> wellSampleDataFilledFull = PlateManager.get().getWellSampleData(sampleIdsSorted, 2, 3, 0, container); - - Pair>> wellSampleDataFilledParital = PlateManager.get().getWellSampleData(sampleIdsSorted, 2, 3, 6, container); - - // Assert - assertEquals(wellSampleDataFilledFull.first, 6, 0); - ArrayList wellLocations = new ArrayList<>(Arrays.asList("A1", "A2", "A3", "B1", "B2", "B3")); - for (int i = 0; i < wellSampleDataFilledFull.second.size(); i++) - { - Map well = wellSampleDataFilledFull.second.get(i); - assertEquals(well.get("sampleId"), sampleIdsSorted[i]); - assertEquals(well.get("wellLocation"), wellLocations.get(i)); - } - - assertEquals(wellSampleDataFilledParital.first, 11, 0); - for (int i = 0; i < wellSampleDataFilledParital.second.size(); i++) - { - Map well = wellSampleDataFilledParital.second.get(i); - assertEquals(well.get("sampleId"), sampleIdsSorted[i + 6]); - assertEquals(well.get("wellLocation"), wellLocations.get(i)); - } - - // Act - try - { - PlateManager.get().getWellSampleData(new int[]{}, 2, 3, 0, container); - } - // Assert - catch (IllegalArgumentException e) - { - assertEquals("Expected validation exception", "No samples are in the current selection.", e.getMessage()); - } - } - - @Test - public void getInstrumentInstructions() throws Exception - { - // Arrange - final ExpMaterial sample1 = ExperimentService.get().createExpMaterial(container, sampleType.generateSampleLSID().setObjectId("sampleOne").toString(), "sampleOne"); - sample1.setCpasType(sampleType.getLSID()); - sample1.save(user); - final ExpMaterial sample2 = ExperimentService.get().createExpMaterial(container, sampleType.generateSampleLSID().setObjectId("sampleTwo").toString(), "sampleTwo"); - sample2.setCpasType(sampleType.getLSID()); - sample2.save(user); - - PlateType plateType = PlateManager.get().getPlateType(8, 12); - assertNotNull("96 well plate type was not found", plateType); - - List> rows = List.of( - CaseInsensitiveHashMap.of( - "wellLocation", "A1", - "sampleId", sample1.getRowId(), - "properties/concentration", 2.25, - "properties/barcode", "B1234") - , - CaseInsensitiveHashMap.of( - "wellLocation", "A2", - "sampleId", sample2.getRowId(), - "properties/concentration", 1.25, - "properties/barcode", "B5678" - ) - ); - Plate p = PlateManager.get().createAndSavePlate(container, user, plateType, "myPlate", null, null, rows); - - // Act - Set includedMetadataCols = WellTable.getMetadataColumns(p.getPlateSet().getRowId(), container, user); - List result = PlateManager.get().getInstrumentInstructions(p.getPlateSet().getRowId(), includedMetadataCols, container, user); - - // Assert - Object[] row1 = result.get(0); - String[] valuesRow1 = new String[]{"myPlate", "A1", "96-well", "sampleOne", "B1234", "2.25"}; - for (int i = 0; i < row1.length; i++) - assertEquals(row1[i].toString(), valuesRow1[i]); - - Object[] row2 = result.get(1); - String[] valuesRow2 = new String[]{"myPlate", "A2", "96-well", "sampleTwo", "B5678", "1.25"}; - for (int i = 0; i < row1.length; i++) - assertEquals(row2[i].toString(), valuesRow2[i]); - } - - @Test - public void getWorklist() throws Exception - { - // Arrange - final ExpMaterial sample1 = ExperimentService.get().createExpMaterial(container, sampleType.generateSampleLSID().setObjectId("sampleA").toString(), "sampleA"); - sample1.setCpasType(sampleType.getLSID()); - sample1.save(user); - final ExpMaterial sample2 = ExperimentService.get().createExpMaterial(container, sampleType.generateSampleLSID().setObjectId("sampleB").toString(), "sampleB"); - sample2.setCpasType(sampleType.getLSID()); - sample2.save(user); - - PlateType plateType = PlateManager.get().getPlateType(8, 12); - assertNotNull("96 well plate type was not found", plateType); - - List> rows1 = List.of( - CaseInsensitiveHashMap.of( - "wellLocation", "A1", - "sampleId", sample1.getRowId(), - "properties/concentration", 2.25, - "properties/barcode", "B1234") - , - CaseInsensitiveHashMap.of( - "wellLocation", "A2", - "sampleId", sample2.getRowId(), - "properties/concentration", 1.25, - "properties/barcode", "B5678" - ) - ); - Plate plateSource = PlateManager.get().createAndSavePlate(container, user, plateType, "myPlate1", null, null, rows1); - - List> rows2 = List.of( - CaseInsensitiveHashMap.of( - "wellLocation", "A1", - "sampleId", sample2.getRowId()) - , - CaseInsensitiveHashMap.of( - "wellLocation", "A2", - "sampleId", sample1.getRowId()) - , - CaseInsensitiveHashMap.of( - "wellLocation", "A3", - "sampleId", sample2.getRowId()) - ); - Plate plateDestination =PlateManager.get().createAndSavePlate(container, user, plateType, "myPlate2", null, null, rows2); - - // Act - Set sourceIncludedMetadataCols = WellTable.getMetadataColumns(plateSource.getPlateSet().getRowId(), container, user); - Set destinationIncludedMetadataCols = WellTable.getMetadataColumns(plateDestination.getPlateSet().getRowId(), container, user); - List plateDataRows = PlateManager.get().getWorklist(plateSource.getPlateSet().getRowId(), plateDestination.getPlateSet().getRowId(), sourceIncludedMetadataCols, destinationIncludedMetadataCols, container, user); - - // Assert - Object[] row1 = plateDataRows.get(0); - String[] valuesRow1 = new String[]{"myPlate1", "A1", "96-well", "sampleA", "B1234", "2.25", "myPlate2", "A2", "96-well"}; - for (int i = 0; i < row1.length; i++) - assertEquals(row1[i].toString(), valuesRow1[i]); - - Object[] row2 = plateDataRows.get(1); - String[] valuesRow2 = new String[]{"myPlate1", "A2", "96-well", "sampleB", "B5678", "1.25", "myPlate2", "A1", "96-well"}; - for (int i = 0; i < row2.length; i++) - assertEquals(row2[i].toString(), valuesRow2[i]); - - Object[] row3 = plateDataRows.get(2); - String[] valuesRow3 = new String[]{"myPlate1", "A2", "96-well", "sampleB", "B5678", "1.25", "myPlate2", "A3", "96-well"}; - for (int i = 0; i < row3.length; i++) - assertEquals(row3[i].toString(), valuesRow3[i]); - } - } } diff --git a/assay/src/org/labkey/assay/plate/PlateManagerTest.java b/assay/src/org/labkey/assay/plate/PlateManagerTest.java new file mode 100644 index 00000000000..3743a70465f --- /dev/null +++ b/assay/src/org/labkey/assay/plate/PlateManagerTest.java @@ -0,0 +1,649 @@ +package org.labkey.assay.plate; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.labkey.api.assay.plate.Plate; +import org.labkey.api.assay.plate.PlateCustomField; +import org.labkey.api.assay.plate.PlateLayoutHandler; +import org.labkey.api.assay.plate.PlateSet; +import org.labkey.api.assay.plate.PlateType; +import org.labkey.api.assay.plate.Position; +import org.labkey.api.assay.plate.WellGroup; +import org.labkey.api.collections.CaseInsensitiveHashMap; +import org.labkey.api.data.ColumnInfo; +import org.labkey.api.data.Container; +import org.labkey.api.data.ContainerFilter; +import org.labkey.api.data.ContainerManager; +import org.labkey.api.data.Results; +import org.labkey.api.data.SimpleFilter; +import org.labkey.api.data.Sort; +import org.labkey.api.data.TableInfo; +import org.labkey.api.data.TableSelector; +import org.labkey.api.exp.api.ExpMaterial; +import org.labkey.api.exp.api.ExpSampleType; +import org.labkey.api.exp.api.ExperimentService; +import org.labkey.api.exp.api.SampleTypeService; +import org.labkey.api.exp.property.Domain; +import org.labkey.api.gwt.client.model.GWTPropertyDescriptor; +import org.labkey.api.query.BatchValidationException; +import org.labkey.api.query.FieldKey; +import org.labkey.api.query.QueryService; +import org.labkey.api.query.QueryUpdateService; +import org.labkey.api.security.User; +import org.labkey.api.util.JunitUtil; +import org.labkey.api.util.Pair; +import org.labkey.api.util.TestContext; +import org.labkey.assay.plate.model.WellBean; +import org.labkey.assay.plate.query.PlateSchema; +import org.labkey.assay.plate.query.WellTable; +import org.labkey.assay.query.AssayDbSchema; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.labkey.api.util.JunitUtil.deleteTestContainer; + +public final class PlateManagerTest +{ + private static Container container; + private static User user; + private static ExpSampleType sampleType; + + @BeforeClass + public static void setupTest() throws Exception + { + container = JunitUtil.getTestContainer(); + user = TestContext.get().getUser(); + + PlateManager.get().deleteAllPlateData(container); + Domain domain = PlateManager.get().getPlateMetadataDomain(container, user); + if (domain != null) + domain.delete(user); + + // create custom properties + List customFields = List.of( + new GWTPropertyDescriptor("barcode", "http://www.w3.org/2001/XMLSchema#string"), + new GWTPropertyDescriptor("concentration", "http://www.w3.org/2001/XMLSchema#double"), + new GWTPropertyDescriptor("negativeControl", "http://www.w3.org/2001/XMLSchema#double") + ); + + PlateManager.get().createPlateMetadataFields(container, user, customFields); + + // create sample type + List props = new ArrayList<>(); + props.add(new GWTPropertyDescriptor("col1", "string")); + props.add(new GWTPropertyDescriptor("col2", "string")); + sampleType = SampleTypeService.get().createSampleType(container, user, "SampleType1", null, props, emptyList(), 0, -1, -1, -1, null, null); + } + + @After + public void cleanupTest() + { + PlateManager.get().deleteAllPlateData(container); + } + + @AfterClass + public static void onComplete() + { + deleteTestContainer(); + container = null; + user = null; + } + + @Test + public void createPlateTemplate() throws Exception + { + // + // INSERT + // + + PlateLayoutHandler handler = PlateManager.get().getPlateLayoutHandler(TsvPlateLayoutHandler.TYPE); + PlateType plateType = PlateManager.get().getPlateType(8, 12); + assertNotNull("96 well plate type was not found", plateType); + + Plate template = handler.createPlate("UNUSED", container, plateType); + template.setName("bob"); + template.setProperty("friendly", "yes"); + assertNull(template.getRowId()); + assertNull(template.getLSID()); + + WellGroup wg1 = template.addWellGroup("wg1", WellGroup.Type.SAMPLE, + PlateManager.get().createPosition(container, 0, 0), + PlateManager.get().createPosition(container, 0, 11) + ); + wg1.setProperty("score", "100"); + assertNull(wg1.getRowId()); + assertNull(wg1.getLSID()); + + int plateId = PlateManager.get().save(container, user, template); + + // + // VERIFY INSERT + // + + assertNotNull(PlateManager.get().getPlate(container, plateId)); + + Plate savedTemplate = PlateManager.get().getPlateByName(container, "bob"); + assertEquals(plateId, savedTemplate.getRowId().intValue()); + assertEquals("bob", savedTemplate.getName()); + assertEquals("yes", savedTemplate.getProperty("friendly")); + assertNotNull(savedTemplate.getLSID()); + assertEquals(plateType.getRowId(), savedTemplate.getPlateType().getRowId()); + + List wellGroups = savedTemplate.getWellGroups(); + assertEquals(3, wellGroups.size()); + + // TsvPlateTypeHandler creates two CONTROL well groups "Positive" and "Negative" + List controlWellGroups = savedTemplate.getWellGroups(WellGroup.Type.CONTROL); + assertEquals(2, controlWellGroups.size()); + + List sampleWellGroups = savedTemplate.getWellGroups(WellGroup.Type.SAMPLE); + assertEquals(1, sampleWellGroups.size()); + WellGroup savedWg1 = sampleWellGroups.get(0); + assertEquals("wg1", savedWg1.getName()); + assertEquals("100", savedWg1.getProperty("score")); + + List savedWg1Positions = savedWg1.getPositions(); + assertEquals(12, savedWg1Positions.size()); + + // + // UPDATE + // + + // rename plate + savedTemplate.setName("sally"); + + // add well group + WellGroup wg2 = savedTemplate.addWellGroup("wg2", WellGroup.Type.SAMPLE, + PlateManager.get().createPosition(container, 1, 0), + PlateManager.get().createPosition(container, 1, 11)); + + // rename existing well group + ((WellGroupImpl) savedWg1).setName("wg1_renamed"); + + // add positions + controlWellGroups.get(0).setPositions(List.of( + PlateManager.get().createPosition(container, 0, 0), + PlateManager.get().createPosition(container, 0, 1))); + + // delete well group + ((PlateImpl) savedTemplate).markWellGroupForDeletion(controlWellGroups.get(1)); + + int newPlateId = PlateManager.get().save(container, user, savedTemplate); + assertEquals(savedTemplate.getRowId().intValue(), newPlateId); + + // + // VERIFY UPDATE + // + + // verify plate + Plate updatedTemplate = PlateManager.get().getPlate(container, plateId); + assertEquals("sally", updatedTemplate.getName()); + assertEquals(savedTemplate.getLSID(), updatedTemplate.getLSID()); + + // verify well group rename + WellGroup updatedWg1 = updatedTemplate.getWellGroup(savedWg1.getRowId()); + assertNotNull(updatedWg1); + assertEquals(savedWg1.getLSID(), updatedWg1.getLSID()); + assertEquals("wg1_renamed", updatedWg1.getName()); + + // verify added well group + WellGroup updatedWg2 = updatedTemplate.getWellGroup(wg2.getRowId()); + assertNotNull(updatedWg2); + + // verify deleted well group + List updatedControlWellGroups = updatedTemplate.getWellGroups(WellGroup.Type.CONTROL); + assertEquals(1, updatedControlWellGroups.size()); + + // verify added positions + assertEquals(2, updatedControlWellGroups.get(0).getPositions().size()); + + // verify plate type information + assertEquals(plateType.getRows().intValue(), updatedTemplate.getRows()); + assertEquals(plateType.getColumns().intValue(), updatedTemplate.getColumns()); + + // + // DELETE + // + + PlateManager.get().deletePlate(container, user, updatedTemplate.getRowId()); + + assertNull(PlateManager.get().getPlate(container, updatedTemplate.getRowId())); + } + + @Test + public void testCreateAndSavePlate() throws Exception + { + // Arrange + PlateType plateType = PlateManager.get().getPlateType(8, 12); + assertNotNull("96 well plate type was not found", plateType); + + // Act + PlateImpl plateImpl = new PlateImpl(container, "testCreateAndSavePlate plate", plateType); + Plate plate = PlateManager.get().createAndSavePlate(container, user, plateImpl, null, null); + + // Assert + assertTrue("Expected plate to have been persisted and provided with a rowId", plate.getRowId() > 0); + assertNotNull("Expected plate to have been persisted and provided with a plateId", plate.getPlateId()); + + // verify access via plate ID + Plate savedPlate = PlateManager.get().getPlate(container, plate.getPlateId()); + assertNotNull("Expected plate to be accessible via it's plate ID", savedPlate); + assertEquals("Plate retrieved by plate ID doesn't match the original plate.", savedPlate.getRowId(), plate.getRowId()); + + // verify container filter access + savedPlate = PlateManager.get().getPlate(ContainerManager.getSharedContainer(), plate.getRowId()); + assertNull("Saved plate should not exist in the shared container", savedPlate); + + savedPlate = PlateManager.get().getPlate(ContainerFilter.Type.CurrentAndSubfolders.create(ContainerManager.getSharedContainer(), user), plate.getRowId()); + assertEquals("Expected plate to be accessible via a container filter", plate.getRowId(), savedPlate.getRowId()); + } + + @Test + public void testAccessPlateByIdentifiers() throws Exception + { + // Arrange + PlateType plateType = PlateManager.get().getPlateType(8, 12); + assertNotNull("96 well plate type was not found", plateType); + PlateSetImpl plateSetImpl = new PlateSetImpl(); + plateSetImpl.setName("testAccessPlateByIdentifiersPlateSet"); + ContainerFilter cf = ContainerFilter.Type.CurrentAndSubfolders.create(ContainerManager.getSharedContainer(), user); + + // Act + PlateSet plateSet = PlateManager.get().createPlateSet(container, user, plateSetImpl, List.of( + new PlateManager.CreatePlateSetPlate("testAccessPlateByIdentifiersFirst", plateType.getRowId(), null), + new PlateManager.CreatePlateSetPlate("testAccessPlateByIdentifiersSecond", plateType.getRowId(), null), + new PlateManager.CreatePlateSetPlate("testAccessPlateByIdentifiersThird", plateType.getRowId(), null) + ), null); + + // Assert + assertTrue("Expected plateSet to have been persisted and provided with a rowId", plateSet.getRowId() > 0); + List plates = plateSet.getPlates(user); + assertEquals("Expected plateSet to have 3 plates", 3, plates.size()); + + // verify access via plate rowId + assertNotNull("Expected plate to be accessible via it's rowId", PlateManager.get().getPlate(cf, plateSet.getRowId(), plates.get(0).getRowId())); + assertNotNull("Expected plate to be accessible via it's rowId", PlateManager.get().getPlate(cf, plateSet.getRowId(), plates.get(1).getRowId())); + assertNotNull("Expected plate to be accessible via it's rowId", PlateManager.get().getPlate(cf, plateSet.getRowId(), plates.get(2).getRowId())); + + // verify access via plate ID + assertNotNull("Expected plate to be accessible via it's plate ID", PlateManager.get().getPlate(cf, plateSet.getRowId(), plates.get(0).getPlateId())); + assertNotNull("Expected plate to be accessible via it's plate ID", PlateManager.get().getPlate(cf, plateSet.getRowId(), plates.get(1).getPlateId())); + assertNotNull("Expected plate to be accessible via it's plate ID", PlateManager.get().getPlate(cf, plateSet.getRowId(), plates.get(2).getPlateId())); + + // verify access via plate name + assertNotNull("Expected plate to be accessible via it's name", PlateManager.get().getPlate(cf, plateSet.getRowId(), "testAccessPlateByIdentifiersFirst")); + // verify error when trying to access non-existing plate name + try + { + PlateManager.get().getPlate(cf, plateSet.getRowId(), "testAccessPlateByIdentifiersBogus"); + fail("Expected a validation error when accessing plates by non-existing name"); + } + catch (IllegalArgumentException e) + { + assertEquals("Expected validation exception", "The plate identifier \"testAccessPlateByIdentifiersBogus\" does not match any plate in the plate set \"testAccessPlateByIdentifiersPlateSet\".", e.getMessage()); + } + } + + @Test + public void testCreatePlateTemplates() throws Exception + { + // Verify plate service assumptions about plate templates + PlateType plateType = PlateManager.get().getPlateType(16, 24); + assertNotNull("384 well plate type was not found", plateType); + Plate plate = PlateManager.get().createPlateTemplate(container, TsvPlateLayoutHandler.TYPE, plateType); + plate.setName("my plate template"); + int templateId = PlateManager.get().save(container, user, plate); + Plate template = PlateManager.get().getPlate(container, templateId); + + // Assert + assertNotNull("Expected plate template to be persisted", template); + assertTrue("Expected saved plate to have the template field set to true", template.isTemplate()); + + plateType = PlateManager.get().getPlateType(8, 12); + assertNotNull("96 well plate type was not found", plateType); + + plate = PlateManager.get().createPlate(container, TsvPlateLayoutHandler.TYPE, plateType); + plate.setName("non plate template"); + PlateManager.get().save(container, user, plate); + + // Verify only plate templates are returned + List templates = PlateManager.get().getPlateTemplates(container); + assertFalse("Expected there to be a plate template", templates.isEmpty()); + for (Plate t : templates) + assertTrue("Expected saved plate to have the template field set to true", t.isTemplate()); + } + + @Test + public void testCreatePlateMetadata() throws Exception + { + PlateType plateType = PlateManager.get().getPlateType(16, 24); + assertNotNull("384 well plate type was not found", plateType); + + Plate plate = PlateManager.get().createPlateTemplate(container, TsvPlateLayoutHandler.TYPE, plateType); + plate.setName("new plate with metadata"); + int plateId = PlateManager.get().save(container, user, plate); + + // Assert + assertTrue("Expected saved plateId to be returned", plateId != 0); + + List fields = PlateManager.get().getPlateMetadataFields(container, user); + + // Verify returned sorted by name + assertEquals("Expected plate custom fields", 3, fields.size()); + assertEquals("Expected barcode custom field", "barcode", fields.get(0).getName()); + assertEquals("Expected concentration custom field", "concentration", fields.get(1).getName()); + assertEquals("Expected negativeControl custom field", "negativeControl", fields.get(2).getName()); + + // assign custom fields to the plate + assertEquals("Expected custom fields to be added to the plate", 3, PlateManager.get().addFields(container, user, plateId, fields).size()); + + // verification when adding custom fields to the plate + try + { + PlateManager.get().addFields(container, user, plateId, fields); + fail("Expected a validation error when adding existing fields"); + } + catch (IllegalArgumentException e) + { + assertEquals("Expected validation exception", "Failed to add plate custom fields. Custom field \"barcode\" already is associated with this plate.", e.getMessage()); + } + + // remove a plate custom field + fields = PlateManager.get().removeFields(container, user, plateId, List.of(fields.get(0))); + assertEquals("Expected 2 plate custom fields", 2, fields.size()); + assertEquals("Expected concentration custom field", "concentration", fields.get(0).getName()); + assertEquals("Expected negativeControl custom field", "negativeControl", fields.get(1).getName()); + + // select wells + SimpleFilter filter = SimpleFilter.createContainerFilter(container); + filter.addCondition(FieldKey.fromParts("PlateId"), plateId); + filter.addCondition(FieldKey.fromParts("Row"), 0); + List wells = new TableSelector(AssayDbSchema.getInstance().getTableInfoWell(), filter, new Sort("Col")).getArrayList(WellBean.class); + + assertEquals("Expected 24 wells to be returned", 24, wells.size()); + + // update + TableInfo wellTable = QueryService.get().getUserSchema(user, container, PlateSchema.SCHEMA_NAME).getTable(WellTable.NAME); + QueryUpdateService qus = wellTable.getUpdateService(); + assertNotNull(qus); + BatchValidationException errors = new BatchValidationException(); + + // add metadata to 2 rows + WellBean well = wells.get(0); + List> rows = List.of(CaseInsensitiveHashMap.of( + "rowid", well.getRowId(), + "properties/concentration", 1.25, + "properties/negativeControl", 5.25 + )); + + qus.updateRows(user, container, rows, null, errors, null, null); + if (errors.hasErrors()) + fail(errors.getMessage()); + + well = wells.get(1); + rows = List.of(CaseInsensitiveHashMap.of( + "rowid", well.getRowId(), + "properties/concentration", 2.25, + "properties/negativeControl", 6.25 + )); + + qus.updateRows(user, container, rows, null, errors, null, null); + if (errors.hasErrors()) + fail(errors.getMessage()); + + FieldKey fkConcentration = FieldKey.fromParts("properties", "concentration"); + FieldKey fkNegativeControl = FieldKey.fromParts("properties", "negativeControl"); + Map columns = QueryService.get().getColumns(wellTable, List.of(fkConcentration, fkNegativeControl)); + + // verify plate metadata property updates + try (Results r = QueryService.get().select(wellTable, columns.values(), filter, new Sort("Col"))) + { + int row = 0; + while (r.next()) + { + if (row == 0) + { + assertEquals(1.25, r.getDouble(fkConcentration), 0); + assertEquals(5.25, r.getDouble(fkNegativeControl), 0); + } + else if (row == 1) + { + assertEquals(2.25, r.getDouble(fkConcentration), 0); + assertEquals(6.25, r.getDouble(fkNegativeControl), 0); + } + else + { + // the remainder should be null + assertEquals(0, r.getDouble(fkConcentration), 0); + assertEquals(0, r.getDouble(fkNegativeControl), 0); + } + row++; + } + } + } + + @Test + public void testCreateAndSavePlateWithData() throws Exception + { + // Arrange + PlateType plateType = PlateManager.get().getPlateType(8, 12); + assertNotNull("96 well plate type was not found", plateType); + + // Act + List> rows = List.of( + CaseInsensitiveHashMap.of( + "wellLocation", "A1", + "properties/concentration", 2.25, + "properties/barcode", "B1234") + , + CaseInsensitiveHashMap.of( + "wellLocation", "A2", + "properties/concentration", 1.25, + "properties/barcode", "B5678" + ) + ); + + PlateImpl plateImpl = new PlateImpl(container, "hit selection plate", plateType); + Plate plate = PlateManager.get().createAndSavePlate(container, user, plateImpl, null, rows); + assertEquals("Expected 2 plate custom fields", 2, plate.getCustomFields().size()); + + TableInfo wellTable = QueryService.get().getUserSchema(user, container, PlateSchema.SCHEMA_NAME).getTable(WellTable.NAME); + FieldKey fkConcentration = FieldKey.fromParts("properties", "concentration"); + FieldKey fkBarcode = FieldKey.fromParts("properties", "barcode"); + Map columns = QueryService.get().getColumns(wellTable, List.of(fkConcentration, fkBarcode)); + + // verify that well data was added + SimpleFilter filter = SimpleFilter.createContainerFilter(container); + filter.addCondition(FieldKey.fromParts("PlateId"), plate.getRowId()); + filter.addCondition(FieldKey.fromParts("Row"), 0); + try (Results r = QueryService.get().select(wellTable, columns.values(), filter, new Sort("Col"))) + { + int row = 0; + while (r.next()) + { + if (row == 0) + { + assertEquals(2.25, r.getDouble(fkConcentration), 0); + assertEquals("B1234", r.getString(fkBarcode)); + } + else if (row == 1) + { + assertEquals(1.25, r.getDouble(fkConcentration), 0); + assertEquals("B5678", r.getString(fkBarcode)); + } + else + { + // the remainder should be null + assertEquals(0, r.getDouble(fkConcentration), 0); + assertNull(r.getString(fkBarcode)); + } + row++; + } + } + } + + @Test + public void getWellSampleData() + { + // Act + int[] sampleIdsSorted = new int[]{0, 3, 5, 8, 10, 11, 12, 13, 15, 17, 19}; + Pair>> wellSampleDataFilledFull = PlateManager.get().getWellSampleData(sampleIdsSorted, 2, 3, 0, container); + Pair>> wellSampleDataFilledPartial = PlateManager.get().getWellSampleData(sampleIdsSorted, 2, 3, 6, container); + + // Assert + assertEquals(wellSampleDataFilledFull.first, 6, 0); + ArrayList wellLocations = new ArrayList<>(Arrays.asList("A1", "A2", "A3", "B1", "B2", "B3")); + for (int i = 0; i < wellSampleDataFilledFull.second.size(); i++) + { + Map well = wellSampleDataFilledFull.second.get(i); + assertEquals(well.get("sampleId"), sampleIdsSorted[i]); + assertEquals(well.get("wellLocation"), wellLocations.get(i)); + } + + assertEquals(wellSampleDataFilledPartial.first, 11, 0); + for (int i = 0; i < wellSampleDataFilledPartial.second.size(); i++) + { + Map well = wellSampleDataFilledPartial.second.get(i); + assertEquals(well.get("sampleId"), sampleIdsSorted[i + 6]); + assertEquals(well.get("wellLocation"), wellLocations.get(i)); + } + + // Act + try + { + PlateManager.get().getWellSampleData(new int[]{}, 2, 3, 0, container); + } + // Assert + catch (IllegalArgumentException e) + { + assertEquals("Expected validation exception", "No samples are in the current selection.", e.getMessage()); + } + } + + @Test + public void getInstrumentInstructions() throws Exception + { + // Arrange + final ExpMaterial sample1 = ExperimentService.get().createExpMaterial(container, sampleType.generateSampleLSID().setObjectId("sampleOne").toString(), "sampleOne"); + sample1.setCpasType(sampleType.getLSID()); + sample1.save(user); + final ExpMaterial sample2 = ExperimentService.get().createExpMaterial(container, sampleType.generateSampleLSID().setObjectId("sampleTwo").toString(), "sampleTwo"); + sample2.setCpasType(sampleType.getLSID()); + sample2.save(user); + + PlateType plateType = PlateManager.get().getPlateType(8, 12); + assertNotNull("96 well plate type was not found", plateType); + + List> rows = List.of( + CaseInsensitiveHashMap.of( + "wellLocation", "A1", + "sampleId", sample1.getRowId(), + "properties/concentration", 2.25, + "properties/barcode", "B1234") + , + CaseInsensitiveHashMap.of( + "wellLocation", "A2", + "sampleId", sample2.getRowId(), + "properties/concentration", 1.25, + "properties/barcode", "B5678" + ) + ); + Plate plate = new PlateImpl(container, "myPlate", plateType); + plate = PlateManager.get().createAndSavePlate(container, user, plate, null, rows); + + // Act + Set includedMetadataCols = WellTable.getMetadataColumns(plate.getPlateSet().getRowId(), container, user); + List result = PlateManager.get().getInstrumentInstructions(plate.getPlateSet().getRowId(), includedMetadataCols, container, user); + + // Assert + Object[] row1 = result.get(0); + String[] valuesRow1 = new String[]{"myPlate", "A1", "96-well", "sampleOne", "B1234", "2.25"}; + for (int i = 0; i < row1.length; i++) + assertEquals(row1[i].toString(), valuesRow1[i]); + + Object[] row2 = result.get(1); + String[] valuesRow2 = new String[]{"myPlate", "A2", "96-well", "sampleTwo", "B5678", "1.25"}; + for (int i = 0; i < row1.length; i++) + assertEquals(row2[i].toString(), valuesRow2[i]); + } + + @Test + public void getWorklist() throws Exception + { + // Arrange + final ExpMaterial sample1 = ExperimentService.get().createExpMaterial(container, sampleType.generateSampleLSID().setObjectId("sampleA").toString(), "sampleA"); + sample1.setCpasType(sampleType.getLSID()); + sample1.save(user); + final ExpMaterial sample2 = ExperimentService.get().createExpMaterial(container, sampleType.generateSampleLSID().setObjectId("sampleB").toString(), "sampleB"); + sample2.setCpasType(sampleType.getLSID()); + sample2.save(user); + + PlateType plateType = PlateManager.get().getPlateType(8, 12); + assertNotNull("96 well plate type was not found", plateType); + + List> rows1 = List.of( + CaseInsensitiveHashMap.of( + "wellLocation", "A1", + "sampleId", sample1.getRowId(), + "properties/concentration", 2.25, + "properties/barcode", "B1234") + , + CaseInsensitiveHashMap.of( + "wellLocation", "A2", + "sampleId", sample2.getRowId(), + "properties/concentration", 1.25, + "properties/barcode", "B5678" + ) + ); + Plate plateSource = PlateManager.get().createAndSavePlate(container, user, new PlateImpl(container, "myPlate1", plateType), null, rows1); + + List> rows2 = List.of( + CaseInsensitiveHashMap.of( + "wellLocation", "A1", + "sampleId", sample2.getRowId()) + , + CaseInsensitiveHashMap.of( + "wellLocation", "A2", + "sampleId", sample1.getRowId()) + , + CaseInsensitiveHashMap.of( + "wellLocation", "A3", + "sampleId", sample2.getRowId()) + ); + Plate plateDestination = PlateManager.get().createAndSavePlate(container, user, new PlateImpl(container, "myPlate2", plateType), null, rows2); + + // Act + Set sourceIncludedMetadataCols = WellTable.getMetadataColumns(plateSource.getPlateSet().getRowId(), container, user); + Set destinationIncludedMetadataCols = WellTable.getMetadataColumns(plateDestination.getPlateSet().getRowId(), container, user); + List plateDataRows = PlateManager.get().getWorklist(plateSource.getPlateSet().getRowId(), plateDestination.getPlateSet().getRowId(), sourceIncludedMetadataCols, destinationIncludedMetadataCols, container, user); + + // Assert + Object[] row1 = plateDataRows.get(0); + String[] valuesRow1 = new String[]{"myPlate1", "A1", "96-well", "sampleA", "B1234", "2.25", "myPlate2", "A2", "96-well"}; + for (int i = 0; i < row1.length; i++) + assertEquals(row1[i].toString(), valuesRow1[i]); + + Object[] row2 = plateDataRows.get(1); + String[] valuesRow2 = new String[]{"myPlate1", "A2", "96-well", "sampleB", "B5678", "1.25", "myPlate2", "A1", "96-well"}; + for (int i = 0; i < row2.length; i++) + assertEquals(row2[i].toString(), valuesRow2[i]); + + Object[] row3 = plateDataRows.get(2); + String[] valuesRow3 = new String[]{"myPlate1", "A2", "96-well", "sampleB", "B5678", "1.25", "myPlate2", "A3", "96-well"}; + for (int i = 0; i < row3.length; i++) + assertEquals(row3[i].toString(), valuesRow3[i]); + } +} diff --git a/assay/src/org/labkey/assay/plate/PlateSetImpl.java b/assay/src/org/labkey/assay/plate/PlateSetImpl.java index fc760acc1eb..c85973c99d9 100644 --- a/assay/src/org/labkey/assay/plate/PlateSetImpl.java +++ b/assay/src/org/labkey/assay/plate/PlateSetImpl.java @@ -33,6 +33,7 @@ public class PlateSetImpl extends Entity implements PlateSet private Integer _primaryPlateSetId; private Integer _rootPlateSetId; private Integer _rowId; + private boolean _template; private PlateSetType _type; @Override @@ -149,6 +150,12 @@ public Integer getPlateCount() return new SqlSelector(table.getSchema(), sql).getObject(Integer.class); } + @JsonIgnore + public boolean isFull() + { + return getPlateCount() >= MAX_PLATES; + } + public String getDescription() { return _description; @@ -179,6 +186,17 @@ public void setRootPlateSetId(Integer rootPlateSetId) _rootPlateSetId = rootPlateSetId; } + @Override + public boolean isTemplate() + { + return _template; + } + + public void setTemplate(boolean template) + { + _template = template; + } + @Override public PlateSetType getType() { diff --git a/assay/src/org/labkey/assay/plate/PlateUrls.java b/assay/src/org/labkey/assay/plate/PlateUrls.java index 8c1926b45e3..4a81be63e44 100644 --- a/assay/src/org/labkey/assay/plate/PlateUrls.java +++ b/assay/src/org/labkey/assay/plate/PlateUrls.java @@ -20,13 +20,8 @@ import org.labkey.api.data.Container; import org.labkey.api.view.ActionURL; -/* -* User: adam -* Date: Aug 30, 2009 -* Time: 9:10:07 PM -*/ public interface PlateUrls extends UrlProvider { - ActionURL getPlateTemplateListURL(Container c); + ActionURL getPlateListURL(Container c); ActionURL getPlateDetailsURL(Container c); } diff --git a/assay/src/org/labkey/assay/plate/TsvPlateLayoutHandler.java b/assay/src/org/labkey/assay/plate/TsvPlateLayoutHandler.java index f3111b99c95..774ecc49fa5 100644 --- a/assay/src/org/labkey/assay/plate/TsvPlateLayoutHandler.java +++ b/assay/src/org/labkey/assay/plate/TsvPlateLayoutHandler.java @@ -20,7 +20,7 @@ public class TsvPlateLayoutHandler extends AbstractPlateLayoutHandler public static final String TYPE = "Standard"; @Override - public String getAssayType() + public @NotNull String getAssayType() { return TYPE; } @@ -33,15 +33,15 @@ public List getLayoutTypes(PlateType plateType) } @Override - public Plate createTemplate(@Nullable String templateTypeName, Container container, @NotNull PlateType plateType) + public Plate createPlate(@Nullable String plateName, Container container, @NotNull PlateType plateType) { validatePlateType(plateType); - Plate template = PlateService.get().createPlateTemplate(container, getAssayType(), plateType); + Plate plate = PlateService.get().createPlate(container, getAssayType(), plateType); - template.addWellGroup("Positive", WellGroup.Type.CONTROL, Collections.emptyList()); - template.addWellGroup("Negative", WellGroup.Type.CONTROL, Collections.emptyList()); + plate.addWellGroup("Positive", WellGroup.Type.CONTROL, Collections.emptyList()); + plate.addWellGroup("Negative", WellGroup.Type.CONTROL, Collections.emptyList()); - return template; + return plate; } @Override diff --git a/assay/src/org/labkey/assay/plate/model/PlateBean.java b/assay/src/org/labkey/assay/plate/model/PlateBean.java index 4e3ed67e135..ba43608154c 100644 --- a/assay/src/org/labkey/assay/plate/model/PlateBean.java +++ b/assay/src/org/labkey/assay/plate/model/PlateBean.java @@ -1,6 +1,5 @@ package org.labkey.assay.plate.model; -import com.fasterxml.jackson.annotation.JsonInclude; import org.labkey.api.data.Entity; import org.labkey.assay.plate.PlateImpl; @@ -9,6 +8,7 @@ */ public class PlateBean extends Entity { + private Boolean _archived; private Integer _rowId; private String _lsid; private String _name; @@ -25,6 +25,7 @@ public static PlateBean from(PlateImpl plate) PlateBean bean = new PlateBean(); bean.setRowId(plate.getRowId()); + bean.setArchived(plate.isArchived()); bean.setLsid(plate.getLSID()); bean.setName(plate.getName()); bean.setTemplate(plate.isTemplate()); @@ -38,6 +39,16 @@ public static PlateBean from(PlateImpl plate) return bean; } + public Boolean getArchived() + { + return _archived; + } + + public void setArchived(Boolean archived) + { + _archived = archived; + } + public Integer getRowId() { return _rowId; diff --git a/assay/src/org/labkey/assay/plate/query/DuplicatePlateValidator.java b/assay/src/org/labkey/assay/plate/query/DuplicatePlateValidator.java index 946921b1b4d..b1da4f6b4e8 100644 --- a/assay/src/org/labkey/assay/plate/query/DuplicatePlateValidator.java +++ b/assay/src/org/labkey/assay/plate/query/DuplicatePlateValidator.java @@ -48,7 +48,7 @@ public boolean next() throws BatchValidationException if (name != null & plateSet != null) { PlateSet ps = PlateManager.get().getPlateSet(_container, plateSet); - if (PlateManager.get().isDuplicatePlate(_container, _user, name, ps)) + if (PlateManager.get().isDuplicatePlateName(_container, _user, name, ps)) _context.getErrors().addRowError(new ValidationException("Plate with name : " + name + " already exists.")); } return true; diff --git a/assay/src/org/labkey/assay/plate/query/PlateTable.java b/assay/src/org/labkey/assay/plate/query/PlateTable.java index 4ec1399f12a..8497667e2b8 100644 --- a/assay/src/org/labkey/assay/plate/query/PlateTable.java +++ b/assay/src/org/labkey/assay/plate/query/PlateTable.java @@ -276,7 +276,9 @@ protected Map updateRow(User user, Container container, Map <%@ page import="org.labkey.assay.PlateController.CopyTemplateBean" %> <%@ page import="org.labkey.assay.PlateController.HandleCopyAction" %> -<%@ page import="org.labkey.assay.PlateController.PlateTemplateListAction" %> +<%@ page import="org.labkey.assay.PlateController.PlateListAction" %> <%@ page import="java.util.List" %> <%@ page extends="org.labkey.api.jsp.JspBase" %> <%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> @@ -41,7 +41,7 @@ - <%= button("Cancel").href(PlateTemplateListAction.class, getContainer()) %> + <%= button("Cancel").href(PlateListAction.class, getContainer()) %> <%= bean.getSelectedDestination() != null ? button("Copy").submit(true) : button("Copy").submit(true).onClick("alert('Please select a destination folder.'); return false;") %> diff --git a/assay/src/org/labkey/assay/plate/view/plateTemplateList.jsp b/assay/src/org/labkey/assay/plate/view/plateList.jsp similarity index 80% rename from assay/src/org/labkey/assay/plate/view/plateTemplateList.jsp rename to assay/src/org/labkey/assay/plate/view/plateList.jsp index b7655a412ad..497c965e32f 100644 --- a/assay/src/org/labkey/assay/plate/view/plateTemplateList.jsp +++ b/assay/src/org/labkey/assay/plate/view/plateList.jsp @@ -41,12 +41,13 @@ <% JspView me = (JspView) HttpView.currentView(); Container c = getContainer(); - List plateTemplates = me.getModelBean().getTemplates(); - Map plateTemplateRunCount = new HashMap<>(); - for (Plate template : plateTemplates) + List plates = me.getModelBean().getTemplates(); + Map plateRunCount = new HashMap<>(); + boolean isAssayDesigner = c.hasPermission(getUser(), DesignAssayPermission.class); + for (Plate plate : plates) { - int count = PlateService.get().getRunCountUsingPlate(c, getUser(), template); - plateTemplateRunCount.put(template, count); + int count = PlateService.get().getRunCountUsingPlate(c, getUser(), plate); + plateRunCount.put(plate, count); } %> @@ -102,7 +103,43 @@ -

Available Plate Templates

+<% + if (isAssayDesigner || c.hasPermission(getUser(), InsertPermission.class)) + { + List
@@ -112,10 +149,9 @@ <% int index = 0; - boolean isAssayDesigner = c.hasPermission(getUser(), DesignAssayPermission.class); - for (Plate template : plateTemplates) + for (Plate plate : plates) { - Integer runCount = plateTemplateRunCount.get(template); + Integer runCount = plateRunCount.get(plate); Link.LinkBuilder editLink = new Link.LinkBuilder("edit"); if (runCount > 0) @@ -126,12 +162,12 @@ } else { - editLink.href(template.detailsURL()); + editLink.href(plate.detailsURL()); } %> - - + + + <% } %>
Name
<%= h(template.getName()) %><%= h(template.getAssayType()) %><%= h(plate.getName()) %><%= h(plate.getAssayType()) %> <%= h(runCount) %> <% @@ -146,20 +182,20 @@ %> <%= link("edit a copy", new ActionURL(PlateController.DesignerAction.class, getContainer()). addParameter("copy", true). - addParameter("templateName", template.getName()). - addParameter("plateId", template.getRowId())) %> + addParameter("templateName", plate.getName()). + addParameter("plateId", plate.getRowId())) %> <% } if (c.hasPermission(getUser(), InsertPermission.class)) { %> <%= link("copy to another folder", new ActionURL(PlateController.CopyTemplateAction.class, getContainer()). - addParameter("plateId", template.getRowId())) %> + addParameter("plateId", plate.getRowId())) %> <% } if (isAssayDesigner || c.hasPermission(getUser(), DeletePermission.class)) { - if (plateTemplates.size() > 1) + if (plates.size() > 1) { Link.LinkBuilder deleteLink = new Link.LinkBuilder("delete"); if (runCount > 0) @@ -170,7 +206,7 @@ } else { - deleteLink.onClick("deletePlate(" + q(template.getName()) + "," + template.getRowId() + ")"); + deleteLink.onClick("deletePlate(" + q(plate.getName()) + "," + plate.getRowId() + ")"); } %> <%= deleteLink %> @@ -190,46 +226,11 @@ index++; } - if (plateTemplates == null || plateTemplates.isEmpty()) + if (plates == null || plates.isEmpty()) { %> -
No plate templates available.
No plates available.
- -<% - if (isAssayDesigner || c.hasPermission(getUser(), InsertPermission.class)) - { - List