diff --git a/api/src/org/labkey/api/study/assay/SpecimenForeignKey.java b/api/src/org/labkey/api/study/assay/SpecimenForeignKey.java
index bbfffbe4d42..53d60658be4 100644
--- a/api/src/org/labkey/api/study/assay/SpecimenForeignKey.java
+++ b/api/src/org/labkey/api/study/assay/SpecimenForeignKey.java
@@ -32,7 +32,6 @@
import org.labkey.api.data.LookupColumn;
import org.labkey.api.data.SQLFragment;
import org.labkey.api.data.SimpleFilter;
-import org.labkey.api.data.Table;
import org.labkey.api.data.TableInfo;
import org.labkey.api.data.dialect.SqlDialect;
import org.labkey.api.exp.api.ExpProtocol;
@@ -44,6 +43,7 @@
import org.labkey.api.query.RowIdForeignKey;
import org.labkey.api.query.UserSchema;
import org.labkey.api.security.permissions.ReadPermission;
+import org.labkey.api.study.SpecimenService;
import org.labkey.api.study.Study;
import org.labkey.api.study.StudyService;
import org.labkey.api.study.TimepointType;
@@ -267,6 +267,11 @@ public void addSpecimenFilter(SimpleFilter filter)
@Override
public StringExpression getURL(ColumnInfo parent)
{
+ if (SpecimenService.get() == null)
+ {
+ return super.getURL(parent);
+ }
+
FieldKey targetStudyFK = _tableMetadata.getTargetStudyFieldKey();
Container targetStudy;
diff --git a/specimen/build.gradle b/specimen/build.gradle
deleted file mode 100644
index c746886c1e4..00000000000
--- a/specimen/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (c) 2021-2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import org.labkey.gradle.util.BuildUtils
-
-plugins {
- id 'org.labkey.build.module'
-}
-
-dependencies {
- BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: BuildUtils.getPlatformModuleProjectPath(project.gradle, "study"), depProjectConfig: "apiJarFile")
- BuildUtils.addLabKeyDependency(project: project, config: "jspImplementation", depProjectPath: BuildUtils.getPlatformModuleProjectPath(project.gradle, "study"), depProjectConfig: "apiJarFile")
-
- BuildUtils.addLabKeyDependency(project: project, config: "modules", depProjectPath: BuildUtils.getPlatformModuleProjectPath(project.gradle, "study"), depProjectConfig: "published", depExtension: "module")
-}
diff --git a/specimen/module.properties b/specimen/module.properties
deleted file mode 100644
index b069d0b2f2e..00000000000
--- a/specimen/module.properties
+++ /dev/null
@@ -1,8 +0,0 @@
-ModuleClass: org.labkey.specimen.SpecimenModule
-Label: Legacy specimen tracking system
-Description: A system for tracking specimens as part of a study. \
- This system has been deprecated, replaced by LabKey Sample Manager.
-URL: https://www.labkey.com/products-services/sample-management-software/
-License: Apache 2.0
-LicenseURL: http://www.apache.org/licenses/LICENSE-2.0
-ManageVersion: true
diff --git a/specimen/schemas/specimens.xsd b/specimen/schemas/specimens.xsd
deleted file mode 100644
index 416ad0ef12a..00000000000
--- a/specimen/schemas/specimens.xsd
+++ /dev/null
@@ -1,201 +0,0 @@
-
-
-
-
-
-
-
-
- Provides information on the files that describe the specimens in the study.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Indicates the type of repository.
- See Manage Specimens.
-
-
-
-
-
- True if specimens can be requested in this repository.
-
-
-
-
- True if specimens can be edited directly.
-
-
-
-
-
-
-
- True if specimen requests are allowed from this location type.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/specimen/src/org/labkey/specimen/AmbiguousLocationException.java b/specimen/src/org/labkey/specimen/AmbiguousLocationException.java
deleted file mode 100644
index acc9a1d1ac5..00000000000
--- a/specimen/src/org/labkey/specimen/AmbiguousLocationException.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (c) 2021-2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.specimen;
-
-import org.labkey.api.data.Container;
-import org.labkey.api.specimen.location.LocationImpl;
-import org.labkey.api.specimen.location.LocationManager;
-
-import java.util.Collection;
-
-public class AmbiguousLocationException extends Exception
-{
- private final Container _container;
- private final Collection _possibleLocationIds;
-
- private LocationImpl[] _possibleLocations = null;
-
- public AmbiguousLocationException(Container container, Collection possibleLocationIds)
- {
- _container = container;
- _possibleLocationIds = possibleLocationIds;
- }
-
- public Collection getPossibleLocationIds()
- {
- return _possibleLocationIds;
- }
-
- public LocationImpl[] getPossibleLocations()
- {
- if (_possibleLocations == null)
- {
- _possibleLocations = new LocationImpl[_possibleLocationIds.size()];
- int idx = 0;
-
- for (Integer id : _possibleLocationIds)
- _possibleLocations[idx++] = LocationManager.get().getLocation(_container, id.intValue());
- }
- return _possibleLocations;
- }
-}
diff --git a/specimen/src/org/labkey/specimen/RequestEventType.java b/specimen/src/org/labkey/specimen/RequestEventType.java
deleted file mode 100644
index cca053f3c89..00000000000
--- a/specimen/src/org/labkey/specimen/RequestEventType.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (c) 2021-2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.specimen;
-
-public enum RequestEventType
-{
- REQUEST_CREATED("Request Created"),
- REQUEST_STATUS_CHANGED("Request Status Changed"),
- REQUIREMENT_ADDED("Requirement Created"),
- REQUIREMENT_REMOVED("Requirement Removed"),
- REQUIREMENT_UPDATED("Requirement Updated"),
- REQUEST_UPDATED("Request Updated"),
- SPECIMEN_ADDED("Specimen Added"),
- SPECIMEN_REMOVED("Specimen Removed"),
- SPECIMEN_LIST_GENERATED("Specimen List Generated"),
- COMMENT_ADDED("Comment/Attachment(s) Added"),
- NOTIFICATION_SENT("Notification Sent");
-
- private final String _displayText;
-
- RequestEventType(String displayText)
- {
- _displayText = displayText;
- }
-
- public String getDisplayText()
- {
- return _displayText;
- }
-}
diff --git a/specimen/src/org/labkey/specimen/RequestedSpecimens.java b/specimen/src/org/labkey/specimen/RequestedSpecimens.java
deleted file mode 100644
index 3920219ce2b..00000000000
--- a/specimen/src/org/labkey/specimen/RequestedSpecimens.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (c) 2021-2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.specimen;
-
-import org.labkey.api.data.Container;
-import org.labkey.api.specimen.Vial;
-import org.labkey.api.specimen.location.LocationManager;
-import org.labkey.api.study.Location;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-
-public class RequestedSpecimens
-{
- private final Collection _providingLocationIds;
- private final List _vials;
-
- private List _providingLocations;
-
- public RequestedSpecimens(List vials, Collection providingLocationIds)
- {
- _vials = vials;
- _providingLocationIds = providingLocationIds;
- }
-
- public RequestedSpecimens(List vials)
- {
- _vials = vials;
- _providingLocationIds = new HashSet<>();
- if (vials != null)
- {
- for (Vial vial : vials)
- _providingLocationIds.add(vial.getCurrentLocation());
- }
- }
-
- public List getProvidingLocations()
- {
- if (_providingLocations == null)
- {
- if (_vials == null || _vials.isEmpty())
- _providingLocations = Collections.emptyList();
- else
- {
- Container container = _vials.getFirst().getContainer();
- _providingLocations = new ArrayList<>(_providingLocationIds.size());
-
- for (Integer locationId : _providingLocationIds)
- _providingLocations.add(LocationManager.get().getLocation(container, locationId.intValue()));
- }
- }
- return _providingLocations;
- }
-
- public List getVials()
- {
- return _vials;
- }
-}
diff --git a/specimen/src/org/labkey/specimen/SpecimenColumns.java b/specimen/src/org/labkey/specimen/SpecimenColumns.java
deleted file mode 100644
index 3e21498960c..00000000000
--- a/specimen/src/org/labkey/specimen/SpecimenColumns.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (c) 2021-2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.specimen;
-
-import org.labkey.specimen.importer.ImportTypes;
-import org.labkey.specimen.importer.ImportableColumn;
-import org.labkey.specimen.importer.SpecimenColumn;
-import org.labkey.specimen.importer.TargetTable;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-// Column definitions that are shared between specimen writers and importers
-public class SpecimenColumns
-{
- public static final String GLOBAL_UNIQUE_ID_TSV_COL = "global_unique_specimen_id";
-
- public static final String LAB_ID_TSV_COL = "lab_id";
- public static final String SPEC_NUMBER_TSV_COL = "specimen_number";
- public static final String EVENT_ID_COL = "record_id";
- public static final String VISIT_COL = "visit_value";
-
- // SpecimenEvent columns that form a psuedo-unique constraint
- public static final SpecimenColumn GLOBAL_UNIQUE_ID, LAB_ID, SHIP_DATE, STORAGE_DATE, LAB_RECEIPT_DATE, DRAW_TIMESTAMP;
- public static final SpecimenColumn VISIT_VALUE;
-
- public static final List BASE_SPECIMEN_COLUMNS = List.of(
- new SpecimenColumn(EVENT_ID_COL, "ExternalId", "BIGINT NOT NULL", TargetTable.SPECIMEN_EVENTS, true),
- new SpecimenColumn("record_source", "RecordSource", "VARCHAR(20)", TargetTable.SPECIMEN_EVENTS),
- GLOBAL_UNIQUE_ID = new SpecimenColumn(GLOBAL_UNIQUE_ID_TSV_COL, "GlobalUniqueId", "VARCHAR(50)", true, TargetTable.VIALS, true),
- LAB_ID = new SpecimenColumn(LAB_ID_TSV_COL, "LabId", "INT", TargetTable.SPECIMEN_EVENTS, "Site", "ExternalId", "LEFT OUTER") {
- @Override
- public boolean isUnique() { return true; }
- },
- new SpecimenColumn("originating_location", "OriginatingLocationId", "INT", TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS, "Site", "ExternalId", "LEFT OUTER"),
- new SpecimenColumn("unique_specimen_id", "UniqueSpecimenId", "VARCHAR(50)", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("ptid", "Ptid", "VARCHAR(32)", true, TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS),
- new SpecimenColumn("parent_specimen_id", "ParentSpecimenId", "INT", TargetTable.SPECIMEN_EVENTS),
- DRAW_TIMESTAMP = new SpecimenColumn("draw_timestamp", "DrawTimestamp", ImportTypes.DATETIME_TYPE, TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS),
- new SpecimenColumn("sal_receipt_date", "SalReceiptDate", ImportTypes.DATETIME_TYPE, TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS),
- new SpecimenColumn(SPEC_NUMBER_TSV_COL, "SpecimenNumber", "VARCHAR(50)", true, TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("class_id", "ClassId", "VARCHAR(20)", TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS),
- VISIT_VALUE = new SpecimenColumn(VISIT_COL, "VisitValue", ImportTypes.NUMERIC_TYPE, TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS),
- new SpecimenColumn("protocol_number", "ProtocolNumber", "VARCHAR(20)", TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS),
- new SpecimenColumn("visit_description", "VisitDescription", "VARCHAR(10)", TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS),
- new SpecimenColumn("other_specimen_id", "OtherSpecimenId", "VARCHAR(50)", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("volume", "Volume", "FLOAT", TargetTable.VIALS_AND_SPECIMEN_EVENTS, "MAX"),
- new SpecimenColumn("volume_units", "VolumeUnits", "VARCHAR(20)", TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS),
- new SpecimenColumn("stored", "Stored", "INT", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("storage_flag", "storageFlag", "INT", TargetTable.SPECIMEN_EVENTS),
- STORAGE_DATE = new SpecimenColumn("storage_date", "StorageDate", ImportTypes.DATETIME_TYPE, TargetTable.SPECIMEN_EVENTS, true),
- new SpecimenColumn("ship_flag", "ShipFlag", "INT", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("ship_batch_number", "ShipBatchNumber", "INT", TargetTable.SPECIMEN_EVENTS),
- SHIP_DATE = new SpecimenColumn("ship_date", "ShipDate", ImportTypes.DATETIME_TYPE, TargetTable.SPECIMEN_EVENTS, true),
- new SpecimenColumn("imported_batch_number", "ImportedBatchNumber", "INT", TargetTable.SPECIMEN_EVENTS),
- LAB_RECEIPT_DATE = new SpecimenColumn("lab_receipt_date", "LabReceiptDate", ImportTypes.DATETIME_TYPE, TargetTable.SPECIMEN_EVENTS, true),
- new SpecimenColumn("expected_time_value", "ExpectedTimeValue", "FLOAT", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("expected_time_unit", "ExpectedTimeUnit", "VARCHAR(15)", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("group_protocol", "GroupProtocol", "INT", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("sub_additive_derivative", "SubAdditiveDerivative", "VARCHAR(50)", TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS),
- new SpecimenColumn("comments", "Comments", "VARCHAR(500)", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("primary_specimen_type_id", "PrimaryTypeId", "INT", TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS, "SpecimenPrimaryType", "ExternalId", "LEFT OUTER"),
- new SpecimenColumn("derivative_type_id", "DerivativeTypeId", "INT", TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS, "SpecimenDerivative", "ExternalId", "LEFT OUTER"),
- new SpecimenColumn("derivative_type_id_2", "DerivativeTypeId2", "INT", TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS, "SpecimenDerivative", "ExternalId", "LEFT OUTER"),
- new SpecimenColumn("additive_type_id", "AdditiveTypeId", "INT", TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS, "SpecimenAdditive", "ExternalId", "LEFT OUTER"),
- new SpecimenColumn("specimen_condition", "SpecimenCondition", "VARCHAR(30)", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("sample_number", "SampleNumber", "INT", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("x_sample_origin", "XSampleOrigin", "VARCHAR(50)", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("external_location", "ExternalLocation", "VARCHAR(50)", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("update_timestamp", "UpdateTimestamp", ImportTypes.DATETIME_TYPE, TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("requestable", "Requestable", ImportTypes.BOOLEAN_TYPE, TargetTable.VIALS),
- new SpecimenColumn("shipped_from_lab", "ShippedFromLab", "VARCHAR(32)", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("shipped_to_lab", "ShippedtoLab", "VARCHAR(32)", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("frozen_time", "FrozenTime", ImportTypes.DURATION_TYPE, TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("primary_volume", "PrimaryVolume", "FLOAT", TargetTable.VIALS_AND_SPECIMEN_EVENTS),
- new SpecimenColumn("primary_volume_units", "PrimaryVolumeUnits", "VARCHAR(20)", TargetTable.VIALS_AND_SPECIMEN_EVENTS),
- new SpecimenColumn("processed_by_initials", "ProcessedByInitials", "VARCHAR(32)", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("processing_date", "ProcessingDate", ImportTypes.DATETIME_TYPE, TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("processing_time", "ProcessingTime", ImportTypes.DURATION_TYPE, TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("quality_comments", "QualityComments", "VARCHAR(500)", TargetTable.SPECIMEN_EVENTS),
- new SpecimenColumn("total_cell_count", "TotalCellCount", "FLOAT", TargetTable.VIALS_AND_SPECIMEN_EVENTS),
- new SpecimenColumn("tube_type", "TubeType", "VARCHAR(64)", TargetTable.VIALS_AND_SPECIMEN_EVENTS),
- new SpecimenColumn("input_hash", "InputHash", ImportTypes.BINARY_TYPE, TargetTable.SPECIMEN_EVENTS) // Not pulled from file... maybe this should be a ComputedColumn
- );
-
- public static final Collection ADDITIVE_COLUMNS = List.of(
- new ImportableColumn("additive_id", "ExternalId", "INT NOT NULL", true),
- new ImportableColumn("ldms_additive_code", "LdmsAdditiveCode", "VARCHAR(30)"),
- new ImportableColumn("labware_additive_code", "LabwareAdditiveCode", "VARCHAR(20)"),
- new ImportableColumn("additive", "Additive", "VARCHAR(100)")
- );
-
- public static final Collection DERIVATIVE_COLUMNS = Arrays.asList(
- new ImportableColumn("derivative_id", "ExternalId", "INT NOT NULL", true),
- new ImportableColumn("ldms_derivative_code", "LdmsDerivativeCode", "VARCHAR(30)"),
- new ImportableColumn("labware_derivative_code", "LabwareDerivativeCode", "VARCHAR(20)"),
- new ImportableColumn("derivative", "Derivative", "VARCHAR(100)")
- );
-
- public static final Collection SITE_COLUMNS = List.of(
- new ImportableColumn("lab_id", "ExternalId", "INT NOT NULL", true),
- new ImportableColumn("ldms_lab_code", "LdmsLabCode", "INT"),
- new ImportableColumn("labware_lab_code", "LabwareLabCode", "VARCHAR(20)", false, true),
- new ImportableColumn("lab_name", "Label", "VARCHAR(200)", false, true),
- new ImportableColumn("lab_upload_code", "LabUploadCode", "VARCHAR(10)"),
- new ImportableColumn("is_sal", "Sal", ImportTypes.BOOLEAN_TYPE, Boolean.FALSE),
- new ImportableColumn("is_repository", "Repository", ImportTypes.BOOLEAN_TYPE, Boolean.FALSE),
- new ImportableColumn("is_clinic", "Clinic", ImportTypes.BOOLEAN_TYPE, Boolean.FALSE),
- new ImportableColumn("is_endpoint", "Endpoint", ImportTypes.BOOLEAN_TYPE, Boolean.FALSE),
- new ImportableColumn("street_address", "StreetAddress", "VARCHAR(200)", false, true),
- new ImportableColumn("city", "City", "VARCHAR(200)", false, true),
- new ImportableColumn("governing_district", "GoverningDistrict", "VARCHAR(200)", false, true),
- new ImportableColumn("country", "Country", "VARCHAR(200)", false, true),
- new ImportableColumn("postal_area", "PostalArea", "VARCHAR(50)", false, true),
- new ImportableColumn("description", "Description", "VARCHAR(500)", false, true)
- );
-
- public static final Collection PRIMARYTYPE_COLUMNS = List.of(
- new ImportableColumn("primary_type_id", "ExternalId", "INT NOT NULL", true),
- new ImportableColumn("primary_type_ldms_code", "PrimaryTypeLdmsCode", "VARCHAR(5)"),
- new ImportableColumn("primary_type_labware_code", "PrimaryTypeLabwareCode", "VARCHAR(5)"),
- new ImportableColumn("primary_type", "PrimaryType", "VARCHAR(100)")
- );
-}
diff --git a/specimen/src/org/labkey/specimen/SpecimenCommentAuditDomainKind.java b/specimen/src/org/labkey/specimen/SpecimenCommentAuditDomainKind.java
deleted file mode 100644
index 2964ad318ec..00000000000
--- a/specimen/src/org/labkey/specimen/SpecimenCommentAuditDomainKind.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (c) 2021-2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.specimen;
-
-import org.labkey.api.audit.query.AbstractAuditDomainKind;
-import org.labkey.api.exp.PropertyDescriptor;
-import org.labkey.api.exp.PropertyType;
-
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-public class SpecimenCommentAuditDomainKind extends AbstractAuditDomainKind
-{
- public static final String NAME = "SpecimenCommentAuditDomain";
- public static final String SPECIMEN_COMMENT_EVENT = "SpecimenCommentEvent";
- public static final String NAMESPACE_PREFIX = "Audit-" + NAME;
- public static final String COLUMN_NAME_VIAL_ID = "VialId";
-
- private final Set _fields;
-
- public SpecimenCommentAuditDomainKind()
- {
- super(SPECIMEN_COMMENT_EVENT);
-
- Set fields = new LinkedHashSet<>();
- fields.add(createPropertyDescriptor(COLUMN_NAME_VIAL_ID, PropertyType.STRING));
- _fields = Collections.unmodifiableSet(fields);
- }
-
- @Override
- public Set getProperties()
- {
- return _fields;
- }
-
- @Override
- protected String getNamespacePrefix()
- {
- return NAMESPACE_PREFIX;
- }
-
- @Override
- public String getKindName()
- {
- return NAME;
- }
-}
diff --git a/specimen/src/org/labkey/specimen/SpecimenCommentAuditEvent.java b/specimen/src/org/labkey/specimen/SpecimenCommentAuditEvent.java
deleted file mode 100644
index b029df144ad..00000000000
--- a/specimen/src/org/labkey/specimen/SpecimenCommentAuditEvent.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (c) 2021-2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.specimen;
-
-import org.labkey.api.audit.AuditTypeEvent;
-import org.labkey.api.data.Container;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import static org.labkey.specimen.SpecimenCommentAuditDomainKind.SPECIMEN_COMMENT_EVENT;
-
-public class SpecimenCommentAuditEvent extends AuditTypeEvent
-{
- private String _vialId;
-
- public SpecimenCommentAuditEvent()
- {
- super();
- }
-
- public SpecimenCommentAuditEvent(Container container, String comment)
- {
- super(SPECIMEN_COMMENT_EVENT, container, comment);
- }
-
- public String getVialId()
- {
- return _vialId;
- }
-
- public void setVialId(String vialId)
- {
- _vialId = vialId;
- }
-
- @Override
- public Map getAuditLogMessageElements()
- {
- Map elements = new LinkedHashMap<>();
- elements.put("vialId", getVialId());
- elements.putAll(super.getAuditLogMessageElements());
- return elements;
- }
-}
diff --git a/specimen/src/org/labkey/specimen/SpecimenCommentAuditProvider.java b/specimen/src/org/labkey/specimen/SpecimenCommentAuditProvider.java
deleted file mode 100644
index cb12be9596e..00000000000
--- a/specimen/src/org/labkey/specimen/SpecimenCommentAuditProvider.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (c) 2021-2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.specimen;
-
-import org.labkey.api.audit.AbstractAuditTypeProvider;
-import org.labkey.api.audit.AuditTypeEvent;
-import org.labkey.api.audit.AuditTypeProvider;
-import org.labkey.api.audit.query.DefaultAuditTypeTable;
-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.DataColumn;
-import org.labkey.api.data.DisplayColumn;
-import org.labkey.api.data.DisplayColumnFactory;
-import org.labkey.api.data.MutableColumnInfo;
-import org.labkey.api.data.RenderContext;
-import org.labkey.api.data.TableInfo;
-import org.labkey.api.query.FieldKey;
-import org.labkey.api.query.UserSchema;
-import org.labkey.api.util.LinkBuilder;
-import org.labkey.api.view.ActionURL;
-import org.labkey.api.writer.HtmlWriter;
-import org.labkey.specimen.actions.SpecimenController;
-
-import java.util.List;
-import java.util.Set;
-
-import static org.labkey.specimen.SpecimenCommentAuditDomainKind.COLUMN_NAME_VIAL_ID;
-import static org.labkey.specimen.SpecimenCommentAuditDomainKind.SPECIMEN_COMMENT_EVENT;
-
-public class SpecimenCommentAuditProvider extends AbstractAuditTypeProvider implements AuditTypeProvider
-{
- private static final List defaultVisibleColumns = List.of(
- FieldKey.fromParts(COLUMN_NAME_CREATED),
- FieldKey.fromParts(COLUMN_NAME_CREATED_BY),
- FieldKey.fromParts(COLUMN_NAME_IMPERSONATED_BY),
- FieldKey.fromParts(COLUMN_NAME_PROJECT_ID),
- FieldKey.fromParts(COLUMN_NAME_CONTAINER),
- FieldKey.fromParts(COLUMN_NAME_VIAL_ID),
- FieldKey.fromParts(COLUMN_NAME_COMMENT)
- );
-
- public SpecimenCommentAuditProvider()
- {
- super(new SpecimenCommentAuditDomainKind());
- }
-
- @Override
- public String getEventName()
- {
- return SPECIMEN_COMMENT_EVENT;
- }
-
- @Override
- public String getLabel()
- {
- return "Specimen Comments and QC";
- }
-
- @Override
- public String getDescription()
- {
- return "Specimen Comments and QC";
- }
-
- @Override
- public Class getEventClass()
- {
- return (Class)SpecimenCommentAuditEvent.class;
- }
-
- @Override
- public TableInfo createTableInfo(UserSchema userSchema, ContainerFilter cf)
- {
- return new DefaultAuditTypeTable(this, createStorageTableInfo(), userSchema, cf, defaultVisibleColumns)
- {
- @Override
- protected void initColumn(MutableColumnInfo col)
- {
- if (COLUMN_NAME_VIAL_ID.equalsIgnoreCase(col.getName()))
- {
- final ColumnInfo containerColumn = getColumn(FieldKey.fromParts(COLUMN_NAME_CONTAINER));
- col.setLabel("Vial Id");
-
- col.setDisplayColumnFactory(new DisplayColumnFactory()
- {
- @Override
- public DisplayColumn createRenderer(final ColumnInfo colInfo)
- {
- return new DataColumn(colInfo)
- {
- @Override
- public void addQueryColumns(Set columns)
- {
- columns.add(containerColumn);
- super.addQueryColumns(columns);
- }
-
- @Override
- public void renderGridCellContents(RenderContext ctx, HtmlWriter out)
- {
- Object containerId = containerColumn.getValue(ctx);
- String globalUniqueId = (String) getValue(ctx);
- if (globalUniqueId == null)
- return;
-
- Container container = ContainerManager.getForId(containerId.toString());
- if (container == null)
- {
- out.write(globalUniqueId);
- return;
- }
-
- ActionURL url = SpecimenController.getCommentURL(container, globalUniqueId);
- out.write(LinkBuilder.simpleLink(globalUniqueId, url));
- }
- };
- }
- });
- }
- }
- };
- }
-
-
- @Override
- public List getDefaultVisibleColumns()
- {
- return defaultVisibleColumns;
- }
-}
diff --git a/specimen/src/org/labkey/specimen/SpecimenManager.java b/specimen/src/org/labkey/specimen/SpecimenManager.java
deleted file mode 100644
index 8ef913b93be..00000000000
--- a/specimen/src/org/labkey/specimen/SpecimenManager.java
+++ /dev/null
@@ -1,689 +0,0 @@
-/*
- * Copyright (c) 2021-2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.specimen;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.labkey.api.audit.AuditLogService;
-import org.labkey.api.collections.LongArrayList;
-import org.labkey.api.data.ColumnInfo;
-import org.labkey.api.data.Container;
-import org.labkey.api.data.DbScope;
-import org.labkey.api.data.SQLFragment;
-import org.labkey.api.data.SimpleFilter;
-import org.labkey.api.data.Sort;
-import org.labkey.api.data.SqlExecutor;
-import org.labkey.api.data.SqlSelector;
-import org.labkey.api.data.Table;
-import org.labkey.api.data.TableInfo;
-import org.labkey.api.data.TableSelector;
-import org.labkey.api.module.Module;
-import org.labkey.api.module.ModuleHtmlView;
-import org.labkey.api.query.FieldKey;
-import org.labkey.api.query.QueryService;
-import org.labkey.api.query.UserSchema;
-import org.labkey.api.security.User;
-import org.labkey.api.specimen.SpecimenEvent;
-import org.labkey.api.specimen.SpecimenEventManager;
-import org.labkey.api.specimen.SpecimenQuerySchema;
-import org.labkey.api.specimen.SpecimenSchema;
-import org.labkey.api.specimen.Vial;
-import org.labkey.api.specimen.location.LocationImpl;
-import org.labkey.api.specimen.location.LocationManager;
-import org.labkey.api.specimen.model.SpecimenComment;
-import org.labkey.api.specimen.model.SpecimenTablesProvider;
-import org.labkey.specimen.model.SpecimenTypeSummary;
-import org.labkey.specimen.model.SpecimenTypeSummaryRow;
-import org.labkey.api.study.Cohort;
-import org.labkey.api.study.Study;
-import org.labkey.api.study.StudyService;
-import org.labkey.api.study.Visit;
-import org.labkey.api.util.DateUtil;
-import org.labkey.api.util.HtmlString;
-import org.labkey.api.util.Path;
-import org.labkey.api.view.NotFoundException;
-import org.labkey.api.view.ViewContext;
-import org.labkey.specimen.model.AdditiveType;
-import org.labkey.specimen.model.DerivativeType;
-import org.labkey.specimen.model.ExtendedSpecimenRequestView;
-import org.labkey.specimen.model.PrimaryType;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-
-public class SpecimenManager
-{
- private final static SpecimenManager INSTANCE = new SpecimenManager();
-
- private SpecimenManager()
- {
- }
-
- public static SpecimenManager get()
- {
- return INSTANCE;
- }
-
- public List extends Visit> getVisitsWithSpecimens(Container container, User user, Cohort cohort)
- {
- Study study = StudyService.get().getStudy(container);
- UserSchema schema = SpecimenQuerySchema.get(study, user);
- TableInfo tinfo = schema.getTable(SpecimenTablesProvider.SIMPLE_SPECIMEN_TABLE_NAME);
-
- FieldKey visitKey = FieldKey.fromParts("Visit");
- Map colMap = QueryService.get().getColumns(tinfo, Collections.singleton(visitKey));
- Collection cols = new ArrayList<>();
- cols.add(colMap.get(visitKey));
- Set unresolvedColumns = new HashSet<>();
- cols = QueryService.get().ensureRequiredColumns(tinfo, cols, null, null, unresolvedColumns);
- if (!unresolvedColumns.isEmpty())
- throw new IllegalStateException("Unable to resolve column(s): " + unresolvedColumns);
- // generate our select SQL:
- SQLFragment specimenSql = Table.getSelectSQL(tinfo, cols, null, null);
-
- SQLFragment visitIdSQL = new SQLFragment("SELECT DISTINCT Visit FROM (" + specimenSql.getSQL() + ") SimpleSpecimenQuery");
- visitIdSQL.addAll(specimenSql.getParamsArray());
-
- List visitIds = new SqlSelector(SpecimenSchema.get().getSchema(), visitIdSQL).getArrayList(Integer.class);
-
- // Get shared visit study
- Study visitStudy = StudyService.get().getStudyForVisits(study);
-
- SimpleFilter filter = new SimpleFilter();
- filter.addInClause(FieldKey.fromParts("RowId"), visitIds);
- if (cohort != null)
- filter.addWhereClause("CohortId IS NULL OR CohortId = ?", new Object[] { cohort.getRowId() });
- return StudyService.get().getVisits(visitStudy, filter, new Sort("DisplayOrder,SequenceNumMin"));
- }
-
- public LocationImpl[] getSitesWithRequests(Container container)
- {
- TableInfo locationTableInfo = SpecimenSchema.get().getTableInfoLocation(container);
- SQLFragment sql = new SQLFragment("SELECT * FROM ").append(locationTableInfo).append(" WHERE rowid IN\n" +
- "(SELECT destinationsiteid FROM study.samplerequest WHERE container = ?)\n" +
- "AND container = ? ORDER BY label").addAll(container, container);
-
- return new SqlSelector(SpecimenSchema.get().getSchema(), sql).getArray(LocationImpl.class);
- }
-
- public Set getEnrollmentSitesWithRequests(Container container, User user)
- {
- UserSchema schema = SpecimenQuerySchema.get(StudyService.get().getStudy(container), user);
- TableInfo tableInfoSpecimenDetail = schema.getTable(SpecimenTablesProvider.SPECIMEN_WRAP_TABLE_NAME);
- if (null == tableInfoSpecimenDetail)
- throw new IllegalStateException("SpecimenDetail table not found.");
- String tableInfoAlias = "Specimen";
- SQLFragment sql = new SQLFragment("SELECT Participant.EnrollmentSiteId FROM ");
- sql.append(tableInfoSpecimenDetail.getFromSQL(tableInfoAlias)).append(", ")
- .append("study.SampleRequestSpecimen AS RequestSpecimen, \n" +
- "study.SampleRequest AS Request, study.SampleRequestStatus AS Status,\n" +
- "study.Participant AS Participant\n" +
- "WHERE Request.Container = Status.Container AND\n" +
- "\tRequest.StatusId = Status.RowId AND\n" +
- "\tRequestSpecimen.SampleRequestId = Request.RowId AND\n" +
- "\tRequestSpecimen.Container = Request.Container AND\n" +
- "\tSpecimen.Container = RequestSpecimen.Container AND\n" +
- "\tSpecimen.GlobalUniqueId = RequestSpecimen.SpecimenGlobalUniqueId AND\n" +
- "\tParticipant.EnrollmentSiteId IS NOT NULL AND\n" +
- "\tParticipant.Container = Specimen.Container AND\n" +
- "\tParticipant.ParticipantId = Specimen.Ptid AND\n" +
- "\tStatus.SpecimensLocked = ? AND\n" +
- "\tRequest.Container = ?");
- sql.add(Boolean.TRUE);
- sql.add(container);
-
- return getSitesWithIdSql(container, "EnrollmentSiteId", sql);
- }
-
- public Set getEnrollmentSitesWithSpecimens(Container container, User user)
- {
- UserSchema schema = SpecimenQuerySchema.get(StudyService.get().getStudy(container), user);
- TableInfo tableInfoSpecimenDetail = schema.getTable(SpecimenTablesProvider.SPECIMEN_WRAP_TABLE_NAME);
- if (null == tableInfoSpecimenDetail)
- throw new IllegalStateException("SpecimenDetail table not found.");
- String tableInfoAlias = "Specimen";
- SQLFragment sql = new SQLFragment("SELECT EnrollmentSiteId FROM ");
- sql.append(tableInfoSpecimenDetail.getFromSQL(tableInfoAlias)).append(", study.Participant AS Participant\n" +
- "WHERE Specimen.Ptid = Participant.ParticipantId AND\n" +
- "\tParticipant.EnrollmentSiteId IS NOT NULL AND\n" +
- "\tSpecimen.Container = Participant.Container AND\n" +
- "\tSpecimen.Container = ?\n" +
- "GROUP BY EnrollmentSiteId");
- sql.add(container);
-
- return getSitesWithIdSql(container, "EnrollmentSiteId", sql);
- }
-
- private Set getSitesWithIdSql(final Container container, final String idColumnName, SQLFragment sql)
- {
- final Set locations = new TreeSet<>((s1, s2) ->
- {
- if (s1 == null && s2 == null)
- return 0;
- if (s1 == null)
- return -1;
- if (s2 == null)
- return 1;
- return s1.getLabel().compareTo(s2.getLabel());
- });
-
- new SqlSelector(SpecimenSchema.get().getSchema(), sql).forEach(rs -> {
- // try getObject first to see if we have a value for our row; getInt will coerce the null to
- // zero, which could (theoretically) be a valid site ID.
- if (rs.getObject(idColumnName) == null)
- locations.add(null);
- else
- locations.add(LocationManager.get().getLocation(container, rs.getInt(idColumnName)));
- });
-
- return locations;
- }
-
- private static final int GET_COMMENT_BATCH_SIZE = 1000;
-
- public Map getSpecimenComments(List vials)
- {
- if (vials == null || vials.isEmpty())
- return Collections.emptyMap();
-
- Container container = vials.getFirst().getContainer();
- final Map result = new HashMap<>();
- int offset = 0;
-
- while (offset < vials.size())
- {
- final Map idToVial = new HashMap<>();
-
- for (int current = offset; current < offset + GET_COMMENT_BATCH_SIZE && current < vials.size(); current++)
- {
- Vial vial = vials.get(current);
- idToVial.put(vial.getGlobalUniqueId(), vial);
- if (!container.equals(vial.getContainer()))
- throw new IllegalArgumentException("All specimens must be from the same container");
- }
-
- SimpleFilter filter = SimpleFilter.createContainerFilter(container);
- filter.addInClause(FieldKey.fromParts("GlobalUniqueId"), idToVial.keySet());
-
- new TableSelector(SpecimenSchema.get().getTableInfoSpecimenComment(), filter, null).forEach(SpecimenComment.class, comment -> {
- Vial vial = idToVial.get(comment.getGlobalUniqueId());
- result.put(vial, comment);
- });
-
- offset += GET_COMMENT_BATCH_SIZE;
- }
-
- return result;
- }
-
- public SpecimenComment getSpecimenCommentForVial(Container container, String globalUniqueId)
- {
- SimpleFilter filter = SimpleFilter.createContainerFilter(container);
- filter.addCondition(FieldKey.fromParts("GlobalUniqueId"), globalUniqueId);
-
- return new TableSelector(SpecimenSchema.get().getTableInfoSpecimenComment(), filter, null).getObject(SpecimenComment.class);
- }
-
- public SpecimenComment getSpecimenCommentForVial(Vial vial)
- {
- return getSpecimenCommentForVial(vial.getContainer(), vial.getGlobalUniqueId());
- }
-
- public SpecimenComment setSpecimenComment(User user, Vial vial, String commentText, boolean qualityControlFlag, boolean qualityControlFlagForced)
- {
- TableInfo commentTable = SpecimenSchema.get().getTableInfoSpecimenComment();
- DbScope scope = commentTable.getSchema().getScope();
- SpecimenComment comment = getSpecimenCommentForVial(vial);
- boolean clearComment = commentText == null && !qualityControlFlag && !qualityControlFlagForced;
- try (DbScope.Transaction transaction = scope.ensureTransaction())
- {
- SpecimenComment result;
- if (clearComment)
- {
- if (comment != null)
- {
- Table.delete(commentTable, comment.getRowId());
- auditSpecimenComment(user, vial, comment.getComment(), null, comment.isQualityControlFlag(), false);
- }
- result = null;
- }
- else
- {
- if (comment != null)
- {
- String prevComment = comment.getComment();
- boolean prevConflictState = comment.isQualityControlFlag();
- comment.setComment(commentText);
- comment.setQualityControlFlag(qualityControlFlag);
- comment.setQualityControlFlagForced(qualityControlFlagForced);
- comment.beforeUpdate(user);
- result = Table.update(user, commentTable, comment, comment.getRowId());
- auditSpecimenComment(user, vial, prevComment, result.getComment(), prevConflictState, result.isQualityControlFlag());
- }
- else
- {
- comment = new SpecimenComment();
- comment.setGlobalUniqueId(vial.getGlobalUniqueId());
- comment.setSpecimenHash(vial.getSpecimenHash());
- comment.setComment(commentText);
- comment.setQualityControlFlag(qualityControlFlag);
- comment.setQualityControlFlagForced(qualityControlFlagForced);
- comment.beforeInsert(user, vial.getContainer().getId());
- result = Table.insert(user, commentTable, comment);
- auditSpecimenComment(user, vial, null, result.getComment(), false, comment.isQualityControlFlag());
- }
- }
- transaction.commit();
- return result;
- }
- }
-
- @Nullable
- public ExtendedSpecimenRequestView getExtendedSpecimenRequestView(ViewContext context)
- {
- if (context == null || context.getContainer() == null)
- return null;
-
- Path path = ModuleHtmlView.getStandardPath("extendedrequest");
-
- for (Module module : context.getContainer().getActiveModules())
- {
- if (ModuleHtmlView.exists(module, path))
- {
- ModuleHtmlView moduleView = ModuleHtmlView.get(module, path);
- assert null != moduleView;
- HtmlString html = moduleView.getHtml();
- String s = ModuleHtmlView.replaceTokens(html.toString(), context);
- return ExtendedSpecimenRequestView.createView(s);
- }
- }
-
- return null;
- }
-
- private boolean safeComp(Object a, Object b)
- {
- if (a == null && b == null)
- return true;
- if (a == null || b == null)
- return false;
- return a.equals(b);
- }
-
- private void auditSpecimenComment(User user, Vial vial, String oldComment, String newComment, boolean prevConflictState, boolean newConflictState)
- {
- String verb = "updated";
- if (oldComment == null)
- verb = "added";
- else if (newComment == null)
- verb = "deleted";
- String message = "";
- if (!safeComp(oldComment, newComment))
- {
- message += "Comment " + verb + ".\n";
- if (oldComment != null)
- message += "Previous value: " + oldComment + "\n";
- if (newComment != null)
- message += "New value: " + newComment + "\n";
- }
-
- if (!safeComp(prevConflictState, newConflictState))
- {
- message = "QC alert flag changed.\n";
- if (oldComment != null)
- message += "Previous value: " + prevConflictState + "\n";
- if (newComment != null)
- message += "New value: " + newConflictState + "\n";
- }
-
- SpecimenCommentAuditEvent event = new SpecimenCommentAuditEvent(vial.getContainer(), message);
- event.setVialId(vial.getGlobalUniqueId());
-
- AuditLogService.get().addEvent(user, event);
- }
-
- public List getVials(Container container, User user, String participantId, Double visit)
- {
- SimpleFilter filter = SimpleFilter.createContainerFilter(container);
- filter.addClause(new SimpleFilter.SQLClause("LOWER(ptid) = LOWER(?)", new Object[] {participantId}, FieldKey.fromParts("ptid")));
- filter.addCondition(FieldKey.fromParts("VisitValue"), visit);
- return getVials(container, user, filter);
- }
-
- public List getVials(Container container, User user, int[] vialsRowIds)
- {
- Set uniqueRowIds = new HashSet<>(vialsRowIds.length);
- for (int vialRowId : vialsRowIds)
- uniqueRowIds.add((long)vialRowId);
- return getVials(container, user, uniqueRowIds);
- }
-
- public List getVials(Container container, User user, String[] globalUniqueIds) throws SpecimenRequestException
- {
- SimpleFilter filter = new SimpleFilter();
- Set uniqueRowIds = new HashSet<>(globalUniqueIds.length);
- Collections.addAll(uniqueRowIds, globalUniqueIds);
- List ids = new ArrayList<>(uniqueRowIds);
- filter.addInClause(FieldKey.fromParts("GlobalUniqueId"), ids);
- List vials = getVials(container, user, filter);
- if (vials == null || vials.size() != ids.size())
- throw new SpecimenRequestException("Vial not found."); // an id has no matching specimen, let caller determine what to report
- return vials;
- }
-
- public List getVials(Container container, User user, String participantId, Date date)
- {
- SimpleFilter filter = new SimpleFilter(new SimpleFilter.SQLClause("LOWER(ptid) = LOWER(?)", new Object[] {participantId}, FieldKey.fromParts("ptid")));
- Calendar endCal = DateUtil.newCalendar(date.getTime());
- endCal.add(Calendar.DATE, 1);
- filter.addClause(new SimpleFilter.SQLClause("DrawTimestamp >= ? AND DrawTimestamp < ?", new Object[] {date, endCal.getTime()}));
- return getVials(container, user, filter);
- }
-
- @Nullable
- public AdditiveType getAdditiveType(Container c, int rowId)
- {
- List additiveTypes = getAdditiveTypes(c, new SimpleFilter(FieldKey.fromParts("RowId"), rowId));
- if (!additiveTypes.isEmpty())
- return additiveTypes.getFirst();
- return null;
- }
-
- private List getAdditiveTypes(final Container container, @Nullable SimpleFilter filter)
- {
- final List additiveTypes = new ArrayList<>();
- new TableSelector(SpecimenSchema.get().getTableInfoSpecimenAdditive(container), filter, null).
- forEachMap(map -> additiveTypes.add(new AdditiveType(container, map)));
- return additiveTypes;
- }
-
- public DerivativeType getDerivativeType(Container c, int rowId)
- {
- List derivativeTypes = getDerivativeTypes(c, new SimpleFilter(FieldKey.fromParts("RowId"), rowId));
- if (!derivativeTypes.isEmpty())
- return derivativeTypes.getFirst();
- return null;
- }
-
- private List getDerivativeTypes(final Container container, @Nullable SimpleFilter filter)
- {
- final List derivativeTypes = new ArrayList<>();
- new TableSelector(SpecimenSchema.get().getTableInfoSpecimenDerivative(container), filter, null).
- forEachMap(map -> derivativeTypes.add(new DerivativeType(container, map)));
- return derivativeTypes;
- }
-
- public boolean isSpecimensEmpty(Container container, User user)
- {
- TableSelector selector = getSpecimensSelector(container, user, null);
- return !selector.exists();
- }
-
- public Vial getVial(Container container, User user, String globalUniqueId)
- {
- SimpleFilter filter = new SimpleFilter(new SimpleFilter.SQLClause("LOWER(GlobalUniqueId) = LOWER(?)", new Object[] { globalUniqueId }));
- List matches = getVials(container, user, filter);
- if (matches == null || matches.isEmpty())
- return null;
- if (matches.size() > 1)
- {
- // we apparently have two specimens with IDs that differ only in case; do a case-sensitive check
- // here to find the right one:
- for (Vial vial : matches)
- {
- if (vial.getGlobalUniqueId().equals(globalUniqueId))
- return vial;
- }
- throw new IllegalStateException("Expected at least one vial to exactly match the specified global unique ID: " + globalUniqueId);
- }
- else
- return matches.getFirst();
- }
-
- /** Looks for any specimens that have the given id as a globalUniqueId */
- public PrimaryType getPrimaryType(Container c, int rowId)
- {
- List primaryTypes = getPrimaryTypes(c, new SimpleFilter(FieldKey.fromParts("RowId"), rowId), null);
- if (!primaryTypes.isEmpty())
- return primaryTypes.getFirst();
- return null;
- }
-
- public List getRequestableVials(Container container, User user, Set vialRowIds)
- {
- SimpleFilter filter = SimpleFilter.createContainerFilter(container);
- filter.addInClause(FieldKey.fromParts("RowId"), vialRowIds).addCondition(FieldKey.fromString("available"), true);
- return getVials(container, user, filter);
- }
-
- public Map> getVialsForSpecimenHashes(Container container, User user, Collection hashes, boolean onlyAvailable)
- {
- SimpleFilter filter = SimpleFilter.createContainerFilter(container);
- filter.addInClause(FieldKey.fromParts("SpecimenHash"), hashes);
- if (onlyAvailable)
- filter.addCondition(FieldKey.fromParts("Available"), true);
- List vials = getVials(container, user, filter);
- Map> map = new HashMap<>();
- for (Vial vial : vials)
- {
- String hash = vial.getSpecimenHash();
- List keyVials = map.computeIfAbsent(hash, k -> new ArrayList<>());
- keyVials.add(vial);
- }
-
- return map;
- }
-
- public List getVials(Container container, User user, Set vialRowIds)
- {
- // Take a set to eliminate dups - issue 26940
-
- SimpleFilter filter = SimpleFilter.createContainerFilter(container);
- filter.addInClause(FieldKey.fromParts("RowId"), vialRowIds);
- List vials = getVials(container, user, filter);
- if (vials.size() != vialRowIds.size())
- {
- List unmatchedRowIds = new LongArrayList(vialRowIds);
- for (Vial vial : vials)
- {
- unmatchedRowIds.remove(vial.getRowId());
- }
- throw new SpecimenRequestException("One or more specimen RowIds had no matching specimen: " + unmatchedRowIds);
- }
- return vials;
- }
-
- public List getVials(final Container container, final User user, SimpleFilter filter)
- {
- // TODO: LinkedList?
- final List vials = new ArrayList<>();
-
- getSpecimensSelector(container, user, filter)
- .forEachMap(map -> vials.add(new Vial(container, map)));
-
- return vials;
- }
-
- public TableSelector getSpecimensSelector(final Container container, final User user, SimpleFilter filter)
- {
- Study study = StudyService.get().getStudy(container);
- if (study == null)
- {
- throw new NotFoundException("No study in container " + container.getPath());
- }
- UserSchema schema = SpecimenQuerySchema.get(study, user);
- TableInfo specimenTable = schema.getTable(SpecimenTablesProvider.SPECIMEN_WRAP_TABLE_NAME);
- return new TableSelector(specimenTable, filter, null);
- }
-
- public Vial getVial(Container container, User user, long rowId)
- {
- SimpleFilter filter = new SimpleFilter(FieldKey.fromParts("RowId"), rowId);
- List vials = getVials(container, user, filter);
- if (vials.isEmpty())
- return null;
- return vials.getFirst();
- }
-
- public void deleteSpecimen(@NotNull Vial vial, boolean clearCaches)
- {
- Container container = vial.getContainer();
- TableInfo tableInfoSpecimenEvent = SpecimenSchema.get().getTableInfoSpecimenEvent(container);
- TableInfo tableInfoVial = SpecimenSchema.get().getTableInfoVial(container);
- if (null == tableInfoSpecimenEvent || null == tableInfoVial)
- return;
-
- String tableInfoSpecimenEventSelectName = tableInfoSpecimenEvent.getSelectName();
- String tableInfoVialSelectName = tableInfoVial.getSelectName();
-
- SQLFragment sqlFragmentEvent = new SQLFragment("DELETE FROM ");
- sqlFragmentEvent.append(tableInfoSpecimenEventSelectName).append(" WHERE VialId = ?");
- sqlFragmentEvent.add(vial.getRowId());
- new SqlExecutor(SpecimenSchema.get().getSchema()).execute(sqlFragmentEvent);
-
- SQLFragment sqlFragment = new SQLFragment("DELETE FROM ");
- sqlFragment.append(tableInfoVialSelectName).append(" WHERE RowId = ?");
- sqlFragment.add(vial.getRowId());
- new SqlExecutor(SpecimenSchema.get().getSchema()).execute(sqlFragment);
-
- if (clearCaches)
- {
- SpecimenRequestManager.get().clearCaches(vial.getContainer());
- }
- }
-
- public long getMaxExternalId(Container container)
- {
- TableInfo tableInfo = SpecimenSchema.get().getTableInfoSpecimenEvent(container);
- SQLFragment sql = new SQLFragment("SELECT MAX(ExternalId) FROM ");
- sql.append(tableInfo);
- return new SqlSelector(tableInfo.getSchema(), sql).getArrayList(Long.class).getFirst();
- }
-
- public String getFirstProcessedByInitials(List dateOrderedEvents)
- {
- SpecimenEvent firstEvent = SpecimenEventManager.get().getFirstEvent(dateOrderedEvents);
- return firstEvent != null ? firstEvent.getProcessedByInitials() : null;
- }
-
- public List getSpecimenEvents(List vials, boolean includeObsolete)
- {
- if (vials == null || vials.isEmpty())
- return Collections.emptyList();
- Collection vialIds = new HashSet<>();
- Container container = null;
- for (Vial vial : vials)
- {
- vialIds.add(vial.getRowId());
- if (container == null)
- container = vial.getContainer();
- else if (!container.equals(vial.getContainer()))
- throw new IllegalArgumentException("All specimens must be from the same container");
- }
- SimpleFilter filter = new SimpleFilter();
- filter.addInClause(FieldKey.fromString("VialId"), vialIds);
- if (!includeObsolete)
- filter.addCondition(FieldKey.fromString("Obsolete"), false);
- return SpecimenEventManager.get().getSpecimenEvents(container, filter);
- }
-
- public List getPrimaryTypes(Container c)
- {
- return getPrimaryTypes(c, null, new Sort("ExternalId"));
- }
-
- public List getPrimaryTypes(final Container container, @Nullable SimpleFilter filter, @Nullable Sort sort)
- {
- final List primaryTypes = new ArrayList<>();
- new TableSelector(SpecimenSchema.get().getTableInfoSpecimenPrimaryType(container), filter, sort).
- forEachMap(map -> primaryTypes.add(new PrimaryType(container, map)));
- return primaryTypes;
- }
-
- public SpecimenTypeSummary getSpecimenTypeSummary(Container container, @NotNull User user)
- {
- UserSchema querySchema = SpecimenQuerySchema.get(StudyService.get().getStudy(container), user);
- TableInfo tableInfoSpecimenWrap = querySchema.getTable(SpecimenTablesProvider.SPECIMEN_WRAP_TABLE_NAME);
- if (null == tableInfoSpecimenWrap)
- throw new IllegalStateException("SpecimenDetail table not found.");
-
- TableInfo additiveTableInfo = querySchema.getTable(SpecimenTablesProvider.ADDITIVE_TYPE_TABLE_NAME);
- TableInfo derivativeTableInfo = querySchema.getTable(SpecimenTablesProvider.DERIVATIVE_TYPE_TABLE_NAME);
- TableInfo primaryTypeTableInfo = querySchema.getTable(SpecimenTablesProvider.PRIMARY_TYPE_TABLE_NAME);
- String tableInfoSelectName = "SpecimenWrap";
-
- // TODO: consider caching
-
- Study study = StudyService.get().getStudy(container);
- if (study == null)
- return null;
-
- SQLFragment specimenTypeSummarySQL = new SQLFragment("SELECT\n" +
- "\tPrimaryType,\n" +
- "\tPrimaryTypeId,\n" +
- "\tDerivative,\n" +
- "\tDerivativeTypeId,\n" +
- "\tAdditive,\n" +
- "\tAdditiveTypeId,\n" +
- "\tSUM(VialCount) AS VialCount\n" +
- "FROM (\n" +
- "\tSELECT\n" +
- "\tPT.PrimaryType AS PrimaryType,\n" +
- "\tPrimaryTypeId,\n" +
- "\tDT.Derivative AS Derivative,\n" +
- "\tDerivativeTypeId,\n" +
- "\tAT.Additive AS Additive,\n" +
- "\tAdditiveTypeId,\n" +
- "\tSpecimens.VialCount\n" +
- "\tFROM\n");
-
- specimenTypeSummarySQL.append("\t\t(SELECT ")
- .append(tableInfoSpecimenWrap.getColumn("PrimaryTypeId").getValueSql(tableInfoSelectName)).append(",")
- .append(tableInfoSpecimenWrap.getColumn("DerivativeTypeId").getValueSql(tableInfoSelectName)).append(",")
- .append(tableInfoSpecimenWrap.getColumn("AdditiveTypeId").getValueSql(tableInfoSelectName)).append(",")
- .append("\n\t\t\tSUM(").append(tableInfoSpecimenWrap.getColumn("VialCount").getValueSql(tableInfoSelectName))
- .append(") AS VialCount\n")
- .append("\n\t\tFROM ").append(tableInfoSpecimenWrap.getFromSQL(tableInfoSelectName)).append("\n")
- .append("\t\tGROUP BY ")
- .append(tableInfoSpecimenWrap.getColumn("PrimaryTypeId").getValueSql(tableInfoSelectName)).append(",")
- .append(tableInfoSpecimenWrap.getColumn("DerivativeTypeId").getValueSql(tableInfoSelectName)).append(",")
- .append(tableInfoSpecimenWrap.getColumn("AdditiveTypeId").getValueSql(tableInfoSelectName))
- .append("\t\t\t) Specimens\n").append(
- "\tLEFT OUTER JOIN ").append(primaryTypeTableInfo.getFromSQL("PT")).append( " ON\n" +
- "\t\tPT.RowId = Specimens.PrimaryTypeId\n" +
- "\tLEFT OUTER JOIN ").append(derivativeTableInfo.getFromSQL("DT")).append(" ON\n" +
- "\t\tDT.RowId = Specimens.DerivativeTypeId\n" +
- "\tLEFT OUTER JOIN ").append(additiveTableInfo.getFromSQL("AT")).append(" ON\n" +
- "\t\tAT.RowId = Specimens.AdditiveTypeId\n" +
- ") ContainerTotals\n" +
- "GROUP BY PrimaryType, PrimaryTypeId, Derivative, DerivativeTypeId, Additive, AdditiveTypeId\n" +
- "ORDER BY PrimaryType, Derivative, Additive"
- );
-
- SpecimenTypeSummaryRow[] rows = new SqlSelector(SpecimenSchema.get().getSchema(), specimenTypeSummarySQL).getArray(SpecimenTypeSummaryRow.class);
-
- return new SpecimenTypeSummary(container, rows);
- }
-}
diff --git a/specimen/src/org/labkey/specimen/SpecimenModule.java b/specimen/src/org/labkey/specimen/SpecimenModule.java
deleted file mode 100644
index 212e54d10bb..00000000000
--- a/specimen/src/org/labkey/specimen/SpecimenModule.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Copyright (c) 2021-2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.labkey.specimen;
-
-import org.apache.commons.collections4.MultiSet;
-import org.apache.commons.collections4.multiset.HashMultiSet;
-import org.apache.commons.lang3.mutable.MutableInt;
-import org.apache.logging.log4j.Logger;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.labkey.api.attachments.AttachmentService;
-import org.labkey.api.audit.AuditLogService;
-import org.labkey.api.data.Container;
-import org.labkey.api.data.ContainerFilter;
-import org.labkey.api.data.ContainerManager;
-import org.labkey.api.data.SQLFragment;
-import org.labkey.api.data.SimpleFilter;
-import org.labkey.api.data.SqlSelector;
-import org.labkey.api.data.TableInfo;
-import org.labkey.api.data.TableSelector;
-import org.labkey.api.module.ModuleContext;
-import org.labkey.api.module.SpringModule;
-import org.labkey.api.pipeline.PipelineJob;
-import org.labkey.api.pipeline.PipelineJobException;
-import org.labkey.api.pipeline.PipelineService;
-import org.labkey.api.query.FieldKey;
-import org.labkey.api.query.QuerySettings;
-import org.labkey.api.query.QueryUpdateService;
-import org.labkey.api.query.QueryView;
-import org.labkey.api.query.UserSchema;
-import org.labkey.api.security.User;
-import org.labkey.api.security.permissions.Permission;
-import org.labkey.api.security.roles.RoleManager;
-import org.labkey.api.specimen.SpecimenMigrationService;
-import org.labkey.api.specimen.SpecimenQuerySchema;
-import org.labkey.api.specimen.SpecimenSchema;
-import org.labkey.api.specimen.SpecimensPage;
-import org.labkey.api.specimen.model.SpecimenTablesProvider;
-import org.labkey.api.study.SpecimenService;
-import org.labkey.api.study.Study;
-import org.labkey.api.study.StudyInternalService;
-import org.labkey.api.study.StudyService;
-import org.labkey.api.study.StudyUrls;
-import org.labkey.api.study.importer.SimpleStudyImportContext;
-import org.labkey.api.study.importer.SimpleStudyImporterRegistry;
-import org.labkey.api.study.writer.SimpleStudyWriterRegistry;
-import org.labkey.api.usageMetrics.UsageMetricsService;
-import org.labkey.api.util.LinkBuilder;
-import org.labkey.api.util.PageFlowUtil;
-import org.labkey.api.util.SafeToRender;
-import org.labkey.api.util.SystemMaintenance;
-import org.labkey.api.util.emailTemplate.EmailTemplateService;
-import org.labkey.api.util.logging.LogHelper;
-import org.labkey.api.view.ActionURL;
-import org.labkey.api.view.HttpView;
-import org.labkey.api.view.ViewContext;
-import org.labkey.api.view.WebPartFactory;
-import org.labkey.specimen.actions.SpecimenApiController;
-import org.labkey.specimen.actions.SpecimenController;
-import org.labkey.specimen.importer.AbstractSpecimenTask;
-import org.labkey.specimen.importer.QueryBasedSpecimenImportUploadTask;
-import org.labkey.specimen.importer.QueryBasedSpecimenTransform;
-import org.labkey.specimen.importer.RequestabilityManager;
-import org.labkey.specimen.importer.SpecimenImporter;
-import org.labkey.specimen.importer.SpecimenSchemaImporter;
-import org.labkey.specimen.importer.SpecimenSettingsImporter;
-import org.labkey.specimen.model.SpecimenRequestEventType;
-import org.labkey.specimen.pipeline.SpecimenPipeline;
-import org.labkey.specimen.query.SpecimenPivotByDerivativeType;
-import org.labkey.specimen.query.SpecimenPivotByPrimaryType;
-import org.labkey.specimen.query.SpecimenPivotByRequestingLocation;
-import org.labkey.specimen.query.SpecimenQueryView;
-import org.labkey.specimen.query.SpecimenUpdateService;
-import org.labkey.specimen.requirements.SpecimenRequestRequirementProvider;
-import org.labkey.specimen.security.permissions.EditSpecimenDataPermission;
-import org.labkey.specimen.security.permissions.ManageRequestSettingsPermission;
-import org.labkey.specimen.security.roles.SpecimenCoordinatorRole;
-import org.labkey.specimen.security.roles.SpecimenRequesterRole;
-import org.labkey.specimen.settings.RepositorySettings;
-import org.labkey.specimen.settings.SettingsManager;
-import org.labkey.specimen.view.ManageSpecimenView;
-import org.labkey.specimen.view.SpecimenReportWebPartFactory;
-import org.labkey.specimen.view.SpecimenRequestNotificationEmailTemplate;
-import org.labkey.specimen.view.SpecimenSearchWebPartFactory;
-import org.labkey.specimen.view.SpecimenToolsWebPartFactory;
-import org.labkey.specimen.view.SpecimenWebPartFactory;
-import org.labkey.specimen.writer.SpecimenArchiveWriter;
-import org.labkey.specimen.writer.SpecimenSettingsWriter;
-import org.labkey.specimen.writer.SpecimenWriter;
-import org.labkey.vfs.FileLike;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-public class SpecimenModule extends SpringModule
-{
- private static final Logger LOG = LogHelper.getLogger(SpecimenModule.class, "Specimen metrics");
- public static final String NAME = "Specimen";
-
- @Override
- public String getName()
- {
- return NAME;
- }
-
- @Override
- @NotNull
- protected Collection createWebPartFactories()
- {
- return List.of(
- new SpecimenReportWebPartFactory(),
- new SpecimenSearchWebPartFactory(HttpView.BODY),
- new SpecimenToolsWebPartFactory(),
- new SpecimenWebPartFactory()
- );
- }
-
- @Override
- public @Nullable Double getSchemaVersion()
- {
- return null;
- }
-
- @Override
- public boolean hasScripts()
- {
- return false;
- }
-
- @Override
- protected void init()
- {
- // Register early so these roles are available to Java code at upgrade time
- RoleManager.registerRole(new SpecimenCoordinatorRole());
- RoleManager.registerRole(new SpecimenRequesterRole());
-
- AttachmentService.get().registerAttachmentParentType(SpecimenRequestEventType.get());
-
- addController("specimen", SpecimenController.class, "study-samples");
- addController("specimen-api", SpecimenApiController.class, "study-samples-api", "specimens-api");
-
- // Register early -- some modules don't declare a runtime dependency on specimen module, but will use the
- // service if it's available
- SpecimenService.setInstance(new SpecimenServiceImpl());
- EmailTemplateService.get().registerTemplate(SpecimenRequestNotificationEmailTemplate.class);
-
- SpecimenMigrationService.setInstance(new SpecimenMigrationService()
- {
- @Override
- public ActionURL getBeginURL(Container c)
- {
- return new ActionURL(SpecimenController.BeginAction.class, c);
- }
-
- @Override
- public ActionURL getSelectedSpecimensURL(Container c)
- {
- return new ActionURL(SpecimenController.SelectedSpecimensAction.class, c);
- }
-
- @Override
- public ActionURL getSpecimensURL(Container c)
- {
- return SpecimenController.getSpecimensURL(c);
- }
-
- @Override
- public void importSpecimenArchive(@Nullable FileLike inputFile, PipelineJob job, SimpleStudyImportContext ctx, boolean merge, boolean syncParticipantVisit) throws PipelineJobException
- {
- AbstractSpecimenTask.doImport(inputFile, job, ctx, merge, syncParticipantVisit);
- }
-
- @Override
- public void clearRequestCaches(Container c)
- {
- SpecimenRequestManager.get().clearCaches(c);
- }
-
- @Override
- public @Nullable QueryUpdateService getSpecimenQueryUpdateService(Container c, TableInfo queryTable)
- {
- return SettingsManager.get().getRepositorySettings(c).isSpecimenDataEditable() ? new SpecimenUpdateService(queryTable) : null;
- }
-
- @Override
- public QueryView getSpecimenQueryView(ViewContext context, QuerySettings settings)
- {
- return SpecimenQueryView.createView(context, settings, SpecimenQueryView.ViewType.VIALS);
- }
-
- @Override
- public void setDefaultRequestabilityRules(Container container, User user)
- {
- RequestabilityManager.getInstance().setDefaultRules(container, user);
- }
-
- @Override
- public boolean isEnableRequests(Container c)
- {
- return SettingsManager.get().getRepositorySettings(c).isEnableRequests();
- }
-
- @Override
- public void addSpecimenPivotTableNames(Set names)
- {
- names.add(SpecimenPivotByPrimaryType.PIVOT_BY_PRIMARY_TYPE);
- names.add(SpecimenPivotByDerivativeType.PIVOT_BY_DERIVATIVE_TYPE);
- names.add(SpecimenPivotByRequestingLocation.PIVOT_BY_REQUESTING_LOCATION);
- }
-
- @Override
- public @Nullable TableInfo getSpecimenPivotTable(UserSchema schema, String name, Study study, ContainerFilter cf)
- {
- if (SpecimenPivotByPrimaryType.PIVOT_BY_PRIMARY_TYPE.equalsIgnoreCase(name))
- {
- return new SpecimenPivotByPrimaryType(schema, study, cf);
- }
- if (SpecimenPivotByDerivativeType.PIVOT_BY_DERIVATIVE_TYPE.equalsIgnoreCase(name))
- {
- return new SpecimenPivotByDerivativeType(schema, study, cf);
- }
- if (SpecimenPivotByRequestingLocation.PIVOT_BY_REQUESTING_LOCATION.equalsIgnoreCase(name))
- {
- return new SpecimenPivotByRequestingLocation(schema, study, cf);
- }
- return null;
- }
-
- @Override
- public @Nullable SafeToRender getSpecimenSettingsLink(Container c, User user)
- {
- // Must have ManageRequestSettingsPermission and requests enabled (otherwise non-admin will see a blank Manage Study page)
- if (c.hasPermission(user, ManageRequestSettingsPermission.class) && isEnableRequests(c))
- {
- return LinkBuilder.labkeyLink(
- "Manage Specimen Request Settings",
- PageFlowUtil.urlProvider(StudyUrls.class).getManageStudyURL(c)
- );
- }
-
- return null;
- }
-
- @Override
- public @NotNull Class extends Permission> getEditSpecimenDataPermission()
- {
- return EditSpecimenDataPermission.class;
- }
- });
-
- SystemMaintenance.addTask(new QueryBasedSpecimenImportUploadTask());
- }
-
- @Override
- protected void startupAfterSpringConfig(ModuleContext moduleContext)
- {
- ContainerManager.addContainerListener(new SpecimenRequestContainerListener());
-
- StudyService.get().registerStudyTabProvider(tabs -> tabs.add(new SpecimensPage("Specimen Data")));
- AuditLogService.get().registerAuditType(new SpecimenCommentAuditProvider());
- PipelineService.get().registerPipelineProvider(new SpecimenPipeline(this));
- StudyInternalService.get().registerManageStudyViewFactory(ctx -> ctx.getContainer().hasActiveModuleByName("specimen") ? new ManageSpecimenView() : null);
-
- SimpleStudyWriterRegistry.registerSimpleStudyWriterProvider(() -> List.of(
- new SpecimenArchiveWriter(),
- new SpecimenSettingsWriter()
- ));
-
- SimpleStudyImporterRegistry.registerSimpleStudyImporterProvider(() -> List.of(
- new SpecimenSchemaImporter(),
- new SpecimenSettingsImporter()
- ));
-
- SpecimenService specimenService = SpecimenService.get();
- if (null != specimenService)
- specimenService.registerSpecimenTransform(new QueryBasedSpecimenTransform());
-
- UsageMetricsService svc = UsageMetricsService.get();
- if (null != svc)
- {
- StudyService ss = StudyService.get();
-
- if (null != ss)
- {
- svc.registerUsageMetrics(NAME, () -> {
- // Collect and add specimen repository statistics: simple vs. advanced study count, event/vial/specimen count, count of studies with requests enabled, request count by status
- MultiSet specimenMultiSet = new HashMultiSet<>();
- MutableInt requestsEnabled = new MutableInt(0);
- MutableInt hasLocations = new MutableInt(0);
-
- ss.getAllStudies(ContainerManager.getRoot()).stream()
- .map(study -> SpecimenQuerySchema.get(study, User.getAdminServiceUser()))
- .forEach(schema -> {
- RepositorySettings settings = SettingsManager.get().getRepositorySettings(schema.getContainer());
-
- if (settings.isSimple())
- {
- specimenMultiSet.add("simple");
- TableInfo simpleSpecimens = schema.getTable(SpecimenTablesProvider.SIMPLE_SPECIMEN_TABLE_NAME);
- specimenMultiSet.add("simpleSpecimens", (int) new TableSelector(simpleSpecimens).getRowCount());
- }
- else
- {
- specimenMultiSet.add("advanced");
- TableInfo events = schema.getTable(SpecimenTablesProvider.SPECIMEN_EVENT_TABLE_NAME);
- TableInfo vials = schema.getTable(SpecimenTablesProvider.SPECIMEN_DETAIL_TABLE_NAME);
- TableInfo specimens = schema.getTable(SpecimenTablesProvider.SPECIMEN_SUMMARY_TABLE_NAME);
- specimenMultiSet.add("events", (int) new TableSelector(events).getRowCount());
- specimenMultiSet.add("vials", (int) new TableSelector(vials).getRowCount());
- specimenMultiSet.add("specimens", (int) new TableSelector(specimens).getRowCount());
- }
-
- if (settings.isEnableRequests())
- requestsEnabled.increment();
-
- TableInfo locations = schema.getTable(SpecimenQuerySchema.LOCATION_TABLE_NAME);
- long locationCount = new TableSelector(locations).getRowCount();
- specimenMultiSet.add("locations", (int) locationCount);
- specimenMultiSet.add("locationsInUse", (int) new TableSelector(locations, new SimpleFilter(FieldKey.fromParts("In Use"), true), null).getRowCount());
- if (locationCount > 0)
- hasLocations.increment();
-
- LOG.debug(specimenMultiSet.toString());
- });
-
- Map specimensMap = specimenMultiSet.uniqueSet().stream().collect(Collectors.toMap(s -> s, specimenMultiSet::getCount));
- Map requestsMap = new SqlSelector(SpecimenSchema.get().getSchema(), new SQLFragment("SELECT Label, COUNT(*) FROM study.SampleRequest INNER JOIN study.SampleRequestStatus srs ON StatusId = srs.RowId GROUP BY Label")).getValueMap(String.class);
- requestsMap.put("enabled", requestsEnabled);
- specimensMap.put("requests", requestsMap);
- specimensMap.put("hasLocations", hasLocations.intValue());
-
- return specimensMap;
- });
- }
- }
- }
-
- @Override
- public @NotNull Set> getUnitTests()
- {
- return Set.of(
- SpecimenWriter.TestCase.class
- );
- }
-
- @Override
- public @NotNull Collection getSchemaNames()
- {
- return Set.of();
- }
-
- @Override
- public @NotNull Set> getIntegrationTests()
- {
- return Set.of(
- SpecimenImporter.TestCase.class,
- SpecimenRequestRequirementProvider.ContainerScopingTestCase.class,
- SpecimenController.ContainerScopingTestCase.class
- );
- }
-}
\ No newline at end of file
diff --git a/specimen/src/org/labkey/specimen/SpecimenRequestContainerListener.java b/specimen/src/org/labkey/specimen/SpecimenRequestContainerListener.java
deleted file mode 100644
index 8f04c51eec5..00000000000
--- a/specimen/src/org/labkey/specimen/SpecimenRequestContainerListener.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 2022-2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.specimen;
-
-import org.labkey.api.data.Container;
-import org.labkey.api.data.ContainerManager;
-import org.labkey.api.security.User;
-
-import java.beans.PropertyChangeEvent;
-
-public class SpecimenRequestContainerListener implements ContainerManager.ContainerListener
-{
- @Override
- public void containerCreated(Container c, User user)
- {
- SpecimenRequestManager.get().clearCaches(c);
- }
-
- @Override
- public void containerDeleted(Container c, User user)
- {
- SpecimenRequestManager.get().clearCaches(c);
- }
-
- @Override
- public void containerMoved(Container c, Container oldParent, User user)
- {
- SpecimenRequestManager.get().clearCaches(c);
- }
-
- @Override
- public void propertyChange(PropertyChangeEvent evt)
- {
- SpecimenRequestManager.get().clearCaches((Container)evt.getSource());
- }
-}
diff --git a/specimen/src/org/labkey/specimen/SpecimenRequestException.java b/specimen/src/org/labkey/specimen/SpecimenRequestException.java
deleted file mode 100644
index ea05a1cb19d..00000000000
--- a/specimen/src/org/labkey/specimen/SpecimenRequestException.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (c) 2021-2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.specimen;
-
-public class SpecimenRequestException extends RuntimeException
-{
- public SpecimenRequestException()
- {
- }
-
- public SpecimenRequestException(String message)
- {
- super(message);
- }
-}
diff --git a/specimen/src/org/labkey/specimen/SpecimenRequestManager.java b/specimen/src/org/labkey/specimen/SpecimenRequestManager.java
deleted file mode 100644
index 0e601503f13..00000000000
--- a/specimen/src/org/labkey/specimen/SpecimenRequestManager.java
+++ /dev/null
@@ -1,1290 +0,0 @@
-/*
- * Copyright (c) 2021-2026 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.labkey.specimen;
-
-import org.apache.commons.collections4.comparators.ComparableComparator;
-import org.apache.commons.lang3.StringUtils;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.labkey.api.action.SpringActionController;
-import org.labkey.api.attachments.AttachmentFile;
-import org.labkey.api.attachments.AttachmentService;
-import org.labkey.api.cache.BlockingCache;
-import org.labkey.api.cache.CacheLoader;
-import org.labkey.api.cache.CacheManager;
-import org.labkey.api.collections.IntHashMap;
-import org.labkey.api.collections.LongArrayList;
-import org.labkey.api.data.ColumnInfo;
-import org.labkey.api.data.Container;
-import org.labkey.api.data.DatabaseCache;
-import org.labkey.api.data.DbScope;
-import org.labkey.api.data.RuntimeSQLException;
-import org.labkey.api.data.SQLFragment;
-import org.labkey.api.data.SimpleFilter;
-import org.labkey.api.data.SqlExecutor;
-import org.labkey.api.data.SqlSelector;
-import org.labkey.api.data.Table;
-import org.labkey.api.data.TableInfo;
-import org.labkey.api.data.TableResultSet;
-import org.labkey.api.exp.Lsid;
-import org.labkey.api.exp.ObjectProperty;
-import org.labkey.api.exp.OntologyManager;
-import org.labkey.api.exp.PropertyType;
-import org.labkey.api.query.FieldKey;
-import org.labkey.api.query.QueryService;
-import org.labkey.api.query.UserSchema;
-import org.labkey.api.query.ValidationException;
-import org.labkey.api.security.User;
-import org.labkey.api.settings.AppProps;
-import org.labkey.api.specimen.SpecimenQuerySchema;
-import org.labkey.api.specimen.SpecimenSchema;
-import org.labkey.api.specimen.Vial;
-import org.labkey.api.specimen.importer.RollupHelper;
-import org.labkey.api.specimen.importer.RollupInstance;
-import org.labkey.api.specimen.importer.VialSpecimenRollup;
-import org.labkey.api.specimen.model.SpecimenTablesProvider;
-import org.labkey.api.study.QueryHelper;
-import org.labkey.api.study.QueryHelper.StudyCacheCollections;
-import org.labkey.api.study.SpecimenUrls;
-import org.labkey.api.study.Study;
-import org.labkey.api.study.StudyService;
-import org.labkey.api.study.StudyUtils;
-import org.labkey.api.util.GUID;
-import org.labkey.api.util.PageFlowUtil;
-import org.labkey.api.util.Pair;
-import org.labkey.api.util.ReentrantLockWithName;
-import org.labkey.api.view.ActionURL;
-import org.labkey.specimen.importer.RequestabilityManager;
-import org.labkey.specimen.model.SpecimenRequestEvent;
-import org.labkey.specimen.requirements.SpecimenRequest;
-import org.labkey.specimen.requirements.SpecimenRequestRequirement;
-import org.labkey.specimen.requirements.SpecimenRequestRequirementProvider;
-import org.labkey.specimen.security.permissions.ManageRequestsPermission;
-import org.labkey.specimen.security.permissions.RequestSpecimensPermission;
-import org.labkey.specimen.settings.SettingsManager;
-
-import java.io.IOException;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.locks.ReentrantLock;
-
-import static org.labkey.api.util.IntegerUtils.asLongElseNull;
-
-public class SpecimenRequestManager
-{
- private static final SpecimenRequestManager INSTANCE = new SpecimenRequestManager();
-
- private final QueryHelper> _requestEventHelper;
- private final QueryHelper> _requestStatusHelper;
- private final QueryHelper> _requestHelper;
-
- public static SpecimenRequestManager get()
- {
- return INSTANCE;
- }
-
- private SpecimenRequestManager()
- {
- _requestEventHelper = new QueryHelper<>(() -> SpecimenSchema.get().getTableInfoSampleRequestEvent(), SpecimenRequestEvent.class);
- _requestStatusHelper = new QueryHelper<>(() -> SpecimenSchema.get().getTableInfoSampleRequestStatus(), SpecimenRequestStatus.class, "SortOrder");
- _requestHelper = new QueryHelper<>(() -> SpecimenSchema.get().getTableInfoSampleRequest(), SpecimenRequest.class, "-Created");
-
- initGroupedValueAllowedColumnMap();
- }
-
- public List getRequestStatuses(Container c, User user)
- {
- List statuses = new ArrayList<>(_requestStatusHelper.getCollection(c));
- // if the 'not-yet-submitted' status doesn't exist, create it here, with sort order -1,
- // so it's always first.
- if (statuses.isEmpty() || statuses.getFirst().getSortOrder() != -1)
- {
- SpecimenRequestStatus notYetSubmittedStatus = new SpecimenRequestStatus();
- notYetSubmittedStatus.setContainer(c);
- notYetSubmittedStatus.setFinalState(false);
- notYetSubmittedStatus.setSpecimensLocked(true);
- notYetSubmittedStatus.setLabel("Not Yet Submitted");
- notYetSubmittedStatus.setSortOrder(-1);
- try (var ignore = SpringActionController.ignoreSqlUpdates())
- {
- Table.insert(user, _requestStatusHelper.getTableInfo(), notYetSubmittedStatus);
- _requestStatusHelper.clearCache(c);
- }
- statuses = new ArrayList<>(_requestStatusHelper.getCollection(c));
- }
- return statuses;
- }
-
- public SpecimenRequestStatus getRequestShoppingCartStatus(Container c, User user)
- {
- List statuses = getRequestStatuses(c, user);
- if (statuses.getFirst().getSortOrder() != -1)
- throw new IllegalStateException("Shopping cart status should be created automatically.");
- return statuses.getFirst();
- }
-
- public SpecimenRequestStatus getInitialRequestStatus(Container c, User user, boolean nonCart)
- {
- List statuses = getRequestStatuses(c, user);
- if (!nonCart && SettingsManager.get().isSpecimenShoppingCartEnabled(c))
- return statuses.get(0);
- else
- return statuses.get(1);
- }
-
- public boolean hasEditRequestPermissions(User user, SpecimenRequest request)
- {
- if (request == null)
- return false;
- Container container = request.getContainer();
- if (!container.hasPermission(user, RequestSpecimensPermission.class))
- return false;
- if (container.hasPermission(user, ManageRequestsPermission.class))
- return true;
-
- if (SettingsManager.get().isSpecimenShoppingCartEnabled(container))
- {
- SpecimenRequestStatus cartStatus = getRequestShoppingCartStatus(container, user);
- if (cartStatus.getRowId() == request.getStatusId() && request.getCreatedBy() == user.getUserId())
- return true;
- }
- return false;
- }
-
- public Set getRequestStatusIdsInUse(Container c)
- {
- Collection requests = _requestHelper.getCollection(c);
- Set uniqueStatuses = new HashSet<>();
- for (SpecimenRequest request : requests)
- uniqueStatuses.add(request.getStatusId());
- return uniqueStatuses;
- }
-
- public SpecimenRequestEvent getRequestEvent(Container c, int rowId)
- {
- return _requestEventHelper.get(c, rowId);
- }
-
- public static class SpecimenRequestInput
- {
- private final String _title;
- private final String _helpText;
- private final int _displayOrder;
-
- private boolean _required;
- private boolean _rememberSiteValue;
- private boolean _multiLine;
- private Map _locationToDefaultValue;
-
- public SpecimenRequestInput(String title, String helpText, int displayOrder, boolean multiLine, boolean required, boolean rememberSiteValue)
- {
- _title = title;
- _required = required;
- _rememberSiteValue = rememberSiteValue;
- _helpText = helpText;
- _displayOrder = displayOrder;
- _multiLine = multiLine;
- }
-
- public SpecimenRequestInput(String title, String helpText, int displayOrder)
- {
- this(title, helpText, displayOrder, false, false, false);
- }
-
- public String getHelpText()
- {
- return _helpText;
- }
-
- public boolean isRememberSiteValue()
- {
- return _rememberSiteValue;
- }
-
- public boolean isRequired()
- {
- return _required;
- }
-
- public String getTitle()
- {
- return _title;
- }
-
- public int getDisplayOrder()
- {
- return _displayOrder;
- }
-
- public boolean isMultiLine()
- {
- return _multiLine;
- }
-
- public void setMultiLine(boolean multiLine)
- {
- _multiLine = multiLine;
- }
-
- public void setRememberSiteValue(boolean rememberSiteValue)
- {
- _rememberSiteValue = rememberSiteValue;
- }
-
- public void setRequired(boolean required)
- {
- _required = required;
- }
-
- public Map getDefaultSiteValues(Container container) throws ValidationException
- {
- if (!isRememberSiteValue())
- throw new UnsupportedOperationException("Only those inputs set to remember site values can be queried for a site default.");
-
- if (_locationToDefaultValue != null)
- return _locationToDefaultValue;
- String defaultObjectLsid = getRequestInputDefaultObjectLsid(container);
- String setItemLsid = ensureOntologyManagerSetItem(container, defaultObjectLsid, getTitle());
- Map locationToValue = new IntHashMap<>();
-
- Map defaultValueProperties = OntologyManager.getPropertyObjects(container, setItemLsid);
- if (defaultValueProperties != null)
- {
- for (Map.Entry defaultValue : defaultValueProperties.entrySet())
- {
- String locationIdString = defaultValue.getKey().substring(defaultValue.getKey().lastIndexOf(".") + 1);
- int locationId = Integer.parseInt(locationIdString);
- locationToValue.put(locationId, defaultValue.getValue().getStringValue());
- }
- }
- _locationToDefaultValue = locationToValue;
- return _locationToDefaultValue;
- }
-
- public void setDefaultSiteValue(Container container, int locationId, String value) throws SQLException
- {
- try
- {
- assert locationId > 0 : "Invalid site id: " + locationId;
- if (!isRememberSiteValue())
- throw new UnsupportedOperationException("Only those inputs configured to remember site values can set a site default.");
- _locationToDefaultValue = null;
- String parentObjectLsid = getRequestInputDefaultObjectLsid(container);
-
- String setItemLsid = ensureOntologyManagerSetItem(container, parentObjectLsid, getTitle());
- String propertyId = parentObjectLsid + "." + locationId;
- ObjectProperty defaultValueProperty = new ObjectProperty(setItemLsid, container, propertyId, value);
- OntologyManager.deleteProperty(setItemLsid, propertyId, container, container);
- OntologyManager.insertProperties(container, setItemLsid, defaultValueProperty);
- }
- catch (ValidationException e)
- {
- throw new SQLException(e.getMessage());
- }
- }
- }
-
- public SpecimenRequestInput[] getNewSpecimenRequestInputs(Container container) throws SQLException
- {
- return getNewSpecimenRequestInputs(container, true);
- }
-
- public SpecimenRequestInput[] getNewSpecimenRequestInputs(Container container, boolean createIfMissing) throws SQLException
- {
- String parentObjectLsid = getRequestInputObjectLsid(container);
- Map resourceProperties = OntologyManager.getPropertyObjects(container, parentObjectLsid);
- SpecimenRequestInput[] inputs = new SpecimenRequestInput[0];
- if (resourceProperties == null || resourceProperties.isEmpty())
- {
- if (createIfMissing)
- {
- try (var ignore = SpringActionController.ignoreSqlUpdates())
- {
- inputs = new SpecimenRequestInput[] {
- new SpecimenRequestInput("Assay Plan", "Please enter a description of or reference to the assay plan(s) that will be used for the requested specimens.", 0, true, true, false),
- new SpecimenRequestInput("Shipping Information", "Please enter your shipping address along with any special instructions.", 1, true, true, true),
- new SpecimenRequestInput("Comments", "Please enter any additional information regarding your request.", 2, true, false, false)
- };
- saveNewSpecimenRequestInputs(container, inputs);
- }
- }
- return inputs;
- }
- else
- {
- inputs = new SpecimenRequestInput[resourceProperties.size()];
- for (Map.Entry parentPropertyEntry : resourceProperties.entrySet())
- {
- String resourcePropertyLsid = parentPropertyEntry.getKey();
- int displayOrder = Integer.parseInt(resourcePropertyLsid.substring(resourcePropertyLsid.lastIndexOf('.') + 1));
-
- Map inputProperties = parentPropertyEntry.getValue().retrieveChildProperties();
- String title = inputProperties.get(parentObjectLsid + ".Title").getStringValue();
- String helpText = null;
- if (inputProperties.get(parentObjectLsid + ".HelpText") != null)
- helpText = inputProperties.get(parentObjectLsid + ".HelpText").getStringValue();
- boolean rememberSiteValue = inputProperties.get(parentObjectLsid + ".RememberSiteValue").getFloatValue() == 1;
- boolean required = inputProperties.get(parentObjectLsid + ".Required").getFloatValue() == 1;
- boolean multiLine = inputProperties.get(parentObjectLsid + ".MultiLine").getFloatValue() == 1;
- inputs[displayOrder] = new SpecimenRequestInput(title, helpText, displayOrder, multiLine, required, rememberSiteValue);
- }
- }
- return inputs;
- }
-
- private static String getRequestInputObjectLsid(Container container)
- {
- return new Lsid(StudyService.SPECIMEN_NAMESPACE_PREFIX, "Folder-" + container.getRowId(), "RequestInput").toString();
- }
-
- private static String getRequestInputDefaultObjectLsid(Container container)
- {
- return new Lsid(StudyService.SPECIMEN_NAMESPACE_PREFIX, "Folder-" + container.getRowId(), "RequestInputDefault").toString();
- }
-
- private static String ensureOntologyManagerSetItem(Container container, String lsidBase, String uniqueItemId) throws ValidationException
- {
- try (var ignore = SpringActionController.ignoreSqlUpdates())
- {
- Long listParentObjectId = OntologyManager.ensureObject(container, lsidBase);
- String listItemReferenceLsidPrefix = lsidBase + "#objectResource.";
- String listItemObjectLsid = lsidBase + "#" + uniqueItemId;
- String listItemPropertyReferenceLsid = listItemReferenceLsidPrefix + uniqueItemId;
-
- // ensure the object that corresponds to a single list item:
- OntologyManager.ensureObject(container, listItemObjectLsid, listParentObjectId);
-
- // check to make sure that the list item is wired up to the top-level list object via a property:
- Map properties = OntologyManager.getPropertyObjects(container, lsidBase);
- if (!properties.containsKey(listItemPropertyReferenceLsid))
- {
- // create the resource property that links the parent object to the list item object:
- ObjectProperty resourceProperty = new ObjectProperty(lsidBase, container,
- listItemPropertyReferenceLsid, listItemObjectLsid, PropertyType.RESOURCE);
- OntologyManager.insertProperties(container, lsidBase, resourceProperty);
- }
- return listItemObjectLsid;
- }
- }
-
- public void saveNewSpecimenRequestInputs(Container container, SpecimenRequestInput[] inputs) throws SQLException
- {
- if (!requestInputsChanged(container, inputs))
- return;
-
- try {
- String parentObjectLsid = getRequestInputObjectLsid(container);
- String defaultValuesObjectLsid = getRequestInputDefaultObjectLsid(container);
- OntologyManager.deleteOntologyObject(parentObjectLsid, container, true);
- OntologyManager.deleteOntologyObject(defaultValuesObjectLsid, container, true);
- for (int i = 0; i < inputs.length; i++)
- {
- SpecimenRequestInput input = inputs[i];
- String setItemLsid = ensureOntologyManagerSetItem(container, parentObjectLsid, "" + i);
- ObjectProperty[] props = new ObjectProperty[5];
- props[0] = new ObjectProperty(setItemLsid, container, parentObjectLsid + ".HelpText", input.getHelpText());
- props[1] = new ObjectProperty(setItemLsid, container, parentObjectLsid + ".Required", input.isRequired() ? 1 : 0);
- props[2] = new ObjectProperty(setItemLsid, container, parentObjectLsid + ".RememberSiteValue", input.isRememberSiteValue() ? 1 : 0);
- props[3] = new ObjectProperty(setItemLsid, container, parentObjectLsid + ".Title", input.getTitle());
- props[4] = new ObjectProperty(setItemLsid, container, parentObjectLsid + ".MultiLine", input.isMultiLine() ? 1 : 0);
- OntologyManager.insertProperties(container, setItemLsid, props);
- }
- }
- catch (ValidationException ve)
- {
- throw new SQLException(ve.getMessage());
- }
- }
-
- private boolean requestInputsChanged(Container container, SpecimenRequestInput[] newInputs) throws SQLException
- {
- SpecimenRequestInput[] oldInputs = getNewSpecimenRequestInputs(container, false);
- if (oldInputs.length == 0)
- return true;
- else if (oldInputs.length != newInputs.length)
- return true;
- else
- {
- for (int i = 0; i < oldInputs.length; i++)
- {
- if (oldInputs[i].isMultiLine() != newInputs[i].isMultiLine() ||
- oldInputs[i].isRememberSiteValue() != newInputs[i].isRememberSiteValue() ||
- oldInputs[i].isRequired() != newInputs[i].isRequired() ||
- !oldInputs[i].getTitle().equals(newInputs[i].getTitle()) ||
- !getSafeString(oldInputs[i].getHelpText()).equals(getSafeString(newInputs[i].getHelpText())))
- return true;
- }
- }
- return false;
- }
-
- private String getSafeString(String str)
- {
- return Objects.requireNonNullElse(str, "");
- }
-
- public void createRequestStatus(User user, SpecimenRequestStatus status)
- {
- _requestStatusHelper.create(user, status);
- }
-
- public void updateRequestStatus(User user, SpecimenRequestStatus status)
- {
- _requestStatusHelper.update(user, status);
- }
-
- public void deleteRequestStatus(SpecimenRequestStatus status)
- {
- _requestStatusHelper.delete(status);
- }
-
- public SpecimenRequestRequirement[] getRequestRequirements(SpecimenRequest request)
- {
- if (request == null)
- return new SpecimenRequestRequirement[0];
- return request.getRequirements();
- }
-
- public void deleteRequestRequirement(User user, SpecimenRequestRequirement requirement) throws AttachmentService.DuplicateFilenameException
- {
- deleteRequestRequirement(user, requirement, true);
- }
-
- public void deleteRequestRequirement(User user, SpecimenRequestRequirement requirement, boolean createEvent) throws AttachmentService.DuplicateFilenameException
- {
- if (createEvent)
- createRequestEvent(user, requirement, RequestEventType.REQUIREMENT_REMOVED, requirement.getRequirementSummary(), null);
- requirement.delete();
- }
-
- public void createRequestRequirement(User user, SpecimenRequestRequirement requirement, boolean createEvent, boolean force) throws AttachmentService.DuplicateFilenameException
- {
- SpecimenRequest request = getRequest(requirement.getContainer(), requirement.getRequestId());
- SpecimenRequestRequirement newRequirement = SpecimenRequestRequirementProvider.get().createRequirement(user, request, requirement, force);
- if (newRequirement != null && createEvent)
- createRequestEvent(user, requirement, RequestEventType.REQUIREMENT_ADDED, requirement.getRequirementSummary(), null);
- }
-
- public void updateRequestRequirement(User user, SpecimenRequestRequirement requirement)
- {
- requirement.update(user);
- }
-
- public boolean isInFinalState(SpecimenRequest request)
- {
- return getRequestStatus(request.getContainer(), request.getStatusId()).isFinalState();
- }
-
- public SpecimenRequestStatus getRequestStatus(Container c, int rowId)
- {
- return _requestStatusHelper.get(c, rowId);
- }
-
- private SpecimenRequestEvent createRequestEvent(User user, SpecimenRequestEvent event)
- {
- return _requestEventHelper.create(user, event);
- }
-
- public SpecimenRequestEvent createRequestEvent(User user, SpecimenRequestRequirement requirement, RequestEventType type, String comments, List attachments) throws AttachmentService.DuplicateFilenameException
- {
- return createRequestEvent(user, requirement.getContainer(), requirement.getRequestId(), requirement.getRowId(), type, comments, attachments);
- }
-
- public SpecimenRequestEvent createRequestEvent(User user, SpecimenRequest request, RequestEventType type, String comments, List attachments) throws AttachmentService.DuplicateFilenameException
- {
- return createRequestEvent(user, request.getContainer(), request.getRowId(), -1, type, comments, attachments);
- }
-
- private SpecimenRequestEvent createRequestEvent(User user, Container container, int requestId, int requirementId, RequestEventType type, String comments, List attachments) throws AttachmentService.DuplicateFilenameException
- {
- SpecimenRequestEvent event = new SpecimenRequestEvent();
- event.setEntryType(type.getDisplayText());
- event.setComments(comments);
- event.setRequestId(requestId);
- event.setCreated(new Date(System.currentTimeMillis()));
- if (requirementId >= 0)
- event.setRequirementId(requirementId);
- event.setContainer(container);
- event.setEntityId(GUID.makeGUID());
- event = createRequestEvent(user, event);
- try
- {
- AttachmentService.get().addAttachments(event, attachments, user);
- }
- catch (AttachmentService.DuplicateFilenameException e)
- {
- // UI should (minimally) catch and display these errors nicely
- throw e;
- }
- catch (IOException e)
- {
- // this is unexpected, and indicative of a larger system problem; we'll convert to a runtime
- // exception, rather than requiring all event loggers to handle this unlikely scenario:
- throw new RuntimeException(e);
- }
- return event;
- }
-
- public Collection getRequests(Container c, @Nullable User user)
- {
- return _requestHelper.getCollection(c).stream()
- .filter(sr -> !sr.isHidden())
- .filter(sr -> user == null || sr.getCreatedBy() == user.getUserId())
- .toList();
- }
-
- public SpecimenRequest getRequest(Container c, int rowId)
- {
- return _requestHelper.get(c, rowId);
- }
-
- public SpecimenRequest createRequest(User user, SpecimenRequest request, boolean createEvent) throws AttachmentService.DuplicateFilenameException
- {
- request = _requestHelper.create(user, request);
- if (createEvent)
- createRequestEvent(user, request, RequestEventType.REQUEST_CREATED, request.getRequestDescription(), null);
- return request;
- }
-
- public void updateRequest(User user, SpecimenRequest request) throws RequestabilityManager.InvalidRuleException
- {
- DbScope scope = SpecimenSchema.get().getScope();
- try (DbScope.Transaction transaction = scope.ensureTransaction())
- {
- _requestHelper.update(user, request);
-
- // update specimen states
- List vials = request.getVials();
- if (!vials.isEmpty())
- {
- SpecimenRequestStatus status = getRequestStatus(request.getContainer(), request.getStatusId());
- updateSpecimenStatus(vials, user, status.isSpecimensLocked());
- }
- transaction.commit();
- }
- }
-
- private static final ReentrantLock REQUEST_ADDITION_LOCK = new ReentrantLockWithName(SpecimenRequestManager.class, "REQUEST_ADDITION_LOCK");
-
- public void createRequestSpecimenMapping(User user, SpecimenRequest request, List vials, boolean createEvents, boolean createRequirements)
- throws RequestabilityManager.InvalidRuleException, AttachmentService.DuplicateFilenameException, SpecimenRequestException
- {
- if (vials == null || vials.isEmpty())
- return;
-
- DbScope scope = SpecimenSchema.get().getScope();
- try (DbScope.Transaction transaction = scope.ensureTransaction(REQUEST_ADDITION_LOCK))
- {
- for (Vial vial : vials)
- {
- if (!request.getContainer().getId().equals(vial.getContainer().getId()))
- throw new IllegalStateException("Mismatched containers.");
-
- if (!vial.isAvailable())
- throw new SpecimenRequestException("Vial unavailable.");
- }
-
- for (Vial vial : vials)
- {
- Map fields = new HashMap<>();
- fields.put("Container", request.getContainer().getId());
- fields.put("SampleRequestId", request.getRowId());
- fields.put("SpecimenGlobalUniqueId", vial.getGlobalUniqueId());
- Table.insert(user, SpecimenSchema.get().getTableInfoSampleRequestSpecimen(), fields);
- if (createEvents)
- SpecimenRequestManager.get().createRequestEvent(user, request, RequestEventType.SPECIMEN_ADDED, vial.getSpecimenDescription(), null);
- }
-
- if (createRequirements)
- SpecimenRequestRequirementProvider.get().generateDefaultRequirements(user, request);
-
- SpecimenRequestStatus status = getRequestStatus(request.getContainer(), request.getStatusId());
- updateSpecimenStatus(vials, user, status.isSpecimensLocked());
-
- transaction.commit();
- }
- }
-
- /**
- * Update the lockedInRequest and available field states for the set of specimens.
- */
- private void updateSpecimenStatus(List vials, User user, boolean lockedInRequest) throws RequestabilityManager.InvalidRuleException
- {
- for (Vial vial : vials)
- {
- vial.setLockedInRequest(lockedInRequest);
- Table.update(user, SpecimenSchema.get().getTableInfoVial(vial.getContainer()), vial.getRowMap(), vial.getRowId());
- }
- updateRequestabilityAndCounts(vials, user);
- if (!vials.isEmpty())
- clearCaches(getContainer(vials));
- }
-
- private void updateRequestabilityAndCounts(List vials, User user) throws RequestabilityManager.InvalidRuleException
- {
- if (vials.isEmpty())
- return;
- Container container = getContainer(vials);
-
- // update requestable flags before updating counts, since available count could change:
- for (int start = 0; start < vials.size(); start += 1000)
- {
- List subset = vials.subList(start, start + Math.min(1000, vials.size() - start));
- RequestabilityManager.getInstance().updateRequestability(container, user, subset);
- }
-
- for (int start = 0; start < vials.size(); start += 1000)
- {
- List subset = vials.subList(start, start + Math.min(1000, vials.size() - start));
- updateVialCounts(container, user, subset);
- }
- }
-
- private Container getContainer(List vials)
- {
- Container container = vials.getFirst().getContainer();
- if (AppProps.getInstance().isDevMode())
- {
- for (int i = 1; i < vials.size(); i++)
- {
- if (!container.equals(vials.get(i).getContainer()))
- throw new IllegalStateException("All specimens must be from the same container");
- }
- }
- return container;
- }
-
- private static final String UPDATE_SPECIMEN_SETS =
- " SET\n" +
- " TotalVolume = VialCounts.TotalVolume,\n" +
- " AvailableVolume = VialCounts.AvailableVolume,\n" +
- " VialCount = VialCounts.VialCount,\n" +
- " LockedInRequestCount = VialCounts.LockedInRequestCount,\n" +
- " AtRepositoryCount = VialCounts.AtRepositoryCount,\n" +
- " AvailableCount = VialCounts.AvailableCount,\n" +
- " ExpectedAvailableCount = VialCounts.ExpectedAvailableCount";
-
- private static final String UPDATE_SPECIMEN_SELECTS =
- "\nFROM (\n" +
- "\tSELECT SpecimenId,\n" +
- "\t\tSUM(Volume) AS TotalVolume,\n" +
- "\t\tSUM(CASE Available WHEN ? THEN Volume ELSE 0 END) AS AvailableVolume,\n" +
- "\t\tCOUNT(GlobalUniqueId) AS VialCount,\n" +
- "\t\tSUM(CASE LockedInRequest WHEN ? THEN 1 ELSE 0 END) AS LockedInRequestCount,\n" +
- "\t\tSUM(CASE AtRepository WHEN ? THEN 1 ELSE 0 END) AS AtRepositoryCount,\n" +
- "\t\tSUM(CASE Available WHEN ? THEN 1 ELSE 0 END) AS AvailableCount,\n" +
- "\t\t(COUNT(GlobalUniqueId) - SUM(\n" +
- "\t\tCASE\n" +
- "\t\t\t(CASE LockedInRequest WHEN ? THEN 1 ELSE 0 END) -- Null is considered false for LockedInRequest\n" +
- "\t\t\t| (CASE Requestable WHEN ? THEN 1 ELSE 0 END)-- Null is considered true for Requestable\n" +
- "\t\t\tWHEN 1 THEN 1 ELSE 0 END)\n" +
- "\t\t) AS ExpectedAvailableCount";
-
- // "\tFROM ";
-
- private void updateVialCounts(Container container, User user, List vials)
- {
- TableInfo tableInfoSpecimen = SpecimenSchema.get().getTableInfoSpecimen(container);
- TableInfo tableInfoVial = SpecimenSchema.get().getTableInfoVial(container);
-
- String tableInfoSpecimenSelectName = tableInfoSpecimen.getSelectName();
- String tableInfoVialSelectName = tableInfoVial.getSelectName();
- RollupHelper.RollupMap matchedRollups = RollupHelper.getVialToSpecimenRollups(container, user);
-
- SQLFragment updateSql = new SQLFragment();
- updateSql.append("UPDATE ").append(tableInfoSpecimenSelectName).append(UPDATE_SPECIMEN_SETS);
- for (List> rollupList : matchedRollups.values())
- for (Pair rollupItem : rollupList)
- {
- ColumnInfo column = tableInfoSpecimen.getColumn(rollupItem.first);
- if (null == column)
- throw new IllegalStateException("Expected Specimen table column to exist.");
- var colSelectName = column.getSelectIdentifier();
- updateSql.append(",\n ").appendIdentifier(colSelectName).append(" = VialCounts.").appendIdentifier(colSelectName);
- }
-
- updateSql.append(UPDATE_SPECIMEN_SELECTS);
- updateSql.add(Boolean.TRUE); // AvailableVolume
- updateSql.add(Boolean.TRUE); // LockedInRequestCount
- updateSql.add(Boolean.TRUE); // AtRepositoryCount
- updateSql.add(Boolean.TRUE); // AvailableCount
- updateSql.add(Boolean.TRUE); // LockedInRequest case of ExpectedAvailableCount
- updateSql.add(Boolean.FALSE); // Requestable case of ExpectedAvailableCount
-
- for (Map.Entry>> entry : matchedRollups.entrySet())
- {
- ColumnInfo vialColumn = tableInfoVial.getColumn(entry.getKey());
- if (null == vialColumn)
- throw new IllegalStateException("Expected Vial table column to exist.");
- var fromName = vialColumn.getSelectIdentifier();
- for (RollupInstance rollupItem : entry.getValue())
- {
- VialSpecimenRollup rollup = rollupItem.second;
- ColumnInfo column = tableInfoSpecimen.getColumn(rollupItem.first);
- if (null == column)
- throw new IllegalStateException("Expected Specimen table column to exist.");
- var toName = column.getSelectIdentifier();
-
- // TODO convert to SQLFragment
- updateSql.append(",\n\t\t").append(rollup.getRollupSql(fromName.getId(), toName.getId()));
- }
- }
-
- updateSql.append("\tFROM ").append(tableInfoVialSelectName).append("\n");
-
- if (vials != null && !vials.isEmpty())
- {
- Set specimenIds = new HashSet<>();
- for (Vial vial : vials)
- specimenIds.add(vial.getSpecimenId());
-
- updateSql.append("WHERE ")
- .append(tableInfoVial.getColumn("SpecimenId").getValueSql(tableInfoVialSelectName))
- .append(" IN (");
- String sep = "";
- for (Long id : specimenIds)
- {
- updateSql.append(sep).append("?");
- updateSql.add(id);
- sep = ", ";
- }
- updateSql.append(")\n");
- }
-
- updateSql.append("\tGROUP BY SpecimenId\n) VialCounts\nWHERE ")
- .append(tableInfoSpecimen.getColumn("RowId").getValueSql(tableInfoSpecimenSelectName))
- .append("= VialCounts.SpecimenId");
- new SqlExecutor(SpecimenSchema.get().getSchema()).execute(updateSql);
- }
-
- public void updateVialCounts(Container container, User user)
- {
- updateVialCounts(container, user, null);
- }
-
- public void clearCaches(Container c)
- {
- _requestEventHelper.clearCache(c);
- _requestHelper.clearCache(c);
- _requestStatusHelper.clearCache(c);
-
- clearGroupedValuesForColumn(c);
- }
-
- void clearRequestStatusHelper(Container c)
- {
- _requestStatusHelper.clearCache(c);
- }
-
- void clearRequestHelper(Container c)
- {
- _requestHelper.clearCache(c);
- }
-
- void clearRequestEventHelper(Container c)
- {
- _requestEventHelper.clearCache(c);
- }
-
- public static class GroupedValueColumnHelper
- {
- private final String _viewColumnName;
- private final String _sqlColumnName;
- private final String _urlFilterName;
- private final String _joinColumnName;
-
- public GroupedValueColumnHelper(String sqlColumnName, String viewColumnName, String urlFilterName, String joinColumnName)
- {
- _sqlColumnName = sqlColumnName;
- _viewColumnName = viewColumnName;
- _urlFilterName = urlFilterName;
- _joinColumnName = joinColumnName;
- }
-
- public String getUrlFilterName()
- {
- return _urlFilterName;
- }
-
- public FieldKey getFieldKey()
- {
- // constructs FieldKey whether it needs join or not
- if (null == _joinColumnName)
- return FieldKey.fromString(_viewColumnName);
- return FieldKey.fromParts(_sqlColumnName, _joinColumnName);
- }
- }
-
- // Map "ViewColumnName" name to object with sql column name and url filter name
- private final Map _groupedValueAllowedColumnMap = new HashMap<>();
-
- private void initGroupedValueAllowedColumnMap()
- { // sqlColumnName viewColumnName urlFilterName joinColumnName
- _groupedValueAllowedColumnMap.put("Primary Type", new GroupedValueColumnHelper("PrimaryTypeId", "PrimaryType", "PrimaryType/Description", "PrimaryType"));
- _groupedValueAllowedColumnMap.put("Derivative Type", new GroupedValueColumnHelper("DerivativeTypeId", "DerivativeType", "DerivativeType/Description", "Derivative"));
- _groupedValueAllowedColumnMap.put("Additive Type", new GroupedValueColumnHelper("AdditiveTypeId", "AdditiveType", "AdditiveType/Description", "Additive"));
- _groupedValueAllowedColumnMap.put("Derivative Type2", new GroupedValueColumnHelper("DerivativeTypeId2", "DerivativeType2", "DerivativeType2/Description", "Derivative"));
- _groupedValueAllowedColumnMap.put("Sub Additive Derivative",new GroupedValueColumnHelper("SubAdditiveDerivative", "SubAdditiveDerivative", "SubAdditiveDerivative", null));
- _groupedValueAllowedColumnMap.put("Clinic", new GroupedValueColumnHelper("originatinglocationid", "Clinic", "Clinic/Label", "Label"));
- _groupedValueAllowedColumnMap.put("Processing Location", new GroupedValueColumnHelper("ProcessingLocation", "ProcessingLocation", "ProcessingLocation/Label", "Label"));
- _groupedValueAllowedColumnMap.put("Protocol Number", new GroupedValueColumnHelper("ProtocolNumber", "ProtocolNumber", "ProtocolNumber", null));
- _groupedValueAllowedColumnMap.put("Tube Type", new GroupedValueColumnHelper("TubeType", "TubeType", "TubeType", null));
- _groupedValueAllowedColumnMap.put("Site Name", new GroupedValueColumnHelper("CurrentLocation", "SiteName", "SiteName/Label", "Label"));
- _groupedValueAllowedColumnMap.put("Available", new GroupedValueColumnHelper("Available", "Available", "Available", null));
- _groupedValueAllowedColumnMap.put("Freezer", new GroupedValueColumnHelper("Freezer", "Freezer", "Freezer", null));
- _groupedValueAllowedColumnMap.put("Fr Container", new GroupedValueColumnHelper("Fr_Container", "Fr_Container", "Fr_Container", null));
- _groupedValueAllowedColumnMap.put("Fr Position", new GroupedValueColumnHelper("Fr_Position", "Fr_Position", "Fr_Position", null));
- _groupedValueAllowedColumnMap.put("Fr Level1", new GroupedValueColumnHelper("Fr_Level1", "Fr_Level1", "Fr_Level1", null));
- _groupedValueAllowedColumnMap.put("Fr Level2", new GroupedValueColumnHelper("Fr_Level2", "Fr_Level2", "Fr_Level2", null));
- }
-
- public Map getGroupedValueAllowedMap()
- {
- return _groupedValueAllowedColumnMap;
- }
-
- public String[] getGroupedValueAllowedColumns()
- {
- Set keySet = _groupedValueAllowedColumnMap.keySet();
- String[] allowedColumns = keySet.toArray(new String[0]);
- Arrays.sort(allowedColumns, new ComparableComparator<>());
- return allowedColumns;
- }
-
- private static class GroupedValueFilter
- {
- private String _viewColumnName;
- private String _filterValueName;
-
- public GroupedValueFilter()
- {
- }
-
- public String getViewColumnName()
- {
- return _viewColumnName;
- }
-
- public String getFilterValueName()
- {
- return _filterValueName;
- }
-
- public void setFilterValueName(String filterValueName)
- {
- _filterValueName = filterValueName;
- }
-
- public void setViewColumnName(String viewColumnName)
- {
- _viewColumnName = viewColumnName;
- }
- }
-
- private final BlockingCache>> GROUPED_VALUES_CACHE = DatabaseCache.get(SpecimenSchema.get().getScope(), 10, 8 * CacheManager.HOUR, "Specimen grouped values", new GroupedValuesCacheLoader());
-
- private static class GroupedResults
- {
- public String viewName;
- public String urlFilterName;
- public String labelValue;
- public long count;
- public Map childGroupedResultsMap;
- }
-
- public void clearGroupedValuesForColumn(Container container)
- {
- GROUPED_VALUES_CACHE.remove(container);
- }
-
- @NotNull
- public Map> getGroupedValuesForColumn(Container container, User user, ArrayList groupings)
- {
- return GROUPED_VALUES_CACHE.get(container, Pair.of(user, groupings));
- }
-
- private class GroupedValuesCacheLoader implements CacheLoader>>
- {
- @Override
- public Map> load(@NotNull Container c, @Nullable Object argument)
- {
- Map> groupedValues = new HashMap<>();
-
- // ColumnName and filter names are "QueryView" names; map them to actual table names before building query
- Study study = StudyService.get().getStudy(c);
- if (study != null)
- {
- @SuppressWarnings("unchecked") Pair> pair = (Pair>)argument;
- assert null != pair;
- User user = pair.first;
- List groupings = pair.second;
- QueryService queryService = QueryService.get();
-
- for (String[] grouping : groupings)
- {
- List fieldKeys = new ArrayList<>();
- for (String aGrouping : grouping)
- {
- if (!StringUtils.isNotBlank(aGrouping))
- break; // Grouping may have null/blank entries for groupBys that are not chosen to be used
- GroupedValueColumnHelper columnHelper = getGroupedValueAllowedMap().get(aGrouping);
- FieldKey fieldKey = columnHelper.getFieldKey();
- fieldKeys.add(fieldKey);
- }
-
- if (fieldKeys.isEmpty())
- continue; // Nothing specified for grouping
-
- // Basic SQL with joins
- UserSchema schema = SpecimenQuerySchema.get(study, user);
- TableInfo tableInfo = schema.getTable(SpecimenTablesProvider.SPECIMEN_WRAP_TABLE_NAME);
- Map columnMap = queryService.getColumns(tableInfo, fieldKeys);
-
- SQLFragment inner = queryService.getSelectBuilder(tableInfo).columns(columnMap.values()).buildSqlFragment();
-
- // Insert COUNT
- String sampleCountName = tableInfo.getSqlDialect().makeLegalIdentifier("SampleCount");
- SQLFragment sql = new SQLFragment("SELECT COUNT(*) As " + sampleCountName);
- for (var col : columnMap.values())
- sql.append(",").appendIdentifier(col.getAlias());
-
- sql.append("\nFROM (").append(inner).append(") AS details");
-
- sql.append("\nGROUP BY ");
- boolean firstGroupBy = true;
- for (ColumnInfo columnInfo : columnMap.values())
- {
- if (!firstGroupBy)
- sql.append(", ");
- firstGroupBy = false;
- sql.appendIdentifier(columnInfo.getAlias());
- }
-
- sql.append("\nORDER BY ");
- boolean firstOrderBy = true;
- for (ColumnInfo columnInfo : columnMap.values())
- {
- if (!firstOrderBy)
- sql.append(", ");
- firstOrderBy = false;
- sql.appendIdentifier(columnInfo.getAlias());
- }
-
- SqlSelector selector = new SqlSelector(tableInfo.getSchema(), sql);
-
- try (TableResultSet resultSet = selector.getResultSet())
- {
- if (null != resultSet)
- {
- // The result set is grouped by all levels together, so at the upper levels, we have to group ourselves
- // Build a tree of GroupedResultsMaps, one level for each grouping level
- //
- Map groupedResultsMap = new HashMap<>();
- while (resultSet.next())
- {
- Map rowMap = resultSet.getRowMap();
- long count = 0;
- Object countObject = rowMap.get(sampleCountName);
- if (asLongElseNull(countObject) instanceof Long l)
- count = l;
-
- Map currentGroupedResultsMap = groupedResultsMap;
-
- for (String s : grouping)
- {
- if (!StringUtils.isNotBlank(s))
- break; // Grouping may have null entries for groupBys that are not chosen to be used
-
- GroupedValueColumnHelper columnHelper = getGroupedValueAllowedMap().get(s);
- ColumnInfo columnInfo = columnMap.get(columnHelper.getFieldKey());
- Object value = columnInfo.getValue(rowMap);
- String labelValue = (null != value) ? value.toString() : null;
- GroupedResults groupedResults = currentGroupedResultsMap.get(labelValue);
- if (null == groupedResults)
- {
- groupedResults = new GroupedResults();
- groupedResults.viewName = s;
- groupedResults.urlFilterName = columnHelper.getUrlFilterName();
- groupedResults.labelValue = labelValue;
- groupedResults.childGroupedResultsMap = new HashMap<>();
- currentGroupedResultsMap.put(labelValue, groupedResults);
- }
- groupedResults.count += count;
- currentGroupedResultsMap = groupedResults.childGroupedResultsMap;
- }
- }
-
- Map groupedValue;
- if (!groupedResultsMap.isEmpty())
- {
- groupedValue = buildGroupedValue(groupedResultsMap, c, new ArrayList<>());
- }
- else
- {
- groupedValue = new HashMap<>(2);
- groupedValue.put("name", grouping[0]);
- groupedValue.put("values", new ArrayList