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 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 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>()); - } - groupedValues.put(grouping[0], groupedValue); - } - } - catch(SQLException e) - { - throw new RuntimeSQLException(e); - } - } - } - - return Collections.unmodifiableMap(groupedValues); - } - } - - private Map buildGroupedValue(Map groupedResultsMap, Container container, List groupedValueFilters) - { - String viewName = null; - ArrayList> groupedValues = new ArrayList<>(); - for (GroupedResults groupedResults : groupedResultsMap.values()) - { - viewName = groupedResults.viewName; // They are all the same in this collection - Map groupedValue = new HashMap<>(5); - groupedValue.put("label", (null != groupedResults.labelValue) ? groupedResults.labelValue : "[empty]"); - groupedValue.put("count", groupedResults.count); - groupedValue.put("url", getURL(container, groupedResults.urlFilterName, groupedValueFilters, groupedResults.labelValue)); - Map childGroupResultsMap = groupedResults.childGroupedResultsMap; - if (null != childGroupResultsMap && !childGroupResultsMap.isEmpty()) - { - GroupedValueFilter groupedValueFilter = new GroupedValueFilter(); - groupedValueFilter.setViewColumnName(groupedResults.viewName); - groupedValueFilter.setFilterValueName(null != groupedResults.labelValue ? groupedResults.labelValue : null); - List groupedValueFiltersCopy = new ArrayList<>(groupedValueFilters); // Need copy because can't share across members of groupedResultsMap - groupedValueFiltersCopy.add(groupedValueFilter); - Map nextLevelGroup = buildGroupedValue(childGroupResultsMap, container, groupedValueFiltersCopy); - groupedValue.put("group", nextLevelGroup); - - } - groupedValues.add(groupedValue); - } - - groupedValues.sort((o, o1) -> - { - String str = (String) o.get("label"); - String str1 = (String) o1.get("label"); - if (null == str) - { - if (null == str1) - return 0; - else - return 1; - } - else if (null == str1) - return -1; - return (str.compareTo(str1)); - }); - - Map groupedValue = new HashMap<>(2); - groupedValue.put("name", viewName); - groupedValue.put("values", groupedValues); - return groupedValue; - } - - private ActionURL getURL(Container container, String groupColumnName, List filterNamesAndValues, String label) - { - ActionURL url = PageFlowUtil.urlProvider(SpecimenUrls.class).getSpecimensURL(container, true); - addFilterParameter(url, groupColumnName, label); - for (GroupedValueFilter filterColumnAndValue : filterNamesAndValues) - addFilterParameter(url, getGroupedValueAllowedMap().get(filterColumnAndValue.getViewColumnName()).getUrlFilterName(), filterColumnAndValue.getFilterValueName()); - return url; - } - - private void addFilterParameter(ActionURL url, String urlColumnName, String label) - { - url.addParameter("SpecimenDetail." + urlColumnName + "~eq", label); - } - - public void deleteRequest(User user, SpecimenRequest request) throws RequestabilityManager.InvalidRuleException, AttachmentService.DuplicateFilenameException - { - DbScope scope = SpecimenSchema.get().getScope(); - try (DbScope.Transaction transaction = scope.ensureTransaction()) - { - List vials = request.getVials(); - List specimenIds = new LongArrayList(vials.size()); - for (Vial vial : vials) - specimenIds.add(vial.getRowId()); - - deleteRequestSpecimenMappings(user, request, specimenIds, false); - - deleteMissingSpecimens(request); - - SpecimenRequestRequirementProvider.get().deleteRequirements(request); - - deleteRequestEvents(request); - _requestHelper.delete(request); - - transaction.commit(); - } - } - - public void deleteRequestSpecimenMappings(User user, SpecimenRequest request, List vialIds, boolean createEvents) - throws RequestabilityManager.InvalidRuleException, AttachmentService.DuplicateFilenameException - { - if (vialIds == null || vialIds.isEmpty()) - return; - - Set vialRowIds = new HashSet<>(vialIds); - List vials = SpecimenManager.get().getVials(request.getContainer(), user, vialRowIds); - List globalUniqueIds = new ArrayList<>(vials.size()); - List descriptions = new ArrayList<>(); - for (Vial vial : vials) - { - globalUniqueIds.add(vial.getGlobalUniqueId()); - descriptions.add(vial.getSpecimenDescription()); - } - - DbScope scope = SpecimenSchema.get().getScope(); - try (DbScope.Transaction transaction = scope.ensureTransaction()) - { - SimpleFilter filter = SimpleFilter.createContainerFilter(request.getContainer()); - filter.addCondition(FieldKey.fromParts("SampleRequestId"), request.getRowId()); - filter.addInClause(FieldKey.fromParts("SpecimenGlobalUniqueId"), globalUniqueIds); - Table.delete(SpecimenSchema.get().getTableInfoSampleRequestSpecimen(), filter); - if (createEvents) - { - for (String description : descriptions) - SpecimenRequestManager.get().createRequestEvent(user, request, RequestEventType.SPECIMEN_REMOVED, description, null); - } - - updateSpecimenStatus(vials, user, false); - - transaction.commit(); - } - } - - public void deleteMissingSpecimens(SpecimenRequest specimenRequest) - { - List missingSpecimens = getMissingSpecimens(specimenRequest); - if (missingSpecimens.isEmpty()) - return; - SimpleFilter filter = SimpleFilter.createContainerFilter(specimenRequest.getContainer()); - filter.addCondition(FieldKey.fromParts("SampleRequestId"), specimenRequest.getRowId()); - filter.addInClause(FieldKey.fromParts("SpecimenGlobalUniqueId"), missingSpecimens); - Table.delete(SpecimenSchema.get().getTableInfoSampleRequestSpecimen(), filter); - } - - public List getMissingSpecimens(SpecimenRequest specimenRequest) - { - Container container = specimenRequest.getContainer(); - TableInfo tableInfoVial = SpecimenSchema.get().getTableInfoVial(container); - SQLFragment sql = new SQLFragment("SELECT SpecimenGlobalUniqueId FROM study.SampleRequestSpecimen WHERE SampleRequestId = ? and Container = ? and \n" + - "SpecimenGlobalUniqueId NOT IN (SELECT "); - sql.add(specimenRequest.getRowId()); - sql.add(container); - sql.append(tableInfoVial.getColumn("GlobalUniqueId").getValueSql("Vial")) - .append(" FROM ").append(tableInfoVial.getFromSQL("Vial")).append(")"); - - return new SqlSelector(SpecimenSchema.get().getSchema(), sql).getArrayList(String.class); - } - - public @NotNull List getRequestIdsForSpecimen(Vial vial) - { - return getRequestIdsForSpecimen(vial, false); - } - - public @NotNull List getRequestIdsForSpecimen(Vial vial, boolean lockingRequestsOnly) - { - if (vial == null) - return Collections.emptyList(); - - SQLFragment sql = new SQLFragment("SELECT SampleRequestId FROM " + SpecimenSchema.get().getTableInfoSampleRequestSpecimen() + - " Map, " + SpecimenSchema.get().getTableInfoSampleRequest() + " Request, " + - SpecimenSchema.get().getTableInfoSampleRequestStatus() + " Status WHERE SpecimenGlobalUniqueId = ? " + - "AND Request.Container = ? AND Map.Container = Request.Container AND Status.Container = Request.Container " + - "AND Map.SampleRequestId = Request.RowId AND Request.StatusId = Status.RowId"); - sql.add(vial.getGlobalUniqueId()); - sql.add(vial.getContainer().getId()); - - if (lockingRequestsOnly) - { - sql.append(" AND Status.SpecimensLocked = ?"); - sql.add(Boolean.TRUE); - } - - return new SqlSelector(SpecimenSchema.get().getSchema(), sql).getArrayList(Integer.class); - } - - private void deleteRequestEvents(SpecimenRequest request) - { - _requestEventHelper.getCollection(request.getContainer()).stream() - .filter(event -> event.getRequestId() == request.getRowId()) - .forEach(event -> { - AttachmentService.get().deleteAttachments(event); - _requestEventHelper.delete(event); - }); - } - - public RequestedSpecimens getRequestableBySpecimenHash(Container c, User user, Set formValues, Integer preferredLocation) throws AmbiguousLocationException - { - Map> vialsByHash = SpecimenManager.get().getVialsForSpecimenHashes(c, user, formValues, true); - - if (vialsByHash == null || vialsByHash.isEmpty()) - return new RequestedSpecimens(Collections.emptyList()); - - if (preferredLocation == null) - { - Collection preferredLocations = StudyUtils.getPreferredProvidingLocations(vialsByHash.values()); - if (preferredLocations.size() == 1) - preferredLocation = preferredLocations.iterator().next(); - else if (preferredLocations.size() > 1) - throw new AmbiguousLocationException(c, preferredLocations); - } - - List requestedSpecimens = new ArrayList<>(vialsByHash.size()); - Set providingLocations = new HashSet<>(); - - for (List vials : vialsByHash.values()) - { - Vial selectedVial = null; - if (preferredLocation == null) - selectedVial = vials.getFirst(); - else - { - for (Iterator it = vials.iterator(); it.hasNext() && selectedVial == null;) - { - Vial vial = it.next(); - if (vial.getCurrentLocation() != null && vial.getCurrentLocation().intValue() == preferredLocation.intValue()) - selectedVial = vial; - } - } - if (selectedVial == null) - throw new IllegalStateException("Vial was not available from specified location " + preferredLocation); - providingLocations.add(selectedVial.getCurrentLocation()); - requestedSpecimens.add(selectedVial); - } - - return new RequestedSpecimens(requestedSpecimens, providingLocations); - } -} - diff --git a/specimen/src/org/labkey/specimen/SpecimenRequestStatus.java b/specimen/src/org/labkey/specimen/SpecimenRequestStatus.java deleted file mode 100644 index 3b94626fb22..00000000000 --- a/specimen/src/org/labkey/specimen/SpecimenRequestStatus.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2008-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.study.AbstractStudyCachable; - -import java.util.Objects; - -public class SpecimenRequestStatus extends AbstractStudyCachable -{ - private int _rowId; - private Container _container; - private Integer _sortOrder; - private String _label; - private boolean _specimensLocked; - private boolean _finalState; - - @Override - public Container getContainer() - { - return _container; - } - - @Override - public void setContainer(Container container) - { - verifyMutability(); - _container = container; - } - - public String getLabel() - { - return _label; - } - - public void setLabel(String label) - { - verifyMutability(); - _label = label; - } - - @Override - public Integer getPrimaryKey() - { - return getRowId(); - } - - public int getRowId() - { - return _rowId; - } - - public void setRowId(int rowId) - { - verifyMutability(); - _rowId = rowId; - } - - public Integer getSortOrder() - { - return _sortOrder; - } - - public void setSortOrder(Integer sortOrder) - { - verifyMutability(); - _sortOrder = sortOrder; - } - - public boolean isFinalState() - { - return _finalState; - } - - public void setFinalState(boolean finalState) - { - verifyMutability(); - _finalState = finalState; - } - - public boolean isSpecimensLocked() - { - return _specimensLocked; - } - - public void setSpecimensLocked(boolean specimensLocked) - { - verifyMutability(); - _specimensLocked = specimensLocked; - } - - public boolean isSystemStatus() - { - return _sortOrder < 0; - } - - @Override - public boolean equals(Object o) - { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - SpecimenRequestStatus that = (SpecimenRequestStatus) o; - return _rowId == that._rowId && _specimensLocked == that._specimensLocked && _finalState == that._finalState && Objects.equals(_container, that._container) && Objects.equals(_sortOrder, that._sortOrder) && Objects.equals(_label, that._label); - } - - @Override - public int hashCode() - { - return Objects.hash(_rowId, _container, _sortOrder, _label, _specimensLocked, _finalState); - } -} diff --git a/specimen/src/org/labkey/specimen/SpecimenServiceImpl.java b/specimen/src/org/labkey/specimen/SpecimenServiceImpl.java deleted file mode 100644 index 75c36cb814b..00000000000 --- a/specimen/src/org/labkey/specimen/SpecimenServiceImpl.java +++ /dev/null @@ -1,443 +0,0 @@ -/* - * Copyright (c) 2008-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.logging.log4j.Logger; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.labkey.api.attachments.AttachmentParentType; -import org.labkey.api.data.Container; -import org.labkey.api.data.DbSchema; -import org.labkey.api.data.PropertyManager; -import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.data.SqlSelector; -import org.labkey.api.data.Table; -import org.labkey.api.data.TableInfo; -import org.labkey.api.exp.Lsid; -import org.labkey.api.exp.api.ExpMaterial; -import org.labkey.api.exp.api.ExpSampleType; -import org.labkey.api.exp.api.ExperimentService; -import org.labkey.api.exp.api.SampleTypeService; -import org.labkey.api.pipeline.PipeRoot; -import org.labkey.api.pipeline.PipelineJob; -import org.labkey.api.pipeline.PipelineService; -import org.labkey.api.query.FieldKey; -import org.labkey.api.query.ValidationException; -import org.labkey.api.security.User; -import org.labkey.api.specimen.DefaultSpecimenTablesTemplate; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.specimen.Vial; -import org.labkey.api.study.ParticipantVisit; -import org.labkey.api.study.SpecimenChangeListener; -import org.labkey.api.study.SpecimenService; -import org.labkey.api.study.SpecimenTablesTemplate; -import org.labkey.api.study.SpecimenTransform; -import org.labkey.api.study.StudyService; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.Pair; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.ViewBackgroundInfo; -import org.labkey.specimen.importer.SpecimenColumn; -import org.labkey.specimen.model.SpecimenRequestEventType; -import org.labkey.specimen.pipeline.SpecimenReloadJob; -import org.labkey.specimen.requirements.SpecimenRequestRequirementProvider; - -import java.util.ArrayList; -import java.util.Collection; -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.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; - -public class SpecimenServiceImpl implements SpecimenService -{ - private final Map _specimenTransformMap = new ConcurrentHashMap<>(); - private final List _changeListeners = new CopyOnWriteArrayList<>(); - - private SpecimenRequestCustomizer _specimenRequestCustomizer = new SpecimenRequestCustomizer() - { - @Override - public boolean allowEmptyRequests() - { - return false; - } - - @Override - public Integer getDefaultDestinationSiteId() - { - return null; - } - - @Override - public boolean omitTypeGroupingsWhenReporting() - { - return false; - } - - @Override - public boolean canChangeStatus(User user) - { - return true; - } - - @Override - public boolean hideRequestWarnings() - { - return false; - } - - @Override - public HtmlString getSubmittedMessage(Container c, int requestId) - { - return HtmlString.EMPTY_STRING; - } - }; - - private class StudyParticipantVisit implements ParticipantVisit - { - private final Container _studyContainer; - private final String _participantID; - private final Double _visitID; - private final String _specimenID; - - private ExpMaterial _material; - private Date _date; - - public StudyParticipantVisit(Container studyContainer, String specimenID, String participantID, Double visitID, Date date) - { - _studyContainer = studyContainer; - _specimenID = specimenID; - _participantID = participantID; - _visitID = visitID; - _date = date; - } - - @Override - public Container getStudyContainer() - { - return _studyContainer; - } - - @Override - public String getParticipantID() - { - return _participantID; - } - - @Override - public Double getVisitID() - { - return _visitID; - } - - @Override - public String getSpecimenID() - { - return _specimenID; - } - - @Override - public Integer getCohortID() - { - throw new UnsupportedOperationException("Not Implemented for StudyParticipantVisit"); - } - - @Override @Nullable - public ExpMaterial getMaterial(boolean createIfNeeded) - { - if (_material == null) - { - if (_specimenID != null) - { - Lsid lsid = getSpecimenMaterialLsid(_studyContainer, _specimenID); - _material = ExperimentService.get().getExpMaterial(lsid.toString()); - if (_material == null && createIfNeeded) - { - _material = ExperimentService.get().createExpMaterial(_studyContainer, lsid.toString(), _specimenID); - _material.save(null); - } - } - else - { - String lsid = new Lsid(ParticipantVisit.ASSAY_RUN_MATERIAL_NAMESPACE, "Folder-" + _studyContainer.getRowId(), "Unknown").toString(); - _material = ExperimentService.get().getExpMaterial(lsid); - if (_material == null && createIfNeeded) - { - _material = ExperimentService.get().createExpMaterial(_studyContainer, lsid, "Unknown"); - _material.save(null); - } - } - } - return _material; - } - - @Override - public Date getDate() - { - return _date; - } - - public void setDate(Date date) - { - _date = date; - } - } - - @Override - public ParticipantVisit getSampleInfo(Container studyContainer, User user, String sampleId) - { - Vial match = SpecimenManager.get().getVial(studyContainer, user, sampleId); - if (match != null) - return new StudyParticipantVisit(studyContainer, sampleId, match.getPtid(), match.getVisitValue(), match.getDrawTimestamp()); - else - return new StudyParticipantVisit(studyContainer, sampleId, null, null, null); - } - - @Override - public Set> getSampleInfo(Container studyContainer, User user, boolean truncateTime) - { - TableInfo tableInfoSpecimen = SpecimenSchema.get().getTableInfoSpecimen(studyContainer); - - String dateExpr = truncateTime ? tableInfoSpecimen.getSqlDialect().getDateTimeToDateCast("DrawTimestamp") : "DrawTimestamp"; - SQLFragment sql = new SQLFragment("SELECT DISTINCT PTID, " + dateExpr + " AS DrawTimestamp FROM "); - sql.append(tableInfoSpecimen); - - final Set> sampleInfo = new HashSet<>(); - - new SqlSelector(tableInfoSpecimen.getSchema(), sql).forEach(rs -> { - String participantId = rs.getString("PTID"); - Date drawDate = rs.getDate("DrawTimestamp"); - if (participantId != null && drawDate != null) - sampleInfo.add(new Pair<>(participantId, drawDate)); - }); - - return sampleInfo; - } - - @Override - public Set> getSampleInfo(Container studyContainer, User user) - { - TableInfo tableInfoSpecimen = SpecimenSchema.get().getTableInfoSpecimen(studyContainer); - - SQLFragment sql = new SQLFragment("SELECT DISTINCT PTID, VisitValue FROM "); - sql.append(tableInfoSpecimen); - - final Set> sampleInfo = new HashSet<>(); - - new SqlSelector(tableInfoSpecimen.getSchema(), sql).forEach(rs -> { - String participantId = rs.getString("PTID"); - double visit = rs.getDouble("VisitValue"); // Never returns null - if (participantId != null) - sampleInfo.add(new Pair<>(participantId, visit)); - }); - - return sampleInfo; - } - - @Override - public Lsid getSpecimenMaterialLsid(@NotNull Container studyContainer, @NotNull String id) - { - return new Lsid(StudyService.SPECIMEN_NAMESPACE_PREFIX, "Folder-" + studyContainer.getRowId(), id); - } - - @Override - public String getActiveSpecimenImporter(@NotNull Container container) - { - PropertyManager.PropertyMap props = PropertyManager.getProperties(container, "enabledSpecimenImporter"); - String activeTransform = props.get("active"); - if (null == activeTransform) - return null; - - // In case module with active transform has been disabled in container - Collection transforms = getSpecimenTransforms(container); - boolean noTransformsActive = transforms.stream().noneMatch(transform -> activeTransform.equals(transform.getName())); - - if (noTransformsActive) - return null; - return props.get("active"); - } - - @Override - public void registerSpecimenTransform(SpecimenTransform transform) - { - if (!_specimenTransformMap.containsKey(transform.getName())) - _specimenTransformMap.put(transform.getName(), transform); - else - throw new IllegalStateException("A specimen transform implementation with the name: " + transform.getName() + " is already registered"); - } - - @Override - public Collection getSpecimenTransforms(Container container) - { - List transforms = new ArrayList<>(); - - for (SpecimenTransform transform : _specimenTransformMap.values()) - { - if (transform.isValid(container)) - transforms.add(transform); - } - return transforms; - } - - @Nullable - @Override - public SpecimenTransform getSpecimenTransform(String name) - { - return _specimenTransformMap.get(name); - } - - @Override - public PipelineJob createSpecimenReloadJob(Container container, User user, SpecimenTransform transform, @Nullable ActionURL url) throws ValidationException - { - PipeRoot root = PipelineService.get().findPipelineRoot(container); - SpecimenReloadJob job = new SpecimenReloadJob(new ViewBackgroundInfo(container, user, url), root, transform.getName()); - - job.setExternalImportConfig(transform.getExternalImportConfig(container, user)); - - return job; - } - - @Override - public void registerSpecimenChangeListener(SpecimenChangeListener listener) - { - _changeListeners.add(listener); - } - - private static final SpecimenTablesTemplate _specimenTablesTemplate = new DefaultSpecimenTablesTemplate(); - - @Nullable - @Override - public TableInfo getTableInfoVial(Container container) - { - return SpecimenSchema.get().getTableInfoVialIfExists(container); - } - - @Nullable - @Override - public TableInfo getTableInfoSpecimen(Container container) - { - return SpecimenSchema.get().getTableInfoSpecimenIfExists(container); - } - - @Nullable - @Override - public TableInfo getTableInfoSpecimenEvent(Container container) - { - return SpecimenSchema.get().getTableInfoSpecimenEventIfExists(container); - } - - @Override - public Map getSpecimenImporterTsvColumnMap() - { - Map colNameMap = new HashMap<>(); - for (SpecimenColumn specimenColumn : SpecimenColumns.BASE_SPECIMEN_COLUMNS) - { - colNameMap.put(specimenColumn.getDbColumnName(), specimenColumn.getPrimaryTsvColumnName()); - } - return colNameMap; - } - - @Override - public SpecimenRequestCustomizer getRequestCustomizer() - { - return _specimenRequestCustomizer; - } - - @Override - public void registerRequestCustomizer(SpecimenRequestCustomizer customizer) - { - _specimenRequestCustomizer = customizer; - } - - @Override - public AttachmentParentType getSpecimenRequestEventType() - { - return SpecimenRequestEventType.get(); - } - - @Override - public void fireSpecimensChanged(Container c, User user, Logger logger) - { - for (SpecimenChangeListener l : _changeListeners) - l.specimensChanged(c, user, logger); - } - - @Override - public void deleteAllSpecimenData(Container c, Set set, User user) - { - // Consider: Move this to a container listener? Stop clearing caches, since that's handled by SpecimenRequestContainerListener. - SimpleFilter containerFilter = SimpleFilter.createContainerFilter(c); - - Table.delete(SpecimenSchema.get().getTableInfoSampleRequestSpecimen(), containerFilter); - assert set.add(SpecimenSchema.get().getTableInfoSampleRequestSpecimen()); - Table.delete(SpecimenSchema.get().getTableInfoSampleRequestEvent(), containerFilter); - SpecimenRequestManager.get().clearRequestEventHelper(c); - assert set.add(SpecimenSchema.get().getTableInfoSampleRequestEvent()); - Table.delete(SpecimenSchema.get().getTableInfoSampleRequest(), containerFilter); - SpecimenRequestManager.get().clearRequestHelper(c); - assert set.add(SpecimenSchema.get().getTableInfoSampleRequest()); - Table.delete(SpecimenSchema.get().getTableInfoSampleRequestStatus(), containerFilter); - SpecimenRequestManager.get().clearRequestStatusHelper(c); - assert set.add(SpecimenSchema.get().getTableInfoSampleRequestStatus()); - - Table.delete(SpecimenSchema.get().getTableInfoSampleAvailabilityRule(), containerFilter); - assert set.add(SpecimenSchema.get().getTableInfoSampleAvailabilityRule()); - - SpecimenRequestRequirementProvider.get().purgeContainer(c, user); - assert set.add(SpecimenSchema.get().getTableInfoSampleRequestRequirement()); - assert set.add(SpecimenSchema.get().getTableInfoSampleRequestActor()); - - DbSchema expSchema = ExperimentService.get().getSchema(); - TableInfo tinfoMaterial = expSchema.getTable("Material"); - - ExpSampleType sampleType = SampleTypeService.get().getSampleType(c, SpecimenService.SAMPLE_TYPE_NAME); - - if (sampleType != null) - { - // Check if any of the samples are referenced in an experiment run - SQLFragment sql = new SQLFragment("SELECT m.RowId FROM "); - sql.append(ExperimentService.get().getTinfoMaterial(), "m"); - sql.append(" INNER JOIN "); - sql.append(ExperimentService.get().getTinfoMaterialInput(), "mi"); - sql.append(" ON m.RowId = mi.MaterialId AND m.CpasType = ?"); - sql.add(sampleType.getLSID()); - - if (new SqlSelector(ExperimentService.get().getSchema(), sql).exists()) - { - // If so, do the slow version of the delete that tears down runs - sampleType.delete(user); - } - else - { - // If not, do the quick version that just kills the samples themselves in the exp.Material table - SimpleFilter materialFilter = new SimpleFilter(containerFilter); - materialFilter.addCondition(FieldKey.fromParts("CpasType"), sampleType.getLSID()); - Table.delete(tinfoMaterial, materialFilter); - } - } - - // VIEW: if this view gets removed, remove this line - assert set.add(SpecimenSchema.get().getSchema().getTable("LockedSpecimens")); - - SpecimenRequestManager.get().clearGroupedValuesForColumn(c); - } -} diff --git a/specimen/src/org/labkey/specimen/SpecimenTableManager.java b/specimen/src/org/labkey/specimen/SpecimenTableManager.java deleted file mode 100644 index 8d4688afbdd..00000000000 --- a/specimen/src/org/labkey/specimen/SpecimenTableManager.java +++ /dev/null @@ -1,213 +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.data.JdbcType; -import org.labkey.api.data.TableInfo; -import org.labkey.api.data.dialect.SqlDialect; -import org.labkey.api.exp.PropertyDescriptor; -import org.labkey.api.exp.property.Domain; -import org.labkey.api.exp.property.DomainProperty; -import org.labkey.api.security.User; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.specimen.importer.EventVialRollup; -import org.labkey.specimen.importer.ImportTypes; -import org.labkey.api.specimen.importer.RollupHelper; -import org.labkey.api.specimen.importer.RollupHelper.RollupMap; -import org.labkey.specimen.importer.SpecimenColumn; -import org.labkey.specimen.importer.TargetTable; -import org.labkey.api.specimen.model.SpecimenTablesProvider; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -// Shared code used by both specimen writers and importers. Combine with SpecimenTablesProvider? -public class SpecimenTableManager -{ - // Event -> Vial Rollup map. Note: This is initialized at construction time in determineSpecimenColumns(). - private final RollupMap _eventToVialRollups = new RollupMap<>(); - private final Container _container; - private final User _user; - - // Provisioned specimen tables - private final TableInfo _tableInfoSpecimen; - private final TableInfo _tableInfoVial; - private final TableInfo _tableInfoSpecimenEvent; - private final TableInfo _tableInfoLocation; - private final TableInfo _tableInfoPrimaryType; - private final TableInfo _tableInfoDerivative; - private final TableInfo _tableInfoAdditive; - private final SqlDialect _dialect; - - public SpecimenTableManager(Container container, User user) - { - _container = container; - _user = user; - - _tableInfoSpecimenEvent = SpecimenSchema.get().getTableInfoSpecimenEvent(_container); - _tableInfoVial = SpecimenSchema.get().getTableInfoVial(_container); - _tableInfoSpecimen = SpecimenSchema.get().getTableInfoSpecimen(_container); - _tableInfoPrimaryType = SpecimenSchema.get().getTableInfoSpecimenPrimaryType(_container); - _tableInfoLocation = SpecimenSchema.get().getTableInfoLocation(_container); - _tableInfoDerivative = SpecimenSchema.get().getTableInfoSpecimenDerivative(_container); - _tableInfoAdditive = SpecimenSchema.get().getTableInfoSpecimenAdditive(_container); - _dialect = _tableInfoSpecimen.getSqlDialect(); - - _specimenColumns = determineSpecimenColumns(); - } - - protected SqlDialect getSqlDialect() - { - return _dialect; - } - - protected TableInfo getTableInfoSpecimen() - { - return _tableInfoSpecimen; - } - - protected TableInfo getTableInfoVial() - { - return _tableInfoVial; - } - - protected TableInfo getTableInfoSpecimenEvent() - { - return _tableInfoSpecimenEvent; - } - - protected TableInfo getTableInfoPrimaryType() - { - return _tableInfoPrimaryType; - } - protected TableInfo getTableInfoLocation() - { - return _tableInfoLocation; - } - - protected TableInfo getTableInfoDerivative() - { - return _tableInfoDerivative; - } - - protected TableInfo getTableInfoAdditive() - { - return _tableInfoAdditive; - } - - public TableInfo getTableInfoFromFkTableName(String fkTableName) - { - if ("Site".equalsIgnoreCase(fkTableName)) - return getTableInfoLocation(); - if ("SpecimenPrimaryType".equalsIgnoreCase(fkTableName)) - return getTableInfoPrimaryType(); - if ("SpecimenDerivative".equalsIgnoreCase(fkTableName)) - return getTableInfoDerivative(); - if ("SpecimenAdditive".equalsIgnoreCase(fkTableName)) - return getTableInfoAdditive(); - throw new IllegalStateException("Unexpected table name."); - } - - protected Container getContainer() - { - return _container; - } - - protected User getUser() - { - return _user; - } - - protected final Collection _specimenColumns; - - public Collection getSpecimenColumns() - { - return _specimenColumns; - } - - protected RollupMap getEventToVialRollups() - { - return _eventToVialRollups; - } - - private Collection determineSpecimenColumns() - { - Collection specimenColumns = new ArrayList<>(SpecimenColumns.BASE_SPECIMEN_COLUMNS); - - SpecimenTablesProvider specimenTablesProvider = new SpecimenTablesProvider(_container, _user, null); - - Domain vialDomain = specimenTablesProvider.getDomain("Vial", true, false); - if (null == vialDomain) - throw new IllegalStateException("Expected Vial domain to already be created."); - - List vialProperties = new ArrayList<>(); - for (DomainProperty domainProperty : vialDomain.getNonBaseProperties()) - vialProperties.add(domainProperty.getPropertyDescriptor()); - - Domain specimenEventDomain = specimenTablesProvider.getDomain("SpecimenEvent", true, false); - if (null == specimenEventDomain) - throw new IllegalStateException("Expected SpecimenEvent domain to already be created."); - - SqlDialect dialect = getTableInfoSpecimen().getSqlDialect(); - - Set eventBaseProperties = specimenEventDomain.getBaseProperties(); - for (DomainProperty domainProperty : specimenEventDomain.getProperties()) - { - PropertyDescriptor property = domainProperty.getPropertyDescriptor(); - if (!eventBaseProperties.contains(domainProperty)) - { - SpecimenColumn specimenColumn = new SpecimenColumn(property.getName().toLowerCase(), property.getImportAliasSet(), property.getStorageColumnName(), getTypeName(property, dialect), TargetTable.SPECIMEN_EVENTS); - specimenColumns.add(specimenColumn); - } - RollupHelper.findRollups(_eventToVialRollups, property, vialProperties, RollupHelper.getEventVialRollups(), false); - } - - return specimenColumns; - } - - private static final Map JDBCtoIMPORTER_TYPE = new HashMap<>(); - static - { - JDBCtoIMPORTER_TYPE.put(JdbcType.DATE, ImportTypes.DATETIME_TYPE); - JDBCtoIMPORTER_TYPE.put(JdbcType.TIMESTAMP, ImportTypes.DATETIME_TYPE); - JDBCtoIMPORTER_TYPE.put(JdbcType.TIME, ImportTypes.DURATION_TYPE); - JDBCtoIMPORTER_TYPE.put(JdbcType.DECIMAL, ImportTypes.NUMERIC_TYPE); - JDBCtoIMPORTER_TYPE.put(JdbcType.BOOLEAN, ImportTypes.BOOLEAN_TYPE); - JDBCtoIMPORTER_TYPE.put(JdbcType.BINARY, ImportTypes.BINARY_TYPE); - JDBCtoIMPORTER_TYPE.put(JdbcType.BIGINT, "BIGINT"); - JDBCtoIMPORTER_TYPE.put(JdbcType.INTEGER, "INT"); - JDBCtoIMPORTER_TYPE.put(JdbcType.REAL, "FLOAT"); - JDBCtoIMPORTER_TYPE.put(JdbcType.DOUBLE, null); - JDBCtoIMPORTER_TYPE.put(JdbcType.VARCHAR, "VARCHAR"); - } - - private String getTypeName(PropertyDescriptor property, SqlDialect dialect) - { - String typeName = JDBCtoIMPORTER_TYPE.get(property.getJdbcType()); - if (null == typeName) - typeName = dialect.getSqlTypeName(property.getJdbcType()); - if (null == typeName) - throw new UnsupportedOperationException("Unsupported JdbcType: " + property.getJdbcType()); - if ("VARCHAR".equals(typeName)) - typeName = String.format("VARCHAR(%d)", property.getScale()); - return typeName; - } -} diff --git a/specimen/src/org/labkey/specimen/actions/ButtonBarLineBreak.java b/specimen/src/org/labkey/specimen/actions/ButtonBarLineBreak.java deleted file mode 100644 index d632f4761e4..00000000000 --- a/specimen/src/org/labkey/specimen/actions/ButtonBarLineBreak.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2008-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.actions; - -import org.labkey.api.data.RenderContext; -import org.labkey.api.util.HtmlString; -import org.labkey.api.view.DisplayElement; -import org.labkey.api.writer.HtmlWriter; - -public class ButtonBarLineBreak extends DisplayElement -{ - @Override - public void render(RenderContext ctx, HtmlWriter out) - { - out.write(HtmlString.BR); - } -} diff --git a/specimen/src/org/labkey/specimen/actions/HiddenFormInputGenerator.java b/specimen/src/org/labkey/specimen/actions/HiddenFormInputGenerator.java deleted file mode 100644 index 884c773c9cb..00000000000 --- a/specimen/src/org/labkey/specimen/actions/HiddenFormInputGenerator.java +++ /dev/null @@ -1,23 +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.actions; - -import org.labkey.api.view.ViewContext; - -public interface HiddenFormInputGenerator -{ - String getHiddenFormInputs(ViewContext ctx); -} diff --git a/specimen/src/org/labkey/specimen/actions/IdForm.java b/specimen/src/org/labkey/specimen/actions/IdForm.java deleted file mode 100644 index 23ca74e7bf3..00000000000 --- a/specimen/src/org/labkey/specimen/actions/IdForm.java +++ /dev/null @@ -1,47 +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.actions; - -import org.labkey.api.action.ReturnUrlForm; - -public class IdForm extends ReturnUrlForm -{ - public enum PARAMS - { - id - } - - private int _id; - - public IdForm() - { - } - - public IdForm(int id) - { - _id = id; - } - - public int getId() - { - return _id; - } - - public void setId(int id) - { - _id = id; - } -} diff --git a/specimen/src/org/labkey/specimen/actions/IdTypes.java b/specimen/src/org/labkey/specimen/actions/IdTypes.java deleted file mode 100644 index 2999b8724d0..00000000000 --- a/specimen/src/org/labkey/specimen/actions/IdTypes.java +++ /dev/null @@ -1,23 +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.actions; - -public enum IdTypes -{ - GlobalUniqueId, - SpecimenHash, - RowId -} diff --git a/specimen/src/org/labkey/specimen/actions/ManageReqsBean.java b/specimen/src/org/labkey/specimen/actions/ManageReqsBean.java deleted file mode 100644 index fea7242f405..00000000000 --- a/specimen/src/org/labkey/specimen/actions/ManageReqsBean.java +++ /dev/null @@ -1,73 +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.actions; - -import org.labkey.api.data.Container; -import org.labkey.api.security.User; -import org.labkey.specimen.model.SpecimenRequestActor; -import org.labkey.specimen.requirements.RequirementProvider; -import org.labkey.specimen.requirements.SpecimenRequestRequirement; -import org.labkey.specimen.requirements.SpecimenRequestRequirementProvider; -import org.labkey.specimen.requirements.SpecimenRequestRequirementType; - -public class ManageReqsBean -{ - private final SpecimenRequestRequirement[] _providerRequirements; - private final SpecimenRequestRequirement[] _receiverRequirements; - private final SpecimenRequestRequirement[] _generalRequirements; - private final SpecimenRequestRequirement[] _originatorRequirements; - private final SpecimenRequestActor[] _actors; - - public ManageReqsBean(User user, Container container) - { - RequirementProvider provider = - SpecimenRequestRequirementProvider.get(); - _originatorRequirements = provider.getDefaultRequirements(container, - SpecimenRequestRequirementType.ORIGINATING_SITE); - _providerRequirements = provider.getDefaultRequirements(container, - SpecimenRequestRequirementType.PROVIDING_SITE); - _receiverRequirements = provider.getDefaultRequirements(container, - SpecimenRequestRequirementType.RECEIVING_SITE); - _generalRequirements = provider.getDefaultRequirements(container, - SpecimenRequestRequirementType.NON_SITE_BASED); - _actors = provider.getActors(container); - } - - public SpecimenRequestRequirement[] getGeneralRequirements() - { - return _generalRequirements; - } - - public SpecimenRequestRequirement[] getReceiverRequirements() - { - return _receiverRequirements; - } - - public SpecimenRequestRequirement[] getProviderRequirements() - { - return _providerRequirements; - } - - public SpecimenRequestRequirement[] getOriginatorRequirements() - { - return _originatorRequirements; - } - - public SpecimenRequestActor[] getActors() - { - return _actors; - } -} diff --git a/specimen/src/org/labkey/specimen/actions/ManageRequestBean.java b/specimen/src/org/labkey/specimen/actions/ManageRequestBean.java deleted file mode 100644 index 8be85093518..00000000000 --- a/specimen/src/org/labkey/specimen/actions/ManageRequestBean.java +++ /dev/null @@ -1,273 +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.actions; - -import org.labkey.api.collections.IntHashMap; -import org.labkey.api.data.ActionButton; -import org.labkey.api.data.Container; -import org.labkey.api.data.MenuButton; -import org.labkey.api.specimen.Vial; -import org.labkey.api.specimen.location.LocationImpl; -import org.labkey.api.specimen.location.LocationManager; -import org.labkey.api.study.Location; -import org.labkey.api.util.ButtonBuilder; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.DisplayElement; -import org.labkey.api.view.ViewContext; -import org.labkey.specimen.SpecimenRequestManager; -import org.labkey.specimen.SpecimenRequestStatus; -import org.labkey.specimen.actions.SpecimenController.ImportVialIdsAction; -import org.labkey.specimen.actions.SpecimenController.OverviewAction; -import org.labkey.specimen.actions.SpecimenController.RemoveRequestSpecimensAction; -import org.labkey.specimen.model.SpecimenRequestActor; -import org.labkey.specimen.notifications.ActorNotificationRecipientSet; -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 java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class ManageRequestBean extends SpecimensViewBean -{ - protected SpecimenRequest _specimenRequest; - protected boolean _requestManager; - protected boolean _requirementsComplete; - protected boolean _finalState; - private List _possibleNotifications; - protected List _missingSpecimens; - private final Boolean _submissionResult; - private Location[] _providingLocations; - private String _returnUrl; - private final Container _container; - - public ManageRequestBean(ViewContext context, SpecimenRequest specimenRequest, boolean forExport, Boolean submissionResult, String returnUrl) - { - super(context, specimenRequest.getVials(), !forExport, !forExport, forExport, false); - _container = context.getContainer(); - _submissionResult = submissionResult; - _requestManager = context.getContainer().hasPermission(context.getUser(), ManageRequestsPermission.class); - _specimenRequest = specimenRequest; - _finalState = SpecimenRequestManager.get().isInFinalState(_specimenRequest); - _requirementsComplete = true; - _missingSpecimens = SpecimenRequestManager.get().getMissingSpecimens(_specimenRequest); - _returnUrl = returnUrl; - SpecimenRequestRequirement[] requirements = specimenRequest.getRequirements(); - for (int i = 0; i < requirements.length && _requirementsComplete; i++) - { - SpecimenRequestRequirement requirement = requirements[i]; - _requirementsComplete = requirement.isComplete(); - } - - if (_specimenQueryView != null) - { - List buttons = new ArrayList<>(); - - MenuButton exportMenuButton = new MenuButton("Export"); - ActionURL exportExcelURL = context.getActionURL().clone().addParameter("export", "excel"); - ActionURL exportTextURL = context.getActionURL().clone().addParameter("export", "tsv"); - exportMenuButton.addMenuItem("Export all to Excel (.xls)", exportExcelURL); - exportMenuButton.addMenuItem("Export all to text file (.tsv)", exportTextURL); - buttons.add(exportMenuButton); - _specimenQueryView.setShowExportButtons(false); - _specimenQueryView.getSettings().setAllowChooseView(false); - - if (SpecimenRequestManager.get().hasEditRequestPermissions(context.getUser(), specimenRequest) && !_finalState) - { - ActionButton addButton = new ActionButton(new ActionURL(OverviewAction.class, _container), "Specimen Search"); - ActionButton deleteButton = new ActionButton(RemoveRequestSpecimensAction.class, "Remove Selected"); - _specimenQueryView.addHiddenFormField("id", "" + specimenRequest.getRowId()); - buttons.add(addButton); - - ActionURL importActionURL = new ActionURL(ImportVialIdsAction.class, _container); - importActionURL.addParameter("id", specimenRequest.getRowId()); - ButtonBuilder.Button importButton = new ButtonBuilder("Upload Specimen Ids") - .href(importActionURL) - .submit(false) - .build(); - buttons.add(importButton); - buttons.add(deleteButton); - } - _specimenQueryView.setButtons(buttons); - } - } - - public List getPossibleNotifications(SpecimenRequest specimenRequest) - { - List possibleNotifications = new ArrayList<>(); - // allow notification of all parties listed in the request requirements: - for (SpecimenRequestRequirement requirement : specimenRequest.getRequirements()) - addIfNotPresent(requirement.getActor(), requirement.getLocation(), possibleNotifications); - - // allow notification of all site-based actors at the destination site, and all study-wide actors: - Map relevantSites = new IntHashMap<>(); - if (specimenRequest.getDestinationSiteId() == null) - { - throw new IllegalStateException("Request " + specimenRequest.getRowId() + " in folder " + - specimenRequest.getContainer().getPath() + " does not have a valid destination site id."); - } - LocationImpl destLocation = LocationManager.get().getLocation(specimenRequest.getContainer(), specimenRequest.getDestinationSiteId().intValue()); - relevantSites.put(destLocation.getRowId(), destLocation); - for (Vial vial : specimenRequest.getVials()) - { - LocationImpl location = LocationManager.get().getCurrentLocation(vial); - if (location != null && !relevantSites.containsKey(location.getRowId())) - relevantSites.put(location.getRowId(), location); - } - - SpecimenRequestActor[] allActors = SpecimenRequestRequirementProvider.get().getActors(specimenRequest.getContainer()); - // add study-wide actors and actors from all relevant sites: - for (SpecimenRequestActor actor : allActors) - { - if (actor.isPerSite()) - { - for (LocationImpl location : relevantSites.values()) - { - if (actor.isPerSite()) - addIfNotPresent(actor, location, possibleNotifications); - } - } - else - addIfNotPresent(actor, null, possibleNotifications); - } - - possibleNotifications.sort((first, second) -> - { - String firstSite = first.getLocation() != null ? first.getLocation().getLabel() : ""; - String secondSite = second.getLocation() != null ? second.getLocation().getLabel() : ""; - int comp = firstSite.compareToIgnoreCase(secondSite); - if (comp == 0) - { - String firstActorLabel = first.getActor().getLabel(); - if (firstActorLabel == null) - firstActorLabel = ""; - String secondActorLabel = second.getActor().getLabel(); - if (secondActorLabel == null) - secondActorLabel = ""; - comp = firstActorLabel.compareToIgnoreCase(secondActorLabel); - } - return comp; - }); - return possibleNotifications; - } - - public synchronized List getPossibleNotifications() - { - if (_possibleNotifications == null) - _possibleNotifications = getPossibleNotifications(_specimenRequest); - return _possibleNotifications; - } - - public SpecimenRequest getSpecimenRequest() - { - return _specimenRequest; - } - - public boolean hasMissingSpecimens() - { - return _missingSpecimens != null && !_missingSpecimens.isEmpty(); - } - - public List getMissingSpecimens() - { - return _missingSpecimens; - } - - public SpecimenRequestStatus getStatus() - { - return SpecimenRequestManager.get().getRequestStatus(_specimenRequest.getContainer(), _specimenRequest.getStatusId()); - } - - public Location getDestinationSite() - { - Integer destinationSiteId = _specimenRequest.getDestinationSiteId(); - if (destinationSiteId != null) - { - return LocationManager.get().getLocation(_specimenRequest.getContainer(), destinationSiteId.intValue()); - } - return null; - } - - public boolean isRequestManager() - { - return _requestManager; - } - - public boolean isFinalState() - { - return _finalState; - } - - public boolean isRequirementsComplete() - { - return _requirementsComplete; - } - - public Boolean isSuccessfulSubmission() - { - return _submissionResult != null && _submissionResult.booleanValue(); - } - - public Location[] getProvidingLocations() - { - if (_providingLocations == null) - { - Set locationSet = new HashSet<>(); - for (Vial vial : _vials) - { - Integer locationId = vial.getCurrentLocation(); - if (locationId != null) - locationSet.add(locationId); - } - _providingLocations = new Location[locationSet.size()]; - int i = 0; - for (Integer locationId : locationSet) - _providingLocations[i++] = LocationManager.get().getLocation(_container, locationId); - } - return _providingLocations; - } - - public String getReturnUrl() - { - return _returnUrl; - } - - public void setReturnUrl(String returnUrl) - { - _returnUrl = returnUrl; - } - - private boolean addIfNotPresent(SpecimenRequestActor actor, LocationImpl location, List list) - { - for (ActorNotificationRecipientSet actorSite : list) - { - if (actorSite.getActor().getRowId() == actor.getRowId()) - { - if (actorSite.getLocation() == null && location == null) - return false; - else - if (actorSite.getLocation() != null && location != null && actorSite.getLocation().getRowId() == location.getRowId()) - return false; - } - } - list.add(new ActorNotificationRecipientSet(actor, location)); - return true; - } -} diff --git a/specimen/src/org/labkey/specimen/actions/ManageRequestInputsBean.java b/specimen/src/org/labkey/specimen/actions/ManageRequestInputsBean.java deleted file mode 100644 index ce8ba5d3484..00000000000 --- a/specimen/src/org/labkey/specimen/actions/ManageRequestInputsBean.java +++ /dev/null @@ -1,51 +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.actions; - -import org.labkey.api.data.Container; -import org.labkey.api.view.ViewContext; -import org.labkey.specimen.SpecimenRequestManager; - -import java.sql.SQLException; - -public class ManageRequestInputsBean -{ - private final SpecimenRequestManager.SpecimenRequestInput[] _inputs; - private final Container _container; - private final String _contextPath; - - public ManageRequestInputsBean(ViewContext context) throws SQLException - { - _container = context.getContainer(); - _inputs = SpecimenRequestManager.get().getNewSpecimenRequestInputs(_container); - _contextPath = context.getContextPath(); - } - - public SpecimenRequestManager.SpecimenRequestInput[] getInputs() - { - return _inputs; - } - - public Container getContainer() - { - return _container; - } - - public String getContextPath() - { - return _contextPath; - } -} diff --git a/specimen/src/org/labkey/specimen/actions/ParticipantCommentAction.java b/specimen/src/org/labkey/specimen/actions/ParticipantCommentAction.java deleted file mode 100644 index e259eebdfe6..00000000000 --- a/specimen/src/org/labkey/specimen/actions/ParticipantCommentAction.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2009-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.actions; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Strings; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.Container; -import org.labkey.api.query.QueryUpdateForm; -import org.labkey.api.security.RequiresPermission; -import org.labkey.api.security.User; -import org.labkey.api.security.permissions.InsertPermission; -import org.labkey.api.security.permissions.ReadPermission; -import org.labkey.api.specimen.Vial; -import org.labkey.api.specimen.model.SpecimenComment; -import org.labkey.api.study.InsertUpdateAction; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.view.DataView; -import org.labkey.api.view.InsertView; -import org.labkey.api.view.NavTree; -import org.labkey.api.view.UpdateView; -import org.labkey.specimen.SpecimenManager; -import org.springframework.validation.BindException; -import org.springframework.web.servlet.ModelAndView; - -import java.util.Map; - -/** - * User: klum - * Date: Oct 7, 2009 - */ -@RequiresPermission(ReadPermission.class) -public abstract class ParticipantCommentAction extends InsertUpdateAction -{ - public ParticipantCommentAction() - { - super(ParticipantCommentForm.class); - } - - @Override - protected void addExtraNavTrail(NavTree root) - { - } - - @Override - public boolean handlePost(ParticipantCommentForm form, BindException errors) throws Exception - { - if (super.handlePost(form, errors)) - { - // clear any vial comments specified - User user = getUser(); - Container container = getContainer(); - for (int rowId : form.getVialCommentsToClear()) - { - Vial vial = SpecimenManager.get().getVial(container, user, rowId); - if (vial != null) - { - SpecimenComment comment = SpecimenManager.get().getSpecimenCommentForVial(vial); - if (comment != null) - { - SpecimenManager.get().setSpecimenComment(user, vial, null, - comment.isQualityControlFlag(), comment.isQualityControlFlagForced()); - } - } - } - return true; - } - return false; - } - - @RequiresPermission(ReadPermission.class) - public static class SpecimenCommentInsertAction extends ParticipantCommentAction - { - private DataView _dataView; - - @Override - public ModelAndView getView(final ParticipantCommentForm form, boolean reshow, BindException errors) throws Exception - { - // super.getView() checks permissions - ModelAndView view = super.getView(form, reshow, errors); - if (!reshow && _dataView instanceof InsertView) - { - ((InsertView)_dataView).setInitialValue(StudyService.get().getSubjectColumnName(getContainer()), form.getParticipantId()); - ((InsertView)_dataView).setInitialValue("SequenceNum", form.getVisitId()); - - if (!StringUtils.isBlank(form.getComment())) - { - Study study = getStudy(); - String commentProperty = form.getVisitId() != 0 ? study.getParticipantVisitCommentProperty() : - study.getParticipantCommentProperty(); - - ((InsertView)_dataView).setInitialValue(ColumnInfo.legalNameFromName(commentProperty), form.getComment()); - } - for (int rowId : form.getVialCommentsToClear()) - _dataView.getDataRegion().addHiddenFormField(ParticipantCommentForm.params.vialCommentsToClear, String.valueOf(rowId)); - } - return view; - } - - @Override - protected DataView createNewView(ParticipantCommentForm form, QueryUpdateForm updateForm, BindException errors) - { - _dataView = super.createNewView(form, updateForm, errors); - return _dataView; - } - - @Override - protected boolean isInsert() - { - return true; - } - } - - @RequiresPermission(InsertPermission.class) - public static class SpecimenCommentUpdateAction extends ParticipantCommentAction - { - @Override - protected DataView createNewView(final ParticipantCommentForm form, QueryUpdateForm updateForm, BindException errors) - { - try - { - if (!StringUtils.isBlank(form.getComment())) - { - updateForm.refreshFromDb(); - - Study study = getStudy(); - String commentProperty = form.getVisitId() != 0 ? study.getParticipantVisitCommentProperty() : - study.getParticipantCommentProperty(); - commentProperty = ColumnInfo.legalNameFromName(commentProperty); - - Object values = updateForm.getOldValues(); - if (values instanceof Map valueMap) - { - String oldComment = String.valueOf(valueMap.get(commentProperty)); - String newComment = form.getComment(); - - if (!StringUtils.isBlank(oldComment) && !Strings.CS.equals(oldComment, form.getComment())) - newComment = oldComment + " : " + form.getComment(); - valueMap.put(commentProperty, newComment); - } - } - DataView view = new UpdateView(updateForm, errors); - - for (int rowId : form.getVialCommentsToClear()) - view.getDataRegion().addHiddenFormField(ParticipantCommentForm.params.vialCommentsToClear, String.valueOf(rowId)); - - return view; - } - catch (Exception e) - { - throw new RuntimeException(e); - } - } - - @Override - protected boolean isInsert() - { - return false; - } - } -} diff --git a/specimen/src/org/labkey/specimen/actions/ParticipantCommentForm.java b/specimen/src/org/labkey/specimen/actions/ParticipantCommentForm.java deleted file mode 100644 index 0d88d6f256e..00000000000 --- a/specimen/src/org/labkey/specimen/actions/ParticipantCommentForm.java +++ /dev/null @@ -1,89 +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.actions; - -import org.labkey.api.study.EditDatasetRowForm; - -public class ParticipantCommentForm extends EditDatasetRowForm -{ - private String _participantId; - private int _visitId; - private String _comment; - private String _oldComment; - private int[] _vialCommentsToClear = new int[0]; - - public enum params - { - participantId, - visitId, - comment, - oldComment, - vialCommentsToClear, - lsid, - datasetId, - returnUrl, - } - - public String getParticipantId() - { - return _participantId; - } - - public void setParticipantId(String participantId) - { - _participantId = participantId; - } - - public int getVisitId() - { - return _visitId; - } - - public void setVisitId(int visitId) - { - _visitId = visitId; - } - - public String getComment() - { - return _comment; - } - - public void setComment(String comment) - { - _comment = comment; - } - - public String getOldComment() - { - return _oldComment; - } - - public void setOldComment(String oldComment) - { - _oldComment = oldComment; - } - - public int[] getVialCommentsToClear() - { - return _vialCommentsToClear; - } - - public void setVialCommentsToClear(int[] vialsCommentsToClear) - { - _vialCommentsToClear = vialsCommentsToClear; - } -} diff --git a/specimen/src/org/labkey/specimen/actions/ReportConfigurationBean.java b/specimen/src/org/labkey/specimen/actions/ReportConfigurationBean.java deleted file mode 100644 index b943deae3fc..00000000000 --- a/specimen/src/org/labkey/specimen/actions/ReportConfigurationBean.java +++ /dev/null @@ -1,137 +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.actions; - -import org.labkey.api.query.CustomView; -import org.labkey.api.query.QueryDefinition; -import org.labkey.api.query.QueryService; -import org.labkey.api.query.SchemaKey; -import org.labkey.api.query.UserSchema; -import org.labkey.api.specimen.SpecimenQuerySchema; -import org.labkey.specimen.report.SpecimenVisitReportParameters; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.view.ViewContext; -import org.labkey.specimen.report.participant.ParticipantSiteReportFactory; -import org.labkey.specimen.report.participant.ParticipantSummaryReportFactory; -import org.labkey.specimen.report.participant.ParticipantTypeReportFactory; -import org.labkey.specimen.report.request.RequestEnrollmentSiteReportFactory; -import org.labkey.specimen.report.request.RequestLocationReportFactory; -import org.labkey.specimen.report.request.RequestParticipantReportFactory; -import org.labkey.specimen.report.request.RequestReportFactory; -import org.labkey.specimen.report.specimentype.TypeCohortReportFactory; -import org.labkey.specimen.report.specimentype.TypeParticipantReportFactory; -import org.labkey.specimen.report.specimentype.TypeSummaryReportFactory; -import org.labkey.specimen.settings.SettingsManager; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class ReportConfigurationBean -{ - private static final String COUNTS_BY_DERIVATIVE_TYPE_TITLE = "Collected Vials by Type and Timepoint"; - private static final String REQUESTS_BY_DERIVATIVE_TYPE_TITLE = "Requested Vials by Type and Timepoint"; - - private final Map> _reportFactories = new LinkedHashMap<>(); - private final int _uniqueId; - private final ViewContext _viewContext; - - private boolean _listView = true; - private boolean _hasReports = true; - - public ReportConfigurationBean(ViewContext viewContext) - { - Study study = StudyService.get().getStudy(viewContext.getContainer()); - _viewContext = viewContext; - registerReportFactory(COUNTS_BY_DERIVATIVE_TYPE_TITLE, new TypeSummaryReportFactory()); - registerReportFactory(COUNTS_BY_DERIVATIVE_TYPE_TITLE, new TypeParticipantReportFactory()); - if (StudyService.get().showCohorts(_viewContext.getContainer(), _viewContext.getUser())) - registerReportFactory(COUNTS_BY_DERIVATIVE_TYPE_TITLE, new TypeCohortReportFactory()); - if (study != null) - { - boolean enableSpecimenRequest = SettingsManager.get().getRepositorySettings(study.getContainer()).isEnableRequests(); - if (!study.isSnapshotStudy() && enableSpecimenRequest) - { - registerReportFactory(REQUESTS_BY_DERIVATIVE_TYPE_TITLE, new RequestReportFactory()); - registerReportFactory(REQUESTS_BY_DERIVATIVE_TYPE_TITLE, new RequestLocationReportFactory()); - registerReportFactory(REQUESTS_BY_DERIVATIVE_TYPE_TITLE, new RequestEnrollmentSiteReportFactory()); - registerReportFactory(REQUESTS_BY_DERIVATIVE_TYPE_TITLE, new RequestParticipantReportFactory()); - } - String subjectNoun = StudyService.get().getSubjectNounSingular(viewContext.getContainer()); - registerReportFactory("Collected Vials by " + subjectNoun + " by Timepoint", new ParticipantSummaryReportFactory()); - registerReportFactory("Collected Vials by " + subjectNoun + " by Timepoint", new ParticipantTypeReportFactory()); - registerReportFactory("Collected Vials by " + subjectNoun + " by Timepoint", new ParticipantSiteReportFactory()); - } - - _uniqueId = 0; - } - - // TODO: listView parameter is always false... remove? - public ReportConfigurationBean(SpecimenVisitReportParameters singleFactory, boolean listView, int uniqueId) - { - _listView = listView; - _viewContext = singleFactory.getViewContext(); - assert (_viewContext != null) : "Expected report factory to be instantiated by Spring."; - registerReportFactory(COUNTS_BY_DERIVATIVE_TYPE_TITLE, singleFactory); - _hasReports = StudyService.get().getStudy(_viewContext.getContainer()) != null && !singleFactory.getReports().isEmpty(); - _uniqueId = uniqueId; - } - - private void registerReportFactory(String category, SpecimenVisitReportParameters factory) - { - // we have to explicitly set the view context for these reports, since the factories aren't being newed-up by Spring in the usual way: - factory.setViewContext(_viewContext); - List factories = _reportFactories.computeIfAbsent(category, _ -> new ArrayList<>()); - - factories.add(factory); - } - - public Set getCategories() - { - return _reportFactories.keySet(); - } - - public List getFactories(String category) - { - return _reportFactories.get(category); - } - - public boolean isListView() - { - return _listView; - } - - public Map getCustomViews(ViewContext context) - { - // 13485 - Use provider to handle NULL study - UserSchema schema = QueryService.get().getUserSchema(context.getUser(), context.getContainer(), SchemaKey.fromParts(SpecimenQuerySchema.SCHEMA_NAME)); - QueryDefinition def = QueryService.get().createQueryDefForTable(schema, "SpecimenDetail"); - return def.getCustomViews(context.getUser(), context.getRequest(), false, false); - } - - public boolean hasReports() - { - return _hasReports; - } - - public int getUniqueId() - { - return _uniqueId; - } -} diff --git a/specimen/src/org/labkey/specimen/actions/SelectSpecimenProviderBean.java b/specimen/src/org/labkey/specimen/actions/SelectSpecimenProviderBean.java deleted file mode 100644 index 1e9a03005e9..00000000000 --- a/specimen/src/org/labkey/specimen/actions/SelectSpecimenProviderBean.java +++ /dev/null @@ -1,48 +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.actions; - -import org.labkey.api.specimen.location.LocationImpl; -import org.labkey.api.view.ActionURL; - -public class SelectSpecimenProviderBean -{ - private final HiddenFormInputGenerator _sourceForm; - private final LocationImpl[] _possibleLocations; - private final ActionURL _formTarget; - - public SelectSpecimenProviderBean(HiddenFormInputGenerator sourceForm, LocationImpl[] possibleLocations, ActionURL formTarget) - { - _sourceForm = sourceForm; - _possibleLocations = possibleLocations; - _formTarget = formTarget; - } - - public LocationImpl[] getPossibleLocations() - { - return _possibleLocations; - } - - public ActionURL getFormTarget() - { - return _formTarget; - } - - public HiddenFormInputGenerator getSourceForm() - { - return _sourceForm; - } -} diff --git a/specimen/src/org/labkey/specimen/actions/ShowGroupMembersAction.java b/specimen/src/org/labkey/specimen/actions/ShowGroupMembersAction.java deleted file mode 100644 index 4bc022f73ba..00000000000 --- a/specimen/src/org/labkey/specimen/actions/ShowGroupMembersAction.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (c) 2009-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.actions; - -import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.Nullable; -import org.labkey.api.action.FormViewAction; -import org.labkey.api.action.SpringActionController; -import org.labkey.api.data.Container; -import org.labkey.api.security.RequiresPermission; -import org.labkey.api.security.SecurityManager; -import org.labkey.api.security.SecurityUrls; -import org.labkey.api.security.User; -import org.labkey.api.security.UserManager; -import org.labkey.api.security.ValidEmail; -import org.labkey.api.security.permissions.UserManagementPermission; -import org.labkey.api.specimen.location.LocationImpl; -import org.labkey.api.specimen.location.LocationManager; -import org.labkey.api.study.StudyUrls; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.JspView; -import org.labkey.api.view.NavTree; -import org.labkey.api.view.NotFoundException; -import org.labkey.specimen.actions.SpecimenController.ManageActorsAction; -import org.labkey.specimen.model.SpecimenRequestActor; -import org.labkey.specimen.requirements.SpecimenRequestRequirementProvider; -import org.labkey.specimen.security.permissions.ManageSpecimenActorsPermission; -import org.springframework.validation.BindException; -import org.springframework.validation.Errors; -import org.springframework.web.servlet.ModelAndView; - -import jakarta.servlet.ServletException; -import java.util.ArrayList; -import java.util.List; - -@RequiresPermission(ManageSpecimenActorsPermission.class) -public class ShowGroupMembersAction extends FormViewAction -{ - private SpecimenRequestActor _actor; - private LocationImpl _location; - - public static ActionURL getShowGroupMembersURL(Container c, int rowId, @Nullable Integer locationId, @Nullable ActionURL returnUrl) - { - ActionURL url = new ActionURL(ShowGroupMembersAction.class, c); - url.addParameter("id", Integer.toString(rowId)); - if (locationId != null) - url.addParameter("locationId", locationId); - if (returnUrl != null) - url.addReturnUrl(returnUrl); - - return url; - } - - @Override - public void validateCommand(UpdateGroupForm target, Errors errors) - { - } - - @Override - public ModelAndView getView(UpdateGroupForm form, boolean reshow, BindException errors) - { - SpecimenRequestActor actor = getActor(form); - LocationImpl location = getLocation(form); - - if (actor == null) - throw new NotFoundException(); - - User[] members = actor.getMembers(location); - - return new JspView<>("/org/labkey/specimen/view/groupMembers.jsp", - new GroupMembersBean(actor, location, members, form.getReturnActionURL()), errors); - } - - @Override - public boolean handlePost(UpdateGroupForm form, BindException errors) throws Exception - { - String[] emailsToDelete = form.getDelete(); - SpecimenRequestActor actor = getActor(form); - LocationImpl location = getLocation(form); - - // getActor is container-scoped; null means the actorId doesn't belong to this folder - if (actor == null) - throw new NotFoundException(); - - if (emailsToDelete != null && emailsToDelete.length > 0) - { - List invalidEmails = new ArrayList<>(); - List emails = SecurityManager.normalizeEmails(emailsToDelete, invalidEmails); - - if (invalidEmails.isEmpty()) - { - User[] users = new User[emails.size()]; - int i = 0; - for (ValidEmail email : emails) - { - users[i++] = UserManager.getUser(email); - assert users[i - 1] != null : email.getEmailAddress() + " is not associated with a user account."; - } - actor.removeMembers(location, users); - } - else - throw new ServletException("Invalid email address" + (invalidEmails.size() > 1 ? "es: " : ": ") + StringUtils.join(invalidEmails.toArray())); - } - - if (form.getNames() != null) - { - String[] names = form.getNames().split("\n"); - List invalidEmails = new ArrayList<>(); - List emails = SecurityManager.normalizeEmails(names, invalidEmails); - - for (String rawEmail : invalidEmails) - { - // Ignore lines of all whitespace, otherwise show an error. - if (!rawEmail.trim().isEmpty()) - { - errors.reject(SpringActionController.ERROR_MSG, "Could not add user " + rawEmail.trim() + ": Invalid email address"); - } - } - - List newMembers = new ArrayList<>(); - - for (ValidEmail email : emails) - { - if (getUser().hasRootPermission(UserManagementPermission.class)) - { - SecurityManager.addUser(getViewContext(), email, form.isSendEmail(), null); - newMembers.add(UserManager.getUser(email)); - } - else - { - User user = UserManager.getUser(email); - - if (user == null) - { - errors.reject(SpringActionController.ERROR_MSG, email.getEmailAddress() + " is not a registered system user."); - } - else - { - newMembers.add(UserManager.getUser(email)); - } - } - } - - actor.addMembers(location, newMembers.toArray(new User[0])); - } - - return !errors.hasErrors(); - } - - - @Override - public ActionURL getSuccessURL(UpdateGroupForm form) - { - return form.getReturnActionURL(new ActionURL(ManageActorsAction.class, getContainer())); - } - - @Override - public void addNavTrail(NavTree root) - { - PageFlowUtil.urlProvider(StudyUrls.class).addManageStudyNavTrail(root, getContainer(), getUser()); - - if (_location != null) - root.addChild("Manage Actors", new ActionURL(ManageActorsAction.class, getContainer()).addParameter("showMemberSites", _actor.getRowId())); - else - root.addChild("Manage Actors", new ActionURL(ManageActorsAction.class, getContainer())); - - String title = _actor.getLabel(); - if (_location != null) - title += ", " + _location.getLabel(); - - root.addChild(title); - } - - private SpecimenRequestActor getActor(UpdateGroupForm form) - { - if (_actor == null) - _actor = SpecimenRequestRequirementProvider.get().getActor(getContainer(), form.getId()); - - return _actor; - } - - private LocationImpl getLocation(UpdateGroupForm form) - { - if (_location == null && form.getLocationId() != null) - _location = LocationManager.get().getLocation(getContainer(), form.getLocationId()); - - return _location; - } - - public static class UpdateGroupForm extends IdForm - { - private Integer _locationId; - private String[] _delete; - private boolean _sendEmail; - private String _names; - - public Integer getLocationId() - { - return _locationId; - } - - public void setLocationId(Integer locationId) - { - _locationId = locationId; - } - - public String getNames() - { - return _names; - } - - public void setNames(String names) - { - _names = names; - } - - public boolean isSendEmail() - { - return _sendEmail; - } - - public void setSendEmail(boolean sendEmail) - { - _sendEmail = sendEmail; - } - - public String[] getDelete() - { - return _delete; - } - - public void setDelete(String[] delete) - { - _delete = delete; - } - } - - public static class GroupMembersBean - { - private final SpecimenRequestActor _actor; - private final LocationImpl _location; - private final User[] _members; - private final ActionURL _returnUrl; - - public GroupMembersBean(SpecimenRequestActor actor, LocationImpl location, User[] members, ActionURL returnUrl) - { - _actor = actor; - _location = location; - _members = members; - _returnUrl = returnUrl; - } - - public SpecimenRequestActor getActor() - { - return _actor; - } - - public User[] getMembers() - { - return _members; - } - - public LocationImpl getLocation() - { - return _location; - } - - public ActionURL getReturnUrl() - { - return _returnUrl; - } - - public ActionURL getCompleteUsersPrefix() - { - return PageFlowUtil.urlProvider(SecurityUrls.class).getCompleteUserURL(_actor.getContainer()); - } - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/actions/ShowSearchAction.java b/specimen/src/org/labkey/specimen/actions/ShowSearchAction.java deleted file mode 100644 index 70c45412f98..00000000000 --- a/specimen/src/org/labkey/specimen/actions/ShowSearchAction.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (c) 2009-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.actions; - -import org.labkey.api.action.FormViewAction; -import org.labkey.api.data.CompareType; -import org.labkey.api.data.Container; -import org.labkey.api.security.RequiresPermission; -import org.labkey.api.security.permissions.ReadPermission; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.study.StudyUrls; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.JspView; -import org.labkey.api.view.NavTree; -import org.labkey.api.view.NotFoundException; -import org.springframework.validation.BindException; -import org.springframework.validation.Errors; -import org.springframework.web.servlet.ModelAndView; - -@RequiresPermission(ReadPermission.class) -public class ShowSearchAction extends FormViewAction -{ - private Study _study; - private String _title; - - public static ActionURL getShowSearchURL(Container c, boolean showVials) - { - return new ActionURL(ShowSearchAction.class, c).addParameter("showVials", showVials); - } - - @Override - public void validateCommand(SearchForm target, Errors errors) - { - } - - @Override - public ModelAndView getView(SearchForm form, boolean reshow, BindException errors) - { - _study = StudyService.get().getStudy(getContainer()); - if (null == _study) - throw new NotFoundException("No study exists in this folder."); - - _title = (form.isShowVials() ? "Individual Vial" : "Grouped Vial") + " Search"; - - SpecimenSearchBean bean = new SpecimenSearchBean(getViewContext(), form.isShowVials(), false); - bean.setAdvancedExpanded(form.isShowAdvanced()); - bean.init(getViewContext(), form.isShowVials(), true); - bean.setWebPartId(1); - setTitle(_title); - - return new JspView<>("/org/labkey/specimen/view/search.jsp", bean); - } - - @Override - public boolean handlePost(SearchForm form, BindException errors) - { - return true; - } - - @Override - public ActionURL getSuccessURL(SearchForm form) - { - ActionURL url = SpecimenController.getSpecimensURL(getContainer(), form.isShowVials()); - for (ShowSearchAction.SearchForm.SearchParam param : form.getSearchParams()) - { - if (param.getCompareType() != null && !param.getCompareType().isEmpty()) - { - CompareType compare = CompareType.valueOf(param.getCompareType()); - if (compare != null && (!compare.isDataValueRequired() || (param.getValue() != null && !param.getValue().isEmpty()))) - url.addParameter(param.getColumnName() + "~" + compare.getPreferredUrlKey(), param.getValue()); - } - } - return url; - } - - @Override - public void addNavTrail(NavTree root) - { - setHelpTopic("specimenShopping"); - root.addChild(_study.getLabel(), PageFlowUtil.urlProvider(StudyUrls.class).getStudyOverviewURL(getContainer())); - root.addChild("Specimen Overview", new ActionURL(SpecimenController.OverviewAction.class, getContainer())); - root.addChild(_title); - } - - public static class SearchForm extends ShowSearchForm - { - private boolean _showAdvanced = false; - private SearchParam[] _searchParams; - - public static class SearchParam - { - private String _compareType; - private String _value; - private String _columnName; - - public String getCompareType() - { - return _compareType; - } - - public void setCompareType(String compareType) - { - _compareType = compareType; - } - - public String getValue() - { - return _value; - } - - public void setValue(String value) - { - _value = value; - } - - public String getColumnName() - { - return _columnName; - } - - public void setColumnName(String columnName) - { - _columnName = columnName; - } - } - - public SearchForm() - { - _searchParams = new SearchParam[100]; - for (int i = 0; i < 100; i++) - _searchParams[i] = new SearchParam(); - } - - public SearchParam[] getSearchParams() - { - return _searchParams; - } - - public void setSearchParams(SearchParam[] searchParams) - { - _searchParams = searchParams; - } - - public boolean isShowAdvanced() - { - return _showAdvanced; - } - - public void setShowAdvanced(boolean showAdvanced) - { - _showAdvanced = showAdvanced; - } - } - - public static class ShowSearchForm - { - private boolean _showVials; - - public boolean isShowVials() - { - return _showVials; - } - - public void setShowVials(boolean showVials) - { - _showVials = showVials; - } - } -} diff --git a/specimen/src/org/labkey/specimen/actions/ShowUploadSpecimensAction.java b/specimen/src/org/labkey/specimen/actions/ShowUploadSpecimensAction.java deleted file mode 100644 index 109fb030950..00000000000 --- a/specimen/src/org/labkey/specimen/actions/ShowUploadSpecimensAction.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright (c) 2009-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.actions; - -import org.apache.commons.beanutils.ConversionException; -import org.apache.commons.lang3.StringUtils; -import org.labkey.api.action.FormViewAction; -import org.labkey.api.action.SimpleViewAction; -import org.labkey.api.action.SpringActionController; -import org.labkey.api.collections.CaseInsensitiveHashMap; -import org.labkey.api.data.Container; -import org.labkey.api.data.RuntimeSQLException; -import org.labkey.api.pipeline.PipelineUrls; -import org.labkey.api.portal.ProjectUrls; -import org.labkey.api.query.ValidationException; -import org.labkey.api.reader.ColumnDescriptor; -import org.labkey.api.reader.TabLoader; -import org.labkey.api.security.RequiresPermission; -import org.labkey.api.security.User; -import org.labkey.api.security.permissions.AdminPermission; -import org.labkey.api.util.LinkBuilder; -import org.labkey.specimen.importer.SimpleSpecimenImporter; -import org.labkey.specimen.settings.RepositorySettings; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.study.StudyUrls; -import org.labkey.api.study.TimepointType; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.HtmlStringBuilder; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.util.Pair; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.HtmlView; -import org.labkey.api.view.HttpView; -import org.labkey.api.view.JspView; -import org.labkey.api.view.NavTree; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.settings.SettingsManager; -import org.springframework.validation.BindException; -import org.springframework.validation.Errors; -import org.springframework.web.servlet.ModelAndView; - -import java.io.IOException; -import java.sql.BatchUpdateException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -@RequiresPermission(AdminPermission.class) -public class ShowUploadSpecimensAction extends FormViewAction -{ - @Override - public void validateCommand(UploadSpecimensForm form, Errors errors) - { - if (StringUtils.trimToNull(form.getTsv()) == null) - errors.reject(SpringActionController.ERROR_MSG, "Please supply data to upload"); - } - - @Override - public ModelAndView getView(UploadSpecimensForm form, boolean reshow, BindException errors) - { - Container container = getContainer(); - RepositorySettings settings = SettingsManager.get().getRepositorySettings(container); - if (!settings.isSimple()) - return HttpView.redirect(PageFlowUtil. urlProvider(PipelineUrls.class).urlBrowse(container)); - - boolean isEmpty = SpecimenManager.get().isSpecimensEmpty(container, getUser()); - if (isEmpty) - { - form.setNoSpecimens(true); - form.setReplaceOrMerge("replace"); - } - return new JspView<>("/org/labkey/specimen/view/uploadSimpleSpecimens.jsp", form, errors); - } - - @Override - public boolean handlePost(UploadSpecimensForm form, BindException errors) throws Exception - { - Container container = getContainer(); - User user = getUser(); - Study study = StudyService.get().getStudy(getContainer()); - SimpleSpecimenImporter importer = new SimpleSpecimenImporter(container, user, - study != null ? study.getTimepointType() : TimepointType.DATE, - StudyService.get().getSubjectNounSingular(container)); - - TabLoader loader = new TabLoader(form.getTsv(), true); - Map columnAliases = new CaseInsensitiveHashMap<>(); - //Make sure we accept the labels - for (Map.Entry entry : importer.getColumnLabels().entrySet()) - columnAliases.put(entry.getValue(), entry.getKey()); - //And a few more aliases - columnAliases.put(StudyService.get().getSubjectColumnName(container), SimpleSpecimenImporter.PARTICIPANT_ID); - columnAliases.put(StudyService.get().getSubjectNounSingular(container), SimpleSpecimenImporter.PARTICIPANT_ID); - columnAliases.put("ParticipantId", SimpleSpecimenImporter.PARTICIPANT_ID); - columnAliases.put("Participant Id", SimpleSpecimenImporter.PARTICIPANT_ID); - columnAliases.put("Participant", SimpleSpecimenImporter.PARTICIPANT_ID); - columnAliases.put("Subject", SimpleSpecimenImporter.PARTICIPANT_ID); - columnAliases.put("SequenceNum", SimpleSpecimenImporter.VISIT); - columnAliases.put("Sequence Num", SimpleSpecimenImporter.VISIT); - columnAliases.put("Visit", SimpleSpecimenImporter.VISIT); - columnAliases.put("Timepoint Number", SimpleSpecimenImporter.VISIT); - columnAliases.put("specimenNumber", SimpleSpecimenImporter.SAMPLE_ID); - columnAliases.put("specimen Number", SimpleSpecimenImporter.SAMPLE_ID); - columnAliases.put("totalVolume", SimpleSpecimenImporter.VOLUME); - columnAliases.put("total Volume", SimpleSpecimenImporter.VOLUME); - columnAliases.put("volumeUnits", SimpleSpecimenImporter.UNITS); - columnAliases.put("volume Units", SimpleSpecimenImporter.UNITS); - columnAliases.put("primaryType", SimpleSpecimenImporter.PRIMARY_SPECIMEN_TYPE); - columnAliases.put("primary Type", SimpleSpecimenImporter.PRIMARY_SPECIMEN_TYPE); - columnAliases.put("additive", SimpleSpecimenImporter.ADDITIVE_TYPE); - columnAliases.put("additiveType", SimpleSpecimenImporter.ADDITIVE_TYPE); - columnAliases.put("additive Type", SimpleSpecimenImporter.ADDITIVE_TYPE); - columnAliases.put("derivative", SimpleSpecimenImporter.DERIVATIVE_TYPE); - columnAliases.put("derivativeType", SimpleSpecimenImporter.DERIVATIVE_TYPE); - columnAliases.put("derivative Type", SimpleSpecimenImporter.DERIVATIVE_TYPE); - columnAliases.put("drawTimestamp", SimpleSpecimenImporter.DRAW_TIMESTAMP); - columnAliases.put("Draw Date", SimpleSpecimenImporter.DRAW_TIMESTAMP); - columnAliases.put("Date", SimpleSpecimenImporter.DRAW_TIMESTAMP); - columnAliases.put("draw Timestamp", SimpleSpecimenImporter.DRAW_TIMESTAMP); - columnAliases.put("Vial Id", SimpleSpecimenImporter.VIAL_ID); - columnAliases.put("globalUniqueId", SimpleSpecimenImporter.VIAL_ID); - columnAliases.put("global Unique Id", SimpleSpecimenImporter.VIAL_ID); - columnAliases.put("Units", SimpleSpecimenImporter.UNITS); - columnAliases.put("Specimen Type", SimpleSpecimenImporter.PRIMARY_SPECIMEN_TYPE); - - //Remember whether we used a different header so we can put up error messages that make sense - Map labels = new HashMap<>(); - ColumnDescriptor[] columns; - try - { - columns = loader.getColumns(); - } - catch (IOException e) - { - errors.reject(SpringActionController.ERROR_MSG, e.getMessage()); - return false; - } - - for (ColumnDescriptor c : columns) - { - if (columnAliases.containsKey(c.name)) - { - labels.put(columnAliases.get(c.name), c.name); - c.name = columnAliases.get(c.name); - } - else - labels.put(c.name, c.name); - } - importer.fixupSpecimenColumns(loader); - List> specimenRows; - try - { - loader.setThrowOnErrors(true); - specimenRows = loader.load(); - } - catch (ConversionException x) - { - errors.reject(SpringActionController.ERROR_MSG, x.getMessage() + " NOTE: Numbers must contain only digits and decimal separators."); - return false; - } - - Set participants = new HashSet<>(); - Set vialIds = new HashSet<>(); - Map> sampleIdMap = new HashMap<>(); - String visitKey = study.getTimepointType() == TimepointType.VISIT ? SimpleSpecimenImporter.VISIT : SimpleSpecimenImporter.DRAW_TIMESTAMP; - - if (specimenRows.isEmpty()) - errors.reject(SpringActionController.ERROR_MSG, "No specimen data was provided."); - int rowNum = 1; - for (Map row : specimenRows) - { - Object vialId = row.get(SimpleSpecimenImporter.VIAL_ID); - - String participant = (String) row.get(SimpleSpecimenImporter.PARTICIPANT_ID); - if (null == participant && null != vialId) - { - participant = (String) vialId; - row.put(SimpleSpecimenImporter.PARTICIPANT_ID, participant); - } - if (null == participant) - { - errors.reject(SpringActionController.ERROR_MSG, "Error in row " + rowNum + ": required field " + (null == labels.get(SimpleSpecimenImporter.PARTICIPANT_ID) ? - StudyService.get().getSubjectNounSingular(container) : - labels.get(SimpleSpecimenImporter.PARTICIPANT_ID)) + " was not supplied"); - } - else - participants.add(participant); - - Object sampleId = row.get(SimpleSpecimenImporter.SAMPLE_ID); - if (null == sampleId && null != vialId) - { - sampleId = row.get(SimpleSpecimenImporter.VIAL_ID); - row.put(SimpleSpecimenImporter.SAMPLE_ID, sampleId); - } - if (null != sampleId) - { - Pair participantVisit = new Pair<>(participant, row.get(visitKey)); - if (sampleIdMap.containsKey(sampleId)) - { - if (!participantVisit.equals(sampleIdMap.get(sampleId))) - errors.reject(SpringActionController.ERROR_MSG, "Error in row " + rowNum + ": the same sample id has multiple " + - StudyService.get().getSubjectNounSingular(container) + "/visits."); - } - else - sampleIdMap.put(sampleId, participantVisit); - } - - if (null == vialId) - { - if (sampleId != null) - { - vialId = sampleId; - } - else - { - errors.reject(SpringActionController.ERROR_MSG, "Error in row " + rowNum + ": missing " + (null == labels.get(SimpleSpecimenImporter.VIAL_ID) ? SimpleSpecimenImporter.VIAL_ID : labels.get(SimpleSpecimenImporter.VIAL_ID))); - } - } - if (!vialIds.add(vialId)) - errors.reject(SpringActionController.ERROR_MSG, "Error in row " + rowNum + ": duplicate vial id " + vialId); - - // Check timepoint field - if (study.getTimepointType() == TimepointType.CONTINUOUS && null == row.get(SimpleSpecimenImporter.DRAW_TIMESTAMP) && null == row.get(SimpleSpecimenImporter.VISIT)) - { - errors.reject(SpringActionController.ERROR_MSG, "Error: row " + rowNum + " must contain a value for field " + - (null == labels.get(SimpleSpecimenImporter.DRAW_TIMESTAMP) ? SimpleSpecimenImporter.DRAW_TIMESTAMP : labels.get(SimpleSpecimenImporter.DRAW_TIMESTAMP)) + - " or " + - (null == labels.get(SimpleSpecimenImporter.VISIT) ? SimpleSpecimenImporter.VISIT : labels.get(SimpleSpecimenImporter.VISIT))); - } - else if (study.getTimepointType() == TimepointType.DATE && null == row.get(SimpleSpecimenImporter.DRAW_TIMESTAMP)) - { - errors.reject(SpringActionController.ERROR_MSG, "Error: row " + rowNum + " does not contain a value for field " + - (null == labels.get(SimpleSpecimenImporter.DRAW_TIMESTAMP) ? SimpleSpecimenImporter.DRAW_TIMESTAMP : labels.get(SimpleSpecimenImporter.DRAW_TIMESTAMP))); - } - else if (study.getTimepointType() == TimepointType.VISIT && null == row.get(SimpleSpecimenImporter.VISIT)) - { - errors.reject(SpringActionController.ERROR_MSG, "Error: row " + rowNum + " does not contain a value for field " + - (null == labels.get(SimpleSpecimenImporter.VISIT) ? SimpleSpecimenImporter.VISIT : labels.get(SimpleSpecimenImporter.VISIT))); - } - - if (errors.getAllErrors().size() >= 3) - break; - - rowNum++; - } - - try - { - if (!errors.hasErrors()) - importer.process(specimenRows, form.isMerge()); - } - catch (RuntimeSQLException e) - { - String message = e.getMessage(); - if (e.getSQLException() instanceof BatchUpdateException && null != e.getSQLException().getNextException()) - message = e.getSQLException().getNextException().getMessage(); - errors.reject(SpringActionController.ERROR_MSG, "A database error was reported during import: " + message); - } - catch (ValidationException | RuntimeException e) - { - errors.reject(SpringActionController.ERROR_MSG, e.getMessage()); - } - return !errors.hasErrors(); - } - - @Override - public ActionURL getSuccessURL(UploadSpecimensForm form) - { - - String redir = form.getRedir(); - if (null != StringUtils.trimToNull(redir)) - return new ActionURL(redir); - else - return new ActionURL(ImportCompleteAction.class, getContainer()); - } - - @Override - public void addNavTrail(NavTree root) - { - Container c = getContainer(); - Study s = StudyService.get().getStudy(getContainer()); - - root.addChild(s.getLabel(), PageFlowUtil.urlProvider(StudyUrls.class).getStudyOverviewURL(getContainer())); - root.addChild("Specimen Overview", new ActionURL(SpecimenController.OverviewAction.class, getContainer())); - root.addChild("Upload Specimens"); - } - - @RequiresPermission(AdminPermission.class) - public static class ImportCompleteAction extends SimpleViewAction - { - @Override - public ModelAndView getView(Object o, BindException errors) - { - ActionURL homeLink = PageFlowUtil.urlProvider(ProjectUrls.class).getStartURL(getContainer()); - ActionURL samplesLink = SpecimenController.getSpecimensURL(getContainer(), true); - return new HtmlView( - HtmlStringBuilder.of("Specimens uploaded successfully.") - .append(HtmlString.BR).append(HtmlString.BR) - .append(LinkBuilder.labkeyLink("study home", homeLink)) - .append(" ") - .append(LinkBuilder.labkeyLink("specimens", samplesLink) - ) - ); - } - - @Override - public void addNavTrail(NavTree root) - { - Study study = StudyService.get().getStudy(getContainer()); - - root.addChild(study.getLabel(), PageFlowUtil.urlProvider(StudyUrls.class).getStudyOverviewURL(getContainer())); - root.addChild("Specimen Overview", new ActionURL(SpecimenController.OverviewAction.class, getContainer())); - root.addChild("Sample Import Complete"); - } - } - - public static class UploadSpecimensForm - { - private String tsv; - private String redir; - private String replaceOrMerge = "merge"; - private boolean noSpecimens = false; - - public String getTsv() - { - return tsv; - } - - public void setTsv(String tsv) - { - this.tsv = tsv; - } - - public String getRedir() - { - return redir; - } - - public void setRedir(String redir) - { - this.redir = redir; - } - - public String getReplaceOrMerge() - { - return replaceOrMerge; - } - - public void setReplaceOrMerge(String replaceOrMerge) - { - this.replaceOrMerge = replaceOrMerge; - } - - public boolean isMerge() - { - return "merge".equals(this.replaceOrMerge); - } - - public boolean isNoSpecimens() - { - return noSpecimens; - } - - public void setNoSpecimens(boolean noSpecimens) - { - this.noSpecimens = noSpecimens; - } - } -} diff --git a/specimen/src/org/labkey/specimen/actions/SpecimenApiController.java b/specimen/src/org/labkey/specimen/actions/SpecimenApiController.java deleted file mode 100644 index 122ec311dfc..00000000000 --- a/specimen/src/org/labkey/specimen/actions/SpecimenApiController.java +++ /dev/null @@ -1,716 +0,0 @@ -/* - * Copyright (c) 2009-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.actions; - -import org.apache.commons.lang3.StringUtils; -import org.json.JSONObject; -import org.labkey.api.action.ApiResponse; -import org.labkey.api.action.ApiSimpleResponse; -import org.labkey.api.action.ApiVersion; -import org.labkey.api.action.HasViewContext; -import org.labkey.api.action.MutatingApiAction; -import org.labkey.api.action.ReadOnlyApiAction; -import org.labkey.api.action.SpringActionController; -import org.labkey.api.data.Container; -import org.labkey.api.security.ActionNames; -import org.labkey.api.security.RequiresPermission; -import org.labkey.api.security.User; -import org.labkey.api.security.UserManager; -import org.labkey.api.security.permissions.ReadPermission; -import org.labkey.api.specimen.Vial; -import org.labkey.api.specimen.location.LocationImpl; -import org.labkey.api.specimen.location.LocationManager; -import org.labkey.api.study.StudyUtils; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.view.NotFoundException; -import org.labkey.api.view.ViewContext; -import org.labkey.specimen.RequestedSpecimens; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.SpecimenRequestException; -import org.labkey.specimen.SpecimenRequestManager; -import org.labkey.specimen.SpecimenRequestStatus; -import org.labkey.specimen.importer.RequestabilityManager; -import org.labkey.specimen.model.AdditiveType; -import org.labkey.specimen.model.DerivativeType; -import org.labkey.specimen.model.PrimaryType; -import org.labkey.specimen.model.SpecimenTypeSummary; -import org.labkey.specimen.requirements.SpecimenRequest; -import org.labkey.specimen.security.permissions.ManageRequestsPermission; -import org.labkey.specimen.security.permissions.RequestSpecimensPermission; -import org.labkey.specimen.settings.RepositorySettings; -import org.labkey.specimen.settings.SettingsManager; -import org.springframework.validation.BindException; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -/* - * User: brittp - * Date: Dec 18, 2008 - * Time: 11:57:24 AM - */ -@SuppressWarnings("UnusedDeclaration") -public class SpecimenApiController extends SpringActionController -{ - private static final DefaultActionResolver _resolver = new DefaultActionResolver(SpecimenApiController.class); - - public SpecimenApiController() - { - setActionResolver(_resolver); - } - - public static class SpecimenApiForm implements HasViewContext - { - private ViewContext _viewContext; - - @Override - public ViewContext getViewContext() - { - return _viewContext; - } - - @Override - public void setViewContext(ViewContext viewContext) - { - _viewContext = viewContext; - } - } - - public static class GetRequestsForm extends SpecimenApiForm - { - Boolean _allUsers; - - public Boolean isAllUsers() - { - return _allUsers; - } - - public void setAllUsers(Boolean allUsers) - { - _allUsers = allUsers; - } - } - - private List> getSpecimenListResponse(List vials) - { - List> response = new ArrayList<>(); - for (Vial vial : vials) - { - Map vialProperties = new HashMap<>(); - response.add(vialProperties); - vialProperties.put("rowId", vial.getRowId()); - vialProperties.put("globalUniqueId", vial.getGlobalUniqueId()); - vialProperties.put("ptid", vial.getPtid()); - vialProperties.put("visitValue", vial.getVisitValue()); - vialProperties.put("primaryTypeId", vial.getPrimaryTypeId()); - if (vial.getPrimaryTypeId() != null) - { - PrimaryType primaryType = SpecimenManager.get().getPrimaryType(vial.getContainer(), vial.getPrimaryTypeId()); - if (primaryType != null) - vialProperties.put("primaryType", primaryType.getPrimaryType()); - } - vialProperties.put("derivativeTypeId", vial.getDerivativeTypeId()); - if (vial.getDerivativeTypeId() != null) - { - DerivativeType derivativeType = SpecimenManager.get().getDerivativeType(vial.getContainer(), vial.getDerivativeTypeId()); - if (derivativeType != null) - vialProperties.put("derivativeType", derivativeType.getDerivative()); - } - vialProperties.put("additiveTypeId", vial.getAdditiveTypeId()); - if (vial.getAdditiveTypeId() != null) - { - AdditiveType additiveType = SpecimenManager.get().getAdditiveType(vial.getContainer(), vial.getAdditiveTypeId()); - if (additiveType != null) - vialProperties.put("additiveType", additiveType.getAdditive()); - } - vialProperties.put("drawTimestamp", vial.getDrawTimestamp()); - vialProperties.put("currentLocation", vial.getCurrentLocation() != null ? - getLocation(getContainer(), vial.getCurrentLocation()) : null); - if (vial.getOriginatingLocationId() != null) - vialProperties.put("originatingLocation", getLocation(getContainer(), vial.getOriginatingLocationId())); - vialProperties.put("subAdditiveDerivative", vial.getSubAdditiveDerivative()); - vialProperties.put("volume", vial.getVolume()); - vialProperties.put("specimenHash", vial.getSpecimenHash()); - vialProperties.put("volumeUnits", vial.getVolumeUnits()); - } - return response; - } - - private Map getRequestResponse(ViewContext context, SpecimenRequest request) - { - Map map = new HashMap<>(); - map.put("requestId", request.getRowId()); - map.put("comments", request.getComments()); - map.put("created", request.getCreated()); - map.put("createdBy", request.getCreatedBy()); - User user = UserManager.getUser(request.getCreatedBy()); - Map userMap = new HashMap<>(); - userMap.put("userId", request.getCreatedBy()); - userMap.put("displayName", user.getDisplayName(context.getUser())); - map.put("createdBy", userMap); - map.put("destination", request.getDestinationSiteId() != null ? getLocation(getContainer(), request.getDestinationSiteId().intValue()) : null); - map.put("statusId", request.getStatusId()); - SpecimenRequestStatus status = SpecimenRequestManager.get().getRequestStatus(request.getContainer(), request.getStatusId()); - if (status != null) - map.put("status", status.getLabel()); - List vials = request.getVials(); - map.put("vials", getSpecimenListResponse(vials)); - return map; - } - - private Map getLocation(Container container, int locationId) - { - LocationImpl location = LocationManager.get().getLocation(container, locationId); - if (location == null) - return null; - return getLocation(location); - } - - private Map getLocation(LocationImpl location1) - { - Map locationMap = new HashMap<>(); - locationMap.put("endpoint", location1.isEndpoint()); - locationMap.put("entityId", location1.getEntityId()); - locationMap.put("label", location1.getLabel()); - locationMap.put("labUploadCode", location1.getLabUploadCode()); - locationMap.put("labwareLabCode", location1.getLabwareLabCode()); - locationMap.put("ldmsLabCode", location1.getLdmsLabCode()); - locationMap.put("repository", location1.isRepository()); - locationMap.put("rowId", location1.getRowId()); - locationMap.put("SAL", location1.isSal()); - locationMap.put("clinic", location1.isClinic()); - locationMap.put("externalId", location1.getExternalId()); - return locationMap; - } - - private List> getRequestListResponse(ViewContext context, List requests) - { - List> response = new ArrayList<>(); - for (SpecimenRequest request : requests) - response.add(getRequestResponse(context, request)); - return response; - } - - @RequiresPermission(ReadPermission.class) - @ApiVersion(9.1) - public class GetRepositoriesAction extends ReadOnlyApiAction - { - @Override - public ApiResponse execute(SpecimenApiForm form, BindException errors) - { - final List> repositories = new ArrayList<>(); - for (LocationImpl location : LocationManager.get().getLocations(getContainer())) - { - if (location.isRepository()) - repositories.add(getLocation(location)); - } - Map result = new HashMap<>(); - result.put("repositories", repositories); - return new ApiSimpleResponse(result); - } - } - - @RequiresPermission(ReadPermission.class) - @ApiVersion(9.1) - public class GetOpenRequestsAction extends ReadOnlyApiAction - { - @Override - public ApiResponse execute(GetRequestsForm requestsForm, BindException errors) - { - Container container = requestsForm.getViewContext().getContainer(); - User user = requestsForm.getViewContext().getUser(); - SpecimenRequestStatus shoppingCartStatus = SpecimenRequestManager.get().getRequestShoppingCartStatus(container, user); - final Map response = new HashMap<>(); - if (user != null && shoppingCartStatus != null) - { - boolean allUsers = getContainer().hasPermission(getUser(), ManageRequestsPermission.class); - if (requestsForm.isAllUsers() != null) - allUsers = requestsForm.isAllUsers(); - Collection allUserRequests = SpecimenRequestManager.get().getRequests(container, allUsers ? null : user); - List nonFinalRequests = new ArrayList<>(); - for (SpecimenRequest request : allUserRequests) - { - if (SpecimenRequestManager.get().hasEditRequestPermissions(getUser(), request) && !SpecimenRequestManager.get().isInFinalState(request)) - { - nonFinalRequests.add(request); - } - } - response.put("requests", getRequestListResponse(getViewContext(), nonFinalRequests)); - } - else - response.put("requests", Collections.emptyList()); - - return new ApiSimpleResponse(response); - } - } - - public static class RequestIdForm extends SpecimenApiForm - { - private int _requestId; - - public int getRequestId() - { - return _requestId; - } - - public void setRequestId(int requestId) - { - _requestId = requestId; - } - } - - @RequiresPermission(ReadPermission.class) - @ApiVersion(9.1) - public class GetRequestAction extends ReadOnlyApiAction - { - @Override - public ApiResponse execute(RequestIdForm requestIdForm, BindException errors) - { - // OK for anyone with read access to see any request in this container, even if they didn't create it - SpecimenRequest request = getRequest(getUser(), getContainer(), requestIdForm.getRequestId(), false, false); - final Map response = new HashMap<>(); - response.put("request", request != null ? getRequestResponse(getViewContext(), request) : null); - return new ApiSimpleResponse(response); - } - } - - public static class GetVialsByRowIdForm extends SpecimenApiForm - { - int[] _rowIds; - - public int[] getRowIds() - { - return _rowIds; - } - - public void setRowIds(int[] rowIds) - { - _rowIds = rowIds; - } - } - - public static class GetProvidingLocationsForm extends RequestIdForm - { - private String[] specimenHashes; - - public String[] getSpecimenHashes() - { - return specimenHashes; - } - - public void setSpecimenHashes(String[] specimenHashes) - { - this.specimenHashes = specimenHashes; - } - } - - @RequiresPermission(ReadPermission.class) - @ApiVersion(9.1) - public class GetProvidingLocations extends ReadOnlyApiAction - { - @Override - public ApiResponse execute(GetProvidingLocationsForm form, BindException errors) - { - Map> vialsByHash = SpecimenManager.get().getVialsForSpecimenHashes(getContainer(), getUser(), - PageFlowUtil.set(form.getSpecimenHashes()), true); - Collection preferredLocations = StudyUtils.getPreferredProvidingLocations(vialsByHash.values()); - final Map response = new HashMap<>(); - List> locations = new ArrayList<>(); - for (Integer locationId : preferredLocations) - locations.add(getLocation(getContainer(), locationId)); - response.put("locations", locations); - return new ApiSimpleResponse(response); - } - } - - @RequiresPermission(ReadPermission.class) - @ApiVersion(9.1) - public class GetVialsByRowIdAction extends ReadOnlyApiAction - { - @Override - public ApiResponse execute(GetVialsByRowIdForm form, BindException errors) - { - Container container = form.getViewContext().getContainer(); - final Map response = new HashMap<>(); - List> vialList; - if (form.getRowIds() != null && form.getRowIds().length > 0) - { - try - { - List vials = SpecimenManager.get().getVials(container, form.getViewContext().getUser(), form.getRowIds()); - vialList = getSpecimenListResponse(vials); - } - catch (SpecimenRequestException e) - { - errors.reject(ERROR_MSG, e.getMessage()); - vialList = Collections.emptyList(); - } - } - else - vialList = Collections.emptyList(); - response.put("vials", vialList); - return new ApiSimpleResponse(response); - } - } - - public static class AddSpecimensToRequestForm extends RequestIdForm - { - private String[] specimenHashes; - private Integer _preferredLocation; - - public String[] getSpecimenHashes() - { - return specimenHashes; - } - - public void setSpecimenHashes(String[] specimenHashes) - { - this.specimenHashes = specimenHashes; - } - - public Integer getPreferredLocation() - { - return _preferredLocation; - } - - public void setPreferredLocation(Integer preferredLocation) - { - _preferredLocation = preferredLocation; - } - } - - private SpecimenRequest getRequest(User user, Container container, int rowId, boolean checkOwnership, boolean checkEditability) - { - SpecimenRequest request = SpecimenRequestManager.get().getRequest(container, rowId); - boolean admin = container.hasPermission(user, ManageRequestsPermission.class); - boolean adminOrOwner = request != null && (admin || request.getCreatedBy() == user.getUserId()); - if (request == null || (checkOwnership && !adminOrOwner)) - throw new IllegalArgumentException("Request " + rowId + " was not found or the current user does not have permissions to access it."); - if (checkEditability) - { - if (admin) - { - if (SpecimenRequestManager.get().isInFinalState(request)) - throw new IllegalArgumentException("Request " + rowId + " is in a final state and cannot be modified."); - } - else - { - SpecimenRequestStatus cartStatus = SpecimenRequestManager.get().getRequestShoppingCartStatus(container, user); - if (cartStatus == null || request.getStatusId() != cartStatus.getRowId()) - throw new IllegalArgumentException("Request " + rowId + " has been submitted and can only be modified by an administrator."); - } - } - return request; - } - - public static class VialRequestForm extends RequestIdForm - { - private String _idType; - private String[] _vialIds; - - public String[] getVialIds() - { - return _vialIds; - } - - public void setVialIds(String[] vialIds) - { - _vialIds = vialIds; - } - - public String getIdType() - { - return _idType; - } - - public void setIdType(String idType) - { - _idType = idType; - } - } - - @RequiresPermission(RequestSpecimensPermission.class) - @ApiVersion(9.1) - public class AddVialsToRequestAction extends MutatingApiAction - { - @Override - public ApiResponse execute(VialRequestForm vialRequestForm, BindException errors) throws Exception - { - SpecimenRequest request = getRequest(getUser(), getContainer(), vialRequestForm.getRequestId(), true, true); - for (String vialId : vialRequestForm.getVialIds()) - { - Vial vial = getVial(vialId, vialRequestForm.getIdType()); - try - { - SpecimenRequestManager.get().createRequestSpecimenMapping(getUser(), request, Collections.singletonList(vial), true, true); - } - catch (RequestabilityManager.InvalidRuleException e) - { - errors.reject(ERROR_MSG, "The specimens could not be added because a requestability rule is configured incorrectly. " + - "Please report this problem to an administrator. Error details: " + e.getMessage()); - return null; - } - catch (SpecimenRequestException e) - { - errors.reject(ERROR_MSG, RequestabilityManager.makeSpecimenUnavailableMessage(vial, null)); - return null; - } - - } - final Map response = getRequestResponse(getViewContext(), request); - return new ApiSimpleResponse(response); - } - } - - private Vial getVial(String vialId, String idType) - { - Vial vial; - if (IdTypes.GlobalUniqueId.name().equals(idType)) - vial = SpecimenManager.get().getVial(getContainer(), getUser(), vialId); - else if (IdTypes.RowId.name().equals(idType)) - { - try - { - int id = Integer.parseInt(vialId); - vial = SpecimenManager.get().getVial(getContainer(), getUser(), id); - } - catch (NumberFormatException e) - { - throw new RuntimeException(vialId + " could not be converted into a valid integer RowId."); - } - } - else - { - throw new RuntimeException("Invalid ID type \"" + idType + "\": only \"" + - IdTypes.GlobalUniqueId.name() + "\" and \"" + IdTypes.RowId.name() + - "\" are valid parameter values."); - } - if (vial == null) - { - throw new NotFoundException("No vial was found with " + idType + " " + vialId + "."); - } - return vial; - } - - @RequiresPermission(RequestSpecimensPermission.class) - @ApiVersion(9.1) - public class RemoveVialsFromRequestAction extends MutatingApiAction - { - @Override - public ApiResponse execute(VialRequestForm vialRequestForm, BindException errors) throws Exception - { - SpecimenRequest request = getRequest(getUser(), getContainer(), vialRequestForm.getRequestId(), true, true); - List rowIds = new ArrayList<>(); - List currentVials = request.getVials(); - for (String vialId : vialRequestForm.getVialIds()) - { - Vial vial = getVial(vialId, vialRequestForm.getIdType()); - Vial toRemove = null; - for (int i = 0; i < currentVials.size() && toRemove == null; i++) - { - Vial possible = currentVials.get(i); - if (possible.getRowId() == vial.getRowId()) - toRemove = possible; - } - if (toRemove != null) - rowIds.add(toRemove.getRowId()); - } - if (!rowIds.isEmpty()) - { - try - { - SpecimenRequestManager.get().deleteRequestSpecimenMappings(getUser(), request, rowIds, true); - } - catch (RequestabilityManager.InvalidRuleException e) - { - errors.reject(ERROR_MSG, "The specimens could not be removed because a requestability rule is configured incorrectly. " + - "Please report this problem to an administrator. Error details: " + e.getMessage()); - return null; - } - } - final Map response = getRequestResponse(getViewContext(), request); - return new ApiSimpleResponse(response); - } - } - - @RequiresPermission(RequestSpecimensPermission.class) - @ApiVersion(9.1) - // labkey-js-api now calls addSpecimensToRequest.api, but older versions still call addSamplesToRequest.api - @ActionNames("addSpecimensToRequest,addSamplesToRequest") - public class AddSpecimensToRequestAction extends MutatingApiAction - { - @Override - public ApiResponse execute(AddSpecimensToRequestForm addSpecimensToRequestForm, BindException errors) throws Exception - { - final SpecimenRequest request = getRequest(getUser(), getContainer(), addSpecimensToRequestForm.getRequestId(), true, true); - Set hashes = new HashSet<>(); - Collections.addAll(hashes, addSpecimensToRequestForm.getSpecimenHashes()); - RequestedSpecimens requested = SpecimenRequestManager.get().getRequestableBySpecimenHash(getContainer(), getUser(), hashes, addSpecimensToRequestForm.getPreferredLocation()); - if (!requested.getVials().isEmpty()) - { - List vials = new ArrayList<>(requested.getVials()); - try - { - SpecimenRequestManager.get().createRequestSpecimenMapping(getUser(), request, vials, true, true); - } - catch (RequestabilityManager.InvalidRuleException e) - { - errors.reject(ERROR_MSG, "The specimens could not be added because a requestability rule is configured incorrectly. " + - "Please report this problem to an administrator. Error details: " + e.getMessage()); - return null; - } - catch (SpecimenRequestException e) - { - errors.reject(ERROR_MSG, "A vial that was available for request has become unavailable."); - return null; - } - } - final Map response = getRequestResponse(getViewContext(), request); - return new ApiSimpleResponse(response); - } - } - - @RequiresPermission(RequestSpecimensPermission.class) - @ApiVersion(9.1) - public class CancelRequestAction extends MutatingApiAction - { - @Override - public ApiResponse execute(RequestIdForm deleteRequestForm, BindException errors) throws Exception - { - SpecimenRequest request = getRequest(getUser(), getContainer(), deleteRequestForm.getRequestId(), true, true); - try - { - SpecimenRequestManager.get().deleteRequest(getUser(), request); - } - catch (RequestabilityManager.InvalidRuleException e) - { - errors.reject(ERROR_MSG, "The request could not be deleted because a requestability rule is configured incorrectly. " + - "Please report this problem to an administrator. Error details: " + e.getMessage()); - return null; - } - - final Map response = getRequestResponse(getViewContext(), request); - return new ApiSimpleResponse(response); - } - } - - private void buildTypeSummary(List> summary, List types) - { - // Recursively descend through the vial type hierarchy, adding a count property and a list of children for each type. - for (SpecimenTypeSummary.TypeCount count : types) - { - Map countProperties = new TreeMap<>(); - summary.add(countProperties); - countProperties.put("label", count.getLabel() != null ? count.getLabel() : "[unknown]"); - countProperties.put("count", count.getVialCount()); - countProperties.put("url", count.getURL()); - List childCounts = count.getChildren(); - if (childCounts != null && !childCounts.isEmpty()) - { - List> childList = new ArrayList<>(); - buildTypeSummary(childList, childCounts); - if (!childList.isEmpty()) - countProperties.put("children", childList); - } - } - } - - @RequiresPermission(ReadPermission.class) - @ApiVersion(11.2) - public class GetVialTypeSummaryAction extends ReadOnlyApiAction - { - @Override - public ApiResponse execute(SpecimenApiForm form, BindException errors) - { - Container container = form.getViewContext().getContainer(); - SpecimenTypeSummary summary = SpecimenManager.get().getSpecimenTypeSummary(container, getUser()); - final Map response = new HashMap<>(); - - List> primaryTypes = new ArrayList<>(); - buildTypeSummary(primaryTypes, summary.getPrimaryTypes()); - response.put("primaryTypes", primaryTypes); - - List> derivativeTypes = new ArrayList<>(); - buildTypeSummary(derivativeTypes, summary.getDerivatives()); - response.put("derivativeTypes", derivativeTypes); - - List> additiveTypes = new ArrayList<>(); - buildTypeSummary(additiveTypes, summary.getAdditives()); - response.put("additiveTypes", additiveTypes); - - return new ApiSimpleResponse(response); - } - } - - @RequiresPermission(ReadPermission.class) - @ApiVersion(13.1) - public static class GetSpecimenWebPartGroupsAction extends ReadOnlyApiAction - { - @Override - public ApiResponse execute(SpecimenApiForm form, BindException errors) - { - // Build a JSON response with up to 2 groupings, each with up to 3 levels - // VALUE --> - // { "count" : int, - // "label" : string, - // "url" : string, - // "group" : GROUP [optional] - // } - // GROUP --> - // { "name" : "", - // "dummy" : bool, (true means dummy so we have at least 1) - // "values" : [VALUE, ...] - // } - // GROUPINGS -- > - // { "groupings" : [GROUP, ...] [could be empty] - // - Container container = form.getViewContext().getContainer(); - RepositorySettings settings = SettingsManager.get().getRepositorySettings(container); - ArrayList groupings = settings.getSpecimenWebPartGroupings(); - - final Map response = new HashMap<>(); - - List> groupingsJSON = new ArrayList<>(); - - Map> groupingMap = SpecimenRequestManager.get().getGroupedValuesForColumn(getContainer(), getUser(), groupings); - for (String[] grouping: groupings) - { - if (null != StringUtils.trimToNull(grouping[0])) // Do nothing if no columns were specified - { - Map groupingJSON = groupingMap.get(grouping[0]); - groupingsJSON.add(groupingJSON); - } - } - if (groupingsJSON.isEmpty()) - { - // no groupings; create default grouping - Map groupingJSON = new JSONObject().toMap(); - groupingJSON.put("dummy", true); - groupingsJSON.add(groupingJSON); - } - response.put("groupings", groupingsJSON); - - return new ApiSimpleResponse(response); - } - } -} diff --git a/specimen/src/org/labkey/specimen/actions/SpecimenController.java b/specimen/src/org/labkey/specimen/actions/SpecimenController.java deleted file mode 100644 index f46bc978519..00000000000 --- a/specimen/src/org/labkey/specimen/actions/SpecimenController.java +++ /dev/null @@ -1,6096 +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.actions; - -import jakarta.mail.Address; -import jakarta.mail.Message; -import jakarta.mail.internet.AddressException; -import jakarta.mail.internet.InternetAddress; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.json.JSONObject; -import org.junit.Test; -import org.labkey.api.action.ApiResponse; -import org.labkey.api.action.ApiSimpleResponse; -import org.labkey.api.action.ExportAction; -import org.labkey.api.action.FormHandlerAction; -import org.labkey.api.action.FormViewAction; -import org.labkey.api.action.HasViewContext; -import org.labkey.api.action.MutatingApiAction; -import org.labkey.api.action.QueryViewAction; -import org.labkey.api.action.ReadOnlyApiAction; -import org.labkey.api.action.ReturnUrlForm; -import org.labkey.api.action.SimpleRedirectAction; -import org.labkey.api.action.SimpleViewAction; -import org.labkey.api.action.SpringActionController; -import org.labkey.api.attachments.Attachment; -import org.labkey.api.attachments.AttachmentFile; -import org.labkey.api.attachments.AttachmentParent; -import org.labkey.api.attachments.AttachmentService; -import org.labkey.api.attachments.BaseDownloadAction; -import org.labkey.api.attachments.ByteArrayAttachmentFile; -import org.labkey.api.audit.TransactionAuditProvider; -import org.labkey.api.collections.IntHashMap; -import org.labkey.api.data.ActionButton; -import org.labkey.api.data.BaseColumnInfo; -import org.labkey.api.data.BeanViewForm; -import org.labkey.api.data.ButtonBar; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.CompareType; -import org.labkey.api.data.Container; -import org.labkey.api.data.DataColumn; -import org.labkey.api.data.DataRegion; -import org.labkey.api.data.DataRegionSelection; -import org.labkey.api.data.DbScope; -import org.labkey.api.data.DisplayColumn; -import org.labkey.api.data.ExcelWriter; -import org.labkey.api.data.MenuButton; -import org.labkey.api.data.ObjectFactory; -import org.labkey.api.data.PropertyManager; -import org.labkey.api.data.PropertyManager.WritablePropertyMap; -import org.labkey.api.data.RenderContext; -import org.labkey.api.data.SimpleDisplayColumn; -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.data.Sort; -import org.labkey.api.data.TSVGridWriter; -import org.labkey.api.data.TSVWriter; -import org.labkey.api.data.Table; -import org.labkey.api.data.TableInfo; -import org.labkey.api.gwt.client.AuditBehaviorType; -import org.labkey.api.module.FolderType; -import org.labkey.api.module.Module; -import org.labkey.api.module.ModuleLoader; -import org.labkey.api.pipeline.PipeRoot; -import org.labkey.api.pipeline.PipelineJob; -import org.labkey.api.pipeline.PipelineService; -import org.labkey.api.pipeline.PipelineStatusUrls; -import org.labkey.api.pipeline.PipelineValidationException; -import org.labkey.api.pipeline.browse.PipelinePathForm; -import org.labkey.api.query.AbstractQueryImportAction; -import org.labkey.api.query.BatchValidationException; -import org.labkey.api.query.CustomView; -import org.labkey.api.query.FieldKey; -import org.labkey.api.query.QueryDefinition; -import org.labkey.api.query.QueryService; -import org.labkey.api.query.QueryUpdateForm; -import org.labkey.api.query.QueryView; -import org.labkey.api.query.UserSchemaAction; -import org.labkey.api.query.ValidationException; -import org.labkey.api.reader.ColumnDescriptor; -import org.labkey.api.reader.DataLoader; -import org.labkey.api.security.ActionNames; -import org.labkey.api.security.RequiresLogin; -import org.labkey.api.security.RequiresPermission; -import org.labkey.api.security.User; -import org.labkey.api.security.ValidEmail; -import org.labkey.api.security.permissions.AbstractContainerScopingTest; -import org.labkey.api.security.permissions.AdminPermission; -import org.labkey.api.security.permissions.ReadPermission; -import org.labkey.api.security.permissions.UpdatePermission; -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.api.study.CohortFilter; -import org.labkey.api.study.Dataset; -import org.labkey.api.study.MapArrayExcelWriter; -import org.labkey.api.study.SpecimenService; -import org.labkey.api.study.SpecimenTransform; -import org.labkey.api.study.SpecimenUrls; -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.TimepointType; -import org.labkey.api.study.Visit; -import org.labkey.api.study.model.CohortService; -import org.labkey.api.study.model.ParticipantDataset; -import org.labkey.api.study.security.permissions.ManageStudyPermission; -import org.labkey.api.util.ButtonBuilder; -import org.labkey.api.util.ConfigurationException; -import org.labkey.api.util.DateUtil; -import org.labkey.api.util.ExceptionUtil; -import org.labkey.api.util.FileStream; -import org.labkey.api.util.GUID; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.LinkBuilder; -import org.labkey.api.util.MailHelper; -import org.labkey.api.util.OptionBuilder; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.util.Pair; -import org.labkey.api.util.SelectBuilder; -import org.labkey.api.util.URLHelper; -import org.labkey.api.util.emailTemplate.EmailTemplateService; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.AjaxCompletion; -import org.labkey.api.view.DisplayElement; -import org.labkey.api.view.GridView; -import org.labkey.api.view.HBox; -import org.labkey.api.view.HtmlView; -import org.labkey.api.view.HttpView; -import org.labkey.api.view.InsertView; -import org.labkey.api.view.JspView; -import org.labkey.api.view.NavTree; -import org.labkey.api.view.NotFoundException; -import org.labkey.api.view.RedirectException; -import org.labkey.api.view.UnauthorizedException; -import org.labkey.api.view.UpdateView; -import org.labkey.api.view.VBox; -import org.labkey.api.view.ViewBackgroundInfo; -import org.labkey.api.view.ViewContext; -import org.labkey.api.view.WebPartView; -import org.labkey.api.writer.HtmlWriter; -import org.labkey.specimen.AmbiguousLocationException; -import org.labkey.specimen.RequestEventType; -import org.labkey.specimen.RequestedSpecimens; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.SpecimenModule; -import org.labkey.specimen.SpecimenRequestException; -import org.labkey.specimen.SpecimenRequestManager; -import org.labkey.specimen.SpecimenRequestStatus; -import org.labkey.specimen.importer.QueryBasedSpecimenTransform; -import org.labkey.specimen.importer.RequestabilityManager; -import org.labkey.specimen.importer.SimpleSpecimenImporter; -import org.labkey.specimen.model.ExtendedSpecimenRequestView; -import org.labkey.specimen.model.SpecimenRequestActor; -import org.labkey.specimen.model.SpecimenRequestEvent; -import org.labkey.specimen.notifications.ActorNotificationRecipientSet; -import org.labkey.specimen.notifications.DefaultRequestNotification; -import org.labkey.specimen.notifications.NotificationRecipientSet; -import org.labkey.specimen.pipeline.SpecimenArchive; -import org.labkey.specimen.pipeline.SpecimenBatch; -import org.labkey.specimen.query.SpecimenEventQueryView; -import org.labkey.specimen.query.SpecimenQueryView; -import org.labkey.specimen.query.SpecimenRequestQueryView; -import org.labkey.specimen.requirements.SpecimenRequest; -import org.labkey.specimen.requirements.SpecimenRequestRequirement; -import org.labkey.specimen.requirements.SpecimenRequestRequirementProvider; -import org.labkey.specimen.requirements.SpecimenRequestRequirementType; -import org.labkey.specimen.security.permissions.EditSpecimenDataPermission; -import org.labkey.specimen.security.permissions.ManageDisplaySettingsPermission; -import org.labkey.specimen.security.permissions.ManageNewRequestFormPermission; -import org.labkey.specimen.security.permissions.ManageNotificationsPermission; -import org.labkey.specimen.security.permissions.ManageRequestRequirementsPermission; -import org.labkey.specimen.security.permissions.ManageRequestSettingsPermission; -import org.labkey.specimen.security.permissions.ManageRequestStatusesPermission; -import org.labkey.specimen.security.permissions.ManageRequestsPermission; -import org.labkey.specimen.security.permissions.ManageSpecimenActorsPermission; -import org.labkey.specimen.security.permissions.RequestSpecimensPermission; -import org.labkey.specimen.security.permissions.SetSpecimenCommentsPermission; -import org.labkey.specimen.security.roles.SpecimenRequesterRole; -import org.labkey.specimen.settings.DisplaySettings; -import org.labkey.specimen.settings.RepositorySettings; -import org.labkey.specimen.settings.RequestNotificationSettings; -import org.labkey.specimen.settings.SettingsManager; -import org.labkey.specimen.settings.StatusSettings; -import org.labkey.specimen.view.NotificationBean; -import org.labkey.specimen.view.SpecimenRequestNotificationEmailTemplate; -import org.labkey.specimen.view.SpecimenSearchWebPart; -import org.labkey.specimen.view.SpecimenWebPart; -import org.labkey.vfs.FileLike; -import org.springframework.validation.BindException; -import org.springframework.validation.Errors; -import org.springframework.validation.ObjectError; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.mvc.Controller; - -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -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.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.labkey.api.util.DOM.Attribute.src; -import static org.labkey.api.util.DOM.Attribute.style; -import static org.labkey.api.util.DOM.IMG; -import static org.labkey.api.util.DOM.SPAN; -import static org.labkey.api.util.DOM.at; - -public class SpecimenController extends SpringActionController -{ - private static final Logger LOG = LogManager.getLogger(SpecimenController.class); - private static final DefaultActionResolver _resolver = new DefaultActionResolver( - SpecimenController.class, - - ShowGroupMembersAction.class, - ShowSearchAction.class, - ShowUploadSpecimensAction.class, - ShowUploadSpecimensAction.ImportCompleteAction.class, - - // Report actions from SpecimenReportActions - SpecimenReportActions.ParticipantSummaryReportAction.class, - SpecimenReportActions.ParticipantTypeReportAction.class, - SpecimenReportActions.ParticipantSiteReportAction.class, - SpecimenReportActions.RequestReportAction.class, - SpecimenReportActions.RequestEnrollmentSiteReportAction.class, - SpecimenReportActions.RequestSiteReportAction.class, - SpecimenReportActions.RequestParticipantReportAction.class, - SpecimenReportActions.TypeParticipantReportAction.class, - SpecimenReportActions.TypeSummaryReportAction.class, - SpecimenReportActions.TypeCohortReportAction.class, - - ParticipantCommentAction.SpecimenCommentInsertAction.class, - ParticipantCommentAction.SpecimenCommentUpdateAction.class - ); - - public static class SpecimenUrlsImpl implements SpecimenUrls - { - @Override - public ActionURL getSpecimensURL(Container c) - { - return SpecimenController.getSpecimensURL(c); - } - - @Override - public ActionURL getSpecimensURL(Container c, boolean showVials) - { - return SpecimenController.getSpecimensURL(c, showVials); - } - - @Override - public ActionURL getManageRequestURL(Container c, int requestId) - { - return SpecimenController.getManageRequestURL(c, requestId, null); - } - - @Override - public ActionURL getManageRequestStatusURL(Container c, int requestId) - { - return SpecimenController.getManageRequestStatusURL(c, requestId); - } - } - - private Study _study = null; - - public SpecimenController() - { - setActionResolver(_resolver); - } - - @Nullable - public Study getStudy() - { - if (null == _study) - _study = StudyService.get().getStudy(getContainer()); - return _study; - } - - @NotNull - public Study getStudyThrowIfNull() throws IllegalStateException - { - Study study = StudyService.get().getStudy(getContainer()); - if (null == study) - { - // We expected to find a study - throw new NotFoundException("No study found."); - } - return study; - } - - @NotNull - public Study getStudyRedirectIfNull() - { - return getStudyRedirectIfNull(getContainer()); - } - - @NotNull - public static Study getStudyRedirectIfNull(Container c) - { - Study study = StudyService.get().getStudy(c); - if (null == study) - { - // redirect to the study home page, where admins will see a 'create study' button, - // and non-admins will simply see a message that no study exists. - throw new RedirectException(urlProvider(StudyUrls.class).getBeginURL(c)); - } - return study; - } - - public void addRootNavTrail(NavTree root) - { - Study study = getStudyRedirectIfNull(); - Container c = getContainer(); - ActionURL rootURL; - FolderType folderType = c.getFolderType(); - Module module = folderType.getDefaultModule(); - if (null != module && "study".equals(module.getName())) - { - rootURL = folderType.getStartURL(c, getUser()); - } - else - { - rootURL = urlProvider(StudyUrls.class).getBeginURL(c); - } - root.addChild(study.getLabel(), rootURL); - } - - private void addBaseSpecimenNavTrail(NavTree root) - { - addRootNavTrail(root); - ActionURL overviewURL = new ActionURL(OverviewAction.class, getContainer()); - root.addChild("Specimen Overview", overviewURL); - } - - private void addSpecimenRequestsNavTrail(NavTree root) - { - addBaseSpecimenNavTrail(root); - root.addChild("Specimen Requests", new ActionURL(ViewRequestsAction.class, getContainer())); - } - - private void addSpecimenRequestNavTrail(NavTree root, int requestId) - { - addSpecimenRequestsNavTrail(root); - root.addChild("Specimen Request " + requestId, getManageRequestURL(requestId, null)); - } - - private void addManageStudyNavTrail(NavTree root) - { - urlProvider(StudyUrls.class).addManageStudyNavTrail(root, getContainer(), getUser()); - } - - private ActionURL getManageStudyURL() - { - return urlProvider(StudyUrls.class).getManageStudyURL(getContainer()); - } - - public void ensureSpecimenRequestsConfigured(boolean checkExistingStatuses) - { - if (!org.labkey.specimen.settings.SettingsManager.get().isSpecimenRequestEnabled(getContainer(), checkExistingStatuses, getUser())) - throw new RedirectException(new ActionURL(SpecimenRequestConfigRequiredAction.class, getContainer())); - } - - public static boolean nullSafeEqual(T first, T second) - { - if (first == null && second == null) - return true; - if (first == null) - return false; - return first.equals(second); - } - - private ActionURL urlFor(Class action) - { - return new ActionURL(action, getContainer()); - } - - public boolean isCommentsMode(Container container, SpecimenQueryView.Mode selectedMode) - { - return (selectedMode == SpecimenQueryView.Mode.COMMENTS) || - (selectedMode == SpecimenQueryView.Mode.DEFAULT && SettingsManager.get().getDisplaySettings(container).isDefaultToCommentsMode()); - } - - public SpecimenQueryView getSpecimenQueryView(boolean showVials, boolean forExport, @Nullable Collection cachedFilterData, SpecimenQueryView.Mode viewMode, CohortFilter cohortFilter) - { - boolean commentsMode = isCommentsMode(getContainer(), viewMode); - - SpecimenQueryView gridView; - RepositorySettings settings = SettingsManager.get().getRepositorySettings(getContainer()); - - if (cachedFilterData != null) - { - gridView = SpecimenQueryView.createView(getViewContext(), cachedFilterData, - showVials ? SpecimenQueryView.ViewType.VIALS : - SpecimenQueryView.ViewType.SUMMARY); - } - else - { - gridView = SpecimenQueryView.createView(getViewContext(), - showVials ? SpecimenQueryView.ViewType.VIALS : - SpecimenQueryView.ViewType.SUMMARY, cohortFilter); - } - gridView.setShowHistoryLinks(showVials && !forExport && !settings.isSimple()); - gridView.setDisableLowVialIndicators(forExport || settings.isSimple()); - gridView.setShowRecordSelectors(settings.isEnableRequests() || commentsMode); // Need to allow buttons - gridView.setShowDeleteButton(false); - - // if we're exporting, we can skip setting up the buttons: - if (forExport) - return gridView; - - List buttons = new ArrayList<>(); - - ActionButton ptidListButton = StudyInternalService.get().createParticipantGroupButton(getViewContext(), gridView.getSettings().getDataRegionName(), cohortFilter, false); - if (ptidListButton != null) - buttons.add(ptidListButton); - - if (settings.isEnableRequests()) - { - MenuButton requestMenuButton = new MenuButton("Request Options"); - requestMenuButton.addMenuItem("View Existing Requests", new ActionURL(SpecimenController.ViewRequestsAction.class, getContainer())); - if (!commentsMode) - { - if (getViewContext().getContainer().hasPermission(getViewContext().getUser(), RequestSpecimensPermission.class)) - { - final String jsRegionObject = DataRegion.getJavaScriptObjectReference(gridView.getSettings().getDataRegionName()); - String createRequestURL = urlFor(ShowCreateSpecimenRequestAction.class).addReturnUrl(getViewContext().getActionURL()).toString(); - - requestMenuButton.addMenuItem("Create New Request", - "if (verifySelected(" + jsRegionObject + ".form, '" + createRequestURL + - "', 'post', 'rows')) " + jsRegionObject + ".form.submit();"); - - if (getViewContext().getContainer().hasPermission(getViewContext().getUser(), ManageRequestsPermission.class) || - SettingsManager.get().isSpecimenShoppingCartEnabled(getViewContext().getContainer())) - { - requestMenuButton.addMenuItem("Add To Existing Request", - "if (verifySelected(" + jsRegionObject + ".form, '#', " + - "'get', 'rows')) { " + jsRegionObject + ".getSelected({success: function (data) { showRequestWindow(data.selected, '" + (showVials ? IdTypes.RowId - : IdTypes.SpecimenHash) + "');}})}"); - } - } - } - else - { - ActionURL endCommentsURL = getViewContext().getActionURL().clone(); - endCommentsURL.replaceParameter(SpecimenViewTypeForm.PARAMS.viewMode, SpecimenQueryView.Mode.REQUESTS.name()); - requestMenuButton.addMenuItem("Enable Request Mode", endCommentsURL); - } - - buttons.add(requestMenuButton); - } - - if (getViewContext().getContainer().hasPermission(getUser(), SetSpecimenCommentsPermission.class)) - { - boolean manualQCEnabled = SettingsManager.get().getDisplaySettings(getViewContext().getContainer()).isEnableManualQCFlagging(); - if (commentsMode) - { - MenuButton commentsMenuButton = new MenuButton("Comments" + (manualQCEnabled ? " and QC" : "")); - final String jsRegionObject = DataRegion.getJavaScriptObjectReference(gridView.getSettings().getDataRegionName()); - String setCommentsURL = urlFor(UpdateCommentsAction.class).toString(); - NavTree setItem = commentsMenuButton.addMenuItem("Set Vial Comment " + (manualQCEnabled ? "or QC State " : "") + "for Selected", - "if (verifySelected(" + jsRegionObject + ".form, '" + setCommentsURL + - "', 'post', 'rows')) " + jsRegionObject + ".form.submit(); return false;"); - setItem.setId("Comments:Set"); - - String clearCommentsURL = urlFor(ClearCommentsAction.class).toString(); - NavTree clearItem = commentsMenuButton.addMenuItem("Clear Vial Comments for Selected", - "if (verifySelected(" + jsRegionObject + ".form, '" + clearCommentsURL + - "', 'post', 'rows') && confirm('This will permanently clear comments for all selected vials. " + - (manualQCEnabled ? "Quality control states will remain unchanged. " : "" )+ "Continue?')) " + - jsRegionObject + ".form.submit();\nreturn false;"); - clearItem.setId("Comments:Clear"); - - ActionURL endCommentsURL = getViewContext().getActionURL().clone(); - endCommentsURL.replaceParameter(SpecimenViewTypeForm.PARAMS.viewMode, SpecimenQueryView.Mode.REQUESTS.name()); - NavTree exitItem = commentsMenuButton.addMenuItem("Exit Comments " + (manualQCEnabled ? "and QC " : "") + "mode", endCommentsURL); - exitItem.setId("Comments:Exit"); - - Study study = getStudy(); - String subjectNoun = StudyService.get().getSubjectNounSingular(getContainer()); - boolean addSep = true; - - Integer participantCommentDatasetId = StudyInternalService.get().getParticipantCommentDatasetId(study); - - if (participantCommentDatasetId != null && participantCommentDatasetId != -1) - { - Dataset ds = StudyService.get().getDataset(study.getContainer(), participantCommentDatasetId); - if (ds != null && ds.getTableInfo(getUser()).hasPermission(getUser(), UpdatePermission.class)) - { - if (addSep) - { - commentsMenuButton.addSeparator(); - addSep = false; - } - NavTree ptidComments = commentsMenuButton.addMenuItem("Manage " + PageFlowUtil.filter(subjectNoun) + " Comments", - urlProvider(StudyUrls.class).getDatasetURL(getContainer(), participantCommentDatasetId)); - ptidComments.setId("Comments:SetParticipant"); - } - } - - Integer participantVisitCommentDatasetId = StudyInternalService.get().getParticipantVisitCommentDatasetId(study); - - if (participantVisitCommentDatasetId != null && participantVisitCommentDatasetId != -1) - { - Dataset ds = StudyService.get().getDataset(study.getContainer(), participantVisitCommentDatasetId); - TableInfo table = null==ds ? null : ds.getTableInfo(getUser()); - if (null != table && table.hasPermission(getUser(), UpdatePermission.class)) - { - if (addSep) - { - commentsMenuButton.addSeparator(); - } - NavTree ptidComments = commentsMenuButton.addMenuItem("Manage " + PageFlowUtil.filter(subjectNoun) + "/Visit Comments", - urlProvider(StudyUrls.class).getDatasetURL(getContainer(), participantVisitCommentDatasetId)); - ptidComments.setId("Comments:SetParticipantVisit"); - } - } - buttons.add(commentsMenuButton); - } - else - { - ActionURL enableCommentsURL = getViewContext().getActionURL().clone(); - enableCommentsURL.replaceParameter(SpecimenViewTypeForm.PARAMS.viewMode, SpecimenQueryView.Mode.COMMENTS.name()); - ActionButton commentsButton = new ActionButton("Enable Comments" + (manualQCEnabled ? "/QC" : ""), enableCommentsURL); - buttons.add(commentsButton); - } - } - - if (getViewContext().hasPermission(AdminPermission.class)) - { - ButtonBuilder.Button upload = new ButtonBuilder("Import Specimens") - .href(new ActionURL(ShowUploadSpecimensAction.class, getContainer())) - .build(); - buttons.add(upload); - } - - if (settings.isEnableRequests() && getViewContext().getContainer().hasPermission(getViewContext().getUser(), RequestSpecimensPermission.class)) - { - buttons.add(new ButtonBarLineBreak()); - buttons.add(new ExcludeSiteDropDown()); - } - - gridView.setButtons(buttons); - return gridView; - } - - public static final class ExcludeSiteDropDown extends DisplayElement - { - @Override - public void render(RenderContext ctx, HtmlWriter out) - { - ActionURL url = ctx.getViewContext().cloneActionURL(); - url.deleteParameter(SpecimenQueryView.PARAMS.excludeRequestedBySite.name()); - - out.write("Hide Previously Requested By "); - - String excludeStr = ctx.getRequest().getParameter(SpecimenQueryView.PARAMS.excludeRequestedBySite.name()); - int locationId = null == StringUtils.trimToNull(excludeStr) ? 0 : Integer.parseInt(excludeStr); - List locations = LocationManager.get().getValidRequestingLocations(ctx.getContainer()); - - new SelectBuilder() - .addOption("", "") - .addOptions(locations.stream().map(location -> new OptionBuilder(location.getDisplayName(), location.getRowId()))) - .selected(locationId) - .onChange("window.location=" + PageFlowUtil.jsString(url + "&excludeRequestedBySite=") + " + this.options[this.selectedIndex].value;") - .className(null) - .appendTo(out); - } - } - - public GridView getRequestEventGridView(HttpServletRequest request, BindException errors, SimpleFilter filter) - { - DataRegion rgn = new DataRegion(); - TableInfo tableInfoRequestEvent = SpecimenSchema.get().getTableInfoSampleRequestEvent(); - rgn.setTable(tableInfoRequestEvent); - rgn.setColumns(tableInfoRequestEvent.getColumns("Created", "EntryType", "Comments", "CreatedBy", "EntityId")); - rgn.getDisplayColumn("EntityId").setVisible(false); - - DataColumn commentsColumn = (DataColumn) rgn.getDisplayColumn("Comments"); - commentsColumn.setWidth("50%"); - commentsColumn.setPreserveNewlines(true); - rgn.addDisplayColumn(new AttachmentDisplayColumn(request)); - GridView grid = new GridView(rgn, errors); - grid.setFilter(filter); - grid.setSort(new Sort("Created")); - return grid; - } - - private static boolean addIfNotPresent(SpecimenRequestActor actor, LocationImpl location, List list) - { - for (ActorNotificationRecipientSet actorSite : list) - { - if (actorSite.getActor().getRowId() == actor.getRowId()) - { - if (actorSite.getLocation() == null && location == null) - return false; - else - if (actorSite.getLocation() != null && location != null && actorSite.getLocation().getRowId() == location.getRowId()) - return false; - } - } - list.add(new ActorNotificationRecipientSet(actor, location)); - return true; - } - - private static List getPossibleNotifications(SpecimenRequest specimenRequest) - { - List possibleNotifications = new ArrayList<>(); - // allow notification of all parties listed in the request requirements: - for (SpecimenRequestRequirement requirement : specimenRequest.getRequirements()) - addIfNotPresent(requirement.getActor(), requirement.getLocation(), possibleNotifications); - - // allow notification of all site-based actors at the destination site, and all study-wide actors: - Map relevantSites = new IntHashMap<>(); - if (specimenRequest.getDestinationSiteId() == null) - { - throw new IllegalStateException("Request " + specimenRequest.getRowId() + " in folder " + - specimenRequest.getContainer().getPath() + " does not have a valid destination site id."); - } - LocationImpl destLocation = LocationManager.get().getLocation(specimenRequest.getContainer(), specimenRequest.getDestinationSiteId().intValue()); - relevantSites.put(destLocation.getRowId(), destLocation); - for (Vial vial : specimenRequest.getVials()) - { - LocationImpl location = LocationManager.get().getCurrentLocation(vial); - if (location != null && !relevantSites.containsKey(location.getRowId())) - relevantSites.put(location.getRowId(), location); - } - - SpecimenRequestActor[] allActors = SpecimenRequestRequirementProvider.get().getActors(specimenRequest.getContainer()); - // add study-wide actors and actors from all relevant sites: - for (SpecimenRequestActor actor : allActors) - { - if (actor.isPerSite()) - { - for (LocationImpl location : relevantSites.values()) - { - if (actor.isPerSite()) - addIfNotPresent(actor, location, possibleNotifications); - } - } - else - addIfNotPresent(actor, null, possibleNotifications); - } - - possibleNotifications.sort((first, second) -> - { - String firstSite = first.getLocation() != null ? first.getLocation().getLabel() : ""; - String secondSite = second.getLocation() != null ? second.getLocation().getLabel() : ""; - int comp = firstSite.compareToIgnoreCase(secondSite); - if (comp == 0) - { - String firstActorLabel = first.getActor().getLabel(); - if (firstActorLabel == null) - firstActorLabel = ""; - String secondActorLabel = second.getActor().getLabel(); - if (secondActorLabel == null) - secondActorLabel = ""; - comp = firstActorLabel.compareToIgnoreCase(secondActorLabel); - } - return comp; - }); - return possibleNotifications; - } - - private String getShortSiteLabel(LocationImpl location) - { - String label; - if (location.getLabel() != null && !location.getLabel().isEmpty()) - label = location.getLabel().substring(0, Math.min(location.getLabel().length(), 15)); - else if (location.getLdmsLabCode() != null) - label = "ldmsId" + location.getLdmsLabCode(); - else if (location.getLabwareLabCode() != null && !location.getLabwareLabCode().isEmpty()) - label = "labwareId" + location.getLabwareLabCode(); - else - label = "rowId" + location.getRowId(); - return label.replaceAll("\\W+", "_"); - } - - private String getSpecimenListFileName(LocationImpl srcLocation, LocationImpl destLocation) - { - String filename = getShortSiteLabel(srcLocation) + "_to_" + getShortSiteLabel(destLocation) + - "_" + DateUtil.formatIsoDate(); - return filename; - } - - public SimpleFilter getSpecimenListFilter(SpecimenRequest specimenRequest, LocationImpl srcLocation, LabSpecimenListsBean.Type type) - { - LabSpecimenListsBean bean = new LabSpecimenListsBean(specimenRequest, type); - List vials = bean.getSpecimens(srcLocation); - Object[] params = new Object[vials.size() + 1]; - params[params.length - 1] = specimenRequest.getContainer().getId(); - StringBuilder whereClause = new StringBuilder(); - whereClause.append("("); - int i = 0; - for (Vial vial : vials) - { - if (i > 0) - whereClause.append(" OR "); - whereClause.append("RowId = ?"); - params[i++] = vial.getRowId(); - } - whereClause.append(") AND Container = ?"); - - SimpleFilter filter = new SimpleFilter(); - filter.addWhereClause(whereClause.toString(), params, FieldKey.fromParts("RowId"), FieldKey.fromParts("Container")); - return filter; - } - - public TSVGridWriter getSpecimenListTsvWriter(SpecimenRequest specimenRequest, LocationImpl srcLocation, - LocationImpl destLocation, LabSpecimenListsBean.Type type) - { - DataRegion dr = createDataRegionForWriters(specimenRequest); - RenderContext ctx = new RenderContext(getViewContext()); - ctx.setContainer(specimenRequest.getContainer()); - ctx.setBaseFilter(getSpecimenListFilter(specimenRequest, srcLocation, type)); - List cols = dr.getDisplayColumns(); - TSVGridWriter tsv = new TSVGridWriter(()->dr.getResults(ctx), cols); - tsv.setFilenamePrefix(getSpecimenListFileName(srcLocation, destLocation)); - return tsv; - } - - public ExcelWriter getSpecimenListXlsWriter(SpecimenRequest specimenRequest, LocationImpl srcLocation, - LocationImpl destLocation, LabSpecimenListsBean.Type type) - { - DataRegion dr = createDataRegionForWriters(specimenRequest); - RenderContext ctx = new RenderContext(getViewContext()); - ctx.setContainer(specimenRequest.getContainer()); - ctx.setBaseFilter(getSpecimenListFilter(specimenRequest, srcLocation, type)); - List cols = dr.getDisplayColumns(); - ExcelWriter xl = new ExcelWriter(() -> dr.getResults(ctx), cols); - xl.setFilenamePrefix(getSpecimenListFileName(srcLocation, destLocation)); - return xl; - } - - private DataRegion createDataRegionForWriters(SpecimenRequest specimenRequest) - { - DataRegion dr = new DataRegion(); - Container container = specimenRequest.getContainer(); - SpecimenQuerySchema querySchema = SpecimenQuerySchema.get(StudyService.get().getStudy(container), getViewContext().getUser()); - TableInfo table = querySchema.getTable(SpecimenTablesProvider.LOCATION_SPECIMEN_LIST_TABLE_NAME, true); - QueryDefinition queryDef = querySchema.getQueryDefForTable(SpecimenTablesProvider.LOCATION_SPECIMEN_LIST_TABLE_NAME); - CustomView defaultView = QueryService.get().getCustomView(getViewContext().getUser(), container, getViewContext().getUser(), querySchema.getName(), queryDef.getName(), null); - List columns = queryDef.getColumns(defaultView, table); - dr.setTable(table); - dr.setColumns(columns); - return dr; - } - - private static final String SELECTED_SPECIMENS_SESSION_ATTRIB_KEY = SpecimenController.class.getName() + "/SelectedSpecimens"; - - private Set getSelectionLsids() - { - // save the selected set of participantdataset lsids in the session; this is the only obvious way - // to let the user apply subsequent filters and switch back and forth between vial and specimen view - // without losing their original participant/visit selection. - Set lsids = null; - if (isPost()) - lsids = DataRegionSelection.getSelected(getViewContext(), true); - HttpSession session = getViewContext().getRequest().getSession(true); - Pair> selectionCache = (Pair>) session.getAttribute(SELECTED_SPECIMENS_SESSION_ATTRIB_KEY); - - boolean newFilter = (lsids != null && !lsids.isEmpty()); - boolean cachedFilter = selectionCache != null && getContainer().equals(selectionCache.getKey()); - if (!newFilter && !cachedFilter) - { - throw new RedirectException(getSpecimensURL()); - } - - if (newFilter) - selectionCache = new Pair<>(getContainer(), lsids); - - session.setAttribute(SELECTED_SPECIMENS_SESSION_ATTRIB_KEY, selectionCache); - return selectionCache.getValue(); - } - - @RequiresPermission(ReadPermission.class) - public class BeginAction extends SimpleRedirectAction - { - @Override - public ActionURL getRedirectURL(Object o) - { - return getSpecimensURL(); - } - } - - @RequiresPermission(ReadPermission.class) - public class OverviewAction extends SimpleViewAction - { - @Override - public ModelAndView getView(Object form, BindException errors) - { - if (null == StudyService.get().getStudy(getContainer())) - return HtmlView.of("This folder does not contain a study."); - SpecimenSearchWebPart specimenSearch = new SpecimenSearchWebPart(true); - SpecimenWebPart specimenSummary = new SpecimenWebPart(true, StudyService.get().getStudy(getContainer())); - return new VBox(specimenSummary, specimenSearch); - } - - @Override - public void addNavTrail(NavTree root) - { - addBaseSpecimenNavTrail(root); - } - } - - public static class SpecimenWebPartForm - { - private String[] _grouping1; - private String[] _grouping2; - private String[] _columns; - - public String[] getGrouping1() - { - return _grouping1; - } - - public void setGrouping1(String[] grouping1) - { - _grouping1 = grouping1; - } - - public String[] getGrouping2() - { - return _grouping2; - } - - public void setGrouping2(String[] grouping2) - { - _grouping2 = grouping2; - } - - public String[] getColumns() - { - return _columns; - } - - public void setColumns(String[] columns) - { - _columns = columns; - } - } - - @RequiresPermission(ManageDisplaySettingsPermission.class) - public static class ManageSpecimenWebPartAction extends SimpleViewAction - { - @Override - public ModelAndView getView(SpecimenWebPartForm form, BindException errors) - { - RepositorySettings settings = SettingsManager.get().getRepositorySettings(getContainer()); - ArrayList groupings = settings.getSpecimenWebPartGroupings(); - form.setGrouping1(groupings.get(0)); - form.setGrouping2(groupings.get(1)); - form.setColumns(SpecimenRequestManager.get().getGroupedValueAllowedColumns()); - return new JspView<>("/org/labkey/specimen/view/manageSpecimenWebPart.jsp", form); - } - - @Override - public void addNavTrail(NavTree root) - { - setHelpTopic("manageSpecimens#group"); - urlProvider(StudyUrls.class).addManageStudyNavTrail(root, getContainer(), getUser()); - root.addChild("Configure Specimen Web Part"); - } - } - - @RequiresPermission(AdminPermission.class) - public class SaveSpecimenWebPartSettingsAction extends MutatingApiAction - { - @Override - public ApiResponse execute(SpecimenWebPartForm form, BindException errors) - { - ApiSimpleResponse response = new ApiSimpleResponse(); - Study study = getStudy(); - if (study != null) - { - Container container = getContainer(); - RepositorySettings settings = SettingsManager.get().getRepositorySettings(container); - ArrayList groupings = new ArrayList<>(2); - groupings.add(form.getGrouping1()); - groupings.add(form.getGrouping2()); - settings.setSpecimenWebPartGroupings(groupings); - org.labkey.specimen.settings.SettingsManager.get().saveRepositorySettings(container, settings); - response.put("success", true); - return response; - } - else - throw new IllegalStateException("A study does not exist in this folder"); - } - } - - public static class PipelineForm extends PipelinePathForm - { - private String replaceOrMerge = "replace"; - - public String getReplaceOrMerge() - { - return replaceOrMerge; - } - - public void setReplaceOrMerge(String replaceOrMerge) - { - this.replaceOrMerge = replaceOrMerge; - } - - public boolean isMerge() - { - return "merge".equals(this.replaceOrMerge); - } - } - - public static void submitSpecimenBatch(Container c, User user, ActionURL url, FileLike f, PipeRoot root, boolean merge) throws IOException - { - if (null == f || !f.exists() || !f.isFile()) - throw new NotFoundException(); - - SpecimenBatch batch = new SpecimenBatch(new ViewBackgroundInfo(c, user, url), f, root, merge); - batch.submit(); - } - - @RequiresPermission(AdminPermission.class) - public static class SubmitSpecimenBatchImportAction extends FormHandlerAction - { - @Override - public void validateCommand(PipelineForm target, Errors errors) - { - } - - @Override - public boolean handlePost(PipelineForm form, BindException errors) throws Exception - { - Container c = getContainer(); - PipeRoot root = PipelineService.get().findPipelineRoot(c); - boolean first = true; - for (FileLike f : form.getValidatedFiles(c)) - { - // Only possibly overwrite when the first archive is loaded: - boolean merge = !first || form.isMerge(); - submitSpecimenBatch(c, getUser(), getViewContext().getActionURL(), f, root, merge); - first = false; - } - return true; - } - - @Override - public ActionURL getSuccessURL(PipelineForm pipelineForm) - { - return urlProvider(PipelineStatusUrls.class).urlBegin(getContainer()); - } - } - - /** - * Legacy method hit via WGET/CURL to programmatically initiate a specimen import; no longer used by the UI, - * but this method should be kept around until we receive verification that the URL is no longer being hit - * programmatically. - */ - @RequiresPermission(AdminPermission.class) - public static class SubmitSpecimenImport extends FormHandlerAction - { - @Override - public void validateCommand(PipelineForm target, Errors errors) - { - } - - @Override - public boolean handlePost(PipelineForm form, BindException errors) throws Exception - { - Container c = getContainer(); - String path = form.getPath(); - FileLike f = null; - - PipeRoot root = PipelineService.get().findPipelineRoot(c); - if (path != null) - { - if (root != null) - f = root.resolvePathToFileLike(path); - } - - submitSpecimenBatch(c, getUser(), getViewContext().getActionURL(), f, root, form.isMerge()); - return true; - } - - @Override - public ActionURL getSuccessURL(PipelineForm pipelineForm) - { - return urlProvider(PipelineStatusUrls.class).urlBegin(getContainer()); - } - } - - public static class ImportSpecimensBean - { - private final String _path; - private final List _archives; - private final List _errors; - private final Container _container; - private final String[] _files; - - private boolean noSpecimens = false; - private boolean _defaultMerge = false; - private boolean _isEditableSpecimens = false; - - public ImportSpecimensBean(Container container, List archives, - String path, String[] files, List errors) - { - _path = path; - _files = files; - _archives = archives; - _errors = errors; - _container = container; - } - - public List getArchives() - { - return _archives; - } - - public String getPath() - { - return _path; - } - - public String[] getFiles() - { - return _files; - } - - public List getErrors() - { - return _errors; - } - - public Container getContainer() - { - return _container; - } - - public boolean isNoSpecimens() - { - return noSpecimens; - } - - public void setNoSpecimens(boolean noSpecimens) - { - this.noSpecimens = noSpecimens; - } - - public boolean isDefaultMerge() - { - return _defaultMerge; - } - - public void setDefaultMerge(boolean defaultMerge) - { - _defaultMerge = defaultMerge; - } - - public boolean isEditableSpecimens() - { - return _isEditableSpecimens; - } - - public void setEditableSpecimens(boolean editableSpecimens) - { - _isEditableSpecimens = editableSpecimens; - } - } - - @RequiresPermission(AdminPermission.class) - public class ImportSpecimenDataAction extends SimpleViewAction - { - private String[] _filePaths = null; - - @Override - public ModelAndView getView(PipelineForm form, BindException bindErrors) - { - List dataFiles = form.getValidatedFiles(getContainer()); - List archives = new ArrayList<>(); - List errors = new ArrayList<>(); - _filePaths = form.getFile(); - for (FileLike dataFile : dataFiles) - { - if (null == dataFile || !dataFile.exists() || !dataFile.isFile()) - { - throw new NotFoundException(); - } - - if (!dataFile.toNioPathForRead().toFile().canRead()) - errors.add("Can't read data file: " + dataFile); - - SpecimenArchive archive = new SpecimenArchive(dataFile); - archives.add(archive); - } - - ImportSpecimensBean bean = new ImportSpecimensBean(getContainer(), archives, form.getPath(), form.getFile(), errors); - boolean isEmpty = SpecimenManager.get().isSpecimensEmpty(getContainer(), getUser()); - if (isEmpty) - { - bean.setNoSpecimens(true); - } - else if (SettingsManager.get().getRepositorySettings(getStudyThrowIfNull().getContainer()).isSpecimenDataEditable()) - { - bean.setDefaultMerge(true); // Repository is editable; make Merge the default - bean.setEditableSpecimens(true); - } - - return new JspView<>("/org/labkey/specimen/view/importSpecimens.jsp", bean); - } - - @Override - public void addNavTrail(NavTree root) - { - String msg; - if (_filePaths.length == 1) - msg = _filePaths[0]; - else - msg = _filePaths.length + " specimen archives"; - root.addChild("Import Study Batch - " + msg); - } - } - - @RequiresPermission(AdminPermission.class) - public class GetSpecimenExcelAction extends ExportAction - { - @Override - public void export(Object o, HttpServletResponse response, BindException errors) throws Exception - { - SimpleSpecimenImporter importer = new SimpleSpecimenImporter(getContainer(), getUser(), getStudyRedirectIfNull().getTimepointType(), StudyService.get().getSubjectNounSingular(getContainer())); - MapArrayExcelWriter xlWriter = new MapArrayExcelWriter(new ArrayList<>(), importer.getSimpleSpecimenColumns()); - // Note: I don't think this is having any effect on the output because ExcelColumn.renderCaption() uses - // the DisplayColumn's caption, not its own caption. That seems wrong... - xlWriter.setColumnModifier(col -> col.setCaption(importer.label(col.getName()))); - xlWriter.renderWorkbook(response); - } - } - - public static class SpecimenEventForm - { - private String _id; - private Container _targetStudy; - - public String getId() - { - return _id; - } - - public void setId(String id) - { - _id = id; - } - - public Container getTargetStudy() - { - return _targetStudy; - } - - @SuppressWarnings("unused") - public void setTargetStudy(Container targetStudy) - { - _targetStudy = targetStudy; - } - } - - @SuppressWarnings("unused") // Referenced in SpecimenForeignKey - @RequiresPermission(ReadPermission.class) - public static class SpecimenEventsRedirectAction extends SimpleViewAction - { - @Override - public ModelAndView getView(SpecimenEventForm form, BindException errors) - { - Container targetStudy = form.getTargetStudy(); - // Note that the query schema used by getVial() below will not select the vial if the user doesn't have - // read permissions in the target study, so technically this permission check is redundant. However, - // getVial() without read permissions leads to an NPE, so checking here improves behavior. - if (form.getId() != null && targetStudy != null && targetStudy.hasPermission(getUser(), ReadPermission.class)) - { - Vial vial = SpecimenManager.get().getVial(targetStudy, getUser(), form.getId()); - if (vial != null) - { - ActionURL url = new ActionURL(SpecimenEventsAction.class, targetStudy).addParameter("id", vial.getRowId()); - throw new RedirectException(url); - } - } - return HtmlView.err("Unable to resolve the Specimen ID and target Study"); - } - - @Override - public void addNavTrail(NavTree root) - { - } - } - - public static class SpecimenEventAttachmentForm implements BaseDownloadAction.InlineDownloader - { - private int _eventId; - private String _name; - - public int getEventId() - { - return _eventId; - } - - public void setEventId(int eventId) - { - _eventId = eventId; - } - - public String getName() - { - return _name; - } - - public void setName(String name) - { - _name = name; - } - } - - public static ActionURL getDownloadURL(SpecimenRequestEvent event, String name) - { - return new ActionURL(DownloadAction.class, event.getContainer()) - .addParameter("eventId", event.getRowId()) - .addParameter("name", name); - } - - @RequiresPermission(ReadPermission.class) - public static class DownloadAction extends BaseDownloadAction - { - @Override - public @Nullable Pair getAttachment(SpecimenEventAttachmentForm form) - { - SpecimenRequestEvent event = SpecimenRequestManager.get().getRequestEvent(getContainer(), form.getEventId()); - if (event == null) - throw new NotFoundException("Specimen event not found"); - - return new Pair<>(event, form.getName()); - } - } - - @RequiresPermission(ReadPermission.class) - public class AutoReportListAction extends SimpleViewAction - { - @Override - public ModelAndView getView(Object form, BindException errors) - { - return new JspView<>("/org/labkey/specimen/view/autoReportList.jsp", new ReportConfigurationBean(getViewContext())); - } - - @Override - public void addNavTrail(NavTree root) - { - setHelpTopic("exploreSpecimens"); - addBaseSpecimenNavTrail(root); - root.addChild("Specimen Reports"); - } - } - - @RequiresPermission(ReadPermission.class) - public class SpecimenRequestConfigRequiredAction extends SimpleViewAction - { - @Override - public ModelAndView getView(Object o, BindException errors) - { - return new JspView<>("/org/labkey/specimen/view/configurationRequired.jsp"); - } - - @Override - public void addNavTrail(NavTree root) - { - addRootNavTrail(root); - root.addChild("Unable to Request Specimens"); - } - } - - @RequiresPermission(ManageRequestSettingsPermission.class) - public class ConfigureRequestabilityRulesAction extends SimpleViewAction - { - @Override - public ModelAndView getView(Object o, BindException errors) - { - ensureSpecimenRequestsConfigured(false); - return new JspView<>("/org/labkey/specimen/view/configRequestabilityRules.jsp"); - } - - @Override - public void addNavTrail(NavTree root) - { - setHelpTopic("coordinateSpecimens#requestability"); - addManageStudyNavTrail(root); - root.addChild("Configure Requestability Rules"); - } - } - - @RequiresPermission(ReadPermission.class) - @RequiresLogin - public class ViewRequestsAction extends SimpleViewAction - { - @Override - public ModelAndView getView(Object o, BindException errors) - { - SpecimenRequestQueryView grid = SpecimenRequestQueryView.createView(getViewContext()); - grid.setExtraLinks(true); - grid.setShowCustomizeLink(false); - grid.setShowDetailsColumn(false); - if (getContainer().hasPermission(getUser(), RequestSpecimensPermission.class)) - { - ActionButton insertButton = new ActionButton(new ActionURL(ShowCreateSpecimenRequestAction.class, getContainer()), "Create New Request", ActionButton.Action.LINK); - grid.setButtons(Collections.singletonList(insertButton)); - } - else - { - grid.setButtons(Collections.emptyList()); - } - - JspView header = new JspView<>("/org/labkey/specimen/view/viewRequestsHeader.jsp", - new ViewRequestsHeaderBean(getViewContext(), grid)); - - return new VBox(header, grid); - } - - @Override - public void addNavTrail(NavTree root) - { - addSpecimenRequestsNavTrail(root); - } - } - - public static class ViewEventForm extends IdForm - { - private boolean _selected; - private boolean _vialView; - - public boolean isSelected() - { - return _selected; - } - - public void setSelected(boolean selected) - { - _selected = selected; - } - - public boolean isVialView() - { - return _vialView; - } - - public void setVialView(boolean vialView) - { - _vialView = vialView; - } - } - - @RequiresPermission(ReadPermission.class) - public class SpecimenEventsAction extends SimpleViewAction - { - private boolean _showingSelectedSpecimens; - - @Override - public ModelAndView getView(ViewEventForm viewEventForm, BindException errors) - { - Study study = getStudyThrowIfNull(); - _showingSelectedSpecimens = viewEventForm.isSelected(); - Vial vial = SpecimenManager.get().getVial(getContainer(), getUser(), viewEventForm.getId()); - if (vial == null) - throw new NotFoundException("Specimen " + viewEventForm.getId() + " does not exist."); - - JspView summaryView = new JspView<>("/org/labkey/specimen/view/specimen.jsp", - new SpecimenEventBean(vial, viewEventForm.getReturnUrl())); - summaryView.setTitle("Vial Summary"); - - SpecimenEventQueryView vialHistoryView = SpecimenEventQueryView.createView(getViewContext(), vial); - vialHistoryView.setTitle("Vial History"); - - VBox vbox; - - if (SettingsManager.get().getRepositorySettings(getStudyRedirectIfNull().getContainer()).isEnableRequests()) - { - List requestIds = SpecimenRequestManager.get().getRequestIdsForSpecimen(vial); - SimpleFilter requestFilter; - WebPartView relevantRequests; - - if (!requestIds.isEmpty()) - { - requestFilter = new SimpleFilter(); - StringBuilder whereClause = new StringBuilder(); - for (int i = 0; i < requestIds.size(); i++) - { - if (i > 0) - whereClause.append(" OR "); - whereClause.append("RequestId = ?"); - } - requestFilter.addWhereClause(whereClause.toString(), requestIds.toArray()); - SpecimenRequestQueryView queryView = SpecimenRequestQueryView.createView(getViewContext(), requestFilter); - queryView.setExtraLinks(true); - relevantRequests = queryView; - } - else - relevantRequests = new JspView<>("/org/labkey/specimen/view/relevantRequests.jsp"); - relevantRequests.setTitle("Relevant Vial Requests"); - vbox = new VBox(summaryView, vialHistoryView, relevantRequests); - } - else - { - vbox = new VBox(summaryView, vialHistoryView); - } - - return vbox; - } - - @Override - public void addNavTrail(NavTree root) - { - addBaseSpecimenNavTrail(root); - if (_showingSelectedSpecimens) - { - root.addChild("Selected Specimens", SpecimenController.getSpecimensURL(getContainer(), true)); - } - root.addChild("Vial History"); - } - } - - private boolean isNullOrBlank(String toCheck) - { - return ((toCheck == null) || toCheck.isEmpty()); - } - - @RequiresPermission(ManageNotificationsPermission.class) - public class ManageNotificationsAction extends FormViewAction - { - @Override - public void validateCommand(RequestNotificationSettings form, Errors errors) - { - String replyTo = form.getReplyTo(); - if (replyTo == null || replyTo.isEmpty()) - { - errors.reject(SpringActionController.ERROR_MSG, "Reply-to cannot be empty."); - } - else if (!RequestNotificationSettings.REPLY_TO_CURRENT_USER_VALUE.equals(replyTo)) - { - try - { - new ValidEmail(replyTo); - } - catch(ValidEmail.InvalidEmailException e) - { - errors.reject(SpringActionController.ERROR_MSG, replyTo + " is not a valid email address."); - } - } - - String subjectSuffix = form.getSubjectSuffix(); - if (subjectSuffix == null || subjectSuffix.isEmpty()) - { - errors.reject(SpringActionController.ERROR_MSG, "Subject suffix cannot be empty."); - } - - try - { - form.getNewRequestNotifyAddresses(); - } - catch (ValidEmail.InvalidEmailException e) - { - errors.reject(SpringActionController.ERROR_MSG, e.getBadEmail() + " is not a valid email address."); - } - - try - { - form.getCCAddresses(); - } - catch (ValidEmail.InvalidEmailException e) - { - errors.reject(SpringActionController.ERROR_MSG, e.getBadEmail() + " is not a valid email address."); - } - } - - @Override - public ModelAndView getView(RequestNotificationSettings form, boolean reshow, BindException errors) - { - ensureSpecimenRequestsConfigured(false); - - // try to get the settings from the form, just in case this is a reshow: - RequestNotificationSettings settings = form; - if (settings == null || settings.getReplyTo() == null) - settings = org.labkey.specimen.settings.SettingsManager.get().getRequestNotificationSettings(getContainer()); - - return new JspView<>("/org/labkey/specimen/view/manageNotifications.jsp", settings, errors); - } - - @Override - public boolean handlePost(RequestNotificationSettings settings, BindException errors) - { - ensureSpecimenRequestsConfigured(false); - - if (!settings.isNewRequestNotifyCheckbox()) - settings.setNewRequestNotify(null); - else - { - if (isNullOrBlank(settings.getNewRequestNotify())) - errors.reject(ERROR_MSG, "New request notify is blank and send email is checked"); - } - if (!settings.isCcCheckbox()) - settings.setCc(null); - else - { - if (isNullOrBlank(settings.getCc())) - errors.reject(ERROR_MSG, "Always CC is blank and send email is checked"); - } - if (errors.hasErrors()) - return false; - - org.labkey.specimen.settings.SettingsManager.get().saveRequestNotificationSettings(getContainer(), settings); - return true; - } - - @Override - public ActionURL getSuccessURL(RequestNotificationSettings form) - { - return getManageStudyURL(); - } - - @Override - public void addNavTrail(NavTree root) - { - setHelpTopic("coordinateSpecimens#notify"); - addManageStudyNavTrail(root); - root.addChild("Manage Notifications"); - } - } - - @RequiresPermission(ManageDisplaySettingsPermission.class) - public class ManageDisplaySettingsAction extends FormViewAction - { - @Override - public void validateCommand(DisplaySettingsForm target, Errors errors) - { - } - - @Override - public ModelAndView getView(DisplaySettingsForm form, boolean reshow, BindException errors) - { - // try to get the settings from the form, just in case this is a reshow: - DisplaySettings settings = form.getBean(); - if (settings == null || settings.getLastVialEnum() == null) - settings = SettingsManager.get().getDisplaySettings(getContainer()); - - return new JspView<>("/org/labkey/specimen/view/manageDisplay.jsp", settings); - } - - @Override - public boolean handlePost(DisplaySettingsForm form, BindException errors) - { - DisplaySettings settings = form.getBean(); - org.labkey.specimen.settings.SettingsManager.get().saveDisplaySettings(getContainer(), settings); - - return true; - } - - @Override - public ActionURL getSuccessURL(DisplaySettingsForm displaySettingsForm) - { - return getManageStudyURL(); - } - - @Override - public void addNavTrail(NavTree root) - { - setHelpTopic("specimenRequest#display"); - addManageStudyNavTrail(root); - root.addChild("Manage Specimen Display Settings"); - } - } - - public static class DisplaySettingsForm extends BeanViewForm - { - public DisplaySettingsForm() - { - super(DisplaySettings.class); - } - } - - public static class UpdateRequestabilityRulesForm implements HasViewContext - { - private ViewContext _viewContext; - private String[] _ruleType; - private String[] _ruleData; - private String[] _markType; - - @Override - public ViewContext getViewContext() - { - return _viewContext; - } - - @Override - public void setViewContext(ViewContext viewContext) - { - _viewContext = viewContext; - } - - public String[] getRuleType() - { - return _ruleType; - } - - @SuppressWarnings("unused") - public void setRuleType(String[] ruleType) - { - _ruleType = ruleType; - } - - public String[] getRuleData() - { - return _ruleData; - } - - @SuppressWarnings("unused") - public void setRuleData(String[] ruleData) - { - _ruleData = ruleData; - } - - public String[] getMarkType() - { - return _markType; - } - - public void setMarkType(String[] markType) - { - _markType = markType; - } - } - - @RequiresPermission(ManageRequestSettingsPermission.class) - public static class UpdateRequestabilityRulesAction extends MutatingApiAction - { - @Override - public ApiResponse execute(UpdateRequestabilityRulesForm form, BindException errors) - { - final List rules = new ArrayList<>(); - for (int i = 0; i < form.getRuleType().length; i++) - { - String typeName = form.getRuleType()[i]; - RequestabilityManager.RuleType type = RequestabilityManager.RuleType.valueOf(typeName); - String dataString = form.getRuleData()[i]; - rules.add(type.createRule(getContainer(), dataString)); - } - RequestabilityManager.getInstance().saveRules(getContainer(), getUser(), rules); - - return new ApiSimpleResponse(Collections.singletonMap("savedCount", rules.size())); - } - } - - @RequiresPermission(ManageStudyPermission.class) - public class ManageRepositorySettingsAction extends FormViewAction - { - @Override - public ModelAndView getView(ManageRepositorySettingsForm from, boolean reshow, BindException errors) - { - return new JspView<>("/org/labkey/specimen/view/manageRepositorySettings.jsp", SettingsManager.get().getRepositorySettings(getContainer())); - } - - @Override - public void validateCommand(ManageRepositorySettingsForm form, Errors errors) - { - } - - @Override - public boolean handlePost(ManageRepositorySettingsForm form, BindException errors) throws Exception - { - RepositorySettings settings = SettingsManager.get().getRepositorySettings(getContainer()); - settings.setSimple(form.isSimple()); - settings.setEnableRequests(!form.isSimple() && form.isEnableRequests()); - settings.setSpecimenDataEditable(!form.isSimple() && form.isSpecimenDataEditable()); - org.labkey.specimen.settings.SettingsManager.get().saveRepositorySettings(getContainer(), settings); - - return true; - } - - @Override - public URLHelper getSuccessURL(ManageRepositorySettingsForm manageRepositorySettingsForm) - { - return getManageStudyURL(); - } - - @Override - public void addNavTrail(NavTree root) - { - setHelpTopic("specimenAdminTutorial"); - addManageStudyNavTrail(root); - root.addChild("Manage Repository Settings"); - } - } - - public static class ManageRepositorySettingsForm - { - private boolean _simple; - private boolean _enableRequests; - private boolean _specimenDataEditable; - - public boolean isSimple() - { - return _simple; - } - - public void setSimple(boolean simple) - { - _simple = simple; - } - - public boolean isEnableRequests() - { - return _enableRequests; - } - - public void setEnableRequests(boolean enableRequests) - { - _enableRequests = enableRequests; - } - - public boolean isSpecimenDataEditable() - { - return _specimenDataEditable; - } - - public void setSpecimenDataEditable(boolean specimenDataEditable) - { - _specimenDataEditable = specimenDataEditable; - } - } - - public static class DefaultRequirementsForm - { - private int _originatorActor; - private String _originatorDescription; - private int _providerActor; - private String _providerDescription; - private int _receiverActor; - private String _receiverDescription; - private int _generalActor; - private String _generalDescription; - private String _nextPage; - - public int getGeneralActor() - { - return _generalActor; - } - - @SuppressWarnings("unused") - public void setGeneralActor(int generalActor) - { - _generalActor = generalActor; - } - - public String getGeneralDescription() - { - return _generalDescription; - } - - @SuppressWarnings("unused") - public void setGeneralDescription(String generalDescription) - { - _generalDescription = generalDescription; - } - - public int getProviderActor() - { - return _providerActor; - } - - @SuppressWarnings("unused") - public void setProviderActor(int providerActor) - { - _providerActor = providerActor; - } - - public String getProviderDescription() - { - return _providerDescription; - } - - @SuppressWarnings("unused") - public void setProviderDescription(String providerDescription) - { - _providerDescription = providerDescription; - } - - public int getReceiverActor() - { - return _receiverActor; - } - - @SuppressWarnings("unused") - public void setReceiverActor(int receiverActor) - { - _receiverActor = receiverActor; - } - - public String getReceiverDescription() - { - return _receiverDescription; - } - - @SuppressWarnings("unused") - public void setReceiverDescription(String receiverDescription) - { - _receiverDescription = receiverDescription; - } - - public int getOriginatorActor() - { - return _originatorActor; - } - - @SuppressWarnings("unused") - public void setOriginatorActor(int originatorActor) - { - _originatorActor = originatorActor; - } - - public String getOriginatorDescription() - { - return _originatorDescription; - } - - @SuppressWarnings("unused") - public void setOriginatorDescription(String originatorDescription) - { - _originatorDescription = originatorDescription; - } - - public String getNextPage() - { - return _nextPage; - } - - @SuppressWarnings("unused") - public void setNextPage(String nextPage) - { - _nextPage = nextPage; - } - } - - @RequiresPermission(ManageRequestRequirementsPermission.class) - public class ManageDefaultReqsAction extends FormViewAction - { - @Override - public void validateCommand(DefaultRequirementsForm target, Errors errors) - { - } - - @Override - public ModelAndView getView(DefaultRequirementsForm defaultRequirementsForm, boolean reshow, BindException errors) - { - ensureSpecimenRequestsConfigured(true); - return new JspView<>("/org/labkey/specimen/view/manageDefaultReqs.jsp", - new ManageReqsBean(getUser(), getContainer())); - } - - @Override - public boolean handlePost(DefaultRequirementsForm form, BindException errors) - { - ensureSpecimenRequestsConfigured(true); - createDefaultRequirement(form.getOriginatorActor(), form.getOriginatorDescription(), SpecimenRequestRequirementType.ORIGINATING_SITE); - createDefaultRequirement(form.getProviderActor(), form.getProviderDescription(), SpecimenRequestRequirementType.PROVIDING_SITE); - createDefaultRequirement(form.getReceiverActor(), form.getReceiverDescription(), SpecimenRequestRequirementType.RECEIVING_SITE); - createDefaultRequirement(form.getGeneralActor(), form.getGeneralDescription(), SpecimenRequestRequirementType.NON_SITE_BASED); - return true; - } - - private void createDefaultRequirement(Integer actorId, String description, SpecimenRequestRequirementType type) - { - if (actorId != null && actorId.intValue() > 0 && description != null && !description.isEmpty()) - { - SpecimenRequestRequirement requirement = new SpecimenRequestRequirement(); - requirement.setContainer(getContainer()); - requirement.setActorId(actorId); - requirement.setDescription(description); - requirement.setRequestId(-1); - SpecimenRequestRequirementProvider.get().createDefaultRequirement(getUser(), requirement, type); - } - } - - @Override - public ActionURL getSuccessURL(DefaultRequirementsForm form) - { - if (form.getNextPage() != null && !form.getNextPage().isEmpty()) - return new ActionURL(form.getNextPage()); - else - return getManageStudyURL(); - } - - @Override - public void addNavTrail(NavTree root) - { - setHelpTopic("coordinateSpecimens#requirements"); - addManageStudyNavTrail(root); - root.addChild("Manage Default Requirements"); - } - } - - @RequiresPermission(ManageRequestRequirementsPermission.class) - public class DeleteDefaultRequirementAction extends FormHandlerAction - { - @Override - public void validateCommand(IdForm target, Errors errors) - { - } - - @Override - public boolean handlePost(IdForm form, BindException errors) throws Exception - { - SpecimenRequestRequirement requirement = - SpecimenRequestRequirementProvider.get().getRequirement(getContainer(), form.getId()); - // we should only be deleting default requirements (those without an associated request): - if (requirement != null && requirement.getRequestId() == -1) - { - SpecimenRequestManager.get().deleteRequestRequirement(getUser(), requirement, false); - return true; - } - - return false; - } - - @Override - public ActionURL getSuccessURL(IdForm requirementForm) - { - return new ActionURL(ManageDefaultReqsAction.class, getContainer()); - } - } - - public static class CompleteSpecimenForm - { - private String _prefix; - - public String getPrefix() - { - return _prefix; - } - - public void setPrefix(String prefix) - { - _prefix = prefix; - } - } - - @RequiresPermission(AdminPermission.class) - public class CompleteSpecimenAction extends ReadOnlyApiAction - { - @Override - public ApiResponse execute(CompleteSpecimenForm form, BindException errors) - { - ApiSimpleResponse response = new ApiSimpleResponse(); - - Study study = getStudy(); - if (study == null) - throw new NotFoundException("No study exists in this folder."); - - List completions = new ArrayList<>(); - for (AjaxCompletion completion : getAjaxCompletions(study)) - completions.add(completion.toJSON()); - - response.put("completions", completions); - return response; - } - } - - private List getAjaxCompletions(Study study) - { - List completions = new ArrayList<>(); - String allString = "All " + PageFlowUtil.filter(StudyService.get().getSubjectNounPlural(study.getContainer())) + " (Large Report)"; - - completions.add(new AjaxCompletion(allString, allString)); - - for (String ptid : StudyService.get().getParticipantIds(study, getViewContext().getUser())) - completions.add(new AjaxCompletion(ptid, ptid)); - - return completions; - } - public static List getSpecimensFromRowIds(long[] requestedSampleIds, Container container, User user) - { - List requestedVials = null; - - if (requestedSampleIds != null) - { - List vials = new ArrayList<>(); - for (long requestedSampleId : requestedSampleIds) - { - Vial current = SpecimenManager.get().getVial(container, user, requestedSampleId); - if (current != null) - vials.add(current); - } - requestedVials = vials; - } - - return requestedVials; - } - - private static long[] toLongArray(Collection intStrings) - { - if (intStrings == null) - return null; - long[] converted = new long[intStrings.size()]; - int index = 0; - for (String intString : intStrings) - converted[index++] = Long.parseLong(intString); - return converted; - } - - public static List getSpecimensFromRowIds(Collection ids, Container container, User user) - { - return getSpecimensFromRowIds(toLongArray(ids), container, user); - } - - public List getSpecimensFromPost(boolean fromGroupedView, boolean onlyAvailable) - { - Set formValues = null; - if ("POST".equalsIgnoreCase(getViewContext().getRequest().getMethod())) - formValues = DataRegionSelection.getSelected(getViewContext(), true); - - if (formValues == null || formValues.isEmpty()) - return null; - - List selectedVials; - if (fromGroupedView) - { - Map> keyToVialMap = - SpecimenManager.get().getVialsForSpecimenHashes(getContainer(), getUser(), formValues, onlyAvailable); - List vials = new ArrayList<>(); - for (List vialList : keyToVialMap.values()) - vials.addAll(vialList); - selectedVials = new ArrayList<>(vials); - } - else - selectedVials = getSpecimensFromRowIds(formValues, getContainer(), getUser()); - return selectedVials; - } - - public static class CreateSpecimenRequestForm extends ReturnUrlForm implements HiddenFormInputGenerator - { - public enum PARAMS - { - returnUrl, - extendedRequestUrl, - ignoreReturnUrl - } - - private String[] _inputs; - private int _destinationLocation; - private long[] _specimenRowIds; - private boolean[] _required; - private boolean _fromGroupedView; - private Integer _preferredLocation; - private boolean _ignoreReturnUrl; - private boolean _extendedRequestUrl; - private String[] _specimenIds; - - @Override - public String getHiddenFormInputs(ViewContext ctx) - { - StringBuilder builder = new StringBuilder(); - if (_inputs != null) - { - for (String input : _inputs) - builder.append("\n"); - } - if (_destinationLocation != 0) - builder.append("\n"); - if (getReturnUrl() != null) - builder.append("\n"); - if (_specimenRowIds != null) - { - for (long specimenId : _specimenRowIds) - builder.append("\n"); - } - else - { - String dataRegionSelectionKey = ctx.getRequest().getParameter(DataRegionSelection.DATA_REGION_SELECTION_KEY); - if (dataRegionSelectionKey != null) - { - builder.append("\n"); - Set specimenFormValues = DataRegionSelection.getSelected(ctx, false); - for (String formValue : specimenFormValues) - { - builder.append("\n"); - } - } - } - - if (_required != null) - { - for (boolean required : _required) - builder.append("\n"); - } - - if (_specimenIds != null) - { - for (String specimenId : _specimenIds) - builder.append("\n"); - } - - builder.append("\n"); - return builder.toString(); - } - - public long[] getSpecimenRowIds() - { - return _specimenRowIds; - } - - public void setSpecimenRowIds(long[] specimenRowIds) - { - _specimenRowIds = specimenRowIds; - } - - public int getDestinationLocation() - { - return _destinationLocation; - } - - public void setDestinationLocation(int destinationLocation) - { - _destinationLocation = destinationLocation; - } - - public String[] getInputs() - { - return _inputs; - } - - public void setInputs(String[] inputs) - { - _inputs = inputs; - } - - public boolean[] getRequired() - { - return _required; - } - - public void setRequired(boolean[] required) - { - _required = required; - } - - public boolean isFromGroupedView() - { - return _fromGroupedView; - } - - public void setFromGroupedView(boolean fromGroupedView) - { - _fromGroupedView = fromGroupedView; - } - - public Integer getPreferredLocation() - { - return _preferredLocation; - } - - public void setPreferredLocation(Integer preferredLocation) - { - _preferredLocation = preferredLocation; - } - - public boolean isIgnoreReturnUrl() - { - return _ignoreReturnUrl; - } - - public void setIgnoreReturnUrl(boolean ignoreReturnUrl) - { - _ignoreReturnUrl = ignoreReturnUrl; - } - - public boolean isExtendedRequestUrl() - { - return _extendedRequestUrl; - } - - public void setExtendedRequestUrl(boolean extendedRequestUrl) - { - _extendedRequestUrl = extendedRequestUrl; - } - - public String[] getSpecimenIds() - { - return _specimenIds; - } - - public void setSpecimenIds(String[] specimenIds) - { - _specimenIds = specimenIds; - } - - public RequestedSpecimens getSelectedSpecimens(ViewContext ctx) throws AmbiguousLocationException - { - Container container = ctx.getContainer(); - User user = ctx.getUser(); - HttpServletRequest request = ctx.getRequest(); - - // first check for explicitly listed specimen row ids (this is the case when posting the final - // specimen request form): - List requestedSpecimens = getSpecimensFromRowIds(getSpecimenRowIds(), container, user); - if (requestedSpecimens != null && !requestedSpecimens.isEmpty()) - return new RequestedSpecimens(requestedSpecimens); - - Set ids; - if ("post".equalsIgnoreCase(request.getMethod()) && - (request.getParameter(DataRegionSelection.DATA_REGION_SELECTION_KEY) != null)) - { - ids = DataRegionSelection.getSelected(ctx, null, true); - if (isFromGroupedView()) - return SpecimenRequestManager.get().getRequestableBySpecimenHash(container, user, ids, getPreferredLocation()); - else - return getRequestableByVialRowIds(ids, container, user); - } - else if (_specimenIds != null && _specimenIds.length > 0) - { - ids = new HashSet<>(); - Collections.addAll(ids, _specimenIds); - if (isFromGroupedView()) - return SpecimenRequestManager.get().getRequestableBySpecimenHash(container, user, ids, getPreferredLocation()); - else - return getRequestableByVialGlobalUniqueIds(ids, container, user); - } - else - return null; - } - - private RequestedSpecimens getRequestableByVialRowIds(Set rowIds, Container container, User user) - { - Set ids = new HashSet<>(); - Arrays.stream(toLongArray(rowIds)).forEach(ids::add); - List requestedSpecimens = SpecimenManager.get().getRequestableVials(container, user, ids); - return new RequestedSpecimens(requestedSpecimens); - } - - private RequestedSpecimens getRequestableByVialGlobalUniqueIds(Set globalUniqueIds, Container container, User user) - { - List requestedVials = null; - - if (globalUniqueIds != null) - { - List vials = new ArrayList<>(); - for (String globalUniqueId : globalUniqueIds) - { - Vial match = SpecimenManager.get().getVial(container, user, globalUniqueId); - if (match != null) - vials.add(match); - } - requestedVials = new ArrayList<>(vials); - } - return new RequestedSpecimens(requestedVials); - } - } - - public abstract static class SpecimensViewBean - { - protected SpecimenQueryView _specimenQueryView; - protected List _vials; - - public SpecimensViewBean(ViewContext context, List vials, boolean showHistoryLinks, - boolean showRecordSelectors, boolean disableLowVialIndicators, boolean restrictRecordSelectors) - { - _vials = vials; - if (vials != null && !vials.isEmpty()) - { - _specimenQueryView = SpecimenQueryView.createView(context, vials, SpecimenQueryView.ViewType.VIALS); - _specimenQueryView.setShowHistoryLinks(showHistoryLinks); - _specimenQueryView.setShowRecordSelectors(showRecordSelectors); - _specimenQueryView.setDisableLowVialIndicators(disableLowVialIndicators); - _specimenQueryView.setRestrictRecordSelectors(restrictRecordSelectors); - } - } - - public SpecimenQueryView getSpecimenQueryView() - { - return _specimenQueryView; - } - - public List getVials() - { - return _vials; - } - } - - public static class NewRequestBean extends SpecimensViewBean - { - private final Container _container; - private final SpecimenRequestManager.SpecimenRequestInput[] _inputs; - private final String[] _inputValues; - private final int _selectedSite; - private final BindException _errors; - private final ActionURL _returnUrl; - - public NewRequestBean(ViewContext context, RequestedSpecimens requestedSpecimens, CreateSpecimenRequestForm form, BindException errors) throws SQLException - { - super(context, requestedSpecimens != null ? requestedSpecimens.getVials() : null, false, false, false, false); - _errors = errors; - _inputs = SpecimenRequestManager.get().getNewSpecimenRequestInputs(context.getContainer()); - _selectedSite = form.getDestinationLocation(); - _inputValues = form.getInputs(); - _container = context.getContainer(); - _returnUrl = form.getReturnActionURL(); - } - - public SpecimenRequestManager.SpecimenRequestInput[] getInputs() - { - return _inputs; - } - - public String getValue(int inputIndex) throws ValidationException - { - if (_inputValues != null && inputIndex < _inputValues.length && _inputValues[inputIndex] != null) - return _inputValues[inputIndex]; - if (_inputs[inputIndex].isRememberSiteValue() && _selectedSite > 0) - return _inputs[inputIndex].getDefaultSiteValues(_container).get(_selectedSite); - return ""; - } - - public int getSelectedSite() - { - return _selectedSite; - } - - public BindException getErrors() - { - return _errors; - } - - public ActionURL getReturnUrl() - { - return _returnUrl; - } - } - - @RequiresPermission(RequestSpecimensPermission.class) - public class HandleCreateSpecimenRequestAction extends FormViewAction - { - private SpecimenRequest _specimenRequest; - - @Override - public ModelAndView getView(CreateSpecimenRequestForm form, boolean reshow, BindException errors) throws Exception - { - return getCreateSpecimenRequestView(form, errors); - } - - @Override - public void addNavTrail(NavTree root) - { - addSpecimenRequestsNavTrail(root); - root.addChild("New Specimen Request"); - } - - @Override - public void validateCommand(CreateSpecimenRequestForm form, Errors errors) - { - boolean missingRequiredInput = false; - if (form.getDestinationLocation() <= 0) - missingRequiredInput = true; - - for (int i = 0; i < form.getInputs().length && !missingRequiredInput; i++) - { - if (form.getRequired()[i] && (form.getInputs()[i] == null || form.getInputs()[i].isEmpty())) - missingRequiredInput = true; - } - - if (missingRequiredInput) - errors.reject(null, "Please provide all required input."); - } - - @Override - public boolean handlePost(CreateSpecimenRequestForm form, BindException errors) throws Exception - { - ensureSpecimenRequestsConfigured(true); - - String[] inputs = form.getInputs(); - long[] specimenIds = form.getSpecimenRowIds(); - StringBuilder comments = new StringBuilder(); - SpecimenRequestManager.SpecimenRequestInput[] expectedInputs = - SpecimenRequestManager.get().getNewSpecimenRequestInputs(getContainer()); - if (inputs.length != expectedInputs.length) - throw new IllegalStateException("Expected a form element for each input."); - - for (int i = 0; i < expectedInputs.length; i++) - { - SpecimenRequestManager.SpecimenRequestInput expectedInput = expectedInputs[i]; - if (form.getDestinationLocation() != 0 && expectedInput.isRememberSiteValue()) - expectedInput.setDefaultSiteValue(getContainer(), form.getDestinationLocation(), inputs[i]); - if (i > 0) - comments.append("\n\n"); - comments.append(expectedInput.getTitle()).append(":\n"); - if (inputs[i] != null && !inputs[i].isEmpty()) - comments.append(inputs[i]); - else - comments.append("[Not provided]"); - } - - _specimenRequest = new SpecimenRequest(); - _specimenRequest.setComments(comments.toString()); - _specimenRequest.setContainer(getContainer()); - Timestamp ts = new Timestamp(System.currentTimeMillis()); - _specimenRequest.setCreated(ts); - _specimenRequest.setModified(ts); - _specimenRequest.setEntityId(GUID.makeGUID()); - Integer defaultSiteId = SpecimenService.get().getRequestCustomizer().getDefaultDestinationSiteId(); - // Default takes precedence if set - if (defaultSiteId != null) - { - _specimenRequest.setDestinationSiteId(defaultSiteId); - } - else if (form.getDestinationLocation() > 0) - { - _specimenRequest.setDestinationSiteId(form.getDestinationLocation()); - } - _specimenRequest.setStatusId(SpecimenRequestManager.get().getInitialRequestStatus(getContainer(), getUser(), false).getRowId()); - - DbScope scope = SpecimenSchema.get().getScope(); - try (DbScope.Transaction transaction = scope.ensureTransaction()) - { - if (!LocationManager.get().isSiteValidRequestingLocation(getContainer(), _specimenRequest.getDestinationSiteId())) - { - errors.reject(ERROR_MSG, "The requesting location is not valid."); - return false; - } - - User user = getUser(); - Container container = getContainer(); - _specimenRequest = SpecimenRequestManager.get().createRequest(getUser(), _specimenRequest, true); - List vials; - if (specimenIds != null && specimenIds.length > 0) - { - vials = new ArrayList<>(); - for (long specimenId : specimenIds) - { - Vial vial = SpecimenManager.get().getVial(container, user, specimenId); - if (vial != null) - { - boolean isAvailable = vial.isAvailable(); - if (!isAvailable) - { - errors.reject(null, RequestabilityManager.makeSpecimenUnavailableMessage(vial, "This specimen has been removed from the list below.")); - } - else - vials.add(vial); - } - else - { - ExceptionUtil.logExceptionToMothership(getViewContext().getRequest(), - new IllegalStateException("Specimen ID " + specimenId + " was not found in container " + container.getId())); - } - } - if (errors.getErrorCount() == 0) - { - try - { - SpecimenRequestManager.get().createRequestSpecimenMapping(getUser(), _specimenRequest, vials, true, true); - } - catch (RequestabilityManager.InvalidRuleException e) - { - errors.reject(ERROR_MSG, "The request could not be created because a requestability rule is configured incorrectly. " + - "Please report this problem to an administrator. Error details: " + e.getMessage()); - return false; - } - catch (SpecimenRequestException e) - { - errors.reject(ERROR_MSG, "A vial that was available for request has become unavailable."); - return false; - } - } - else - { - long[] validSpecimenIds = new long[vials.size()]; - int index = 0; - for (Vial vial : vials) - validSpecimenIds[index++] = vial.getRowId(); - form.setSpecimenRowIds(validSpecimenIds); - return false; - } - } - transaction.commit(); - } - - if (!SettingsManager.get().isSpecimenShoppingCartEnabled(getContainer())) - { - try - { - sendNewRequestNotifications(_specimenRequest, errors); - } - catch (ConfigurationException | IOException e) - { - errors.reject(ERROR_MSG, e.getMessage()); - } - } - - if (errors.hasErrors()) - return false; - - Study study = getStudy(); - if (null == study) - throw new NotFoundException("No study exists in this folder."); - StudyInternalService.get().setLastSpecimenRequest(study, _specimenRequest.getRowId()); - return true; - } - - @Override - public ActionURL getSuccessURL(CreateSpecimenRequestForm createSpecimenRequestForm) - { - ActionURL modifiedReturnURL = null; - if (createSpecimenRequestForm.isExtendedRequestUrl()) - { - return getExtendedRequestURL(_specimenRequest.getRowId(), null); - } - if (createSpecimenRequestForm.getReturnUrl() != null) - { - modifiedReturnURL = createSpecimenRequestForm.getReturnActionURL(); - } - if (modifiedReturnURL != null && !createSpecimenRequestForm.isIgnoreReturnUrl()) - return modifiedReturnURL; - else - return getManageRequestURL(_specimenRequest.getRowId(), modifiedReturnURL); - } - } - - private ModelAndView getCreateSpecimenRequestView(CreateSpecimenRequestForm form, BindException errors) throws SQLException - { - ensureSpecimenRequestsConfigured(true); - - RequestedSpecimens requested; - - try - { - requested = form.getSelectedSpecimens(getViewContext()); - } - catch (AmbiguousLocationException e) - { - // Even though this method (getCreateSpecimenRequestView) is used from multiple places, only HandleCreateSpecimenRequestAction - // receives a post; therefore, it's safe to say that the selectSpecimenProvider.jsp form should always post to - // HandleCreateSpecimenRequestAction. - return new JspView<>("/org/labkey/specimen/view/selectSpecimenProvider.jsp", - new SelectSpecimenProviderBean(form, e.getPossibleLocations(), new ActionURL(ShowCreateSpecimenRequestAction.class, getContainer())), errors); - } - - return new JspView<>("/org/labkey/specimen/view/requestSpecimens.jsp", - new NewRequestBean(getViewContext(), requested, form, errors)); - } - - @RequiresPermission(RequestSpecimensPermission.class) - public class ShowCreateSpecimenRequestAction extends SimpleViewAction - { - @Override - public ModelAndView getView(CreateSpecimenRequestForm form, BindException errors) throws Exception - { - return getCreateSpecimenRequestView(form, errors); - } - - @Override - public void addNavTrail(NavTree root) - { - setHelpTopic("specimenShopping"); - addSpecimenRequestsNavTrail(root); - root.addChild("New Specimen Request"); - } - } - - @RequiresPermission(RequestSpecimensPermission.class) - public class ShowAPICreateSpecimenRequestAction extends SimpleViewAction - { - @Override - public ModelAndView getView(CreateSpecimenRequestForm form, BindException errors) throws Exception - { - return getCreateSpecimenRequestView(form, errors); - } - - @Override - public void addNavTrail(NavTree root) - { - addSpecimenRequestsNavTrail(root); - root.addChild("New Specimen Request"); - } - } - - private ActionURL getManageRequestURL(int requestID, @Nullable ActionURL returnUrl) - { - return getManageRequestURL(getContainer(), requestID, returnUrl); - } - - public static ActionURL getManageRequestURL(Container c, int requestID, @Nullable ActionURL returnUrl) - { - ActionURL url = new ActionURL(ManageRequestAction.class, c); - url.addParameter(IdForm.PARAMS.id, Integer.toString(requestID)); - if (returnUrl != null) - url.addReturnUrl(returnUrl); - return url; - } - - private ActionURL getExtendedRequestURL(int requestID, String returnUrl) - { - ActionURL url = new ActionURL(ExtendedSpecimenRequestAction.class, getContainer()); - url.addParameter(IdForm.PARAMS.id, Integer.toString(requestID)); -// if (returnUrl != null) -// url.addReturnURL(returnUrl); - return url; - } - - @RequiresPermission(RequestSpecimensPermission.class) - public class ExtendedSpecimenRequestAction extends SimpleViewAction - { - @Override - public ModelAndView getView(CreateSpecimenRequestForm form, BindException errors) - { - VBox vbox = new VBox(); - - ExtendedSpecimenRequestView view = SpecimenManager.get().getExtendedSpecimenRequestView(getViewContext()); - if (view != null && view.isActive()) - { - HtmlView requestView = HtmlView.unsafe(view.getBody()); - vbox.addView(requestView); - } - else - { - vbox.addView(HtmlView.of("An extended specimen request view has not been provided for this folder.")); - } - return vbox; - } - - @Override - public void addNavTrail(NavTree root) - { - addSpecimenRequestsNavTrail(root); - root.addChild("Extended Specimen Request"); - } - } - - public static class BulkEditForm - { - private String _newLabel; - private String _newId; - private String _nextPage; - private String _order; - private int[] _ids; - private String[] _labels; - - public String getNewLabel() - { - return _newLabel; - } - - public void setNewLabel(String newLabel) - { - _newLabel = newLabel; - } - - public String getNextPage() - { - return _nextPage; - } - - public void setNextPage(String nextPage) - { - _nextPage = nextPage; - } - - public String getOrder() - { - return _order; - } - - public void setOrder(String order) - { - _order = order; - } - - public String[] getLabels() - { - return _labels; - } - - public void setLabels(String[] labels) - { - _labels = labels; - } - - public int[] getIds() - { - return _ids; - } - - public void setIds(int[] ids) - { - _ids = ids; - } - - public String getNewId() - { - return _newId; - } - - public void setNewId(String newId) - { - _newId = newId; - } - } - - private abstract class DisplayManagementSubpageAction
extends FormViewAction - { - private final String _jsp; - private final String _title; - private final String _helpTopic; - - public DisplayManagementSubpageAction(String jsp, String title, String helpTopic) - { - _jsp = jsp; - _title = title; - _helpTopic = helpTopic; - } - - @Override - public void validateCommand(Form target, Errors errors) - { - } - - @Override - public ModelAndView getView(Form form, boolean reshow, BindException errors) - { - ensureSpecimenRequestsConfigured(false); - return new JspView<>(_jsp, getStudyRedirectIfNull()); - } - - @Override - public void addNavTrail(NavTree root) - { - setHelpTopic(_helpTopic); - addManageStudyNavTrail(root); - root.addChild(_title); - } - } - - public static class ActorEditForm extends BulkEditForm - { - boolean _newPerSite; - - public boolean isNewPerSite() - { - return _newPerSite; - } - - @SuppressWarnings("unused") - public void setNewPerSite(boolean newPerSite) - { - _newPerSite = newPerSite; - } - } - - private Map getIdToRequestActorMap(Container container) - { - SpecimenRequestActor[] actors = SpecimenRequestRequirementProvider.get().getActors(container); - Map idToStatus = new IntHashMap<>(); - for (SpecimenRequestActor actor : actors) - idToStatus.put(actor.getRowId(), actor); - return idToStatus; - } - - @RequiresPermission(ManageSpecimenActorsPermission.class) - public class ManageActorsAction extends DisplayManagementSubpageAction - { - public ManageActorsAction() - { - super("/org/labkey/specimen/view/manageActors.jsp", "Manage Specimen Request Actors", "coordinateSpecimens#actor"); - } - - @Override - public boolean handlePost(ActorEditForm form, BindException errors) - { - ensureSpecimenRequestsConfigured(false); - - int[] rowIds = form.getIds(); - String[] labels = form.getLabels(); - if (labels != null) - { - for (String label : labels) - { - if (label == null || label.isEmpty()) - errors.reject(ERROR_MSG, "Actor name cannot be empty."); - } - } - if (!errors.hasErrors()) - { - if (rowIds != null && rowIds.length > 0) - { - // get a map of id to actor objects before starting our updates; this prevents us from - // blowing then repopulating the cache with each update: - Map idToActor = getIdToRequestActorMap(getContainer()); - for (int i = 0; i < rowIds.length; i++) - { - int rowId = rowIds[i]; - String label = labels[i]; - SpecimenRequestActor actor = idToActor.get(rowId); - if (actor != null && !nullSafeEqual(label, actor.getLabel())) - { - actor = actor.createMutable(); - actor.setLabel(label); - actor.update(getUser()); - } - } - } - - if (form.getNewLabel() != null && !form.getNewLabel().isEmpty()) - { - SpecimenRequestActor actor = new SpecimenRequestActor(); - actor.setLabel(form.getNewLabel()); - SpecimenRequestActor[] actors = SpecimenRequestRequirementProvider.get().getActors(getContainer()); - actor.setSortOrder(actors.length); - actor.setContainer(getContainer()); - actor.setPerSite(form.isNewPerSite()); - actor.create(getUser()); - } - } - return !errors.hasErrors(); - } - - @Override - public ActionURL getSuccessURL(ActorEditForm form) - { - if (form.getNextPage() != null && !form.getNextPage().isEmpty()) - return new ActionURL(form.getNextPage()); - else - return getManageStudyURL(); - } - } - - @RequiresPermission(ManageSpecimenActorsPermission.class) - public class ManageActorOrderAction extends DisplayManagementSubpageAction - { - public ManageActorOrderAction() - { - super("/org/labkey/specimen/view/manageActorOrder.jsp", "Manage Actor Order", "specimenRequest"); - } - - @Override - public boolean handlePost(BulkEditForm form, BindException errors) - { - ensureSpecimenRequestsConfigured(false); - - String order = form.getOrder(); - if (order != null && !order.isEmpty()) - { - String[] rowIds = order.split(","); - // get a map of id to actor objects before starting our updates; this prevents us from - // blowing then repopulating the cache with each update: - Map idToActor = getIdToRequestActorMap(getContainer()); - for (int i = 0; i < rowIds.length; i++) - { - int rowId = Integer.parseInt(rowIds[i]); - SpecimenRequestActor actor = idToActor.get(rowId); - if (actor != null && actor.getSortOrder() != i) - { - actor = actor.createMutable(); - actor.setSortOrder(i); - actor.update(getUser()); - } - } - } - return true; - } - - @Override - public ActionURL getSuccessURL(BulkEditForm bulkEditForm) - { - return new ActionURL(ManageActorsAction.class, getContainer()); - } - } - - @RequiresPermission(ManageRequestStatusesPermission.class) - public class DeleteActorAction extends FormHandlerAction - { - @Override - public void validateCommand(IdForm target, Errors errors) - { - } - - @Override - public boolean handlePost(IdForm form, BindException errors) - { - SpecimenRequestActor actor = SpecimenRequestRequirementProvider.get().getActor(getContainer(), form.getId()); - if (actor != null) - actor.delete(getUser()); - - return true; - } - - @Override - public ActionURL getSuccessURL(IdForm idForm) - { - return new ActionURL(ManageActorsAction.class, getContainer()); - } - } - - private void updateRequestStatusOrder(Container container, int[] rowIds, boolean fixedRowIncluded) - { - // get a map of id to status objects before starting our updates; this prevents us from - // blowing then repopulating the cache with each update: - Map idToStatus = getIdToRequestStatusMap(container); - for (int i = 0; i < rowIds.length; i++) - { - int rowId = rowIds[i]; - int statusOrder = fixedRowIncluded ? i : i + 1; // One caller doesn't have the first (fixed) status - SpecimenRequestStatus status = idToStatus.get(rowId); - if (status != null && !status.isSystemStatus() && status.getSortOrder() != statusOrder) - { - status = status.createMutable(); - status.setSortOrder(statusOrder); - SpecimenRequestManager.get().updateRequestStatus(getUser(), status); - } - } - } - - private Map getIdToRequestStatusMap(Container container) - { - Collection statuses = SpecimenRequestManager.get().getRequestStatuses(container, getUser()); - Map idToStatus = new IntHashMap<>(); - for (SpecimenRequestStatus status : statuses) - idToStatus.put(status.getRowId(), status); - return idToStatus; - } - - public static class StatusEditForm extends BulkEditForm - { - private int[] _finalStateIds = new int[0]; - private int[] _specimensLockedIds = new int[0]; - private boolean _newSpecimensLocked; - private boolean _newFinalState; - private boolean _useShoppingCart; - - public int[] getFinalStateIds() - { - return _finalStateIds; - } - - public void setFinalStateIds(int[] finalStateIds) - { - _finalStateIds = finalStateIds; - } - - public boolean isNewFinalState() - { - return _newFinalState; - } - - public void setNewFinalState(boolean newFinalState) - { - _newFinalState = newFinalState; - } - - public boolean isNewSpecimensLocked() - { - return _newSpecimensLocked; - } - - public void setNewSpecimensLocked(boolean newSpecimensLocked) - { - _newSpecimensLocked = newSpecimensLocked; - } - - public int[] getSpecimensLockedIds() - { - return _specimensLockedIds; - } - - public void setSpecimensLockedIds(int[] specimensLockedIds) - { - _specimensLockedIds = specimensLockedIds; - } - - public boolean isUseShoppingCart() - { - return _useShoppingCart; - } - - public void setUseShoppingCart(boolean useShoppingCart) - { - _useShoppingCart = useShoppingCart; - } - } - - @RequiresPermission(ManageRequestStatusesPermission.class) - public class ManageStatusesAction extends DisplayManagementSubpageAction - { - public ManageStatusesAction() - { - super("/org/labkey/specimen/view/manageStatuses.jsp", "Manage Specimen Request Statuses", "specimenRequest#status"); - } - - @Override - public boolean handlePost(StatusEditForm form, BindException errors) - { - ensureSpecimenRequestsConfigured(false); - - int[] rowIds = form.getIds(); - String[] labels = form.getLabels(); - if (rowIds != null && rowIds.length > 0) - { - // get a map of id to status objects before starting our updates; this prevents us from - // blowing then repopulating the cache with each update: - Map idToStatus = getIdToRequestStatusMap(getContainer()); - Set finalStates = new HashSet<>(form.getFinalStateIds().length); - for (int id : form.getFinalStateIds()) - finalStates.add(id); - Set lockedSpecimenStates = new HashSet<>(form.getSpecimensLockedIds().length); - for (int id : form.getSpecimensLockedIds()) - lockedSpecimenStates.add(id); - - for (int i = 0; i < rowIds.length; i++) - { - int rowId = rowIds[i]; - SpecimenRequestStatus status = idToStatus.get(rowId); - if (status != null && !status.isSystemStatus()) - { - String label = labels[i]; - boolean isFinalState = finalStates.contains(rowId); - boolean specimensLocked = lockedSpecimenStates.contains(rowId); - if (!nullSafeEqual(label, status.getLabel()) || - isFinalState != status.isFinalState() || - specimensLocked != status.isSpecimensLocked()) - { - status = status.createMutable(); - status.setLabel(label); - status.setFinalState(isFinalState); - status.setSpecimensLocked(specimensLocked); - SpecimenRequestManager.get().updateRequestStatus(getUser(), status); - } - } - } - } - - if (form.getNewLabel() != null && !form.getNewLabel().isEmpty()) - { - SpecimenRequestStatus status = new SpecimenRequestStatus(); - status.setLabel(form.getNewLabel()); - Collection statuses = SpecimenRequestManager.get().getRequestStatuses(getContainer(), getUser()); - status.setSortOrder(statuses.size()); - status.setContainer(getContainer()); - status.setFinalState(form.isNewFinalState()); - status.setSpecimensLocked(form.isNewSpecimensLocked()); - SpecimenRequestManager.get().createRequestStatus(getUser(), status); - } - - StatusSettings settings = SettingsManager.get().getStatusSettings(getContainer()); - if (settings.isUseShoppingCart() != form.isUseShoppingCart()) - { - settings.setUseShoppingCart(form.isUseShoppingCart()); - org.labkey.specimen.settings.SettingsManager.get().saveStatusSettings(getContainer(), settings); - } - return true; - } - - @Override - public ActionURL getSuccessURL(StatusEditForm form) - { - if (form.getNextPage() != null && !form.getNextPage().isEmpty()) - return new ActionURL(form.getNextPage()); - else - return getManageStudyURL(); - } - } - - @RequiresPermission(ManageSpecimenActorsPermission.class) - public class ManageStatusOrderAction extends DisplayManagementSubpageAction - { - public ManageStatusOrderAction() - { - super("/org/labkey/specimen/view/manageStatusOrder.jsp", "Manage Status Order", "specimenRequest"); - } - - @Override - public boolean handlePost(BulkEditForm form, BindException errors) - { - ensureSpecimenRequestsConfigured(false); - - String order = form.getOrder(); - if (order != null && !order.isEmpty()) - { - String[] rowIdStrings = order.split(","); - int[] rowIds = new int[rowIdStrings.length]; - for (int i = 0; i < rowIdStrings.length; i++) - rowIds[i] = Integer.parseInt(rowIdStrings[i]); - updateRequestStatusOrder(getContainer(), rowIds, false); - } - return true; - } - - @Override - public ActionURL getSuccessURL(BulkEditForm bulkEditForm) - { - return new ActionURL(ManageStatusesAction.class, getContainer()); - } - } - - @RequiresPermission(AdminPermission.class) - public class DeleteStatusAction extends FormHandlerAction - { - @Override - public void validateCommand(IdForm target, Errors errors) - { - } - - @Override - public boolean handlePost(IdForm form, BindException errors) throws Exception - { - Collection statuses = SpecimenRequestManager.get().getRequestStatuses(getContainer(), getUser()); - SpecimenRequestStatus status = SpecimenRequestManager.get().getRequestStatus(getContainer(), form.getId()); - if (status != null) - { - SpecimenRequestManager.get().deleteRequestStatus(status); - int[] remainingIds = new int[statuses.size() - 1]; - int idx = 0; - for (SpecimenRequestStatus remainingStatus : statuses) - { - if (remainingStatus.getRowId() != form.getId()) - remainingIds[idx++] = remainingStatus.getRowId(); - } - updateRequestStatusOrder(getContainer(), remainingIds, true); - } - return true; - } - - @Override - public ActionURL getSuccessURL(IdForm idForm) - { - return new ActionURL(ManageStatusesAction.class, getContainer()); - } - } - - public void writeExportData(SpecimenQueryView view, String type) throws IOException - { - switch (type) - { - case "excel" -> view.exportToExcel(getViewContext().getResponse()); - case "tsv" -> view.exportToTsv(getViewContext().getResponse()); - default -> throw new IllegalArgumentException(type + " is not a supported export type."); - } - } - - public static class ManageRequestForm extends IdForm - { - public enum PARAMS - { - newSite, - newActor, - newDescription, - export, - submissionResult, - returnUrl - } - - private Integer _newSite; - private Integer _newActor; - private String _newDescription; - private String _export; - private Boolean _submissionResult; - - public Integer getNewActor() - { - return _newActor; - } - - public void setNewActor(Integer newActor) - { - _newActor = newActor; - } - - public Integer getNewSite() - { - return _newSite; - } - - public void setNewSite(Integer newSite) - { - _newSite = newSite; - } - - public String getNewDescription() - { - return _newDescription; - } - - public void setNewDescription(String newDescription) - { - _newDescription = newDescription; - } - - public String getExport() - { - return _export; - } - - public void setExport(String export) - { - _export = export; - } - - public Boolean isSubmissionResult() - { - return _submissionResult; - } - - public void setSubmissionResult(Boolean submissionResult) - { - _submissionResult = submissionResult; - } - } - - @RequiresPermission(ReadPermission.class) - public class ManageRequestAction extends FormViewAction - { - private int _requestId; - - @Override - public void validateCommand(ManageRequestForm target, Errors errors) - { - } - - @Override - public ModelAndView getView(ManageRequestForm form, boolean reshow, BindException errors) throws Exception - { - SpecimenRequest request = SpecimenRequestManager.get().getRequest(getContainer(), form.getId()); - if (request == null) - throw new NotFoundException(); - - _requestId = request.getRowId(); - ManageRequestBean bean = new ManageRequestBean(getViewContext(), request, form.getExport() != null, - form.isSubmissionResult(), form.getReturnUrl()); - if (form.getExport() != null) - { - bean.getSpecimenQueryView().getSettings().setMaxRows(Table.ALL_ROWS); // #34998; exporting specimens in a request should include all of them - writeExportData(bean.getSpecimenQueryView(), form.getExport()); - return null; - } - else - { - GridView attachmentsGrid = getRequestEventAttachmentsGridView(getViewContext().getRequest(), errors, _requestId); - SpecimenQueryView queryView = bean.getSpecimenQueryView(); - if (null != queryView) - queryView.setTitle("Associated Specimens"); - HBox hbox = new HBox(new JspView<>("/org/labkey/specimen/view/manageRequest.jsp", bean), attachmentsGrid); - hbox.setTableWidth(""); - return new VBox(hbox, queryView); - } - } - - @Override - public boolean handlePost(ManageRequestForm form, BindException errors) throws Exception - { - if (!getContainer().hasPermission(getUser(), ManageRequestsPermission.class)) - throw new UnauthorizedException("You do not have permissions to create new specimen request requirements!"); - - SpecimenRequest request = SpecimenRequestManager.get().getRequest(getContainer(), form.getId()); - if (request == null) - throw new NotFoundException(); - - if (form.getNewActor() != null && form.getNewActor() > 0) - { - SpecimenRequestActor actor = SpecimenRequestRequirementProvider.get().getActor(getContainer(), form.getNewActor()); - if (actor != null) - { - // an actor is valid if a site has been provided for a per-site actor, or if no site - // has been provided for a non-site-specific actor. The UI should enforce this already, - // so this is just a backup check. - boolean validActor = (actor.isPerSite() && form.getNewSite() != null && form.getNewSite() > 0) || - (!actor.isPerSite() && (form.getNewSite() == null || form.getNewSite() <= 0)); - if (validActor) - { - SpecimenRequestRequirement requirement = new SpecimenRequestRequirement(); - requirement.setContainer(getContainer()); - if (form.getNewSite() != null && form.getNewSite() > 0) - requirement.setSiteId(form.getNewSite()); - requirement.setActorId(form.getNewActor()); - requirement.setDescription(form.getNewDescription()); - requirement.setRequestId(request.getRowId()); - SpecimenRequestManager.get().createRequestRequirement(getUser(), requirement, true, true); - } - } - } - return true; - } - - @Override - public ActionURL getSuccessURL(ManageRequestForm manageRequestForm) - { - return getManageRequestURL(manageRequestForm.getId(), manageRequestForm.getReturnActionURL()); - } - - @Override - public void addNavTrail(NavTree root) - { - addSpecimenRequestNavTrail(root, _requestId); - } - } - - private static class AttachmentDisplayColumn extends SimpleDisplayColumn - { - private final HttpServletRequest _request; - - public AttachmentDisplayColumn(HttpServletRequest request) - { - super(); - _request = request; - setCaption("Attachments"); - } - - @Override - public void renderGridCellContents(RenderContext ctx, HtmlWriter out) - { - Map cols = ctx.getRow(); - SpecimenRequestEvent event = ObjectFactory.Registry.getFactory(SpecimenRequestEvent.class).fromMap(cols); - Collection attachments = AttachmentService.get().getAttachments(event); - - if (!attachments.isEmpty()) - { - for (Attachment attachment : attachments) - { - out.write(LinkBuilder.simpleLink( - SPAN( - IMG(at(style,"padding-right:4pt;", src, _request.getContextPath() + attachment.getFileIcon())), - attachment.getName() - )) - .href(getDownloadURL(event, attachment.getName()))); - out.write(HtmlString.BR); - } - } - else - { - out.write(HtmlString.NBSP); - } - } - } - - public GridView getRequestEventAttachmentsGridView(HttpServletRequest request, BindException errors, int requestId) - { - DataRegion rgn = new DataRegion() { - private int i = 0; - - @Override - protected void renderTableRow(RenderContext ctx, HtmlWriter out, boolean showRecordSelectors, List renderers, int rowIndex) throws SQLException - { - // This is so we don't show rows that have no attachments - SpecimenRequestEvent event = ObjectFactory.Registry.getFactory(SpecimenRequestEvent.class).fromMap(ctx.getRow()); - List attachments = AttachmentService.get().getAttachments(event); - if (!attachments.isEmpty()) - { - super.renderTableRow(ctx, out, showRecordSelectors, renderers, i++); - } - } - }; - - TableInfo tableInfoRequestEvent = SpecimenSchema.get().getTableInfoSampleRequestEvent(); - rgn.setTable(tableInfoRequestEvent); - rgn.setColumns(tableInfoRequestEvent.getColumns("Created", "EntityId")); - rgn.getDisplayColumn("EntityId").setVisible(false); - rgn.getDisplayColumn("Created").setVisible(false); - rgn.setShowBorders(true); - rgn.setShowPagination(false); - DisplayColumn attachmentDisplayColumn = new AttachmentDisplayColumn(request); - rgn.addDisplayColumn(attachmentDisplayColumn); - GridView grid = new GridView(rgn, errors); - SimpleFilter filter = new SimpleFilter(FieldKey.fromString("RequestId"), requestId); - filter.addCondition(FieldKey.fromString("RequirementId"), null, CompareType.ISBLANK); // if null, then event is NOT a requirement - grid.setFilter(filter); - FieldKey fieldKey = FieldKey.fromString("Created"); - Sort sort = new Sort(); - sort.insertSortColumn(fieldKey, Sort.SortDirection.DESC); - grid.setSort(sort); - return grid; - } - - public static class ModifySpecimenRequestForm extends IdForm implements HiddenFormInputGenerator - { - public enum PARAMS - { - specimenIds, - returnUrl - } - - private String _specimenIds; - - public String getSpecimenIds() - { - return _specimenIds; - } - - public void setSpecimenIds(String specimenIds) - { - _specimenIds = specimenIds; - } - - @Override - public String getHiddenFormInputs(ViewContext ctx) - { - StringBuilder builder = new StringBuilder(); - if (getId() != 0) - builder.append("\n"); - if (_specimenIds != null) - builder.append(""); - return builder.toString(); - } - } - - private void requiresEditRequestPermissions(SpecimenRequest request) - { - if (!SpecimenRequestManager.get().hasEditRequestPermissions(getUser(), request)) - throw new UnauthorizedException(); - } - - // Additional permission checking takes place before handlePost() - @RequiresPermission(ReadPermission.class) - public class RemoveRequestSpecimensAction extends FormHandlerAction - { - @Override - public void validateCommand(ModifySpecimenRequestForm target, Errors errors) - { - } - - @Override - public boolean handlePost(ModifySpecimenRequestForm form, BindException errors) throws Exception - { - SpecimenRequest request = SpecimenRequestManager.get().getRequest(getContainer(), form.getId()); - requiresEditRequestPermissions(request); - long[] ids = toLongArray(DataRegionSelection.getSelected(getViewContext(), true)); - List specimenIds = new ArrayList<>(); - for (long id : ids) - specimenIds.add(id); - try - { - SpecimenRequestManager.get().deleteRequestSpecimenMappings(getUser(), request, specimenIds, true); - } - catch (RequestabilityManager.InvalidRuleException e) - { - errors.reject(ERROR_MSG, "The specimens could not be removed because a requestability rule is configured incorrectly. " + - "Please report this problem to an administrator. Error details: " + e.getMessage()); - return false; - } - return true; - } - - @Override - public ActionURL getSuccessURL(ModifySpecimenRequestForm addToSpecimenRequestForm) - { - return getManageRequestURL(addToSpecimenRequestForm.getId(), addToSpecimenRequestForm.getReturnActionURL()); - } - } - - @RequiresPermission(RequestSpecimensPermission.class) - public class ImportVialIdsAction extends AbstractQueryImportAction - { - private int _requestId = -1; - - @Override - protected void initRequest(IdForm form) throws ServletException - { - _requestId = form.getId(); - setHasColumnHeaders(false); - setImportMessage("Upload a list of Global Unique Identifiers from a TXT, CSV or Excel file or paste the list directly into the text box below. " + - "The list must have only one column and no header row."); - setNoTableInfo(); - setHideTsvCsvCombo(true); - } - - @Override - public ModelAndView getView(IdForm form, BindException errors) throws Exception - { - initRequest(form); - SpecimenRequest request = SpecimenRequestManager.get().getRequest(getContainer(), _requestId); - if (request == null) - throw new NotFoundException(); - - if (!SpecimenRequestManager.get().hasEditRequestPermissions(getUser(), request) || - SpecimenRequestManager.get().isInFinalState(request)) - { - return HtmlView.err("You do not have permissions to modify this request."); - } - - return getDefaultImportView(form, errors); - } - - @Override - protected void validatePermission(User user, BindException errors) - { - checkPermissions(); - SpecimenRequest request = SpecimenRequestManager.get().getRequest(getContainer(), _requestId); - - if (!SpecimenRequestManager.get().hasEditRequestPermissions(getUser(), request) || - SpecimenRequestManager.get().isInFinalState(request)) - { - // No permission - errors.reject(SpringActionController.ERROR_MSG, "You do not have permission to modify this request."); - } - } - - @Override - protected boolean canInsert(User user) - { - return getContainer().hasPermission(user, RequestSpecimensPermission.class); - } - - @Override - protected int importData(DataLoader dl, FileStream file, String originalName, BatchValidationException errors, @Nullable AuditBehaviorType auditBehaviorType, @Nullable TransactionAuditProvider.TransactionAuditEvent auditEvent, @Nullable String auditUserComment) throws IOException - { - List errorList = new LinkedList<>(); - - ColumnDescriptor[] columns = new ColumnDescriptor[2]; // Only 1 actual column of type String - columns[0] = new ColumnDescriptor("Actual", String.class); - columns[1] = new ColumnDescriptor("Dummy", String.class); - dl.setColumns(columns); - - List> rows = dl.load(); - String[] globalIds = new String[rows.size()]; - int i = 0; - for (Map row : rows) - { - String idActual = (String)row.get("Actual"); - if (idActual == null) - { - errorList.add("Malformed input data."); - break; - } - - String idDummy = (String)row.get("Dummy"); - if (idDummy != null) - { - errorList.add("Only one id per line is allowed."); - break; - } - globalIds[i] = idActual; - i += 1; - } - - if (errorList.isEmpty()) - { - // Get all the specimen objects. If it throws an exception then there was an error and we'll - // root around to figure out what to report - try - { - SpecimenRequest request = SpecimenRequestManager.get().getRequest(getContainer(), _requestId); - List vials = SpecimenManager.get().getVials(getContainer(), getUser(), globalIds); - - if (vials != null && vials.size() == globalIds.length) - { - // All the specimens exist; - // Check for Availability and then add them to the request. - // There still may be errors (like someone already has requested that specimen) which will be - // caught by createRequestSpecimenMapping - - for (Vial s : vials) - { - if (!s.isAvailable()) // || s.isLockedInRequest()) - { - errorList.add(RequestabilityManager.makeSpecimenUnavailableMessage(s, null)); - } - } - - if (errorList.isEmpty()) - { - ArrayList vialList = new ArrayList<>(vials.size()); - vialList.addAll(vials); - SpecimenRequestManager.get().createRequestSpecimenMapping(getUser(), request, vialList, true, true); - } - } - else - { - errorList.add("Duplicate Ids found."); - } - } - catch (RequestabilityManager.InvalidRuleException e) - { - errorList.add("The request could not be created because a requestability rule is configured incorrectly. " + - "Please report this problem to an administrator. Error details: " + e.getMessage()); - } - catch (SpecimenRequestException e) - { - // There was an error; some id had no specimen matching - boolean hasSpecimenError = false; - for (String id : globalIds) - { - Vial vial = SpecimenManager.get().getVial(getContainer(), getUser(), id); - if (vial == null) - { - errorList.add("Specimen " + id + " not found."); - hasSpecimenError = true; - } - } - - if (!hasSpecimenError) - { // Expected one of them to not be found, so this is unusual - errorList.add("Error adding all of the specimens together."); - } - } - } - - if (!errorList.isEmpty()) - { - for (String error : errorList) - errors.addRowError(new ValidationException(error)); - } - - if (errors.hasErrors()) - return 0; - return rows.size(); - } - - @Override - public void addNavTrail(NavTree root) - { - addSpecimenRequestNavTrail(root, _requestId); - root.addChild("Upload Specimen Identifiers"); - } - - @Override - protected ActionURL getSuccessURL(IdForm form) - { - return getManageRequestURL(getContainer(), _requestId, null); - } - } - - public static class ManageRequestStatusForm extends IdForm - { - private int _status; - private String _comments; - private String _requestDescription; - private String[] _notificationIdPairs; - private boolean _emailInactiveUsers; - - public int getStatus() - { - return _status; - } - - public void setStatus(int status) - { - _status = status; - } - - public String getComments() - { - return _comments; - } - - public void setComments(String comments) - { - _comments = comments; - } - - public String[] getNotificationIdPairs() - { - return _notificationIdPairs; - } - - public void setNotificationIdPairs(String[] notificationIdPairs) - { - _notificationIdPairs = notificationIdPairs; - } - - public String getRequestDescription() - { - return _requestDescription; - } - - public void setRequestDescription(String requestDescription) - { - _requestDescription = requestDescription; - } - - public boolean isEmailInactiveUsers() - { - return _emailInactiveUsers; - } - - public void setEmailInactiveUsers(boolean emailInactiveUsers) - { - _emailInactiveUsers = emailInactiveUsers; - } - } - - private List getNotifications(SpecimenRequest specimenRequest, String[] notificationIdPairs) - { - List siteActors = new ArrayList<>(); - if (notificationIdPairs == null || notificationIdPairs.length == 0) - return siteActors; - for (String notificationIdPair : notificationIdPairs) - siteActors.add(ActorNotificationRecipientSet.getFromFormValue(specimenRequest.getContainer(), notificationIdPair)); - return siteActors; - } - - private void sendNewRequestNotifications(SpecimenRequest request, BindException errors) throws Exception - { - RequestNotificationSettings settings = org.labkey.specimen.settings.SettingsManager.get().getRequestNotificationSettings(request.getContainer()); - Address[] notify = settings.getNewRequestNotifyAddresses(); - if (notify != null && notify.length > 0) - { - SpecimenRequestEvent event = SpecimenRequestManager.get().createRequestEvent(getUser(), request, - RequestEventType.REQUEST_CREATED, null, Collections.emptyList()); - DefaultRequestNotification notification = new DefaultRequestNotification(request, Collections.singletonList(new NotificationRecipientSet(notify)), - "New Request Created", event, null, null, getViewContext()); - sendNotification(notification, true, errors); - } - } - - private void sendNotification(DefaultRequestNotification notification, boolean includeInactiveUsers, BindException errors) throws Exception - { - RequestNotificationSettings settings = org.labkey.specimen.settings.SettingsManager.get().getRequestNotificationSettings(getContainer()); - - SpecimenRequest specimenRequest = notification.getSpecimenRequest(); - String specimenList = null; - if (RequestNotificationSettings.SpecimensAttachmentEnum.InEmailBody == settings.getSpecimensAttachmentEnum()) - { - specimenList = notification.getSpecimenListHTML(getViewContext()); - } - - NotificationBean notificationBean = new NotificationBean(getViewContext(), notification, specimenList, getStudy().getLabel()); - SpecimenRequestNotificationEmailTemplate template = EmailTemplateService.get().getEmailTemplate(SpecimenRequestNotificationEmailTemplate.class, getContainer()); - template.init(notificationBean); - if (settings.isReplyToCurrentUser()) - template.setOriginatingUser(getUser()); - - MailHelper.MultipartMessage message = MailHelper.createMultipartMessage(); - template.renderSenderToMessage(message, getContainer()); - message.setEncodedHtmlContent(template.renderBody(getContainer())); - message.setSubject(template.renderSubject(getContainer())); - - boolean first = true; - for (NotificationRecipientSet recipient : notification.getRecipients()) - { - for (String email : recipient.getEmailAddresses(includeInactiveUsers)) - { - if (first) - { - Address[] ccAddresses = settings.getCCAddresses(); - if (ccAddresses != null && ccAddresses.length > 0) - message.setRecipients(Message.RecipientType.CC, ccAddresses); - first = false; - } - else - message.setRecipients(Message.RecipientType.CC, ""); - - try - { - message.setRecipient(Message.RecipientType.TO, new InternetAddress(email)); - MailHelper.send(message, getUser(), getContainer()); - } - catch (AddressException | NullPointerException e) - { - errors.reject(SpringActionController.ERROR_MSG, e.getMessage() == null ? e.toString() : e.getMessage()); // Bad address; also InternetAddress constructor can throw null - } - } - if (notification.getRequirement() != null) - SpecimenRequestManager.get().createRequestEvent(getUser(), notification.getRequirement(), - RequestEventType.NOTIFICATION_SENT, "Notification sent to " + recipient.getLongRecipientDescription(), null); - else - SpecimenRequestManager.get().createRequestEvent(getUser(), specimenRequest, - RequestEventType.NOTIFICATION_SENT, "Notification sent to " + recipient.getLongRecipientDescription(), null); - } - } - - public static ActionURL getManageRequestStatusURL(Container c, int requestId) - { - return new ActionURL(ManageRequestStatusAction.class, c).addParameter("id", requestId); - } - - @RequiresPermission(RequestSpecimensPermission.class) - public class ManageRequestStatusAction extends FormViewAction - { - private SpecimenRequest _specimenRequest; - - @Override - public void validateCommand(ManageRequestStatusForm target, Errors errors) - { - } - - @Override - public ModelAndView getView(ManageRequestStatusForm form, boolean reshow, BindException errors) - { - _specimenRequest = SpecimenRequestManager.get().getRequest(getContainer(), form.getId()); - if (_specimenRequest == null) - throw new NotFoundException(); - - return new JspView<>("/org/labkey/specimen/view/manageRequestStatus.jsp", - new ManageRequestBean(getViewContext(), _specimenRequest, false, null, null), errors); - } - - @Override - public boolean handlePost(final ManageRequestStatusForm form, BindException errors) throws Exception - { - _specimenRequest = SpecimenRequestManager.get().getRequest(getContainer(), form.getId()); - if (_specimenRequest == null) - throw new NotFoundException(); - - requiresEditRequestPermissions(_specimenRequest); - - boolean statusChanged = form.getStatus() != _specimenRequest.getStatusId(); - boolean detailsChanged = !nullSafeEqual(form.getRequestDescription(), _specimenRequest.getComments()); - - List files = getAttachmentFileList(); - boolean hasAttachments = !files.isEmpty(); - - boolean hasComments = form.getComments() != null && !form.getComments().isEmpty(); - if (statusChanged || detailsChanged || hasComments || hasAttachments) - { - RequestEventType changeType; - String comment = ""; - String eventSummary; - if (statusChanged || detailsChanged) - { - if (statusChanged) - { - SpecimenRequestStatus prevStatus = SpecimenRequestManager.get().getRequestStatus(getContainer(), _specimenRequest.getStatusId()); - SpecimenRequestStatus newStatus = SpecimenRequestManager.get().getRequestStatus(getContainer(), form.getStatus()); - comment += "Status changed from \"" + (prevStatus != null ? prevStatus.getLabel() : "N/A") + "\" to \"" + - (newStatus != null ? newStatus.getLabel() : "N/A") + "\"\n"; - } - if (detailsChanged) - { - String prevDetails = _specimenRequest.getComments(); - String newDetails = form.getRequestDescription(); - comment += "Description changed from \"" + (prevDetails != null ? prevDetails : "N/A") + "\" to \"" + - (newDetails != null ? newDetails : "N/A") + "\"\n"; - } - eventSummary = comment; - if (hasComments) - comment += form.getComments(); - _specimenRequest = _specimenRequest.createMutable(); - _specimenRequest.setStatusId(form.getStatus()); - _specimenRequest.setComments(form.getRequestDescription()); - _specimenRequest.setModified(new Date()); - try - { - SpecimenRequestManager.get().updateRequest(getUser(), _specimenRequest); - } - catch (RequestabilityManager.InvalidRuleException e) - { - errors.reject(ERROR_MSG, "The request could not be updated because a requestability rule is configured incorrectly. " + - "Please report this problem to an administrator. Error details: " + e.getMessage()); - return false; - } - changeType = RequestEventType.REQUEST_STATUS_CHANGED; - } - else - { - changeType = RequestEventType.COMMENT_ADDED; - comment = form.getComments(); - eventSummary = "Comments added."; - } - - SpecimenRequestEvent event; - try - { - event = SpecimenRequestManager.get().createRequestEvent(getUser(), _specimenRequest, changeType, comment, files); - } - catch (Exception e) - { - errors.reject(ERROR_MSG, "The request could not be updated because of an unexpected error. " + - "Please report this problem to an administrator. Error details: " + e.getMessage()); - return false; - } - try - { - List recipients = getNotifications(_specimenRequest, form.getNotificationIdPairs()); - DefaultRequestNotification notification = new DefaultRequestNotification(_specimenRequest, recipients, - eventSummary, event, form.getComments(), null, getViewContext()); - sendNotification(notification, form.isEmailInactiveUsers(), errors); - } - catch (ConfigurationException | IOException e) - { - errors.reject(ERROR_MSG, "The request was updated successfully, but the notification failed: " + e.getMessage()); - return false; - } - } - - return !errors.hasErrors(); - } - - @Override - public ActionURL getSuccessURL(ManageRequestStatusForm manageRequestForm) - { - return getManageRequestURL(_specimenRequest.getRowId(), null); - } - - @Override - public void addNavTrail(NavTree root) - { - addSpecimenRequestNavTrail(root, _specimenRequest.getRowId()); - root.addChild("Update Request"); - } - } - - private enum CommentsConflictResolution - { - SKIP, - APPEND, - REPLACE - } - - public static class UpdateSpecimenCommentsForm - { - private String _comments; - private int[] _rowId; - private boolean _fromGroupedView; - private String _referrer; - private boolean _saveCommentsPost; - private String _conflictResolve; - private Boolean _qualityControlFlag; - - public String getComments() - { - return _comments; - } - - public void setComments(String comments) - { - _comments = comments; - } - - public int[] getRowId() - { - return _rowId; - } - - public void setRowId(int[] rowId) - { - _rowId = rowId; - } - - public boolean isFromGroupedView() - { - return _fromGroupedView; - } - - public void setFromGroupedView(boolean fromGroupedView) - { - _fromGroupedView = fromGroupedView; - } - - public String getReferrer() - { - return _referrer; - } - - public void setReferrer(String referrer) - { - _referrer = referrer; - } - - public boolean isSaveCommentsPost() - { - return _saveCommentsPost; - } - - public void setSaveCommentsPost(boolean saveCommentsPost) - { - _saveCommentsPost = saveCommentsPost; - } - - public String getConflictResolve() - { - return _conflictResolve; - } - - public void setConflictResolve(String conflictResolve) - { - _conflictResolve = conflictResolve; - } - - public CommentsConflictResolution getConflictResolveEnum() - { - return _conflictResolve == null ? CommentsConflictResolution.REPLACE : CommentsConflictResolution.valueOf(_conflictResolve); - } - - public Boolean isQualityControlFlag() - { - return _qualityControlFlag; - } - - public void setQualityControlFlag(Boolean qualityControlFlag) - { - _qualityControlFlag = qualityControlFlag; - } - } - - public static class UpdateParticipantCommentsForm extends UpdateSpecimenCommentsForm - { - private boolean _copyToParticipant; - private boolean _deleteVialComment; - private int _copySpecimenId; - private String _copyParticipantId; - - public boolean isCopyToParticipant() - { - return _copyToParticipant; - } - - public void setCopyToParticipant(boolean copyToParticipant) - { - _copyToParticipant = copyToParticipant; - } - - public boolean isDeleteVialComment() - { - return _deleteVialComment; - } - - public void setDeleteVialComment(boolean deleteVialComment) - { - _deleteVialComment = deleteVialComment; - } - - public int getCopySampleId() - { - return _copySpecimenId; - } - - public void setCopySampleId(int copySpecimenId) - { - _copySpecimenId = copySpecimenId; - } - - public String getCopyParticipantId() - { - return _copyParticipantId; - } - - public void setCopyParticipantId(String copyParticipantId) - { - _copyParticipantId = copyParticipantId; - } - } - - @RequiresPermission(SetSpecimenCommentsPermission.class) - public class ClearCommentsAction extends FormHandlerAction - { - @Override - public void validateCommand(UpdateSpecimenCommentsForm target, Errors errors) - { - } - - @Override - public boolean handlePost(UpdateSpecimenCommentsForm updateSpecimenCommentsForm, BindException errors) throws Exception - { - List selectedVials = getSpecimensFromPost(updateSpecimenCommentsForm.isFromGroupedView(), false); - if (selectedVials != null) - { - for (Vial vial : selectedVials) - { - // use the currently saved history conflict state; if it's been forced before, this will prevent it - // from being cleared. - SpecimenComment comment = SpecimenManager.get().getSpecimenCommentForVial(vial); - if (comment != null) - { - SpecimenManager.get().setSpecimenComment(getUser(), vial, null, - comment.isQualityControlFlag(), comment.isQualityControlFlagForced()); - } - } - } - return true; - } - - @Override - public ActionURL getSuccessURL(UpdateSpecimenCommentsForm updateSpecimenCommentsForm) - { - return new ActionURL(updateSpecimenCommentsForm.getReferrer()); - } - } - - @RequiresPermission(SetSpecimenCommentsPermission.class) - public class UpdateCommentsAction extends FormViewAction - { - private ActionURL _successUrl; - - @Override - public void validateCommand(UpdateParticipantCommentsForm specimenCommentsForm, Errors errors) - { - if (specimenCommentsForm.isSaveCommentsPost() && - (specimenCommentsForm.getRowId() == null || - specimenCommentsForm.getRowId().length == 0)) - { - errors.reject(null, "No vials were selected."); - } - } - - @Override - public ModelAndView getView(UpdateParticipantCommentsForm specimenCommentsForm, boolean reshow, BindException errors) - { - List selectedVials = getSpecimensFromPost(specimenCommentsForm.isFromGroupedView(), false); - - if (selectedVials == null || selectedVials.isEmpty()) - { - // are the vial IDs on the URL? - int[] rowId = specimenCommentsForm.getRowId(); - if (rowId != null && rowId.length > 0) - { - try - { - List vials = SpecimenManager.get().getVials(getContainer(), getUser(), rowId); - selectedVials = new ArrayList<>(vials); - } - catch (SpecimenRequestException e) - { - errors.reject(ERROR_MSG, e.getMessage()); - } - } - if (selectedVials == null || selectedVials.isEmpty()) - return HtmlView.unsafe("No vials selected. " + LinkBuilder.labkeyLink("back").onClick("back()")); - } - - return new JspView<>("/org/labkey/specimen/view/updateComments.jsp", - new UpdateSpecimenCommentsBean(getViewContext(), selectedVials, specimenCommentsForm.getReferrer()), errors); - } - - @Override - public boolean handlePost(UpdateParticipantCommentsForm commentsForm, BindException errors) throws Exception - { - if (commentsForm.getRowId() == null) - return false; - - User user = getUser(); - Container container = getContainer(); - List vials = new ArrayList<>(); - for (int rowId : commentsForm.getRowId()) - vials.add(SpecimenManager.get().getVial(container, user, rowId)); - - Map currentComments = SpecimenManager.get().getSpecimenComments(vials); - - // copying or moving vial comments to participant level comments - if (commentsForm.isCopyToParticipant()) - { - if (commentsForm.getCopySampleId() != -1) - { - Vial vial = SpecimenManager.get().getVial(container, user, commentsForm.getCopySampleId()); - if (vial != null) - { - _successUrl = new ActionURL(CopyParticipantCommentAction.class, container). - addParameter(ParticipantCommentForm.params.participantId, vial.getPtid()). - addParameter(ParticipantCommentForm.params.visitId, String.valueOf(vial.getVisitValue())). - addParameter(ParticipantCommentForm.params.comment, commentsForm.getComments()). - addReturnUrl(new URLHelper(commentsForm.getReferrer())); - } - } - else - { - _successUrl = new ActionURL(CopyParticipantCommentAction.class, container). - addParameter(ParticipantCommentForm.params.participantId, commentsForm.getCopyParticipantId()). - addParameter(ParticipantCommentForm.params.comment, commentsForm.getComments()). - addReturnUrl(new URLHelper(commentsForm.getReferrer())); - } - - // delete existing vial comments if move is specified - if (commentsForm.isDeleteVialComment()) - { - for (Vial vial : vials) - { - SpecimenComment comment = SpecimenManager.get().getSpecimenCommentForVial(vial); - if (comment != null) - { - _successUrl.addParameter(ParticipantCommentForm.params.vialCommentsToClear, vial.getRowId()); - } - } - } - return true; - } - - for (Vial vial : vials) - { - SpecimenComment previousComment = currentComments.get(vial); - - boolean newConflictState; - boolean newForceState; - if (commentsForm.isQualityControlFlag() != null && SettingsManager.get().getDisplaySettings(container).isEnableManualQCFlagging()) - { - // if a state has been specified in the post, we consider this to be 'forcing' the state: - newConflictState = commentsForm.isQualityControlFlag().booleanValue(); - newForceState = true; - } - else - { - // if we aren't forcing the state, just re-save whatever was previously saved: - newConflictState = previousComment != null && previousComment.isQualityControlFlag(); - newForceState = previousComment != null && previousComment.isQualityControlFlagForced(); - } - - if (previousComment == null || commentsForm.getConflictResolveEnum() == CommentsConflictResolution.REPLACE) - { - SpecimenManager.get().setSpecimenComment(getUser(), vial, commentsForm.getComments(), - newConflictState, newForceState); - } - else if (commentsForm.getConflictResolveEnum() == CommentsConflictResolution.APPEND) - { - SpecimenManager.get().setSpecimenComment(getUser(), vial, previousComment.getComment() + "\n" + commentsForm.getComments(), - newConflictState, newForceState); - } - // If we haven't updated by now, our user has selected CommentsConflictResolution.SKIP and previousComments is non-null - // so we no-op for this vial. - } - _successUrl = new ActionURL(commentsForm.getReferrer()); - return true; - } - - @Override - public ActionURL getSuccessURL(UpdateParticipantCommentsForm commentsForm) - { - return _successUrl; - } - - @Override - public void addNavTrail(NavTree root) - { - addBaseSpecimenNavTrail(root); - root.addChild("Set vial comments"); - } - } - - @RequiresPermission(RequestSpecimensPermission.class) - public class SubmitRequestAction extends FormHandlerAction - { - @Override - public void validateCommand(IdForm target, Errors errors) - { - } - - @Override - public boolean handlePost(IdForm form, BindException errors) throws Exception - { - if (!SettingsManager.get().isSpecimenShoppingCartEnabled(getContainer())) - throw new UnauthorizedException(); - - SpecimenRequest request = SpecimenRequestManager.get().getRequest(getContainer(), form.getId()); - requiresEditRequestPermissions(request); - List vials = request.getVials(); - if (!vials.isEmpty() || SpecimenService.get().getRequestCustomizer().allowEmptyRequests()) - { - SpecimenRequestStatus newStatus = SpecimenRequestManager.get().getInitialRequestStatus(getContainer(), getUser(), true); - request = request.createMutable(); - request.setStatusId(newStatus.getRowId()); - try - { - SpecimenRequestManager.get().updateRequest(getUser(), request); - SpecimenRequestManager.get().createRequestEvent(getUser(), request, - RequestEventType.REQUEST_STATUS_CHANGED, "Request submitted for processing.", null); - if (SettingsManager.get().isSpecimenShoppingCartEnabled(getContainer())) - { - try - { - sendNewRequestNotifications(request, errors); - } - catch (ConfigurationException e) - { - errors.reject(ERROR_MSG, e.getMessage()); - } - } - } - catch (RequestabilityManager.InvalidRuleException e) - { - errors.reject(ERROR_MSG, "The request could not be submitted because a requestability rule is configured incorrectly. " + - "Please report this problem to an administrator. Error details: " + e.getMessage()); - } - catch (IOException e) - { - errors.reject(ERROR_MSG, e.getMessage()); - } - } - else - { - errors.addError(new ObjectError("Specimen Request", new String[] {"NullError"}, null, "Only requests containing specimens can be submitted.")); - } - - return !errors.hasErrors(); - } - - @Override - public ActionURL getSuccessURL(IdForm idForm) - { - return getManageRequestURL(idForm.getId(), null).addParameter("submissionResult", true); - } - } - - @RequiresPermission(RequestSpecimensPermission.class) - public class DeleteRequestAction extends FormHandlerAction - { - @Override - public void validateCommand(IdForm target, Errors errors) - { - } - - @Override - public boolean handlePost(IdForm form, BindException errors) throws Exception - { - SpecimenRequest request = SpecimenRequestManager.get().getRequest(getContainer(), form.getId()); - requiresEditRequestPermissions(request); - SpecimenRequestStatus cartStatus = SpecimenRequestManager.get().getRequestShoppingCartStatus(getContainer(), getUser()); - if (request.getStatusId() != cartStatus.getRowId()) - throw new UnauthorizedException(); - - try - { - SpecimenRequestManager.get().deleteRequest(getUser(), request); - } - catch (RequestabilityManager.InvalidRuleException e) - { - errors.reject(ERROR_MSG, "The request could not be canceled because a requestability rule is configured incorrectly. " + - "Please report this problem to an administrator. Error details: " + e.getMessage()); - } - return true; - } - - @Override - public ActionURL getSuccessURL(IdForm form) - { - return new ActionURL(ViewRequestsAction.class, getContainer()); - } - } - - private ActionURL getSpecimensURL() - { - return getSpecimensURL(getContainer()); - } - - public static ActionURL getSpecimensURL(Container c) - { - return new ActionURL(SpecimensAction.class, c); - } - - public static ActionURL getSpecimensURL(Container c, boolean showVials) - { - return getSpecimensURL(c).addParameter(SpecimenViewTypeForm.PARAMS.showVials, showVials); - } - - public static ActionURL getCommentURL(Container c, String globalUniqueId) - { - return getSpecimensURL(c, true) - .addParameter(SpecimenViewTypeForm.PARAMS.viewMode, SpecimenQueryView.Mode.COMMENTS.name()) - .addParameter("SpecimenDetail.GlobalUniqueId~eq", globalUniqueId); - } - - @RequiresPermission(ReadPermission.class) - @ActionNames("specimens,samples") - public class SpecimensAction extends QueryViewAction - { - private boolean _vialView; - - public SpecimensAction() - { - super(SpecimenViewTypeForm.class); - } - - @Override - protected SpecimenQueryView createQueryView(SpecimenViewTypeForm form, BindException errors, boolean forExport, String dataRegion) - { - Study study = getStudyThrowIfNull(); - - _vialView = form.isShowVials(); - CohortFilter cohortFilter = CohortService.get().getFromURL(getContainer(), getUser(), getViewContext().getActionURL(), _vialView ? "SpecimenDetail" : "SpecimenSummary"); - SpecimenQueryView view = getSpecimenQueryView(_vialView, forExport, null, form.getViewModeEnum(), cohortFilter); - if (isCommentsMode(getContainer(), form.getViewModeEnum())) - view.setRestrictRecordSelectors(false); - return view; - } - - @Override - protected ModelAndView getHtmlView(SpecimenViewTypeForm form, BindException errors) throws Exception - { - Study study = getStudyRedirectIfNull(); - - SpecimenQueryView view = createInitializedQueryView(form, errors, form.getExportType() != null, null); - SpecimenHeaderBean bean = new SpecimenHeaderBean(getViewContext(), view); - // Get last selected request - Integer lastSpecimenRequest = StudyInternalService.get().getLastSpecimenRequest(study); - if (null != lastSpecimenRequest) - bean.setSelectedRequest(lastSpecimenRequest); - JspView header = new JspView<>("/org/labkey/specimen/view/specimenHeader.jsp", bean); - return new VBox(header, view); - } - - @Override - public void addNavTrail(NavTree root) - { - addBaseSpecimenNavTrail(root); - root.addChild(_vialView ? "Vials" : "Grouped Vials"); - } - } - - @RequiresPermission(ReadPermission.class) - public class RequestHistoryAction extends SimpleViewAction - { - private int _requestId; - - @Override - public ModelAndView getView(IdForm form, BindException errors) - { - _requestId = form.getId(); - @Nullable SpecimenRequest request = SpecimenRequestManager.get().getRequest(getContainer(), _requestId); - if (null == request) - throw new NotFoundException("Specimen request " + _requestId + " was not found in this study"); - HtmlView header = new HtmlView(LinkBuilder.labkeyLink("View Request", SpecimenController.getManageRequestURL(getContainer(), form.getId(), null))); - SimpleFilter filter = new SimpleFilter(FieldKey.fromParts("RequestId"), form.getId()); - GridView historyGrid = getRequestEventGridView(getViewContext().getRequest(), errors, filter); - return new VBox(header, historyGrid); - } - - @Override - public void addNavTrail(NavTree root) - { - addSpecimenRequestNavTrail(root, _requestId); - root.addChild("Request History"); - } - } - - @RequiresPermission(ManageNewRequestFormPermission.class) - public class ManageRequestInputsAction extends SimpleViewAction - { - @Override - public ModelAndView getView(PipelineForm pipelineForm, BindException errors) throws Exception - { - ensureSpecimenRequestsConfigured(false); - return new JspView<>("/org/labkey/specimen/view/manageRequestInputs.jsp", - new ManageRequestInputsBean(getViewContext())); - } - - @Override - public void addNavTrail(NavTree root) - { - setHelpTopic("coordinateSpecimens#form"); - addManageStudyNavTrail(root); - root.addChild("Manage New Request Form"); - } - } - - public static final class ManageRequestInputsForm - { - private String[] _title; - private String[] _helpText; - private int[] _multiline; - private int[] _required; - private int[] _rememberSiteValue; - - public String[] getHelpText() - { - return _helpText; - } - - public void setHelpText(String[] helpText) - { - _helpText = helpText; - } - - public String[] getTitle() - { - return _title; - } - - public void setTitle(String[] title) - { - _title = title; - } - - public int[] getMultiline() - { - return _multiline; - } - - public void setMultiline(int[] multiline) - { - _multiline = multiline; - } - - public int[] getRememberSiteValue() - { - return _rememberSiteValue; - } - - public void setRememberSiteValue(int[] rememberSiteValue) - { - _rememberSiteValue = rememberSiteValue; - } - - public int[] getRequired() - { - return _required; - } - - public void setRequired(int[] required) - { - _required = required; - } - } - - @RequiresPermission(ManageNewRequestFormPermission.class) - public class HandleUpdateRequestInputsAction extends FormHandlerAction - { - @Override - public void validateCommand(ManageRequestInputsForm target, Errors errors) - { - } - - @Override - public boolean handlePost(ManageRequestInputsForm form, BindException errors) throws Exception - { - SpecimenRequestManager.SpecimenRequestInput[] inputs = new SpecimenRequestManager.SpecimenRequestInput[form.getTitle().length]; - for (int i = 0; i < form.getTitle().length; i++) - { - String title = form.getTitle()[i]; - String helpText = form.getHelpText()[i]; - inputs[i] = new SpecimenRequestManager.SpecimenRequestInput(title, helpText, i); - } - - if (form.getMultiline() != null) - { - for (int index : form.getMultiline()) - inputs[index].setMultiLine(true); - } - if (form.getRequired() != null) - { - for (int index : form.getRequired()) - inputs[index].setRequired(true); - } - if (form.getRememberSiteValue() != null) - { - for (int index : form.getRememberSiteValue()) - inputs[index].setRememberSiteValue(true); - } - SpecimenRequestManager.get().saveNewSpecimenRequestInputs(getContainer(), inputs); - return true; - } - - @Override - public ActionURL getSuccessURL(ManageRequestInputsForm manageRequestInputsForm) - { - return getManageStudyURL(); - } - } - - public static class RequirementForm extends IdForm - { - private int _requirementId; - - public int getRequirementId() - { - return _requirementId; - } - - public void setRequirementId(int requirementId) - { - _requirementId = requirementId; - } - } - - public static class ManageRequirementForm extends RequirementForm - { - private boolean _complete; - private String _comment; - private String[] _notificationIdPairs; - private boolean _emailInactiveUsers; - - public String getComment() - { - return _comment; - } - - public void setComment(String comment) - { - _comment = comment; - } - - public boolean isComplete() - { - return _complete; - } - - public void setComplete(boolean complete) - { - _complete = complete; - } - - public String[] getNotificationIdPairs() - { - return _notificationIdPairs; - } - - public void setNotificationIdPairs(String[] notificationIdPairs) - { - _notificationIdPairs = notificationIdPairs; - } - - public boolean isEmailInactiveUsers() - { - return _emailInactiveUsers; - } - - public void setEmailInactiveUsers(boolean emailInactiveUsers) - { - _emailInactiveUsers = emailInactiveUsers; - } - } - - @RequiresPermission(ManageRequestsPermission.class) - public class DeleteRequirementAction extends FormHandlerAction - { - @Override - public void validateCommand(RequirementForm target, Errors errors) - { - } - - @Override - public boolean handlePost(RequirementForm form, BindException errors) throws Exception - { - SpecimenRequestRequirement requirement = - SpecimenRequestRequirementProvider.get().getRequirement(getContainer(), form.getRequirementId()); - // getRequirement is container-scoped; null means the requirementId doesn't belong to this folder - if (requirement != null && requirement.getRequestId() == form.getId()) - { - SpecimenRequestManager.get().deleteRequestRequirement(getUser(), requirement); - return true; - } - - return false; - } - - @Override - public ActionURL getSuccessURL(RequirementForm requirementForm) - { - return getManageRequestURL(requirementForm.getId(), null); - } - } - - public class ManageRequirementBean - { - private final GridView _historyView; - private final SpecimenRequestRequirement _requirement; - private final boolean _requestManager; - private final List _possibleNotifications; - private final boolean _finalState; - - public ManageRequirementBean(ViewContext context, SpecimenRequest request, SpecimenRequestRequirement requirement) - { - _requirement = requirement; - _possibleNotifications = SpecimenController.getPossibleNotifications(request); - SimpleFilter filter = new SimpleFilter(FieldKey.fromParts("RequestId"), requirement.getRequestId()); - filter.addCondition(FieldKey.fromParts("RequirementId"), requirement.getRowId()); - _requestManager = context.getContainer().hasPermission(context.getUser(), ManageRequestsPermission.class); - _historyView = getRequestEventGridView(context.getRequest(), null, filter); - _finalState = SpecimenRequestManager.get().isInFinalState(request); - } - - public boolean isDefaultNotification(ActorNotificationRecipientSet notification) - { - RequestNotificationSettings settings = org.labkey.specimen.settings.SettingsManager.get().getRequestNotificationSettings(getContainer()); - if (settings.getDefaultEmailNotifyEnum() == RequestNotificationSettings.DefaultEmailNotifyEnum.All) - return true; // All should be checked - else if (settings.getDefaultEmailNotifyEnum() == RequestNotificationSettings.DefaultEmailNotifyEnum.None) - return false; // None should be checked - // Otherwise use Actor Notification - - Integer requirementActorId = _requirement.getActorId(); - Integer notificationActorId = notification.getActor() != null ? notification.getActor().getRowId() : null; - Integer requirementSiteId = _requirement.getSiteId(); - Integer notificationSiteId = notification.getLocation() != null ? notification.getLocation().getRowId() : null; - return nullSafeEqual(requirementActorId, notificationActorId) && - nullSafeEqual(requirementSiteId, notificationSiteId); - } - - public GridView getHistoryView() - { - return _historyView; - } - - public SpecimenRequestRequirement getRequirement() - { - return _requirement; - } - - public List getPossibleNotifications() - { - return _possibleNotifications; - } - - public boolean isRequestManager() - { - return _requestManager; - } - - public boolean isFinalState() - { - return _finalState; - } - } - - @RequiresPermission(ReadPermission.class) - public class ManageRequirementAction extends FormViewAction - { - private SpecimenRequest _specimenRequest; - - @Override - public void validateCommand(ManageRequirementForm target, Errors errors) - { - } - - @Override - public ModelAndView getView(ManageRequirementForm form, boolean reshow, BindException errors) - { - _specimenRequest = SpecimenRequestManager.get().getRequest(getContainer(), form.getId()); - final SpecimenRequestRequirement requirement = - SpecimenRequestRequirementProvider.get().getRequirement(getContainer(), form.getRequirementId()); - if (_specimenRequest == null || requirement == null || requirement.getRequestId() != form.getId()) - throw new NotFoundException(); - - return new JspView<>("/org/labkey/specimen/view/manageRequirement.jsp", - new ManageRequirementBean(getViewContext(), _specimenRequest, requirement), errors); - } - - @Override - public boolean handlePost(final ManageRequirementForm form, BindException errors) throws Exception - { - if (!getContainer().hasPermission(getUser(), ManageRequestsPermission.class)) - throw new UnauthorizedException("You do not have permission to update requirements!"); - - _specimenRequest = SpecimenRequestManager.get().getRequest(getContainer(), form.getId()); - final SpecimenRequestRequirement requirement = - SpecimenRequestRequirementProvider.get().getRequirement(getContainer(), form.getRequirementId()); - if (_specimenRequest == null || requirement == null || requirement.getRequestId() != form.getId()) - throw new NotFoundException(); - - List files = getAttachmentFileList(); - RequestEventType eventType; - StringBuilder comment = new StringBuilder(); - comment.append(requirement.getRequirementSummary()); - String eventSummary; - if (form.isComplete() != requirement.isComplete()) - { - SpecimenRequestRequirement clone = requirement.createMutable(); - clone.setComplete(form.isComplete()); - SpecimenRequestManager.get().updateRequestRequirement(getUser(), clone); - eventType = RequestEventType.REQUEST_STATUS_CHANGED; - comment.append("\nStatus changed to ").append(form.isComplete() ? "complete" : "incomplete"); - eventSummary = comment.toString(); - } - else - { - eventType = RequestEventType.COMMENT_ADDED; - eventSummary = "Comment added."; - } - - if (form.getComment() != null && !form.getComment().isEmpty()) - comment.append("\n").append(form.getComment()); - - SpecimenRequestEvent event; - try - { - event = SpecimenRequestManager.get().createRequestEvent(getUser(), requirement, eventType, comment.toString(), files); - } - catch (Exception e) - { - errors.reject(ERROR_MSG, "The request could not be updated because of an unexpected error. " + - "Please report this problem to an administrator. Error details: " + e.getMessage()); - return false; - } - try - { - - List recipients = getNotifications(_specimenRequest, form.getNotificationIdPairs()); - DefaultRequestNotification notification = new DefaultRequestNotification(_specimenRequest, recipients, - eventSummary, event, form.getComment(), requirement, getViewContext()); - sendNotification(notification, form.isEmailInactiveUsers(), errors); - } - catch (ConfigurationException | IOException e) - { - errors.reject(ERROR_MSG, "The request was updated successfully, but the notification failed: " + e.getMessage()); - return false; - } - - return !errors.hasErrors(); - } - - @Override - public ActionURL getSuccessURL(ManageRequirementForm manageRequirementForm) - { - return getManageRequestURL(_specimenRequest.getRowId(), null); - } - - @Override - public void addNavTrail(NavTree root) - { - addSpecimenRequestNavTrail(root, _specimenRequest.getRowId()); - root.addChild("Manage Requirement"); - } - } - - @RequiresPermission(ManageRequestsPermission.class) - public class DeleteMissingRequestSpecimensAction extends FormHandlerAction - { - @Override - public void validateCommand(IdForm target, Errors errors) - { - } - - @Override - public boolean handlePost(IdForm form, BindException errors) throws Exception - { - SpecimenRequest request = SpecimenRequestManager.get().getRequest(getContainer(), form.getId()); - if (request == null) - throw new NotFoundException("Specimen request " + form.getId() + " does not exist."); - - SpecimenRequestManager.get().deleteMissingSpecimens(request); - return true; - } - - @Override - public ActionURL getSuccessURL(IdForm requirementForm) - { - return getManageRequestURL(requirementForm.getId(), null); - } - } - - public static class LabSpecimenListsBean - { - public enum Type - { - PROVIDING("Providing"), - ORIGINATING("Originating"); - - private final String _display; - - Type(String display) - { - _display = display; - } - - public String getDisplay() - { - return _display; - } - } - - private final SpecimenRequest _specimenRequest; - private final Type _type; - - private Map> _specimensBySiteId; - private List _possibleNotifications; - private boolean _requirementsComplete; - - public LabSpecimenListsBean(SpecimenRequest specimenRequest, LabSpecimenListsBean.Type type) - { - _specimenRequest = specimenRequest; - _type = type; - _requirementsComplete = true; - for (int i = 0; i < specimenRequest.getRequirements().length && _requirementsComplete; i++) - { - SpecimenRequestRequirement requirement = specimenRequest.getRequirements()[i]; - _requirementsComplete = requirement.isComplete(); - } - } - - public SpecimenRequest getSpecimenRequest() - { - return _specimenRequest; - } - - private synchronized Map> getSpecimensBySiteId() - { - if (_specimensBySiteId == null) - { - _specimensBySiteId = new HashMap<>(); - List vials = _specimenRequest.getVials(); - for (Vial vial : vials) - { - LocationImpl location; - if (_type == LabSpecimenListsBean.Type.ORIGINATING) - location = LocationManager.get().getOriginatingLocation(vial); - else - location = LocationManager.get().getCurrentLocation(vial); - if (location != null) - { - List current = _specimensBySiteId.computeIfAbsent(location.getRowId(), k -> new ArrayList<>()); - current.add(vial); - } - } - } - return _specimensBySiteId; - } - - public synchronized List getPossibleNotifications() - { - if (_possibleNotifications == null) - _possibleNotifications = SpecimenController.getPossibleNotifications(_specimenRequest); - return _possibleNotifications; - } - - public Set getLabs() - { - Map> siteIdToSpecimens = getSpecimensBySiteId(); - Set locations = new HashSet<>(siteIdToSpecimens.size()); - for (Integer locationId : siteIdToSpecimens.keySet()) - locations.add(LocationManager.get().getLocation(_specimenRequest.getContainer(), locationId)); - return locations; - } - - public List getSpecimens(LocationImpl location) - { - Map> siteSpecimenLists = getSpecimensBySiteId(); - return siteSpecimenLists.get(location.getRowId()); - } - - public Type getType() - { - return _type; - } - - public boolean isRequirementsComplete() - { - return _requirementsComplete; - } - } - - @RequiresPermission(ManageRequestsPermission.class) - public class LabSpecimenListsAction extends SimpleViewAction - { - private int _requestId; - private LabSpecimenListsBean.Type _type; - - @Override - public ModelAndView getView(LabSpecimenListsForm form, BindException errors) - { - SpecimenRequest request = SpecimenRequestManager.get().getRequest(getContainer(), form.getId()); - if (request == null || form.getListType() == null) - throw new NotFoundException(); - - _requestId = request.getRowId(); - - try - { - _type = LabSpecimenListsBean.Type.valueOf(form.getListType()); - } - catch (IllegalArgumentException e) - { - // catch malformed/old URL case, where the posted value of 'type' isn't a valid Type: - throw new NotFoundException("Unrecognized list type."); - } - return new JspView<>("/org/labkey/specimen/view/labSpecimenLists.jsp", new LabSpecimenListsBean(request, _type)); - } - - @Override - public void addNavTrail(NavTree root) - { - addSpecimenRequestNavTrail(root, _requestId); - root.addChild(_type.getDisplay() + " Lab Vial Lists"); - } - } - - public static class LabSpecimenListsForm extends IdForm - { - private String _listType; - - public String getListType() - { - return _listType; - } - - public void setListType(String listType) - { - _listType = listType; - } - } - - public static class ExportSiteForm extends IdForm - { - private String _export; - private String _specimenIds; - private String _listType; - private int _sourceSiteId; - private int _destSiteId; - - public String getExport() - { - return _export; - } - - public void setExport(String export) - { - _export = export; - } - - public String getSpecimenIds() - { - return _specimenIds; - } - - public void setSpecimenIds(String specimenIds) - { - _specimenIds = specimenIds; - } - - public int getDestSiteId() - { - return _destSiteId; - } - - public void setDestSiteId(int destSiteId) - { - _destSiteId = destSiteId; - } - - public int getSourceSiteId() - { - return _sourceSiteId; - } - - public void setSourceSiteId(int sourceSiteId) - { - _sourceSiteId = sourceSiteId; - } - - public String getListType() - { - return _listType; - } - - public void setListType(String listType) - { - _listType = listType; - } - } - - @RequiresPermission(ManageRequestsPermission.class) - public class DownloadSpecimenListAction extends SimpleViewAction - { - @Override - public ModelAndView getView(ExportSiteForm form, BindException errors) throws Exception - { - SpecimenRequest specimenRequest = SpecimenRequestManager.get().getRequest(getContainer(), form.getId()); - LocationImpl sourceLocation = LocationManager.get().getLocation(getContainer(), form.getSourceSiteId()); - LocationImpl destLocation = LocationManager.get().getLocation(getContainer(), form.getDestSiteId()); - if (specimenRequest == null || sourceLocation == null || destLocation == null) - throw new NotFoundException(); - - LabSpecimenListsBean.Type type = LabSpecimenListsBean.Type.valueOf(form.getListType()); - if (null != form.getExport()) - { - if (EXPORT_TSV.equals(form.getExport())) - { - try (TSVGridWriter writer = getSpecimenListTsvWriter(specimenRequest, sourceLocation, destLocation, type)) - { - writer.write(getViewContext().getResponse()); - } - } - else if (EXPORT_XLS.equals(form.getExport())) - { - ExcelWriter writer = getSpecimenListXlsWriter(specimenRequest, sourceLocation, destLocation, type); - writer.renderWorkbook(getViewContext().getResponse()); - } - } - return null; - } - - @Override - public void addNavTrail(NavTree root) - { - throw new UnsupportedOperationException("Not Yet Implemented"); - } - } - - private static final String EXPORT_TSV = "tsv"; - private static final String EXPORT_XLS = "xls"; - - public static class EmailSpecimenListForm extends IdForm - { - private boolean _sendXls; - private boolean _sendTsv; - private String _comments; - private String[] _notify; - private String _listType; - private boolean _emailInactiveUsers; - - public String getComments() - { - return _comments; - } - - public void setComments(String comments) - { - _comments = comments; - } - - public String[] getNotify() - { - return _notify; - } - - public void setNotify(String[] notify) - { - _notify = notify; - } - - public boolean isSendTsv() - { - return _sendTsv; - } - - public void setSendTsv(boolean sendTsv) - { - _sendTsv = sendTsv; - } - - public boolean isSendXls() - { - return _sendXls; - } - - public void setSendXls(boolean sendXls) - { - _sendXls = sendXls; - } - - public String getListType() - { - return _listType; - } - - public void setListType(String listType) - { - _listType = listType; - } - - public boolean isEmailInactiveUsers() - { - return _emailInactiveUsers; - } - - public void setEmailInactiveUsers(boolean emailInactiveUsers) - { - _emailInactiveUsers = emailInactiveUsers; - } - } - - @RequiresPermission(ManageRequestsPermission.class) - public class EmailLabSpecimenListsAction extends FormHandlerAction - { - @Override - public void validateCommand(EmailSpecimenListForm target, Errors errors) - { - } - - @Override - public boolean handlePost(EmailSpecimenListForm form, BindException errors) throws Exception - { - final SpecimenRequest request = SpecimenRequestManager.get().getRequest(getContainer(), form.getId()); - if (request == null) - throw new NotFoundException(); - - LocationImpl receivingLocation = LocationManager.get().getLocation(getContainer(), request.getDestinationSiteId()); - if (receivingLocation == null) - throw new NotFoundException(); - - final LabSpecimenListsBean.Type type; - try - { - type = LabSpecimenListsBean.Type.valueOf(form.getListType()); - } - catch (IllegalArgumentException e) - { - throw new NotFoundException(); - } - - Map> notifications = new HashMap<>(); - if (form.getNotify() != null) - { - for (String tuple : form.getNotify()) - { - String[] idStrs = tuple.split(","); - if (idStrs.length != 3) - throw new IllegalStateException("Expected triple."); - int[] ids = new int[3]; - for (int i = 0; i < 3; i++) - ids[i] = Integer.parseInt(idStrs[i]); - LocationImpl originatingOrProvidingLocation = LocationManager.get().getLocation(getContainer(), ids[0]); - SpecimenRequestActor notifyActor = SpecimenRequestRequirementProvider.get().getActor(getContainer(), ids[1]); - // getActor is container-scoped; null means the actorId doesn't belong to this folder - if (notifyActor == null) - throw new NotFoundException("No notification actor found for id " + ids[1] + " in this folder"); - LocationImpl notifyLocation = null; - if (notifyActor.isPerSite() && ids[2] >= 0) - notifyLocation = LocationManager.get().getLocation(getContainer(), ids[2]); - List emailRecipients = notifications.computeIfAbsent(originatingOrProvidingLocation, k -> new ArrayList<>()); - emailRecipients.add(new ActorNotificationRecipientSet(notifyActor, notifyLocation)); - } - - for (final LocationImpl originatingOrProvidingLocation : notifications.keySet()) - { - List formFiles = getAttachmentFileList(); - if (form.isSendTsv()) - { - try (TSVGridWriter tsvWriter = getSpecimenListTsvWriter(request, originatingOrProvidingLocation, receivingLocation, type)) - { - StringBuilder tsvBuilder = new StringBuilder(); - tsvWriter.write(tsvBuilder); - formFiles.add(new ByteArrayAttachmentFile(tsvWriter.getFilenamePrefix() + ".tsv", tsvBuilder.toString().getBytes(StandardCharsets.UTF_8), TSVWriter.DELIM.TAB.contentType)); - } - } - - if (form.isSendXls()) - { - try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); OutputStream ostream = new BufferedOutputStream(byteStream)) - { - ExcelWriter xlsWriter = getSpecimenListXlsWriter(request, originatingOrProvidingLocation, receivingLocation, type); - xlsWriter.renderWorkbook(ostream); - formFiles.add(new ByteArrayAttachmentFile(xlsWriter.getFilenamePrefix() + "." + xlsWriter.getDocumentType().name(), byteStream.toByteArray(), xlsWriter.getDocumentType().getMimeType())); - } - } - - final StringBuilder content = new StringBuilder(); - if (form.getComments() != null) - content.append(form.getComments()); - - String header = type.getDisplay() + " location notification of specimen shipment to " + receivingLocation.getDisplayName(); - try - { - SpecimenRequestEvent event = SpecimenRequestManager.get().createRequestEvent(getUser(), request, - RequestEventType.SPECIMEN_LIST_GENERATED, header + "\n" + content, formFiles); - - final Container container = getContainer(); - final User user = getUser(); - List emailRecipients = notifications.get(originatingOrProvidingLocation); - DefaultRequestNotification notification = new DefaultRequestNotification(request, emailRecipients, - header, event, content.toString(), null, getViewContext()) - { - @Override - protected List getSpecimenList() - { - SimpleFilter filter = getSpecimenListFilter(getSpecimenRequest(), originatingOrProvidingLocation, type); - return SpecimenManager.get().getVials(container, user, filter); -// return new TableSelector(StudySchema.getInstance().getTableInfoSpecimenDetail(container), filter, null).getArrayList(Specimen.class); - } - - }; - sendNotification(notification, form.isEmailInactiveUsers(), errors); - } - catch (ConfigurationException | IOException e) - { - errors.reject(ERROR_MSG, e.getMessage()); - } - - if (errors.hasErrors()) - break; - } - } - - return !errors.hasErrors(); - } - - @Override - public ActionURL getSuccessURL(EmailSpecimenListForm emailSpecimenListForm) - { - return getManageRequestURL(emailSpecimenListForm.getId(), null); - } - } - - public static class EnabledSpecimenImportForm - { - private String _activeTransform; - - public String getActiveTransform() - { - return _activeTransform; - } - - @SuppressWarnings("unused") - public void setActiveTransform(String activeTransform) - { - _activeTransform = activeTransform; - } - } - - @RequiresPermission(AdminPermission.class) - public static class ChooseImporterAction extends FormViewAction - { - @Override - public void addNavTrail(NavTree root) - { - root.addChild("Specimen Import Mechanism"); - } - - @Override - public void validateCommand(EnabledSpecimenImportForm target, Errors errors) - { - } - - @Override - public ModelAndView getView(EnabledSpecimenImportForm form, boolean reshow, BindException errors) throws Exception - { - setHelpTopic("externalSpecimens"); - return new JspView<>("/org/labkey/specimen/view/chooseImporter.jsp", form, errors); - } - - @Override - public boolean handlePost(EnabledSpecimenImportForm form, BindException errors) throws Exception - { - WritablePropertyMap props = PropertyManager.getWritableProperties(getContainer(), "enabledSpecimenImporter", true); - props.put("active", form.getActiveTransform()); - props.save(); - return true; - } - - @Override - public URLHelper getSuccessURL(EnabledSpecimenImportForm configForm) - { - Container c = getContainer(); - SpecimenService specimenService = SpecimenService.get(); - - String active = specimenService.getActiveSpecimenImporter(c); - SpecimenTransform activeTransform = specimenService.getSpecimenTransform(active); - return activeTransform.getManageAction(c, getUser()); - } - } - - public static class ManageLocationTypesForm - { - private boolean _repository; - private boolean _clinic; - private boolean _sal; - private boolean _endpoint; - - public boolean isRepository() - { - return _repository; - } - - public void setRepository(boolean repository) - { - _repository = repository; - } - - public boolean isClinic() - { - return _clinic; - } - - public void setClinic(boolean clinic) - { - _clinic = clinic; - } - - public boolean isSal() - { - return _sal; - } - - public void setSal(boolean sal) - { - _sal = sal; - } - - public boolean isEndpoint() - { - return _endpoint; - } - - public void setEndpoint(boolean endpoint) - { - _endpoint = endpoint; - } - } - - @RequiresPermission(AdminPermission.class) - public class ManageLocationTypesAction extends SimpleViewAction - { - @Override - public ModelAndView getView(ManageLocationTypesForm form, BindException errors) - { - Study study = getStudyRedirectIfNull(); - form.setRepository(study.isAllowReqLocRepository()); - form.setClinic(study.isAllowReqLocClinic()); - form.setSal(study.isAllowReqLocSal()); - form.setEndpoint(study.isAllowReqLocEndpoint()); - return new JspView<>("/org/labkey/specimen/view/manageLocationTypes.jsp", form); - } - - @Override - public void addNavTrail(NavTree root) - { - setHelpTopic("manageLocations"); - addManageStudyNavTrail(root); - root.addChild("Manage Location Types"); - } - } - - @RequiresPermission(AdminPermission.class) - public class SaveLocationsTypeSettingsAction extends MutatingApiAction - { - @Override - public ApiResponse execute(ManageLocationTypesForm form, BindException errors) - { - ApiSimpleResponse response = new ApiSimpleResponse(); - Study study = getStudyThrowIfNull(); - StudyService.get().saveLocationSettings(study, getUser(), form.isRepository(), form.isClinic(), form.isSal(), form.isEndpoint()); - response.put("success", true); - return response; - } - } - public static class ManageCommentsForm - { - private Integer _participantCommentDatasetId; - private String _participantCommentProperty; - private Integer _participantVisitCommentDatasetId; - private String _participantVisitCommentProperty; - private boolean _reshow; - - public Integer getParticipantCommentDatasetId() - { - return _participantCommentDatasetId; - } - - public void setParticipantCommentDatasetId(Integer participantCommentDatasetId) - { - _participantCommentDatasetId = participantCommentDatasetId; - } - - public String getParticipantCommentProperty() - { - return _participantCommentProperty; - } - - public void setParticipantCommentProperty(String participantCommentProperty) - { - _participantCommentProperty = participantCommentProperty; - } - - public Integer getParticipantVisitCommentDatasetId() - { - return _participantVisitCommentDatasetId; - } - - public void setParticipantVisitCommentDatasetId(Integer participantVisitCommentDatasetId) - { - _participantVisitCommentDatasetId = participantVisitCommentDatasetId; - } - - public String getParticipantVisitCommentProperty() - { - return _participantVisitCommentProperty; - } - - public void setParticipantVisitCommentProperty(String participantVisitCommentProperty) - { - _participantVisitCommentProperty = participantVisitCommentProperty; - } - - public boolean isReshow() - { - return _reshow; - } - - public void setReshow(boolean reshow) - { - _reshow = reshow; - } - } - - @RequiresPermission(AdminPermission.class) - public class ManageSpecimenCommentsAction extends FormViewAction - { - @Override - public void validateCommand(ManageCommentsForm form, Errors errors) - { - String subjectNoun = StudyService.get().getSubjectNounSingular(getContainer()); - final Study study = getStudyRedirectIfNull(getContainer()); - if (form.getParticipantCommentDatasetId() != null && form.getParticipantCommentDatasetId() != -1) - { - Dataset ds = StudyService.get().getDataset(getContainer(), form.getParticipantCommentDatasetId()); - if (ds != null && !ds.isDemographicData()) - { - errors.reject(ERROR_MSG, "The Dataset specified to contain " + subjectNoun + " comments must be a demographics dataset."); - } - - if (form.getParticipantCommentProperty() == null) - errors.reject(ERROR_MSG, "A Comment field name must be specified for the " + subjectNoun + " Comment Assignment."); - } - - if (study.getTimepointType() != TimepointType.CONTINUOUS) - { - if (form.getParticipantVisitCommentDatasetId() != null && form.getParticipantVisitCommentDatasetId() != -1) - { - Dataset ds = StudyService.get().getDataset(getContainer(), form.getParticipantVisitCommentDatasetId()); - if (ds != null && ds.isDemographicData()) - { - errors.reject(ERROR_MSG, "The Dataset specified to contain " + subjectNoun + "/Visit comments cannot be a demographics dataset."); - } - - if (form.getParticipantVisitCommentProperty() == null) - errors.reject(ERROR_MSG, "A Comment field name must be specified for the " + subjectNoun + "/Visit Comment Assignment."); - } - } - } - - @Override - public ModelAndView getView(ManageCommentsForm form, boolean reshow, BindException errors) - { - Study study = getStudyRedirectIfNull(); - StudyInternalService svc = StudyInternalService.get(); - - if (!svc.hasEditableDatasets(study)) - return HtmlView.of("Comments can only be configured for studies with editable datasets."); - - if (!form.isReshow()) - { - form.setParticipantCommentDatasetId(svc.getParticipantCommentDatasetId(study)); - form.setParticipantCommentProperty(svc.getParticipantCommentProperty(study)); - - if (study.getTimepointType() != TimepointType.CONTINUOUS) - { - form.setParticipantVisitCommentDatasetId(svc.getParticipantVisitCommentDatasetId(study)); - form.setParticipantVisitCommentProperty(svc.getParticipantVisitCommentProperty(study)); - } - } - JspView view = new JspView<>("/org/labkey/specimen/view/manageComments.jsp", form, errors); - view.setTitle("Comment Configuration"); - - return view; - } - - @Override - public boolean handlePost(ManageCommentsForm form, BindException errors) - { - StudyInternalService.get().saveCommentsSettings(getStudyThrowIfNull(), getUser(), form.getParticipantCommentDatasetId(), form.getParticipantCommentProperty(), - form.getParticipantVisitCommentDatasetId(), form.getParticipantVisitCommentProperty()); - return true; - } - - @Override - public ActionURL getSuccessURL(ManageCommentsForm form) - { - return getManageStudyURL(); - } - - @Override - public void addNavTrail(NavTree root) - { - setHelpTopic("manageComments"); - addManageStudyNavTrail(root); - root.addChild("Manage Comments"); - } - } - - public record PtidVisit(String ptid, String visit){} - - @RequiresPermission(ReadPermission.class) - public class SelectedSpecimensAction extends QueryViewAction - { - private boolean _vialView; - private Collection _filterPds = null; - - public SelectedSpecimensAction() - { - super(SpecimenViewTypeForm.class); - } - - @Override - protected ModelAndView getHtmlView(SpecimenViewTypeForm form, BindException errors) throws Exception - { - Study study = getStudyRedirectIfNull(); - Set ptidVisits = new HashSet<>(); - for (ParticipantDataset pd : getFilterPds()) - { - if (pd.getSequenceNum() == null) - { - ptidVisits.add(new PtidVisit(pd.getParticipantId(), null)); - } - else if (study.getTimepointType() != TimepointType.VISIT && pd.getVisitDate() != null) - { - ptidVisits.add(new PtidVisit(pd.getParticipantId(), DateUtil.formatDate(pd.getContainer(), pd.getVisitDate()))); - } - else - { - Visit visit = pd.getSequenceNum() != null ? StudyInternalService.get().getVisitForSequence(study, pd.getSequenceNum()) : null; - ptidVisits.add(new PtidVisit(pd.getParticipantId(), visit != null ? visit.getLabel() : StudyInternalService.get().formatSequenceNum(pd.getSequenceNum()))); - } - } - SpecimenQueryView view = createInitializedQueryView(form, errors, form.getExportType() != null, null); - JspView header = new JspView<>("/org/labkey/specimen/view/specimenHeader.jsp", - new SpecimenHeaderBean(getViewContext(), view, ptidVisits)); - return new VBox(header, view); - } - - private @NotNull Collection getFilterPds() - { - if (_filterPds == null) - { - Set lsids = getSelectionLsids(); - _filterPds = StudyInternalService.get().getParticipantDatasets(getContainer(), lsids); - } - return _filterPds; - } - - @Override - protected SpecimenQueryView createQueryView(SpecimenViewTypeForm form, BindException errors, boolean forExport, String dataRegion) - { - _vialView = form.isShowVials(); - Set lsids = getSelectionLsids(); - SpecimenQueryView view; - CohortFilter cohortFilter = CohortService.get().getFromURL(getContainer(), getUser(), getViewContext().getActionURL(), SpecimenQueryView.ViewType.SUMMARY.getQueryName()); - if (lsids != null) - { - view = getSpecimenQueryView(form.isShowVials(), forExport, getFilterPds(), form.getViewModeEnum(), cohortFilter); - } - else - view = getSpecimenQueryView(form.isShowVials(), forExport, null, form.getViewModeEnum(), cohortFilter); - view.setAllowExportExternalQuery(false); - return view; - } - - @Override - public void addNavTrail(NavTree root) - { - addRootNavTrail(root); - root.addChild(_vialView ? "Selected Vials" : "Selected Specimens"); - } - } - - @RequiresPermission(ReadPermission.class) - public class CopyParticipantCommentAction extends SimpleViewAction - { - @Override - public ModelAndView getView(final ParticipantCommentForm form, BindException errors) - { - Study study = getStudyRedirectIfNull(); - Dataset ds; - - if (form.getVisitId() != 0) - { - ds = StudyService.get().getDataset(study.getContainer(), StudyInternalService.get().getParticipantVisitCommentDatasetId(study)); - } - else - { - ds = StudyService.get().getDataset(study.getContainer(), StudyInternalService.get().getParticipantCommentDatasetId(study)); - } - - if (ds != null) - { - QueryView datasetQueryView = StudyInternalService.get().getDatasetQueryView(getViewContext(), study, getUser(), ds, form.getParticipantId(), form.getVisitId(), errors); - String lsid = getExistingComment(datasetQueryView); - ActionURL url; - if (lsid != null) - url = new ActionURL(ParticipantCommentAction.SpecimenCommentUpdateAction.class, getContainer()). - addParameter(ParticipantCommentForm.params.lsid, lsid); - else - url = new ActionURL(ParticipantCommentAction.SpecimenCommentInsertAction.class, getContainer()). - addParameter(ParticipantCommentForm.params.participantId, form.getParticipantId()); - - url.addParameter(ParticipantCommentForm.params.datasetId, ds.getDatasetId()). - addParameter(ParticipantCommentForm.params.comment, form.getComment()). - addReturnUrl(form.getReturnActionURL()). - addParameter(ParticipantCommentForm.params.visitId, form.getVisitId()); - - for (int rowId : form.getVialCommentsToClear()) - url.addParameter(ParticipantCommentForm.params.vialCommentsToClear, rowId); - return HttpView.redirect(url); - } - return HtmlView.of("Dataset could not be found"); - } - - @Override - public void addNavTrail(NavTree root) - { - } - - private String getExistingComment(QueryView queryView) - { - final TableInfo table = queryView.getTable(); - if (table != null) - { - try (ResultSet rs = queryView.getResultSet()) - { - while (rs.next()) - { - String lsid = rs.getString("lsid"); - if (lsid != null) - return lsid; - } - } - catch (Exception e) - { - LOG.error("Error encountered trying to get {} comments", StudyService.get().getSubjectNounSingular(getContainer()), e); - } - } - return null; - } - } - - @RequiresPermission(EditSpecimenDataPermission.class) - public static class UpdateSpecimenQueryRowAction extends UserSchemaAction - { - @Override - public ModelAndView getView(QueryUpdateForm tableForm, boolean reshow, BindException errors) - { - // Don't allow GlobalUniqueId to be edited - TableInfo tableInfo = tableForm.getTable(); - if (null != tableInfo.getColumn("GlobalUniqueId")) - ((BaseColumnInfo)tableInfo.getColumn("GlobalUniqueId")).setReadOnly(true); - - var vialComments = tableInfo.getColumn("VialComments"); - if (null != vialComments) - { - ((BaseColumnInfo)vialComments).setUserEditable(false); - ((BaseColumnInfo)vialComments).setHidden(true); - } - - StudyInternalService.get().fixSpecimenRequestableColumn(tableForm); - ButtonBar bb = createSubmitCancelButtonBar(tableForm); - UpdateView view = new UpdateView(tableForm, errors); - view.getDataRegion().setButtonBar(bb); - return view; - } - - @Override - public boolean handlePost(QueryUpdateForm tableForm, BindException errors) - { - doInsertUpdate(tableForm, errors, false); - return 0 == errors.getErrorCount(); - } - - @Override - public void addNavTrail(NavTree root) - { - super.addNavTrail(root); - root.addChild("Edit " + _form.getQueryName()); - } - } - - @RequiresPermission(EditSpecimenDataPermission.class) - public static class InsertSpecimenQueryRowAction extends UserSchemaAction - { - @Override - public ModelAndView getView(QueryUpdateForm tableForm, boolean reshow, BindException errors) - { - TableInfo tableInfo = tableForm.getTable(); - var vialComments = tableInfo.getColumn("VialComments"); - if (null != vialComments) - { - ((BaseColumnInfo)vialComments).setUserEditable(false); - ((BaseColumnInfo)vialComments).setHidden(true); - } - - StudyInternalService.get().fixSpecimenRequestableColumn(tableForm); - InsertView view = new InsertView(tableForm, errors); - view.getDataRegion().setButtonBar(createSubmitCancelButtonBar(tableForm)); - return view; - } - - @Override - public boolean handlePost(QueryUpdateForm tableForm, BindException errors) - { - doInsertUpdate(tableForm, errors, true); - return 0 == errors.getErrorCount(); - } - - @Override - public void addNavTrail(NavTree root) - { - super.addNavTrail(root); - root.addChild("Insert " + _form.getQueryName()); - } - } - - public static class ConfigForm - { - private String _schemaName; - private String _queryName; - private String _viewName; - private String _enabled; - private int _userId; - - public int getUserId() - { - return _userId; - } - - public void setUserId(int userId) - { - _userId = userId; - } - - public String getSchemaName() - { - return _schemaName; - } - - public void setSchemaName(String schemaName) - { - _schemaName = schemaName; - } - - public String getQueryName() - { - return _queryName; - } - - public void setQueryName(String queryName) - { - _queryName = queryName; - } - - public String getViewName() - { - return _viewName; - } - - public void setViewName(String viewName) - { - _viewName = viewName; - } - - public String getEnabled() - { - return _enabled; - } - - public void setEnabled(String enabled) - { - _enabled = enabled; - } - - public Map getOptions() - { - Map valueMap = new HashMap<>(); - valueMap.put("schemaName", _schemaName); - valueMap.put("queryName", _queryName); - valueMap.put("viewName", _viewName); - valueMap.put("enabled", _enabled); - valueMap.put("userId", String.valueOf(_userId)); - return valueMap; - } - } - - @RequiresPermission(AdminPermission.class) - public static class ConfigureQueryImportAction extends FormViewAction - { - @Override - public void addNavTrail(NavTree root) - { - root.addChild("Query-based specimen import"); - } - - @Override - public void validateCommand(ConfigForm target, Errors errors) - { - } - - @Override - public ModelAndView getView(ConfigForm form, boolean reshow, BindException errors) - { - return new JspView<>("/org/labkey/specimen/view/configureQueryImport.jsp", form, errors); - } - - @Override - public boolean handlePost(ConfigForm form, BindException errors) - { - String QBSpecimenImportKey = QueryBasedSpecimenTransform.PROPERTY_MAP_KEY; - WritablePropertyMap props = PropertyManager.getWritableProperties(getContainer(), QBSpecimenImportKey, true); - form.setUserId(getUser().getUserId()); - Map valuesToPersist = form.getOptions(); - - if (!valuesToPersist.isEmpty()) - { - props.putAll(valuesToPersist); - props.save(); - return true; - } - - return false; - } - - @Override - public URLHelper getSuccessURL(ConfigForm configForm) - { - return PageFlowUtil.urlProvider(StudyUrls.class).getManageStudyURL(getContainer()); - } - } - - @RequiresPermission(AdminPermission.class) - public static class ReloadQueryBasedImportAction extends MutatingApiAction - { - @Override - public Object execute(ConfigForm form, BindException errors) throws Exception - { - ApiSimpleResponse response = new ApiSimpleResponse(); - - try - { - SpecimenTransform transform = SpecimenService.get().getSpecimenTransform(QueryBasedSpecimenTransform.NAME); - PipelineJob job = SpecimenService.get().createSpecimenReloadJob(getContainer(), getUser(), transform, getViewContext().getActionURL()); - - PipelineService.get().queueJob(job); - response.put("success", true); - } - catch (PipelineValidationException e) - { - throw new IOException(e); - } - return response; - } - } - - /** - * Verifies that actions resolving a specimen object by its global rowId reject ids that belong to a different - * container, even when the caller has the action's required permission in the current folder. These exercise the - * action-level guards that depend on the container scoping enforced by {@link SpecimenRequestRequirementProvider}. - */ - public static class ContainerScopingTestCase extends AbstractContainerScopingTest - { - // Actors are required by requirements through a NOT NULL foreign key, so several fixtures need one - private SpecimenRequestActor createActor(Container c, String label) - { - SpecimenRequestActor actor = new SpecimenRequestActor(); - actor.setContainer(c); - actor.setLabel(label); - return actor.create(getAdmin()); - } - - @Test - public void testShowGroupMembersActionRejectsForeignActor() throws Exception - { - Container folderA = createContainer("A"); - Container folderB = createContainer("B"); - - // An actor that lives in folder A - SpecimenRequestActor actor = createActor(folderA, "Group members scoping actor"); - int actorId = actor.getRowId(); - - // Addressing the action through folder B, where the actor does not live, must 404 rather than expose it - ActionURL foreignUrl = new ActionURL(ShowGroupMembersAction.class, folderB).addParameter("id", String.valueOf(actorId)); - assertStatus(HttpServletResponse.SC_NOT_FOUND, post(foreignUrl, getAdmin())); - - // Positive control: the same request through the actor's own folder is accepted (redirect on success), - // proving the guard rejects only the cross-container case rather than every request - ActionURL ownUrl = new ActionURL(ShowGroupMembersAction.class, folderA).addParameter("id", String.valueOf(actorId)); - assertStatus(HttpServletResponse.SC_FOUND, post(ownUrl, getAdmin())); - } - - @Test - public void testDeleteRequirementActionRejectsForeignRequirement() throws Exception - { - Container folderA = createContainer("A"); - Container folderB = createContainer("B"); - SpecimenRequestRequirementProvider provider = SpecimenRequestRequirementProvider.get(); - - // Build a real request chain in folder A: status -> request -> requirement. Deleting a requirement logs a - // request event whose RequestId is a foreign key into SampleRequest, so the request row must actually - // exist (a fake id would fail the FK and mask what this test is checking). Capture each insert's return so - // we have the generated rowId. - SpecimenRequestStatus status = new SpecimenRequestStatus(); - status.setContainer(folderA); - status.setLabel("Delete requirement scoping status"); - status.setSortOrder(1); // non-null and >= 0 so the bean isn't treated as a system status - status = Table.insert(getAdmin(), SpecimenSchema.get().getTableInfoSampleRequestStatus(), status); - - SpecimenRequest request = new SpecimenRequest(); - request.setContainer(folderA); - request.setStatusId(status.getRowId()); - request = SpecimenRequestManager.get().createRequest(getAdmin(), request, false); - int requestId = request.getRowId(); - - SpecimenRequestActor actor = createActor(folderA, "Delete requirement scoping actor"); - SpecimenRequestRequirement requirement = new SpecimenRequestRequirement(); - requirement.setContainer(folderA); - requirement.setRequestId(requestId); - requirement.setActorId(actor.getRowId()); - requirement.setDescription("Delete requirement scoping test"); - requirement = requirement.persist(getAdmin(), GUID.makeGUID()); - int requirementId = requirement.getRowId(); - - // Deleting through folder B, where the requirement does not live, must be a no-op: the row survives - ActionURL foreignUrl = new ActionURL(DeleteRequirementAction.class, folderB) - .addParameter("id", String.valueOf(requestId)) - .addParameter("requirementId", String.valueOf(requirementId)); - post(foreignUrl, getAdmin()); - assertNotNull("Cross-container delete must not remove the requirement", provider.getRequirement(folderA, requirementId)); - - // Positive control: deleting through the requirement's own folder removes it, proving the guard rejects - // only the cross-container case rather than every delete - ActionURL ownUrl = new ActionURL(DeleteRequirementAction.class, folderA) - .addParameter("id", String.valueOf(requestId)) - .addParameter("requirementId", String.valueOf(requirementId)); - post(ownUrl, getAdmin()); - assertNull("Same-container delete should remove the requirement", provider.getRequirement(folderA, requirementId)); - } - - @Test - public void testDeleteActorActionRejectsForeignActor() throws Exception - { - Container folderA = createContainer("A"); - Container folderB = createContainer("B"); - SpecimenRequestRequirementProvider provider = SpecimenRequestRequirementProvider.get(); - - // An actor that lives in folder A - SpecimenRequestActor actor = createActor(folderA, "Delete actor scoping actor"); - int actorId = actor.getRowId(); - - // Deleting through folder B, where the actor does not live, must be a graceful no-op rather than a 500: - // the fix makes getActor container-scoped, so handlePost sees null and skips actor.delete() instead of - // NPEing. The action still redirects (302) and the actor must survive in its own folder. - ActionURL foreignUrl = new ActionURL(DeleteActorAction.class, folderB) - .addParameter("id", String.valueOf(actorId)); - assertStatus(HttpServletResponse.SC_FOUND, post(foreignUrl, getAdmin())); - assertNotNull("Cross-container delete must not remove the actor", provider.getActor(folderA, actorId)); - - // Positive control: deleting through the actor's own folder removes it, proving the guard rejects only the - // cross-container case rather than every delete - ActionURL ownUrl = new ActionURL(DeleteActorAction.class, folderA) - .addParameter("id", String.valueOf(actorId)); - assertStatus(HttpServletResponse.SC_FOUND, post(ownUrl, getAdmin())); - assertNull("Same-container delete should remove the actor", provider.getActor(folderA, actorId)); - } - - @Test - public void testManageRequestStatusRequiresEditPermission() throws Exception - { - // ManageRequestStatusAction is annotated @RequiresPermission(RequestSpecimensPermission.class), which only - // proves the caller may make requests. However, a requester shouldn't be able to change ANOTHER user's request - // - // SpecimenRequesterRole (RequestSpecimensPermission only, no ManageRequestsPermission) is applicable only - // in a study folder that has the Specimen module, so stand up a minimal study. - Container folder = createContainer("ManageStatus"); - Set modules = new HashSet<>(folder.getActiveModules()); - modules.add(ModuleLoader.getInstance().getModule("study")); - modules.add(ModuleLoader.getInstance().getModule(SpecimenModule.NAME)); - folder.setActiveModules(modules, getAdmin()); - StudyService.get().createStudy(folder, getAdmin(), "Specimen scoping study", TimepointType.VISIT, true); - - // A request OWNED BY THE ADMIN (not the attacker), in a freshly created status - SpecimenRequestStatus status = new SpecimenRequestStatus(); - status.setContainer(folder); - status.setLabel("Manage status scoping status"); - status.setSortOrder(1); // non-null and >= 0 so the bean isn't treated as a system status - status = Table.insert(getAdmin(), SpecimenSchema.get().getTableInfoSampleRequestStatus(), status); - - SpecimenRequest request = new SpecimenRequest(); - request.setContainer(folder); - request.setStatusId(status.getRowId()); - request = SpecimenRequestManager.get().createRequest(getAdmin(), request, false); - int requestId = request.getRowId(); - - // A plain requester: holds RequestSpecimensPermission (so the action's annotation passes) but is neither a - // coordinator nor the request's owner, so hasEditRequestPermissions() is false. - User requester = createUserInRole(folder, SpecimenRequesterRole.class); - - // Mutating another user's request must be rejected at the per-request guard (403) rather than applied. The - // request id and a no-op status keep the form valid, so without the fix the action proceeds and does NOT 403. - ActionURL url = new ActionURL(ManageRequestStatusAction.class, folder) - .addParameter("id", String.valueOf(requestId)) - .addParameter("status", String.valueOf(status.getRowId())); - assertStatus(HttpServletResponse.SC_FORBIDDEN, post(url, requester)); - - // Positive control: an admin holds ManageRequestsPermission, so the same request passes the guard rather - // than being blocked at 403 -- proving the guard rejects only the unprivileged requester, not every caller. - assertNotEquals("An admin must pass the edit-request guard, not be blocked at 403", - HttpServletResponse.SC_FORBIDDEN, post(url, getAdmin()).getStatus()); - } - } -} diff --git a/specimen/src/org/labkey/specimen/actions/SpecimenEventBean.java b/specimen/src/org/labkey/specimen/actions/SpecimenEventBean.java deleted file mode 100644 index 1246da6fd6a..00000000000 --- a/specimen/src/org/labkey/specimen/actions/SpecimenEventBean.java +++ /dev/null @@ -1,40 +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.actions; - -import org.labkey.api.action.ReturnUrlForm; -import org.labkey.api.specimen.Vial; - -public class SpecimenEventBean extends ReturnUrlForm -{ - private Vial _vial; - - public SpecimenEventBean(Vial vial, String returnUrl) - { - _vial = vial; - setReturnUrl(returnUrl); - } - - public Vial getVial() - { - return _vial; - } - - public void setVial(Vial vial) - { - _vial = vial; - } -} diff --git a/specimen/src/org/labkey/specimen/actions/SpecimenHeaderBean.java b/specimen/src/org/labkey/specimen/actions/SpecimenHeaderBean.java deleted file mode 100644 index 91d0f3f15a7..00000000000 --- a/specimen/src/org/labkey/specimen/actions/SpecimenHeaderBean.java +++ /dev/null @@ -1,158 +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.actions; - -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.TableInfo; -import org.labkey.api.query.FieldKey; -import org.labkey.api.query.QueryService; -import org.labkey.api.specimen.SpecimenQuerySchema; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.NotFoundException; -import org.labkey.api.view.ViewContext; -import org.labkey.specimen.actions.SpecimenController.PtidVisit; -import org.labkey.specimen.query.SpecimenQueryView; - -import java.util.Collections; -import java.util.Iterator; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -public final class SpecimenHeaderBean -{ - private final ActionURL _otherViewURL; - private final ViewContext _viewContext; - private final boolean _showingVials; - private final Set _ptidVisits; - - private Integer _selectedRequest; - - public SpecimenHeaderBean(ViewContext context, SpecimenQueryView view) - { - this(context, view, Collections.emptySet()); - } - - public SpecimenHeaderBean(ViewContext context, SpecimenQueryView view, Set ptidVisits) throws RuntimeException - { - Map params = context.getRequest().getParameterMap(); - - String currentTable = view.isShowingVials() ? "SpecimenDetail" : "SpecimenSummary"; - String otherTable = view.isShowingVials() ? "SpecimenSummary" : "SpecimenDetail"; - ActionURL otherView = context.cloneActionURL(); - otherView.deleteParameters(); - - Study study = StudyService.get().getStudy(context.getContainer()); - if (null == study) - throw new NotFoundException("No study exists in this folder."); - SpecimenQuerySchema schema = SpecimenQuerySchema.get(study, context.getUser()); - - TableInfo otherTableInfo = schema.getTable(otherTable); - - for (Map.Entry param : params.entrySet()) - { - int dotIndex = param.getKey().indexOf('.'); - - if (dotIndex >= 0) - { - String table = param.getKey().substring(0, dotIndex); - String columnClause = param.getKey().substring(dotIndex + 1); - String[] columnClauseParts = columnClause.split("~"); - String column = columnClauseParts[0]; - - if (table.equals(currentTable)) - { - // use the query service to check to see if the current filter column is present - // in the other view. If so, we'll add a filter parameter with the same value on the - // other view. Otherwise, we'll keep the parameter, but we won't map it to the other view: - boolean translatable = column.equals("sort"); - - if (!translatable) - { - Map presentCols = QueryService.get().getColumns(otherTableInfo, - Collections.singleton(FieldKey.fromString(column))); - translatable = !presentCols.isEmpty(); - } - - if (translatable) - { - String key = otherTable + "." + columnClause; - otherView.addParameter(key, param.getValue()[0]); - continue; - } - } - - if (table.equals(currentTable) || table.equals(otherTable)) - otherView.addParameter(param.getKey(), param.getValue()[0]); - } - } - - otherView.replaceParameter("showVials", Boolean.toString(!view.isShowingVials())); - if (null != params.get(SpecimenQueryView.PARAMS.excludeRequestedBySite.name())) - otherView.replaceParameter(SpecimenQueryView.PARAMS.excludeRequestedBySite.name(), - params.get(SpecimenQueryView.PARAMS.excludeRequestedBySite.name())[0]); - _otherViewURL = otherView; - _viewContext = context; - _showingVials = view.isShowingVials(); - _ptidVisits = ptidVisits; - } - - public Integer getSelectedRequest() - { - return _selectedRequest; - } - - public void setSelectedRequest(Integer selectedRequest) - { - _selectedRequest = selectedRequest; - } - - public ActionURL getOtherViewURL() - { - return _otherViewURL; - } - - public ViewContext getViewContext() - { - return _viewContext; - } - - public boolean isShowingVials() - { - return _showingVials; - } - - public Set getPtidVisits() - { - return _ptidVisits; - } - - public boolean isSingleVisitFilter() - { - if (getPtidVisits().isEmpty()) - return false; - Iterator ptidVisit = getPtidVisits().iterator(); - String firstVisit = ptidVisit.next().visit(); - while (ptidVisit.hasNext()) - { - if (!Objects.equals(firstVisit, ptidVisit.next().visit())) - return false; - } - return true; - } -} diff --git a/specimen/src/org/labkey/specimen/actions/SpecimenReportActions.java b/specimen/src/org/labkey/specimen/actions/SpecimenReportActions.java deleted file mode 100644 index 86ca94bb301..00000000000 --- a/specimen/src/org/labkey/specimen/actions/SpecimenReportActions.java +++ /dev/null @@ -1,143 +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.actions; - -import org.labkey.api.security.RequiresPermission; -import org.labkey.api.security.permissions.ReadPermission; -import org.labkey.specimen.report.SpecimenVisitReportAction; -import org.labkey.specimen.report.participant.ParticipantSiteReportFactory; -import org.labkey.specimen.report.participant.ParticipantSummaryReportFactory; -import org.labkey.specimen.report.participant.ParticipantTypeReportFactory; -import org.labkey.specimen.report.request.RequestEnrollmentSiteReportFactory; -import org.labkey.specimen.report.request.RequestLocationReportFactory; -import org.labkey.specimen.report.request.RequestParticipantReportFactory; -import org.labkey.specimen.report.request.RequestReportFactory; -import org.labkey.specimen.report.specimentype.TypeCohortReportFactory; -import org.labkey.specimen.report.specimentype.TypeParticipantReportFactory; -import org.labkey.specimen.report.specimentype.TypeSummaryReportFactory; - -public class SpecimenReportActions -{ - @RequiresPermission(ReadPermission.class) - public static class ParticipantSummaryReportAction extends SpecimenVisitReportAction - { - public ParticipantSummaryReportAction() - { - super(ParticipantSummaryReportFactory.class); - } - // no implementation needed; this action exists only to provide an entry point - // with request->bean translation for this report type. - } - - @RequiresPermission(ReadPermission.class) - public static class ParticipantTypeReportAction extends SpecimenVisitReportAction - { - public ParticipantTypeReportAction() - { - super(ParticipantTypeReportFactory.class); - } - // no implementation needed; this action exists only to provide an entry point - // with request->bean translation for this report type. - } - - @RequiresPermission(ReadPermission.class) - public static class ParticipantSiteReportAction extends SpecimenVisitReportAction - { - public ParticipantSiteReportAction() - { - super(ParticipantSiteReportFactory.class); - } - // no implementation needed; this action exists only to provide an entry point - // with request->bean translation for this report type. - } - - @RequiresPermission(ReadPermission.class) - public static class RequestReportAction extends SpecimenVisitReportAction - { - public RequestReportAction() - { - super(RequestReportFactory.class); - } - // no implementation needed; this action exists only to provide an entry point - // with request->bean translation for this report type. - } - - @RequiresPermission(ReadPermission.class) - public static class RequestEnrollmentSiteReportAction extends SpecimenVisitReportAction - { - public RequestEnrollmentSiteReportAction() - { - super(RequestEnrollmentSiteReportFactory.class); - } - // no implementation needed; this action exists only to provide an entry point - // with request->bean translation for this report type. - } - - @RequiresPermission(ReadPermission.class) - public static class RequestSiteReportAction extends SpecimenVisitReportAction - { - public RequestSiteReportAction() - { - super(RequestLocationReportFactory.class); - } - // no implementation needed; this action exists only to provide an entry point - // with request->bean translation for this report type. - } - - @RequiresPermission(ReadPermission.class) - public static class RequestParticipantReportAction extends SpecimenVisitReportAction - { - public RequestParticipantReportAction() - { - super(RequestParticipantReportFactory.class); - } - // no implementation needed; this action exists only to provide an entry point - // with request->bean translation for this report type. - } - - @RequiresPermission(ReadPermission.class) - public static class TypeParticipantReportAction extends SpecimenVisitReportAction - { - public TypeParticipantReportAction() - { - super(TypeParticipantReportFactory.class); - } - // no implementation needed; this action exists only to provide an entry point - // with request->bean translation for this report type. - } - - @RequiresPermission(ReadPermission.class) - public static class TypeSummaryReportAction extends SpecimenVisitReportAction - { - public TypeSummaryReportAction() - { - super(TypeSummaryReportFactory.class); - } - // no implementation needed; this action exists only to provide an entry point - // with request->bean translation for this report type. - } - - @RequiresPermission(ReadPermission.class) - public static class TypeCohortReportAction extends SpecimenVisitReportAction - { - public TypeCohortReportAction() - { - super(TypeCohortReportFactory.class); - } - // no implementation needed; this action exists only to provide an entry point - // with request->bean translation for this report type. - } -} diff --git a/specimen/src/org/labkey/specimen/actions/SpecimenSearchBean.java b/specimen/src/org/labkey/specimen/actions/SpecimenSearchBean.java deleted file mode 100644 index 842ac15d930..00000000000 --- a/specimen/src/org/labkey/specimen/actions/SpecimenSearchBean.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2010-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.actions; - -import org.labkey.specimen.query.SpecimenQueryView; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.ViewContext; - -/** - * User: brittp - * Date: Oct 21, 2010 4:01:33 PM - */ -public class SpecimenSearchBean -{ - private boolean _detailsView; - private ActionURL _baseViewURL; - private String _dataRegionName; - private boolean _inWebPart; - private int _webPartId = 0; - private boolean _advancedExpanded; - - public SpecimenSearchBean() - { - } - - public SpecimenSearchBean(ViewContext context, boolean detailsView, boolean inWebPart) - { - init(context, detailsView, inWebPart); - } - - public void init(ViewContext context, boolean detailsView, boolean inWebPart) - { - _inWebPart = inWebPart; - _detailsView = detailsView; - SpecimenQueryView view = SpecimenQueryView.createView(context, detailsView ? SpecimenQueryView.ViewType.VIALS : - SpecimenQueryView.ViewType.SUMMARY); - - _dataRegionName = view.getDataRegionName(); - _baseViewURL = view.getBaseViewURL(); - } - - public boolean isDetailsView() - { - return _detailsView; - } - - public ActionURL getBaseViewURL() - { - return _baseViewURL; - } - - public String getDataRegionName() - { - return _dataRegionName; - } - - public boolean isInWebPart() - { - return _inWebPart; - } - - public boolean isAdvancedExpanded() - { - return _advancedExpanded; - } - - public void setAdvancedExpanded(boolean advancedExpanded) - { - _advancedExpanded = advancedExpanded; - } - - public int getWebPartId() - { - return _webPartId; - } - - public void setWebPartId(int webPartId) - { - _webPartId = webPartId; - } -} diff --git a/specimen/src/org/labkey/specimen/actions/SpecimenViewTypeForm.java b/specimen/src/org/labkey/specimen/actions/SpecimenViewTypeForm.java deleted file mode 100644 index e2926988730..00000000000 --- a/specimen/src/org/labkey/specimen/actions/SpecimenViewTypeForm.java +++ /dev/null @@ -1,57 +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.actions; - -import org.labkey.api.action.QueryViewAction; -import org.labkey.specimen.query.SpecimenQueryView; - -public class SpecimenViewTypeForm extends QueryViewAction.QueryExportForm -{ - public enum PARAMS - { - showVials, - viewMode - } - - private boolean _showVials; - private SpecimenQueryView.Mode _viewMode = SpecimenQueryView.Mode.DEFAULT; - - public boolean isShowVials() - { - return _showVials; - } - - public void setShowVials(boolean showVials) - { - _showVials = showVials; - } - - public String getViewMode() - { - return _viewMode.name(); - } - - public SpecimenQueryView.Mode getViewModeEnum() - { - return _viewMode; - } - - public void setViewMode(String viewMode) - { - if (viewMode != null) - _viewMode = SpecimenQueryView.Mode.valueOf(viewMode); - } -} diff --git a/specimen/src/org/labkey/specimen/actions/SpecimensViewBean.java b/specimen/src/org/labkey/specimen/actions/SpecimensViewBean.java deleted file mode 100644 index 33336aae0c2..00000000000 --- a/specimen/src/org/labkey/specimen/actions/SpecimensViewBean.java +++ /dev/null @@ -1,52 +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.actions; - -import org.labkey.api.specimen.Vial; -import org.labkey.specimen.query.SpecimenQueryView; -import org.labkey.api.view.ViewContext; - -import java.util.List; - -public abstract class SpecimensViewBean -{ - protected SpecimenQueryView _specimenQueryView; - protected List _vials; - - public SpecimensViewBean(ViewContext context, List vials, boolean showHistoryLinks, - boolean showRecordSelectors, boolean disableLowVialIndicators, boolean restrictRecordSelectors) - { - _vials = vials; - if (vials != null && !vials.isEmpty()) - { - _specimenQueryView = SpecimenQueryView.createView(context, vials, SpecimenQueryView.ViewType.VIALS); - _specimenQueryView.setShowHistoryLinks(showHistoryLinks); - _specimenQueryView.setShowRecordSelectors(showRecordSelectors); - _specimenQueryView.setDisableLowVialIndicators(disableLowVialIndicators); - _specimenQueryView.setRestrictRecordSelectors(restrictRecordSelectors); - } - } - - public SpecimenQueryView getSpecimenQueryView() - { - return _specimenQueryView; - } - - public List getVials() - { - return _vials; - } -} diff --git a/specimen/src/org/labkey/specimen/actions/UpdateSpecimenCommentsBean.java b/specimen/src/org/labkey/specimen/actions/UpdateSpecimenCommentsBean.java deleted file mode 100644 index c89a842c58b..00000000000 --- a/specimen/src/org/labkey/specimen/actions/UpdateSpecimenCommentsBean.java +++ /dev/null @@ -1,142 +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.actions; - -import org.apache.commons.lang3.time.DateUtils; -import org.labkey.api.specimen.Vial; -import org.labkey.api.specimen.model.SpecimenComment; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyInternalService; -import org.labkey.api.study.TimepointType; -import org.labkey.api.study.Visit; -import org.labkey.api.view.ViewContext; -import org.labkey.specimen.SpecimenManager; - -import java.math.BigDecimal; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.TreeMap; - -public class UpdateSpecimenCommentsBean extends SpecimensViewBean -{ - private final String _referrer; - private final String _currentComment; - private final boolean _mixedComments; - private final boolean _currentFlagState; - private final boolean _mixedFlagState; - private final Map> _participantVisitMap; - - public UpdateSpecimenCommentsBean(ViewContext context, List vials, String referrer) - { - super(context, vials, false, false, true, true); - _referrer = referrer; - Map currentComments = SpecimenManager.get().getSpecimenComments(vials); - boolean mixedComments = false; - boolean mixedFlagState = false; - SpecimenComment prevComment = currentComments.get(vials.getFirst()); - - for (int i = 1; i < vials.size() && (!mixedFlagState || !mixedComments); i++) - { - SpecimenComment comment = currentComments.get(vials.get(i)); - - // a missing comment indicates a 'false' for history conflict: - boolean currentFlagState = comment != null && comment.isQualityControlFlag(); - boolean previousFlagState = prevComment != null && prevComment.isQualityControlFlag(); - mixedFlagState = mixedFlagState || currentFlagState != previousFlagState; - String currentCommentString = comment != null ? comment.getComment() : null; - String previousCommentString = prevComment != null ? prevComment.getComment() : null; - mixedComments = mixedComments || !Objects.equals(previousCommentString, currentCommentString); - prevComment = comment; - } - - _currentComment = !mixedComments && prevComment != null ? prevComment.getComment() : null; - _currentFlagState = mixedFlagState || (prevComment != null && prevComment.isQualityControlFlag()); - _mixedComments = mixedComments; - _mixedFlagState = mixedFlagState; - _participantVisitMap = generateParticipantVisitMap(vials, SpecimenController.getStudyRedirectIfNull(context.getContainer())); - } - - protected Map> generateParticipantVisitMap(List vials, Study study) - { - Map> pvMap = new TreeMap<>(); - - if (TimepointType.CONTINUOUS == study.getTimepointType()) - return Collections.emptyMap(); - - boolean isDateStudy = TimepointType.DATE == study.getTimepointType(); - Date startDate = isDateStudy ? study.getStartDate() : new Date(); - - for (Vial vial : vials) - { - Double visit; - if (isDateStudy) - if (null != vial.getDrawTimestamp()) - visit = Double.valueOf((vial.getDrawTimestamp().getTime() - startDate.getTime()) / DateUtils.MILLIS_PER_DAY); - else - visit = null; - else - visit = vial.getVisitValue(); - - if (visit != null) - { - String ptid = vial.getPtid(); - BigDecimal sequenceNum = StudyInternalService.get().getSequenceNum(visit); - Visit v = StudyInternalService.get().getVisitForSequence(study, sequenceNum); - if (ptid != null && v != null) - { - if (!pvMap.containsKey(ptid)) - pvMap.put(ptid, new HashMap<>()); - pvMap.get(ptid).put(v.getDisplayString(), vial.getRowId()); - } - } - } - return pvMap; - } - - public String getReferrer() - { - return _referrer; - } - - public String getCurrentComment() - { - return _currentComment; - } - - public boolean isMixedComments() - { - return _mixedComments; - } - - public boolean isCurrentFlagState() - { - return _currentFlagState; - } - - public boolean isMixedFlagState() - { - return _mixedFlagState; - } - - public Map> getParticipantVisitMap() - { - return _participantVisitMap; - } -} diff --git a/specimen/src/org/labkey/specimen/actions/ViewRequestsHeaderBean.java b/specimen/src/org/labkey/specimen/actions/ViewRequestsHeaderBean.java deleted file mode 100644 index f1e97679c0c..00000000000 --- a/specimen/src/org/labkey/specimen/actions/ViewRequestsHeaderBean.java +++ /dev/null @@ -1,53 +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.actions; - -import org.labkey.api.view.ViewContext; -import org.labkey.specimen.SpecimenRequestManager; -import org.labkey.specimen.SpecimenRequestStatus; -import org.labkey.specimen.query.SpecimenRequestQueryView; - -import java.util.Collection; - -public class ViewRequestsHeaderBean -{ - public static final String PARAM_STATUSLABEL = "SpecimenRequest.Status/Label~eq"; - public static final String PARAM_CREATEDBY = "SpecimenRequest.CreatedBy/DisplayName~eq"; - - private final SpecimenRequestQueryView _view; - private final ViewContext _context; - - public ViewRequestsHeaderBean(ViewContext context, SpecimenRequestQueryView view) - { - _view = view; - _context = context; - } - - public SpecimenRequestQueryView getView() - { - return _view; - } - - public Collection getStauses() - { - return SpecimenRequestManager.get().getRequestStatuses(_context.getContainer(), _context.getUser()); - } - - public boolean isFilteredStatus(SpecimenRequestStatus status) - { - return status.getLabel().equals(_context.getActionURL().getParameter(PARAM_STATUSLABEL)); - } -} diff --git a/specimen/src/org/labkey/specimen/importer/AbstractSpecimenTask.java b/specimen/src/org/labkey/specimen/importer/AbstractSpecimenTask.java deleted file mode 100644 index 7b2e21f4a18..00000000000 --- a/specimen/src/org/labkey/specimen/importer/AbstractSpecimenTask.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (c) 2009-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.importer; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.labkey.api.admin.ImportException; -import org.labkey.api.pipeline.CancelledException; -import org.labkey.api.pipeline.PipelineJob; -import org.labkey.api.pipeline.PipelineJobException; -import org.labkey.api.pipeline.RecordedActionSet; -import org.labkey.api.pipeline.TaskFactory; -import org.labkey.api.study.SpecimenService; -import org.labkey.api.study.SpecimenTransform; -import org.labkey.api.study.importer.SimpleStudyImportContext; -import org.labkey.api.util.DateUtil; -import org.labkey.api.util.FileType; -import org.labkey.api.util.FileUtil; -import org.labkey.api.writer.FileSystemFile; -import org.labkey.api.writer.VirtualFile; -import org.labkey.api.writer.ZipUtil; -import org.labkey.specimen.pipeline.SpecimenJobSupport; -import org.labkey.specimen.writer.SpecimenArchiveDataTypes; -import org.labkey.vfs.FileLike; - -import java.io.IOException; -import java.sql.BatchUpdateException; -import java.util.Date; - -/* -* User: adam -* Date: Aug 31, 2009 -* Time: 9:12:22 AM -*/ -public abstract class AbstractSpecimenTask> extends PipelineJob.Task -{ - public static final FileType ARCHIVE_FILE_TYPE = new FileType(".specimens"); - - protected AbstractSpecimenTask(FactoryType factory, PipelineJob job) - { - super(factory, job); - } - - @Override - @NotNull - public RecordedActionSet run() throws PipelineJobException - { - try - { - PipelineJob job = getJob(); - FileLike specimenArchive = getSpecimenFile(job); - SimpleStudyImportContext ctx = getImportContext(job); - - doImport(specimenArchive, job, ctx, isMerge(), true, getImportHelper()); - } - catch (CancelledException | PipelineJobException e) - { - throw e; - } - catch (Exception e) - { - throw new PipelineJobException("Error attempting to load specimen archive", e); - } - - return new RecordedActionSet(); - } - - protected FileLike getSpecimenFile(PipelineJob job) throws Exception - { - SpecimenJobSupport support = job.getJobSupport(SpecimenJobSupport.class); - return support.getSpecimenArchivePath(); - } - - protected SimpleStudyImportContext getImportContext(PipelineJob job) - { - return job.getJobSupport(SpecimenJobSupport.class).getImportContext(); - } - - public static void doImport(@Nullable FileLike inputFile, PipelineJob job, SimpleStudyImportContext ctx, boolean merge, - boolean syncParticipantVisit) throws PipelineJobException - { - doImport(inputFile, job, ctx, merge, syncParticipantVisit, new DefaultImportHelper()); - } - - private static void doPostTransform(SpecimenTransform transformer, FileLike inputFile, PipelineJob job) throws PipelineJobException - { - if (transformer.getFileType().isType(inputFile)) - { - if (job != null) - job.setStatus("OPTIONAL POST TRANSFORMING STEP " + transformer.getName() + " DATA"); - FileLike specimenArchive = ARCHIVE_FILE_TYPE.getFile(inputFile.getParent(), transformer.getFileType().getBaseName(inputFile)); - transformer.postTransform(job, inputFile, specimenArchive); - } - } - - public AbstractSpecimenTask(TaskFactory factory, PipelineJob job) - { - super(factory, job); - } - - public static void doImport(@Nullable FileLike inputFile, PipelineJob job, SimpleStudyImportContext ctx, boolean merge, - boolean syncParticipantVisit, ImportHelper importHelper) throws PipelineJobException - { - // do nothing if we've specified data types and specimen is not one of them - if (!ctx.isDataTypeSelected(SpecimenArchiveDataTypes.SPECIMENS)) - return; - - try - { - VirtualFile specimenDir; - specimenDir = importHelper.getSpecimenDir(job, ctx, inputFile); - if (specimenDir == null) - return; - - if (job != null) - job.setStatus("PROCESSING SPECIMENS"); - ctx.getLogger().info("Starting specimen import..."); - SpecimenImporter importer = new SpecimenImporter(ctx.getContainer(), ctx.getUser()); - importer.process(specimenDir, merge, ctx, job, syncParticipantVisit); - - // if there's an inputFile, perform any tasks after the transform and import has been completed - if (inputFile != null) - { - String activeImporter = SpecimenService.get().getActiveSpecimenImporter(ctx.getContainer()); - if (null != activeImporter) - { - SpecimenTransform activeTransformer = SpecimenService.get().getSpecimenTransform(activeImporter); - if (activeTransformer != null && activeTransformer.getFileType().isType(inputFile)) - doPostTransform(activeTransformer, inputFile, job); - } - else - { - for (SpecimenTransform transformer : SpecimenService.get().getSpecimenTransforms(ctx.getContainer())) - { - if (transformer.getFileType().isType(inputFile)) - { - doPostTransform(transformer, inputFile, job); - break; - } - } - } - } - } - catch (CancelledException e) - { - throw e; // rethrow so pipeline marks it canceled - } - catch (Exception e) - { - if (e instanceof BatchUpdateException bue && null != bue.getNextException()) - e = bue.getNextException(); - throw new PipelineJobException(e); - } - finally - { - // do any temp file cleanup - importHelper.afterImport(ctx); - } - } - - protected boolean isMerge() - { - return getJob().getJobSupport(SpecimenJobSupport.class).isMerge(); - } - - public ImportHelper getImportHelper() - { - return new DefaultImportHelper(); - } - - public interface ImportHelper - { - VirtualFile getSpecimenDir(PipelineJob job, SimpleStudyImportContext ctx, @Nullable FileLike inputFile) throws IOException, ImportException, PipelineJobException; - void afterImport(SimpleStudyImportContext ctx); - } - - protected static class DefaultImportHelper implements ImportHelper - { - private FileLike _unzipDir; - - @Override - public VirtualFile getSpecimenDir(PipelineJob job, SimpleStudyImportContext ctx, @Nullable FileLike inputFile) throws IOException, ImportException, PipelineJobException - { - // backwards compatibility, if we are given a specimen archive as a zip file, we need to extract it - if (inputFile != null) - { - // Might need to transform to a file type that we know how to import - FileLike specimenArchive = inputFile; - - for (SpecimenTransform transformer : SpecimenService.get().getSpecimenTransforms(ctx.getContainer())) - { - if (transformer.getFileType().isType(inputFile.getName())) - { - if (job != null) - job.setStatus("TRANSFORMING " + transformer.getName() + " DATA"); - specimenArchive = ARCHIVE_FILE_TYPE.getFile(inputFile.getParent(), transformer.getFileType().getBaseName(inputFile)); - transformer.transform(job, inputFile, specimenArchive); - break; - } - } - - if (null == specimenArchive) - { - ctx.getLogger().info("No specimen archive"); - return null; - } - else - { - if (job != null) - job.setStatus("UNZIPPING SPECIMEN ARCHIVE"); - - ctx.getLogger().info("Unzipping specimen archive {}", specimenArchive); - String tempDirName = DateUtil.formatDateTime(new Date(), "yyMMddHHmmssSSS"); - _unzipDir = specimenArchive.getParent().resolveChild(tempDirName); - ZipUtil.unzipToDirectory(specimenArchive, _unzipDir, ctx.getLogger()); - - ctx.getLogger().info("Archive unzipped to {}", _unzipDir); - return new FileSystemFile(_unzipDir); - } - } - else - { - // the specimen files must already be "extracted" in the specimens directory - return ctx.getDir("specimens"); - } - } - - @Override - public void afterImport(SimpleStudyImportContext ctx) - { - if (_unzipDir != null) - delete(_unzipDir, ctx); - } - - protected void delete(FileLike path, SimpleStudyImportContext ctx) - { - if (path.isDirectory()) - { - FileUtil.deleteDir(path); - } - } - } -} diff --git a/specimen/src/org/labkey/specimen/importer/AbstractSpecimenTaskFactory.java b/specimen/src/org/labkey/specimen/importer/AbstractSpecimenTaskFactory.java deleted file mode 100644 index ebd73cfa267..00000000000 --- a/specimen/src/org/labkey/specimen/importer/AbstractSpecimenTaskFactory.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2009-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.importer; - -import org.labkey.api.pipeline.AbstractTaskFactory; -import org.labkey.api.pipeline.AbstractTaskFactorySettings; -import org.labkey.api.pipeline.PipelineJob; -import org.labkey.api.util.FileType; - -import java.util.Collections; -import java.util.List; - -public abstract class AbstractSpecimenTaskFactory> extends AbstractTaskFactory -{ - public AbstractSpecimenTaskFactory(Class namespaceClass) - { - super(namespaceClass); - } - - @Override - public List getInputTypes() - { - return Collections.emptyList(); - } - - @Override - public List getProtocolActionNames() - { - return Collections.emptyList(); - } - - @Override - public String getStatusName() - { - return "LOAD SPECIMENS"; - } - - @Override - public boolean isJobComplete(PipelineJob job) - { - return false; - } -} diff --git a/specimen/src/org/labkey/specimen/importer/EditableSpecimenImporter.java b/specimen/src/org/labkey/specimen/importer/EditableSpecimenImporter.java deleted file mode 100644 index a78b3d3202b..00000000000 --- a/specimen/src/org/labkey/specimen/importer/EditableSpecimenImporter.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright (c) 2013-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.importer; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.NotNull; -import org.labkey.api.collections.CaseInsensitiveHashSet; -import org.labkey.api.data.Container; -import org.labkey.api.data.DbSchema; -import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.SqlExecutor; -import org.labkey.api.data.TableInfo; -import org.labkey.api.query.ValidationException; -import org.labkey.api.security.User; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.specimen.importer.EventVialRollup; -import org.labkey.api.specimen.importer.RollupHelper; -import org.labkey.api.specimen.importer.RollupHelper.RollupMap; -import org.labkey.api.specimen.importer.RollupInstance; -import org.labkey.specimen.SpecimenColumns; -import org.labkey.specimen.SpecimenRequestManager; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * User: davebradlee - * Date: 6/13/13 - * Time: 12:13 PM - */ -public class EditableSpecimenImporter extends SpecimenImporter -{ - private static final String GUID_COLNAME = "GlobalUniqueId"; - - private final boolean _insert; - - public EditableSpecimenImporter(Container container, User user, boolean insert) - { - super(container, user); - _insert = insert; - } - - public void process(List> rows, boolean merge) throws IOException, ValidationException - { - process(rows, merge, null); - } - - public void process(List> rows, boolean merge, Logger logger) throws IOException, ValidationException - { - if (null == logger) - logger = LogManager.getLogger(getClass()); - _process(rows, merge, logger); - } - - private void _process(List> rows, boolean merge, Logger logger) throws IOException, ValidationException - { - if (merge) - { - _generateGlobalUniqueIds = markEventsObsolete(rows); // Generate this many new ids (needed if not present) - } - - List> specimenRows = new ArrayList<>(); - for (Map row : rows) - { - Map specimenRow = new HashMap<>(); - for (String colName : row.keySet()) - { - specimenRow.put(colName, row.get(colName)); - } - specimenRows.add(specimenRow); - } - - Map sifMap = new HashMap<>(); - addSpecimenImportFile(sifMap, _specimensTableType, specimenRows); - - try - { - super.process(sifMap, merge, logger, null, true, true); - } - catch (ValidationException ex) - { - if (logger != null) - logger.error("Error during import", ex); - throw ex; - } - } - - private void addSpecimenImportFile(Map fileNameMap, SpecimenTableType type, List> specimenRows) - { - assert null != type : "Unknown type!"; - fileNameMap.put(type, new IteratorSpecimenImportFile(specimenRows, type)); - } - - public List> mapColumnNamesToTsvColumnNames(List> rows) - { - List> newRows = new ArrayList<>(); - - Set seenFields = new CaseInsensitiveHashSet(); - for (Map row : rows) - { - Map newRow = new HashMap<>(); - for (String columnName : row.keySet()) - { - Object value = row.get(columnName); - if (null != value) - { - columnName = getMappedColumnName(columnName); - SpecimenColumn specCol = findSpecimenColumnFromDbName(columnName); - if (null != specCol) - { - newRow.put(specCol.getPrimaryTsvColumnName(), value); - seenFields.add(specCol.getPrimaryTsvColumnName()); - } - } - } - newRows.add(newRow); - } - - // Make sure every row has each of seenFields - for (Map row : newRows) - { - for (String field : seenFields) - if (!row.containsKey(field)) - row.put(field, null); - } - return newRows; - } - - private Map _dbNameToSpecimenColumnMap; - - private SpecimenColumn findSpecimenColumnFromDbName(String name) - { - if (null == _dbNameToSpecimenColumnMap) - { - _dbNameToSpecimenColumnMap = new HashMap<>(); - for (SpecimenColumn col : getSpecimenColumns()) - _dbNameToSpecimenColumnMap.put(col.getDbColumnName().toLowerCase(), col); - } - - return _dbNameToSpecimenColumnMap.get(name.toLowerCase()); - } - - @NotNull - public String getMappedColumnName(@NotNull String columnName) - { - String specialName = getSpecialColumnName(columnName); - if (null != specialName) - return specialName; - return columnName.toLowerCase(); - } - - private Map _specialColumnNameMap; - - private String getSpecialColumnName(String name) - { - if (null == _specialColumnNameMap) - { - _specialColumnNameMap = new HashMap<>(); - _specialColumnNameMap.put("participantid", "ptid"); - _specialColumnNameMap.put("primarytype", "primarytypeid"); - _specialColumnNameMap.put("additivetype", "additivetypeid"); - _specialColumnNameMap.put("derivativetype", "derivativetypeid"); - _specialColumnNameMap.put("derivativetype2", "derivativetypeid2"); - _specialColumnNameMap.put("sequencenum", "visitvalue"); - _specialColumnNameMap.put("sitename", "labid"); - _specialColumnNameMap.put("clinic", "originatinglocationid"); - _specialColumnNameMap.put("firstprocessedbyinitials", "processedbyinitials"); - _specialColumnNameMap.put("latestcomments", "comments"); - _specialColumnNameMap.put("latestqualitycomments", "qualitycomments"); - - // Add any rollups from optionals whose names are not matching - RollupMap matchedRollups = RollupHelper.getEventToVialRollups(getContainer(), getUser()); - for (Map.Entry>> entry : matchedRollups.entrySet()) - { - String fromName = entry.getKey(); - List> rollupItems = entry.getValue(); - for (RollupInstance rollupItem : rollupItems) - { - String toName = rollupItem.first; - if (!fromName.equalsIgnoreCase(toName)) - _specialColumnNameMap.put(toName, fromName); - } - } - } - - return _specialColumnNameMap.get(name.toLowerCase()); - } - - @Override - protected void remapTempTableLookupIndexes(DbSchema schema, String tempTable, List loadedColumns) - { - // do nothing - } - - @Override - protected void checkForConflictingSpecimens(DbSchema schema, String tempTable, List loadedColumns) - { - // Only check if inserting - if (_insert) - super.checkForConflictingSpecimens(schema, tempTable, loadedColumns); - } - - private int markEventsObsolete(List> rows) - { - Container container = getContainer(); - TableInfo tableInfoSpecimenEvent = SpecimenSchema.get().getTableInfoSpecimenEvent(container); - TableInfo tableInfoVial = SpecimenSchema.get().getTableInfoVial(container); - - SQLFragment sqlPrefix = new SQLFragment(); - sqlPrefix.append("UPDATE ").append(tableInfoSpecimenEvent) - .append(" SET Obsolete = ").append(tableInfoSpecimenEvent.getSqlDialect().getBooleanTRUE()) - .append(" WHERE VialId IN ") - .append("(SELECT RowId FROM ").append(tableInfoVial) - .append(" WHERE Obsolete = ").append(tableInfoSpecimenEvent.getSqlDialect().getBooleanFALSE()) - .append(" AND " + GUID_COLNAME).append(" "); - - int noGuidRowCount = 0; - int guidRowCount = 0; - ArrayList guids = new ArrayList<>(); - for (Map row : rows) - { - String guid = (String)row.get(SpecimenColumns.GLOBAL_UNIQUE_ID_TSV_COL); - if (null == guid) - { - noGuidRowCount++; - continue; - } - guidRowCount++; - guids.add(guid); - if (guids.size() == 1000) - { - SQLFragment sql = new SQLFragment(sqlPrefix); - tableInfoSpecimenEvent.getSqlDialect().appendInClauseSql(sql, guids); - sql.append(")"); - new SqlExecutor(SpecimenSchema.get().getSchema()).execute(sql); - guids.clear(); - } - } - if (!guids.isEmpty()) - { - SQLFragment sql = new SQLFragment(sqlPrefix); - tableInfoSpecimenEvent.getSqlDialect().appendInClauseSql(sql, guids); - sql.append(")"); - new SqlExecutor(SpecimenSchema.get().getSchema()).execute(sql); - } - - if (guidRowCount > 0) - { - SpecimenRequestManager.get().clearCaches(container); - } - return noGuidRowCount; - } -} diff --git a/specimen/src/org/labkey/specimen/importer/FileSystemSpecimenImportFile.java b/specimen/src/org/labkey/specimen/importer/FileSystemSpecimenImportFile.java deleted file mode 100644 index b87ad73f8a8..00000000000 --- a/specimen/src/org/labkey/specimen/importer/FileSystemSpecimenImportFile.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2013-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.importer; - -import org.labkey.api.reader.Readers; -import org.labkey.api.reader.TabLoader; -import org.labkey.api.writer.VirtualFile; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; - -public class FileSystemSpecimenImportFile implements SpecimenImportFile -{ - private final VirtualFile _dir; - private final String _fileName; - private final SpecimenTableType _tableType; - - public FileSystemSpecimenImportFile(VirtualFile dir, String fileName, SpecimenTableType tableType) - { - _dir = dir; - _fileName = fileName; - _tableType = tableType; - } - - @Override - public SpecimenTableType getTableType() - { - return _tableType; - } - - @Override - public TabLoader getDataLoader() throws IOException - { - Reader reader = Readers.getReader(getInputStream()); - TabLoader loader = new TabLoader(reader, true, null, true); // Close on complete - loader.setInferTypes(false); - - return loader; - } - - private InputStream getInputStream() throws IOException - { - return _dir.getInputStream(_fileName); - } -} diff --git a/specimen/src/org/labkey/specimen/importer/ImportTypes.java b/specimen/src/org/labkey/specimen/importer/ImportTypes.java deleted file mode 100644 index 34615d80f93..00000000000 --- a/specimen/src/org/labkey/specimen/importer/ImportTypes.java +++ /dev/null @@ -1,32 +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.importer; - -import org.labkey.api.specimen.SpecimenSchema; - -// Extracted from SpecimenImporter to ease the specimen module migration -public class ImportTypes -{ - private ImportTypes() - { - } - - public static final String DATETIME_TYPE = "SpecimenImporter/DateTime"; - public static final String DURATION_TYPE = "SpecimenImporter/TimeOnlyDate"; - public static final String NUMERIC_TYPE = "NUMERIC(15,4)"; - public static final String BOOLEAN_TYPE = SpecimenSchema.get().getSqlDialect().getBooleanDataType(); - public static final String BINARY_TYPE = SpecimenSchema.get().getSqlDialect().getBinaryDataType(); -} diff --git a/specimen/src/org/labkey/specimen/importer/ImportableColumn.java b/specimen/src/org/labkey/specimen/importer/ImportableColumn.java deleted file mode 100644 index 7e1dfc141f8..00000000000 --- a/specimen/src/org/labkey/specimen/importer/ImportableColumn.java +++ /dev/null @@ -1,190 +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.importer; - -import org.jetbrains.annotations.Nullable; -import org.labkey.api.data.DatabaseIdentifier; -import org.labkey.api.data.JdbcType; -import org.labkey.api.data.dialect.SqlDialect; -import org.labkey.api.exp.PropertyDescriptor; -import org.labkey.api.reader.ColumnDescriptor; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.util.TimeOnlyDate; - -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.List; - -public class ImportableColumn -{ - protected final String _dbType; - - private final String _tsvColumnName; - private final Collection _tsvColumnAliases; - private final String _dbColumnName; - private final boolean _maskOnExport; - private final boolean _unique; - private final int _size; - private final Class _javaClass; - private final JdbcType _jdbcType; - private Object _defaultValue = null; - - public ImportableColumn(String tsvColumnName, String dbColumnName, String databaseType) - { - this(tsvColumnName, dbColumnName, databaseType, false); - } - - public ImportableColumn(String tsvColumnName, String dbColumnName, String databaseType, Object defaultValue) - { - this(tsvColumnName, dbColumnName, databaseType, false); - _defaultValue = defaultValue; - } - - public ImportableColumn(String tsvColumnName, String dbColumnName, String databaseType, boolean unique) - { - this(tsvColumnName, dbColumnName, databaseType, unique, false); - } - - public ImportableColumn(String tsvColumnName, String dbColumnName, String databaseType, boolean unique, boolean maskOnExport) - { - this(tsvColumnName, Collections.emptyList(), dbColumnName, databaseType, unique, maskOnExport); - } - - public ImportableColumn(String tsvColumnName, Collection tsvColumnAliases, String dbColumnName, String databaseType, boolean unique, boolean maskOnExport) - { - _tsvColumnName = tsvColumnName; - _tsvColumnAliases = List.copyOf(tsvColumnAliases); - _dbColumnName = dbColumnName; - _unique = unique; - _maskOnExport = maskOnExport; - - switch (databaseType) - { - case ImportTypes.DURATION_TYPE: - _dbType = SpecimenSchema.get().getSqlDialect().getDefaultDateTimeDataType(); - _javaClass = TimeOnlyDate.class; - break; - case ImportTypes.DATETIME_TYPE: - _dbType = SpecimenSchema.get().getSqlDialect().getDefaultDateTimeDataType(); - _javaClass = Date.class; - break; - default: - _dbType = databaseType.toUpperCase(); - _javaClass = determineJavaType(_dbType); - break; - } - - _jdbcType = JdbcType.valueOf(getJavaClass()); - - if (_dbType.startsWith("VARCHAR(")) - { - assert _dbType.charAt(_dbType.length() - 1) == ')' : "Unexpected VARCHAR type format: " + _dbType; - String sizeStr = _dbType.substring(8, _dbType.length() - 1); - _size = Integer.parseInt(sizeStr); - } - else - { - _size = -1; - } - } - - // Can't use standard JdbcType.valueOf() method since this uses contains() - private static Class determineJavaType(String dbType) - { - if (dbType.contains(ImportTypes.DATETIME_TYPE)) - throw new IllegalStateException("Java types for DateTime/Timestamp columns should be previously initialized."); - - if (dbType.contains("VARCHAR")) - return String.class; - else if (dbType.contains("FLOAT") || dbType.contains("DOUBLE") || dbType.contains(ImportTypes.NUMERIC_TYPE)) - return Double.class; - else if (dbType.contains("BIGINT")) - return Long.class; - else if (dbType.contains("INT")) - return Integer.class; - else if (dbType.contains(ImportTypes.BOOLEAN_TYPE)) - return Boolean.class; - else if (dbType.contains(ImportTypes.BINARY_TYPE)) - return byte[].class; - else if (dbType.contains("DATE")) - return Date.class; - else if (dbType.contains("TIME")) - return Date.class; - else - throw new UnsupportedOperationException("Unrecognized sql type: " + dbType); - } - - public ColumnDescriptor getColumnDescriptor() - { - return new ColumnDescriptor(_tsvColumnName, getJavaClass()); - } - - public String getDbColumnName() - { - return _dbColumnName; - } - - private DatabaseIdentifier _legalDbColumnName; - - public DatabaseIdentifier getLegalDbColumnName(SqlDialect dialect) - { - if (null == _legalDbColumnName) - _legalDbColumnName = PropertyDescriptor.getLegalSelectNameFromStorageName(dialect, getDbColumnName()); - return _legalDbColumnName; - } - - // Preferred TSV column name. For example, the name that gets exported. - public String getPrimaryTsvColumnName() - { - return _tsvColumnName; - } - - public Collection getImportAliases() - { - return _tsvColumnAliases; - } - - public boolean isUnique() - { - return _unique; - } - - public Class getJavaClass() - { - return _javaClass; - } - - public JdbcType getJdbcType() - { - return _jdbcType; - } - - public int getMaxSize() - { - return _size; - } - - public boolean isMaskOnExport() - { - return _maskOnExport; - } - - public @Nullable Object getDefaultValue() - { - return _defaultValue; - } -} diff --git a/specimen/src/org/labkey/specimen/importer/IteratorSpecimenImportFile.java b/specimen/src/org/labkey/specimen/importer/IteratorSpecimenImportFile.java deleted file mode 100644 index 1ce3fbcbb9a..00000000000 --- a/specimen/src/org/labkey/specimen/importer/IteratorSpecimenImportFile.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2013-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.importer; - -import org.labkey.api.reader.DataLoader; -import org.labkey.api.reader.MapLoader; - -import java.util.List; -import java.util.Map; - -public class IteratorSpecimenImportFile implements SpecimenImportFile -{ - private final List> _rows; - private final SpecimenTableType _tableType; - - public IteratorSpecimenImportFile(List> rows, SpecimenTableType tableType) - { - _rows = rows; - _tableType = tableType; - } - - @Override - public SpecimenTableType getTableType() - { - return _tableType; - } - - @Override - public DataLoader getDataLoader() - { - return new MapLoader(_rows); - } -} diff --git a/specimen/src/org/labkey/specimen/importer/QueryBasedExport.java b/specimen/src/org/labkey/specimen/importer/QueryBasedExport.java deleted file mode 100644 index b093e46ece5..00000000000 --- a/specimen/src/org/labkey/specimen/importer/QueryBasedExport.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 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.importer; - -import org.labkey.api.action.NullSafeBindException; -import org.labkey.api.data.ColumnHeaderType; -import org.labkey.api.data.Container; -import org.labkey.api.data.ShowRows; -import org.labkey.api.data.TSVGridWriter; -import org.labkey.api.data.TSVWriter; -import org.labkey.api.pipeline.PipelineJob; -import org.labkey.api.pipeline.PipelineJobException; -import org.labkey.api.query.QueryService; -import org.labkey.api.query.QuerySettings; -import org.labkey.api.query.QueryView; -import org.labkey.api.query.UserSchema; -import org.labkey.api.security.User; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.ViewContext; -import org.labkey.specimen.actions.SpecimenController.ConfigureQueryImportAction; -import org.labkey.vfs.FileLike; -import org.springframework.validation.BindException; -import org.springframework.validation.ObjectError; - -import java.io.IOException; -import java.util.List; - -public class QueryBasedExport -{ - private final QueryBasedSpecimenReloadConfig _config; - private final PipelineJob _job; - private final FileLike _archive; - - public QueryBasedExport (QueryBasedSpecimenReloadConfig config, PipelineJob job, FileLike archive) - { - _config = config; - _job = job; - _archive = archive; - } - - public QueryBasedSpecimenReloadConfig getConfig() - { - return _config; - } - - public void exportRepository() throws PipelineJobException - { - _job.info("Starting query-based specimen export"); - - User user = _job.getUser(); - Container c = _job.getContainer(); - String schemaName = _config.getSchemaName(); - String queryName = _config.getQueryName(); - String viewName = _config.getViewName(); - - // We fake up a ViewContext here as we're in a background job, and and no request-based ViewContext is available - try (ViewContext.StackResetter reset = ViewContext.pushMockViewContext(user, c, new ActionURL(ConfigureQueryImportAction.class, c))) - { - ViewContext viewContext = reset.getContext(); - - UserSchema userSchema = QueryService.get().getUserSchema(user, c, schemaName); - if (null == userSchema) - throw new PipelineJobException(String.format("Schema %s is either inaccessible or deleted.", schemaName)); - - QuerySettings querySettings = new QuerySettings(viewContext, "query", queryName); - querySettings.setSchemaName(schemaName); - querySettings.setViewName(viewName); - querySettings.setShowRows(ShowRows.ALL); - - _job.info(String.format("Requesting data from query '%s' of schema '%s'", queryName, schemaName)); - BindException errors = new NullSafeBindException(new Object(), "form"); - QueryView queryView = userSchema.createView(viewContext, querySettings, errors); - - if (errors.hasErrors()) - { - List allErrors = errors.getAllErrors(); - for (ObjectError error : allErrors) - { - _job.error(error.toString()); - } - return; - } - - _job.info("creating the exported data .csv file"); - try (TSVGridWriter tsvWriter = queryView.getTsvWriter()) - { - tsvWriter.setDelimiterCharacter(TSVWriter.DELIM.COMMA); - tsvWriter.setColumnHeaderType(ColumnHeaderType.DisplayFieldKey); - tsvWriter.write(_archive.openOutputStream()); - _job.info("finished writing data file: " + _archive.getName()); - } - catch (IOException e) - { - _job.error("Error writing TSV: " + e.getMessage()); - } - } - _job.info("Finished writing data file: " + _archive.getName()); // use Format - } -} diff --git a/specimen/src/org/labkey/specimen/importer/QueryBasedSpecimenImportUploadTask.java b/specimen/src/org/labkey/specimen/importer/QueryBasedSpecimenImportUploadTask.java deleted file mode 100644 index 2be5ede02d2..00000000000 --- a/specimen/src/org/labkey/specimen/importer/QueryBasedSpecimenImportUploadTask.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 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.importer; - -import org.apache.logging.log4j.Logger; -import org.labkey.api.data.Container; -import org.labkey.api.data.PropertyManager; -import org.labkey.api.module.ModuleLoader; -import org.labkey.api.pipeline.PipelineJob; -import org.labkey.api.pipeline.PipelineService; -import org.labkey.api.security.User; -import org.labkey.api.security.UserManager; -import org.labkey.api.study.SpecimenService; -import org.labkey.api.study.SpecimenTransform; -import org.labkey.api.study.StudyService; -import org.labkey.api.util.SystemMaintenance; -import org.labkey.specimen.SpecimenModule; - -import java.util.stream.Stream; - -public class QueryBasedSpecimenImportUploadTask implements SystemMaintenance.MaintenanceTask -{ - @Override - public String getDescription() - { - return "Query-Based Specimen Import Upload Task"; - } - - @Override - public String getName() - { - return "QueryBasedSpecimenUpload"; - } - - @Override - public void run(Logger log) - { - String QBSpecimenImportKey = QueryBasedSpecimenTransform.PROPERTY_MAP_KEY; - try (Stream stream = PropertyManager.getNormalStore().streamMatchingContainers(PropertyManager.SHARED_USER, QBSpecimenImportKey)) - { - stream.forEach(c -> { - try - { - // Study must have been deleted - if (null == StudyService.get().getStudy(c)) - { - log.error("Query-based specimen import failed: Study does not exist in folder {}", c.getPath()); - return; - } - - if (!c.getActiveModules().contains(ModuleLoader.getInstance().getModule(SpecimenModule.class))) - return; - - SpecimenTransform transform = SpecimenService.get().getSpecimenTransform(QueryBasedSpecimenTransform.NAME); - PropertyManager.PropertyMap props = PropertyManager.getProperties(c, QBSpecimenImportKey); - boolean enabled = ("on").equals(props.get("enabled")); - - if (!enabled) - { - log.info("Prohibiting queuing specimen import for {}. Query-based specimen import is not enabled.", c.getName()); - return; - } - if (!transform.isActive(c)) - { - log.info("Prohibiting queuing specimen import for {}. Query-based specimen import is not the active import mechanism.", c.getName()); - return; - } - log.info("Queuing specimen import for {}", c.getName()); - - int userId = Integer.parseInt(props.get("userId")); - User reloadUser = UserManager.getUser(userId); - PipelineJob job = SpecimenService.get().createSpecimenReloadJob(c, reloadUser, transform, null); - - PipelineService.get().queueJob(job); - } - catch (Exception e) - { - log.error("Query-based specimen import failed", e); - } - }); - } - } -} diff --git a/specimen/src/org/labkey/specimen/importer/QueryBasedSpecimenReloadConfig.java b/specimen/src/org/labkey/specimen/importer/QueryBasedSpecimenReloadConfig.java deleted file mode 100644 index 09a432499f5..00000000000 --- a/specimen/src/org/labkey/specimen/importer/QueryBasedSpecimenReloadConfig.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 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.importer; - -import org.labkey.api.study.SpecimenTransform; - -public class QueryBasedSpecimenReloadConfig implements SpecimenTransform.ExternalImportConfig -{ - private String schemaName; - private String queryName; - private String viewName; - - public String getSchemaName() - { - return schemaName; - } - - public void setSchemaName(String schemaName) - { - this.schemaName = schemaName; - } - - public String getQueryName() - { - return queryName; - } - - public void setQueryName(String queryName) - { - this.queryName = queryName; - } - - public String getViewName() - { - return viewName; - } - - public void setViewName(String viewName) - { - this.viewName = viewName; - } - - @Override - public String getBaseServerUrl() - { - return null; - } - - @Override - public String getUsername() - { - return null; - } - - @Override - public String getPassword() - { - return null; - } -} diff --git a/specimen/src/org/labkey/specimen/importer/QueryBasedSpecimenTransform.java b/specimen/src/org/labkey/specimen/importer/QueryBasedSpecimenTransform.java deleted file mode 100644 index d98a60524db..00000000000 --- a/specimen/src/org/labkey/specimen/importer/QueryBasedSpecimenTransform.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (c) 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.importer; - -import org.jetbrains.annotations.Nullable; -import org.labkey.api.data.Container; -import org.labkey.api.data.PropertyManager; -import org.labkey.api.data.TSVMapWriter; -import org.labkey.api.module.ModuleLoader; -import org.labkey.api.pipeline.PipelineJob; -import org.labkey.api.pipeline.PipelineJobException; -import org.labkey.api.security.User; -import org.labkey.api.study.SpecimenService; -import org.labkey.api.study.SpecimenTransform; -import org.labkey.api.util.FileType; -import org.labkey.api.view.ActionURL; -import org.labkey.specimen.SpecimenModule; -import org.labkey.specimen.actions.SpecimenController.ConfigureQueryImportAction; -import org.labkey.vfs.FileLike; - -import java.util.ArrayList; -import java.util.Map; - -public class QueryBasedSpecimenTransform implements SpecimenTransform -{ - public static final String NAME = "QueryBased"; - public static final String PROPERTY_MAP_KEY = "queryBasedSpecimenLoader"; - - @Override - public String getName() - { - return NAME; - } - - @Override - public boolean isValid(Container container) - { - return container.getActiveModules().contains(ModuleLoader.getInstance().getModule(SpecimenModule.class)); - } - - @Override - public boolean isActive(Container container) - { - PropertyManager.PropertyMap props = PropertyManager.getProperties(container, PROPERTY_MAP_KEY); - boolean enabled = ("on").equals(props.get("enabled")); - // If selected is null, the container has only one transform - String selected = SpecimenService.get().getActiveSpecimenImporter(container); - - return enabled && (selected == null || NAME.equals(selected)); - } - - @Override - public FileType getFileType() - { - return new FileType(".qbst.csv"); - } - - protected static class QueryBasedTSVWriter extends TSVMapWriter - { - private int _rowCount; - public QueryBasedTSVWriter(Iterable> rows) - { - super(new ArrayList<>(), rows); // start with empty column list - } - - public int getRowCount() - { - return _rowCount; - } - - @Override - public void writeRow(Map row) - { - if (_rowCount == 0) - { - writeFileHeader(); - setColumns(row.keySet()); - writeColumnHeaders(); - } - super.writeRow(row); - _rowCount++; - } - } - - @Override - public void transform(@Nullable PipelineJob job, FileLike input, FileLike output) throws PipelineJobException - { - QueryBasedTransformTask task = new QueryBasedTransformTask(job); - task.transform(input, output); - } - - @Override - public void postTransform(@Nullable PipelineJob job, FileLike input, FileLike outputArchive) - { - // noop - } - - @Override - public @Nullable ActionURL getManageAction(Container c, User user) - { - return new ActionURL(ConfigureQueryImportAction.class, c); - } - - @Override - public ExternalImportConfig getExternalImportConfig(Container c, User user) - { - Map props = PropertyManager.getProperties(c, PROPERTY_MAP_KEY); - QueryBasedSpecimenReloadConfig config = new QueryBasedSpecimenReloadConfig(); - - String schemaName = props.get("schemaName"); - String queryName = props.get("queryName"); - String viewName = props.get("viewName"); - - config.setSchemaName(schemaName); - config.setQueryName(queryName); - config.setViewName(viewName); - - return config; - } - - @Override - public void importFromExternalSource(@Nullable PipelineJob job, ExternalImportConfig importConfig, FileLike inputArchive) throws PipelineJobException - { - if (importConfig instanceof QueryBasedSpecimenReloadConfig queryConfig) - { - QueryBasedExport export = new QueryBasedExport(queryConfig, job, inputArchive); - export.exportRepository(); - } - } -} diff --git a/specimen/src/org/labkey/specimen/importer/QueryBasedTransformTask.java b/specimen/src/org/labkey/specimen/importer/QueryBasedTransformTask.java deleted file mode 100644 index e05e6341705..00000000000 --- a/specimen/src/org/labkey/specimen/importer/QueryBasedTransformTask.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 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.importer; - -import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.Nullable; -import org.labkey.api.collections.CaseInsensitiveHashSet; -import org.labkey.api.pipeline.AbstractSpecimenTransformTask; -import org.labkey.api.pipeline.PipelineJob; -import org.labkey.api.pipeline.PipelineJobException; -import org.labkey.api.reader.DataLoader; -import org.labkey.api.reader.DataLoaderFactory; -import org.labkey.api.writer.PrintWriters; -import org.labkey.vfs.FileLike; - -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -public class QueryBasedTransformTask extends AbstractSpecimenTransformTask -{ - public QueryBasedTransformTask(@Nullable PipelineJob job) - { - super(job); - } - - private final Map _primaryIds = new LinkedHashMap<>(); - private final Map _derivativeIds = new LinkedHashMap<>(); - private final Map _additiveIds = new LinkedHashMap<>(); - - @Nullable - private String getPrimaryType(Map row) - { - String colNameLK = getNonNullValue(row, "primary_type"); - return ("".equals(colNameLK)) ? getNonNullValue(row, "PrimaryType") : colNameLK; - } - - @Nullable - private String getDerivative(Map row) - { - String colNameLK = getNonNullValue(row, "derivative_type"); - return ("".equals(colNameLK)) ? getNonNullValue(row, "DerivativeType") : colNameLK; - } - - @Nullable - private String getAdditive(Map row) - { - String colNameLK = getNonNullValue(row, "additive_type"); - return ("".equals(colNameLK)) ? getNonNullValue(row, "AdditiveType") : colNameLK; - } - - private Integer getType(String type, Map typeMap) - { - Integer id = typeMap.get(type); - if (id == null && !StringUtils.isEmpty(type)) - { - id = typeMap.size() + 1; - typeMap.put(type, id); - } - return id; - } - - public void transform(FileLike input, FileLike output) throws PipelineJobException - { - info("Starting to transform input file " + input + " to output file " + output); - - try - { - DataLoaderFactory df = DataLoader.get().findFactory(input, null); - if (null == df) - throw new PipelineJobException("Unable to create a data loader factory for the file: " + input.getName()); - DataLoader loader = df.createLoader(input.openInputStream(), true, _job.getContainer()); - loader.setInferTypes(false); - - try (ZipOutputStream zOut = new ZipOutputStream(output.openOutputStream())) - { - zOut.putNextEntry(new ZipEntry("specimens.tsv")); - PrintWriter writer = PrintWriters.getPrintWriter(zOut); - final QueryBasedSpecimenTransform.QueryBasedTSVWriter tsvWriter = new QueryBasedSpecimenTransform.QueryBasedTSVWriter(Collections.emptyList()); - - tsvWriter.setFileHeader(Collections.singletonList("# " + "specimens")); - tsvWriter.setPrintWriter(writer); - try - { - loader.forEach(row -> { - if (MapFilter.getMapFilter(this).test(row)) - { - if (!row.containsKey("records_id")) - row.put("record_id", tsvWriter.getRowCount()); - - row.put("primary_specimen_type_id", getType(getPrimaryType(row), _primaryIds)); - row.put("derivative_type_id", getType(getDerivative(row), _derivativeIds)); - row.put("additive_type_id", getType(getAdditive(row), _additiveIds)); - - tsvWriter.writeRow(row); - } - }); - } - finally - { - writer.flush(); - zOut.closeEntry(); - } - - if (tsvWriter.getRowCount() > 0) - info("After removing duplicates, there are " + tsvWriter.getRowCount() + " rows of data"); - else - throw new PipelineJobException("There are no rows of data"); - - writePrimaries(getPrimaryIds(), zOut); - writeDerivatives(getDerivativeIds(), zOut); - writeAdditives(getAdditiveIds(), zOut); - } - } - - catch (IOException e) - { - throw new PipelineJobException(e); - } - } - - @Override - protected Map transformRow(Map inputRow, int rowIndex, Map labIds, Map primaryIds, Map derivativeIds) - { - return null; - } - - @Override - protected Set getIgnoredHashColumns() - { - return new CaseInsensitiveHashSet(); - } - - @Override - protected Map getLabIds() - { - return new LinkedHashMap<>(); - } - - @Override - protected Map getPrimaryIds() - { - return _primaryIds; - } - - @Override - protected Map getDerivativeIds() - { - return _derivativeIds; - } - - @Override - protected Map getAdditiveIds() - { - return _additiveIds; - } -} diff --git a/specimen/src/org/labkey/specimen/importer/RequestabilityManager.java b/specimen/src/org/labkey/specimen/importer/RequestabilityManager.java deleted file mode 100644 index fab4ed541f5..00000000000 --- a/specimen/src/org/labkey/specimen/importer/RequestabilityManager.java +++ /dev/null @@ -1,849 +0,0 @@ -/* - * Copyright (c) 2010-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.importer; - -import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.Nullable; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.CompareType; -import org.labkey.api.data.Container; -import org.labkey.api.data.DbSchema; -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.Table; -import org.labkey.api.data.TableInfo; -import org.labkey.api.data.TableSelector; -import org.labkey.api.query.CustomView; -import org.labkey.api.query.FieldKey; -import org.labkey.api.query.QueryAction; -import org.labkey.api.query.QueryParam; -import org.labkey.api.query.QueryService; -import org.labkey.api.query.QueryView; -import org.labkey.api.query.UserSchema; -import org.labkey.api.security.User; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.specimen.Vial; -import org.labkey.api.study.SpecimenUrls; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.view.ActionURL; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * User: brittp - * Date: Mar 19, 2010 12:48:46 PM - */ -public class RequestabilityManager -{ - public static class RuleBean - { - private Integer _rowId; // SERIAL NOT NULL - private Container _container; // EntityId NOT NULL - private int _sortOrder; // INTEGER NOT NULL - private RuleType _ruleType; // VARCHAR(50) - private String _ruleData; // VARCHAR(250) - private MarkType _markType; // VARCHAR(30) - - public RuleBean() - { - // no-arg constructor for creation via Table-layer reflection - } - - public RuleBean(RequestableRule rule) - { - _container = rule.getContainer(); - _ruleType = rule.getType(); - _ruleData = rule.getRuleData(); - if (_ruleData != null && _ruleData.length() > 250) - throw new IllegalArgumentException("Rule data is currently limited to 250 characters."); - _markType = rule.getMarkType(); - } - - public Integer getRowId() - { - return _rowId; - } - - public void setRowId(Integer rowId) - { - _rowId = rowId; - } - - public Container getContainer() - { - return _container; - } - - public void setContainer(Container container) - { - _container = container; - } - - public int getSortOrder() - { - return _sortOrder; - } - - public void setSortOrder(int sortOrder) - { - _sortOrder = sortOrder; - } - - public String getRuleType() - { - return _ruleType.name(); - } - - public void setRuleType(String ruleType) - { - _ruleType = RuleType.valueOf(ruleType); - } - - public String getMarkType() - { - return _markType.name(); - } - - public void setMarkType(String markType) - { - _markType = MarkType.valueOf(markType); - } - - public String getRuleData() - { - return _ruleData; - } - - public void setRuleData(String ruleData) - { - _ruleData = ruleData; - } - - public RequestableRule createRule() - { - return _ruleType.createRule(_container, _ruleData); - } - } - - public enum MarkType - { - AVAILABLE - { - @Override - public String getLabel() - { - return "Available"; - } - }, - - UNAVAILABLE - { - @Override - public String getLabel() - { - return "Unavailable"; - } - }, - - AVAILABLE_OR_UNAVAILABLE - { - @Override - public String getLabel() - { - return "Available or unavailable"; - } - }; - - public abstract String getLabel(); - } - - - public enum RuleType - { - ADMIN_OVERRIDE - { - @Override - public RequestableRule createRule(Container container, String ruleData) - { - return new AdminOverrideRule(container); - } - - @Override - public String getName() - { - return "Administrator Override"; - } - - @Override - public String getDescription() - { - return "Marks vials available or unavailable based on the 'requestable' column in the specimen data feed."; - } - - @Override - public MarkType getDefaultMarkType() - { - return MarkType.AVAILABLE_OR_UNAVAILABLE; - } - - @Override - public ActionURL getDefaultTestURL(Container container) - { - ActionURL testURL = PageFlowUtil.urlProvider(SpecimenUrls.class).getSpecimensURL(container, true); - testURL.addFilter("SpecimenDetail", FieldKey.fromParts("Requestable"), CompareType.NONBLANK, null); - return testURL; - } - }, - AT_REPOSITORY - { - @Override - public RequestableRule createRule(Container container, String ruleData) - { - return new RepositoryRule(container); - } - - @Override - public String getName() - { - return "At Repository Check"; - } - - @Override - public String getDescription() - { - return "Marks vials unavailable if they are not currently held by a repository."; - } - - @Override - public MarkType getDefaultMarkType() - { - return MarkType.UNAVAILABLE; - } - - @Override - public ActionURL getDefaultTestURL(Container container) - { - ActionURL testURL = PageFlowUtil.urlProvider(SpecimenUrls.class).getSpecimensURL(container, true); - testURL.addFilter("SpecimenDetail", FieldKey.fromParts("AtRepository"), CompareType.EQUAL, Boolean.FALSE); - return testURL; - - } - }, - LOCKED_IN_REQUEST - { - @Override - public RequestableRule createRule(Container container, String ruleData) - { - return new LockedInRequestRule(container); - } - - @Override - public String getName() - { - return "Locked In Request Check"; - } - - @Override - public String getDescription() - { - return "Marks vials unavailable if they are part of an active specimen request."; - } - - @Override - public MarkType getDefaultMarkType() - { - return MarkType.UNAVAILABLE; - } - - @Override - public ActionURL getDefaultTestURL(Container container) - { - ActionURL testURL = PageFlowUtil.urlProvider(SpecimenUrls.class).getSpecimensURL(container, true); - testURL.addFilter("SpecimenDetail", FieldKey.fromParts("LockedInRequest"), CompareType.EQUAL, Boolean.TRUE); - return testURL; - - } - }, - LOCKED_WHILE_PROCESSING - { - @Override - public RequestableRule createRule(Container container, String ruleData) - { - return new LockedWhileProcessingRule(container); - } - - @Override - public String getName() - { - return "Locked While Processing Check"; - } - - @Override - public String getDescription() - { - return "Marks vials unavailable if they are being processed."; - } - - @Override - public MarkType getDefaultMarkType() - { - return MarkType.UNAVAILABLE; - } - - @Override - public ActionURL getDefaultTestURL(Container container) - { - return null; - } - }, - CUSTOM_QUERY - { - @Override - public RequestableRule createRule(Container container, String ruleData) - { - return new CustomQueryRule(container, ruleData); - } - - @Override - public String getName() - { - return "Custom Query"; - } - - @Override - public String getDescription() - { - return "Marks vials available or unavailable based on the results of an administrator-defined query."; - } - - @Override - public MarkType getDefaultMarkType() - { - return null; - } - - @Override - public ActionURL getDefaultTestURL(Container container) - { - return null; - } - }; - - public abstract RequestableRule createRule(Container container, String ruleData); - - public abstract String getName(); - - public abstract String getDescription(); - - public abstract MarkType getDefaultMarkType(); - - @Nullable - public abstract ActionURL getDefaultTestURL(Container container); - } - - private static final RequestabilityManager _instance = new RequestabilityManager(); - - private RequestabilityManager() - { - // private constructor to ensure use of singleton. - } - - public static class InvalidRuleException extends Exception - { - public InvalidRuleException(String msg) - { - super(msg); - } - - public InvalidRuleException(String msg, Exception cause) - { - super(msg, cause); - } - } - - public static abstract class RequestableRule - { - protected Container _container; - - public RequestableRule(Container container) - { - _container = container; - } - - public abstract SQLFragment getAvailabilityReason(); - - public int updateRequestability(User user, List vials) throws InvalidRuleException - { - TableInfo tableInfoVial = SpecimenSchema.get().getTableInfoVial(_container); - - SQLFragment reason = getAvailabilityReason(); - SQLFragment updateSQL = new SQLFragment("UPDATE "); - updateSQL.append(tableInfoVial).append(" SET "); - updateSQL.append(getAvailableAssignmentSQL()); - updateSQL.append(", AvailabilityReason = "); - updateSQL.append(reason); - updateSQL.append(" WHERE "); - updateSQL.append(getFilterSQL(_container, user, vials)); - return new SqlExecutor(SpecimenSchema.get().getSchema()).execute(updateSQL); - } - - protected SQLFragment getAvailableAssignmentSQL() - { - MarkType type = getMarkType(); - if (!(type == MarkType.AVAILABLE || type == MarkType.UNAVAILABLE)) - throw new IllegalStateException("Rules that do not specify a simple mark type must override getUpdateSQL."); - - return new SQLFragment("Available = ?", type == MarkType.AVAILABLE ? Boolean.TRUE : Boolean.FALSE); - } - - protected SQLFragment getGlobalUniqueIdInSQL(List vials) - { - SQLFragment sql = new SQLFragment(); - if (vials != null && !vials.isEmpty()) - { - sql.append("GlobalUniqueId "); - sql.append(getSpecimenGlobalUniqueIdSet(vials)); - } - return sql; - } - - protected SQLFragment getSpecimenGlobalUniqueIdSet(List vials) - { - List specimenIDs = new ArrayList<>(vials.size()); - for (Vial vial : vials) - { - specimenIDs.add(vial.getGlobalUniqueId()); - } - return SpecimenSchema.get().getSqlDialect().appendInClauseSql(new SQLFragment(), specimenIDs); - } - - protected abstract SQLFragment getFilterSQL(Container container, User user, List vials) throws InvalidRuleException; - - public MarkType getMarkType() - { - return getType().getDefaultMarkType(); - } - - public String getRuleData() - { - return null; - } - - public abstract RuleType getType(); - - @Nullable - public ActionURL getTestURL(User user) - { - return getType().getDefaultTestURL(_container); - } - - public String getName() - { - String name = getType().getName(); - String extra = getExtraName(); - if (extra != null) - name += ": " + extra; - return name; - } - - public Container getContainer() - { - return _container; - } -/* - If the rule's type isn't enough to identify the rule, getExtraName can be used to provide the user - with additional information. - */ - protected String getExtraName() - { - return null; - } - } - - public static final String CUSTOM_QUERY_DATA_SEPARATOR = "~"; - - private static class CustomQueryRule extends RequestableRule - { - private final String _schemaName; - private final String _queryName; - private String _viewName; - private final boolean _markRequestable; - - protected CustomQueryRule(Container container, String state) - { - super(container); - String[] values = state.split(CUSTOM_QUERY_DATA_SEPARATOR); - _schemaName = values[0]; - _queryName = values[1]; - _viewName = values[2]; - if (_viewName != null && _viewName.isEmpty()) - _viewName = null; - _markRequestable = Boolean.parseBoolean(values[3]); - } - - @Override - public ActionURL getTestURL(User user) - { - ActionURL url = QueryService.get().urlFor(user, _container, QueryAction.executeQuery, _schemaName, _queryName); - if (_viewName != null) - url.addParameter(QueryView.DATAREGIONNAME_DEFAULT + "." + QueryParam.viewName.name(), _viewName); - return url; - } - - @Override - public MarkType getMarkType() - { - return _markRequestable ? MarkType.AVAILABLE : MarkType.UNAVAILABLE; - } - - @Override - public String getRuleData() - { - return _schemaName + CUSTOM_QUERY_DATA_SEPARATOR + - _queryName + CUSTOM_QUERY_DATA_SEPARATOR + - (_viewName != null ? _viewName : "") + CUSTOM_QUERY_DATA_SEPARATOR + - _markRequestable; - } - - @Override - public SQLFragment getFilterSQL(Container container, User user, List vials) throws InvalidRuleException - { - SimpleFilter viewFilter = new SimpleFilter(); - if (_viewName != null) - { - CustomView baseView = QueryService.get().getCustomView(user, container, user, _schemaName, _queryName, _viewName); - if (baseView != null) - { - // copy our saved view filter into our SimpleFilter via an ActionURL (yuck...) - ActionURL url = new ActionURL(); - baseView.applyFilterAndSortToURL(url, "mockDataRegion"); - viewFilter.addUrlFilters(url, "mockDataRegion"); - } - else - throw new InvalidRuleException("Could not find view " + _viewName + " on query " + _queryName + " in schema " + _schemaName + "."); - } - - if (vials != null && !vials.isEmpty()) - { - Set globalUniqueIds = new HashSet<>(); - for (Vial vial : vials) - globalUniqueIds.add(vial.getGlobalUniqueId()); - viewFilter.addCondition(new SimpleFilter.InClause(FieldKey.fromParts("GlobalUniqueId"), globalUniqueIds)); - } - - UserSchema schema = QueryService.get().getUserSchema(user, container, _schemaName); - TableInfo tinfo = schema.getTable(_queryName); - if (null == tinfo) - throw new InvalidRuleException("Query " + _queryName + " in schema " + _schemaName + " not found."); - ColumnInfo globalUniqueIdCol = tinfo.getColumn("GlobalUniqueId"); - if (globalUniqueIdCol == null) - throw new InvalidRuleException("Query " + _queryName + " in schema " + _schemaName + " doesn't include required column name 'GlobalUniqueId'."); - - SQLFragment vialListSql = Table.getSelectSQL(tinfo, Collections.singleton(globalUniqueIdCol), viewFilter, null); - - SQLFragment updateFilter = new SQLFragment("GlobalUniqueId IN (SELECT GlobalUniqueId FROM ("); - updateFilter.append(vialListSql); - updateFilter.append(") X )"); - - return updateFilter; - } - - @Override - public SQLFragment getAvailabilityReason() - { - return new SQLFragment("?", "This vial is " + getMarkType().getLabel().toLowerCase() + " because it was found in the set called \"" + _queryName + "\"."); - } - - @Override - public String getExtraName() - { - return _schemaName + "." + _queryName + (_viewName != null ? ", view " + _viewName : ""); - } - - @Override - public RuleType getType() - { - return RuleType.CUSTOM_QUERY; - } - } - - private static class RepositoryRule extends RequestableRule - { - public RepositoryRule(Container container) - { - super(container); - } - - @Override - public SQLFragment getFilterSQL(Container container, User user, List vials) - { - SQLFragment sql = new SQLFragment("AtRepository = ?", Boolean.FALSE); - if (vials != null && !vials.isEmpty()) - sql.append(" AND ").append(getGlobalUniqueIdInSQL(vials)); - return sql; - } - - @Override - public RuleType getType() - { - return RuleType.AT_REPOSITORY; - } - - @Override - public SQLFragment getAvailabilityReason() - { - return new SQLFragment("'This vial is unavailable because it is not currently held by a repository.'"); - } - } - - private static class AdminOverrideRule extends RequestableRule - { - public AdminOverrideRule(Container container) - { - super(container); - } - - @Override - public SQLFragment getAvailabilityReason() - { - SQLFragment sql = new SQLFragment("CASE WHEN Requestable = ? THEN 'This vial''s availability status was set by an administrator. Please contact an administrator for more information.' ELSE ' ' END"); - sql.add(false); - return sql; - } - - @Override - public RuleType getType() - { - return RuleType.ADMIN_OVERRIDE; - } - - @Override - public SQLFragment getFilterSQL(Container container, User user, List vials) - { - SQLFragment sql = new SQLFragment("Requestable IS NOT NULL"); - if (vials != null && !vials.isEmpty()) - sql.append(" AND ").append(getGlobalUniqueIdInSQL(vials)); - return sql; - } - - @Override - protected SQLFragment getAvailableAssignmentSQL() - { - return new SQLFragment("Available = Requestable"); - } - } - - private static class LockedInRequestRule extends RequestableRule - { - public LockedInRequestRule(Container container) - { - super(container); - } - - @Override - public SQLFragment getFilterSQL(Container container, User user, List vials) - { - SQLFragment sql = new SQLFragment("LockedInRequest = ?", Boolean.TRUE); - if (vials != null && !vials.isEmpty()) - sql.append(" AND ").append(getGlobalUniqueIdInSQL(vials)); - return sql; - } - - @Override - public RuleType getType() - { - return RuleType.LOCKED_IN_REQUEST; - } - - @Override - public SQLFragment getAvailabilityReason() - { - return new SQLFragment("'This vial is unavailable because it is locked in a specimen request.'"); - } - } - - private static class LockedWhileProcessingRule extends RequestableRule - { - public LockedWhileProcessingRule(Container container) - { - super(container); - } - - @Override - public SQLFragment getFilterSQL(Container container, User user, List vials) - { - TableInfo tableInfoVial = SpecimenSchema.get().getTableInfoVial(_container); - - SQLFragment sql = new SQLFragment(); - if (vials != null && !vials.isEmpty()) - sql.append(getGlobalUniqueIdInSQL(vials)).append(" AND "); - - sql.append(" RowId IN (SELECT SV.RowId FROM ").append(tableInfoVial).append(" SV") - .append(" LEFT OUTER JOIN (SELECT Container, SampleRequestId, SpecimenGlobalUniqueId FROM ") - .append(SpecimenSchema.get().getTableInfoSampleRequestSpecimen()) - .append(") SRS ON (GlobalUniqueId = SpecimenGlobalUniqueId)") - .append(" LEFT OUTER JOIN (SELECT RowId, StatusId FROM ") - .append(SpecimenSchema.get().getTableInfoSampleRequest()) - .append(") SR ON (SR.RowId = SRS.SampleRequestId)") - .append(" LEFT OUTER JOIN (SELECT RowId, FinalState FROM ") - .append(SpecimenSchema.get().getTableInfoSampleRequestStatus()) - .append(") SRST ON (SRST.RowId = SR.StatusId)") - .append(" WHERE SRS.Container = ? AND SRST.FinalState = ?"); - sql.add(container.getId()); - sql.add(false); - if (vials != null && !vials.isEmpty()) - sql.append(" AND SpecimenGlobalUniqueId ").append(getSpecimenGlobalUniqueIdSet(vials)); - - sql.append(")"); - return sql; - } - - @Override - public RuleType getType() - { - return RuleType.LOCKED_WHILE_PROCESSING; - } - - @Override - public SQLFragment getAvailabilityReason() - { - return new SQLFragment("'This vial is unavailable because it is being processed.'"); - } - } - - public static RequestabilityManager getInstance() - { - return _instance; - } - - public List getRules(Container container) - { - TableInfo ruleTableInfo = SpecimenSchema.get().getTableInfoSampleAvailabilityRule(); - List ruleBeans = new TableSelector(ruleTableInfo, - SimpleFilter.createContainerFilter(container), - new Sort("SortOrder")).getArrayList(RuleBean.class); - List rules = new ArrayList<>(); - for (RuleBean bean : ruleBeans) - rules.add(bean.createRule()); - - return rules; - } - - public void saveRules(Container container, User user, List rules) - { - TableInfo ruleTableInfo = SpecimenSchema.get().getTableInfoSampleAvailabilityRule(); - DbScope scope = SpecimenSchema.get().getScope(); - try (DbScope.Transaction transaction = scope.ensureTransaction()) - { - Table.delete(ruleTableInfo, new SimpleFilter(FieldKey.fromString("Container"), container.getId())); - - int sortOrder = 0; - for (RequestableRule rule : rules) - { - RuleBean bean = new RuleBean(rule); - bean.setSortOrder(sortOrder++); - Table.insert(user, ruleTableInfo, bean); - } - - transaction.commit(); - } - } - - public void setDefaultRules(Container container, User user) - { - List rules = new ArrayList<>(); - rules.add(new RepositoryRule(container)); - rules.add(new AdminOverrideRule(container)); - rules.add(new LockedInRequestRule(container)); - saveRules(container, user, rules); - } - - private void updateRequestability(Container container, User user, boolean resetToAvailable, Logger logger, List vials) throws InvalidRuleException - { - DbSchema schema = SpecimenSchema.get().getSchema(); - assert schema.getScope().isTransactionActive() : "Requestability should always be updated within a transaction wrapping the addition/removal of vials."; - if (logger != null) - logger.info("Updating vial availability..."); - - if (resetToAvailable) - { - TableInfo tableInfoVial = SpecimenSchema.get().getTableInfoVial(container); - - if (logger != null) - logger.info("\tResetting vials to default available state."); - SQLFragment updateSQL = new SQLFragment("UPDATE "); - updateSQL.append(tableInfoVial) - .append(" SET Available = ?, AvailabilityReason = ? "); - updateSQL.add(Boolean.TRUE); - updateSQL.add(null); - if (vials != null && !vials.isEmpty()) - { - updateSQL.append("WHERE RowId IN ("); - String sep = ""; - for (Vial vial : vials) - { - updateSQL.append(sep).append("?"); - sep = ", "; - updateSQL.add(vial.getRowId()); - } - updateSQL.append(")"); - } - new SqlExecutor(schema).execute(updateSQL); - - if (logger != null) - logger.info("\tReset complete."); - } - - for (RequestableRule rule : getRules(container)) - { - String action = rule.getMarkType().getLabel().toLowerCase(); - if (logger != null) - logger.info("\tMarking vials {} based on {}", action, rule.getName()); - int updatedCount = rule.updateRequestability(user, vials); - if (logger != null) - logger.info("\t{} vials marked {}.", updatedCount, action); - } - - if (logger != null) - logger.info("Vial availability update complete."); - - } - - public void updateRequestability(Container container, User user, List vials) throws InvalidRuleException - { - updateRequestability(container, user, true, null, vials); - } - - public void updateRequestability(Container container, User user, boolean resetToAvailable, boolean editingSpecimens, Logger logger) throws InvalidRuleException - { - updateRequestability(container, user, (resetToAvailable || editingSpecimens), logger, null); - } - - public static String makeSpecimenUnavailableMessage(Vial vial, @Nullable String additionalText) - { - return String.format("Specimen %s%s%s", - vial.getGlobalUniqueId(), - null != vial.getAvailabilityReason() ? vial.getAvailabilityReason().replaceFirst("This vial", "") : " is not available.", - null != additionalText ? " " + additionalText : ""); - } -} diff --git a/specimen/src/org/labkey/specimen/importer/SimpleSpecimenImporter.java b/specimen/src/org/labkey/specimen/importer/SimpleSpecimenImporter.java deleted file mode 100644 index 1ce059def0b..00000000000 --- a/specimen/src/org/labkey/specimen/importer/SimpleSpecimenImporter.java +++ /dev/null @@ -1,429 +0,0 @@ -/* - * Copyright (c) 2008-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.importer; - -import org.apache.commons.beanutils.ConvertUtils; -import org.apache.logging.log4j.Logger; -import org.labkey.api.collections.CaseInsensitiveHashMap; -import org.labkey.api.data.Container; -import org.labkey.api.data.Filter; -import org.labkey.api.data.RuntimeSQLException; -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.TableInfo; -import org.labkey.api.data.TableSelector; -import org.labkey.api.query.ValidationException; -import org.labkey.api.reader.ColumnDescriptor; -import org.labkey.api.reader.TabLoader; -import org.labkey.api.security.User; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.study.StudyUtils; -import org.labkey.api.study.TimepointType; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.util.logging.LogHelper; -import org.springframework.jdbc.BadSqlGrammarException; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class SimpleSpecimenImporter extends SpecimenImporter -{ - public static final String VIAL_ID = "global_unique_specimen_id"; - public static final String SAMPLE_ID = "specimen_number"; - public static final String DRAW_TIMESTAMP = "draw_timestamp"; - public static final String VISIT = "visit_value"; - public static final String VOLUME = "volume"; - public static final String UNITS = "volume_units"; - public static final String PRIMARY_SPECIMEN_TYPE = "primary_specimen_type"; - public static final String DERIVATIVE_TYPE = "derivative_type"; - public static final String ADDITIVE_TYPE = "additive_type"; - public static final String PARTICIPANT_ID = "ptid"; - - private static final Logger LOG = LogHelper.getLogger(SimpleSpecimenImporter.class, "Specimen import status and errors"); - private static final Map DEFAULT_COLUMN_LABELS = new CaseInsensitiveHashMap<>(); - - static - { - DEFAULT_COLUMN_LABELS.put(VIAL_ID, "Global Unique Id"); - DEFAULT_COLUMN_LABELS.put(SAMPLE_ID, "Sample Id"); - DEFAULT_COLUMN_LABELS.put(DRAW_TIMESTAMP, "Draw Timestamp"); - DEFAULT_COLUMN_LABELS.put(VISIT, "Visit"); - DEFAULT_COLUMN_LABELS.put(VOLUME, "Volume"); - DEFAULT_COLUMN_LABELS.put(UNITS, "Volume Units"); - DEFAULT_COLUMN_LABELS.put(PRIMARY_SPECIMEN_TYPE, "Primary Type"); - DEFAULT_COLUMN_LABELS.put(ADDITIVE_TYPE, "Additive Type"); - DEFAULT_COLUMN_LABELS.put(DERIVATIVE_TYPE, "Derivative Type"); - DEFAULT_COLUMN_LABELS.put(PARTICIPANT_ID, "Subject Id"); - } - - private final Map _columnLabels; - private final TimepointType _timepointType; - - public SimpleSpecimenImporter(Container container, User user, TimepointType timepointType, String participantIdLabel) - { - super(container, user); - _timepointType = timepointType; - _columnLabels = new HashMap<>(DEFAULT_COLUMN_LABELS); - _columnLabels.put(PARTICIPANT_ID, participantIdLabel + " Id"); - } - - public String label(String columnName) - { - String str = _columnLabels.get(columnName); - return str == null ? columnName : str; - } - - public Map getColumnLabels() - { - return _columnLabels; - } - - public void fixupSpecimenColumns(TabLoader tl) throws IOException - { - ColumnDescriptor[] cols = tl.getColumns(); - ColumnDescriptor[] mappedCols = new ColumnDescriptor[cols.length]; - - for (int i = 0; i < cols.length; i++) - { - ColumnDescriptor col = cols[i]; - SpecimenColumn specCol = findSpecimenColumn(col.name); - if (null == specCol) - { - //Default cols should be type string so lookups can be created - col.clazz = String.class; - mappedCols[i] = col; - } - else - mappedCols[i] = specCol.getColumnDescriptor(); - - } - tl.setColumns(mappedCols); - } - - private Map fixupSpecimenRow(Map row) - { - Map result = new HashMap<>(); - for (String key : row.keySet()) - { - Object value = row.get(key); - if (value instanceof String) - { - SpecimenColumn specCol = findSpecimenColumn(key); - Class type = String.class; - if (null != specCol) - type = specCol.getColumnDescriptor().clazz; - value = ConvertUtils.convert((String)value, type); - } - result.put(key, value); - } - - return result; - } - - public ColumnDescriptor[] getSimpleSpecimenColumns() - { - List colNames = new ArrayList<>(Arrays.asList( - VIAL_ID, - SAMPLE_ID, - DRAW_TIMESTAMP, - PARTICIPANT_ID, - VOLUME, - UNITS, - PRIMARY_SPECIMEN_TYPE, - DERIVATIVE_TYPE, - ADDITIVE_TYPE)); - if (_timepointType == TimepointType.VISIT) - colNames.add(3, VISIT); - - ColumnDescriptor[] cols = new ColumnDescriptor[colNames.size()]; - - for (int i = 0; i < cols.length; i++) - { - SpecimenColumn specCol = findSpecimenColumn(colNames.get(i)); - cols[i] = null == specCol ? new ColumnDescriptor(colNames.get(i), String.class) : specCol.getColumnDescriptor(); - } - - return cols; - } - - public void process(List> rows, boolean merge) throws IOException, ValidationException - { - //Map from column name to - Container container = getContainer(); - Study study = StudyService.get().getStudy(container); - Map lookupTables = new HashMap<>(); - lookupTables.put("additive_type", new LookupTable(SpecimenSchema.get().getTableInfoSpecimenAdditive(container), container, _additivesTableType, "additive_type_id", "additive_id", "additive", "Additive")); - lookupTables.put("derivative_type", new LookupTable(SpecimenSchema.get().getTableInfoSpecimenDerivative(container), container, _derivativesTableType, "derivative_type_id", "derivative_id", "derivative", "Derivative")); - lookupTables.put("primary_specimen_type", new LookupTable(SpecimenSchema.get().getTableInfoSpecimenPrimaryType(container), container, _primaryTypesTableType, "primary_specimen_type_id", "primary_type_id", "primary_type", "PrimaryType")); - LabLookupTable labLookup = new LabLookupTable(SpecimenSchema.get().getTableInfoLocation(container), container, _labsTableType); - lookupTables.put("lab", labLookup); - - List> specimenRows = new ArrayList<>(); - int recordId = 1; - - for (Map row : rows) - { - Map specimenRow = new HashMap<>(); - for (String colName : row.keySet()) - { - //If this column should be mapped to an integer lookup, do so - LookupTable lookupTable = lookupTables.get(colName); - if (null == lookupTable) - specimenRow.put(colName, row.get(colName)); - else - specimenRow.put(lookupTable.getForeignKeyCol(), lookupTable.getVal((String) row.get(colName))); - } - //Make specimen requestable - if (null == row.get("lab")) - specimenRow.put(labLookup.getForeignKeyCol(), labLookup.getDefaultLabId()); - - if (study.getTimepointType() != TimepointType.VISIT) - specimenRow.put(VISIT, StudyUtils.sequenceNumFromDate((Date) specimenRow.get(DRAW_TIMESTAMP))); - - if (!row.containsKey(VIAL_ID)) - specimenRow.put(VIAL_ID, specimenRow.get(SAMPLE_ID)); - specimenRow.put("record_id", recordId++); - specimenRows.add(specimenRow); - } - - Map sifMap = new HashMap<>(); - addSpecimenImportFile(sifMap, _specimensTableType, specimenRows); - for (LookupTable lookupTable : lookupTables.values()) - addSpecimenImportFile(sifMap, lookupTable.getTableType(), lookupTable.toMaps()); - - try - { - super.process(sifMap, merge, LOG, null, true, false); - } - catch (ValidationException ex) - { - LOG.error("Error during import", ex); - throw ex; - } - catch (BadSqlGrammarException ex) - { - if (areDuplicateLabIds(labLookup)) - throw new RuntimeException("Error possibly caused by duplicate ExternalIds in Location tables: " + ex.getSQLException().getMessage()); - throw new RuntimeSQLException(ex.getSQLException()); - } - } - - - private void addSpecimenImportFile(Map fileNameMap, SpecimenTableType type, List> specimenRows) - { - assert null != type : "Unknown type!"; - fileNameMap.put(type, new IteratorSpecimenImportFile(specimenRows, type)); - } - - - private static class LookupTable - { - private final SpecimenTableType tableType; - private final String foreignKeyCol; - private final String tsvIdCol; - private final String tsvLabelCol; - // additive, derivative, primarytype, and site all use ExternalId as the id column. - private final String dbIdCol = "ExternalId"; - private final String dbRowIdCol = "RowId"; - private final String dbLabelCol; - - // Maps of label -> id - private final HashMap existingKeyMap = new HashMap<>(); - private final HashMap keyMap = new HashMap<>(); - private int minId = 0; - private int lastId = 0; - - LookupTable(TableInfo table, Container c, SpecimenTableType tableType, String foreignKeyCol, String tsvIdCol, String tsvLabelCol, String dbLabelCol) - { - this.tableType = tableType; - this.foreignKeyCol = foreignKeyCol; - this.tsvIdCol = tsvIdCol; - this.tsvLabelCol = tsvLabelCol; - this.dbLabelCol = dbLabelCol; - - getKeyMap(table, c); - } - - private void getKeyMap(TableInfo table, Container c) - { - final List> missingExternalId = new ArrayList<>(); - Filter filter = SimpleFilter.createContainerFilter(c); - new TableSelector(table, PageFlowUtil.set(dbRowIdCol, dbIdCol, dbLabelCol), filter, new Sort("RowId")).forEachMap(map -> { - Integer id = (Integer)map.get(dbIdCol); - if (id == null) - { - missingExternalId.add(map); - } - else - { - existingKeyMap.put((String)map.get(dbLabelCol), id); - minId = Math.min(minId, id); - lastId = Math.max(lastId, id); - } - }); - - // UNDONE: Temporary fix for 11.1: Create fake ExternalId for rows with null ExternalId - for (Map map : missingExternalId) - { - Integer rowId = (Integer)map.get(dbRowIdCol); - int id = --minId; - existingKeyMap.put((String)map.get(dbLabelCol), id); - - SQLFragment sql = new SQLFragment() - .append("UPDATE ") - .append(table.getSelectName()) - .append(" SET ") - .append(dbIdCol) - .append(" = ?") - .add(id) - .append(" WHERE ") - .append(dbRowIdCol) - .append(" = ?") - .add(rowId); - - new SqlExecutor(table.getSchema()).execute(sql); - } - } - - Integer getVal(String label) - { - Integer i = keyMap.get(label); - if (null == i) - { - i = existingKeyMap.get(label); - if (null == i) - { - i = lastId = lastId+1; - keyMap.put(label, i); - } - else - { - keyMap.put(label, i); - } - } - return i; - } - - List> toMaps() - { - List> maps = new ArrayList<>(keyMap.size()); - - for (Map.Entry entry : keyMap.entrySet()) - { - Map m = new HashMap<>(); - m.put(tsvLabelCol, entry.getKey()); - m.put(tsvIdCol, entry.getValue()); - maps.add(m); - } - - maps.sort((a, b) -> - { - Integer aId = (Integer) a.get(tsvIdCol); - Integer bId = (Integer) b.get(tsvIdCol); - return aId.compareTo(bId); - }); - - return maps; - } - - public SpecimenTableType getTableType() - { - return tableType; - } - - public String getTsvIdCol() - { - return tsvIdCol; - } - - public String getForeignKeyCol() - { - return foreignKeyCol; - } - - public Map getExistingKeyMap() - { - return existingKeyMap; - } - } - - private static class LabLookupTable extends LookupTable - { - static final String DEFAULT_LAB = "Not Specified"; - private final Integer defaultLabId; - - LabLookupTable(TableInfo tableInfo, Container c, SpecimenTableType tableType) - { - super(tableInfo, c, tableType, "lab_id", "lab_id", "lab_name", "Label"); - defaultLabId = getVal(DEFAULT_LAB); - } - - @Override - List> toMaps() - { - List> maps = super.toMaps(); - //Treat each like a repository so that specimens might be requestable. - for (Map m : maps) - { - m.put("is_repository", Boolean.TRUE); - } - return maps; - } - - @Override - Integer getVal(String label) - { - return null == label ? defaultLabId : super.getVal(label); - } - - Integer getDefaultLabId() - { - return defaultLabId; - } - } - - private Map specimenColumnMap; - - private SpecimenColumn findSpecimenColumn(String name) - { - if (null == specimenColumnMap) - specimenColumnMap = SpecimenImporter.createImportMap(getSpecimenColumns()); - return specimenColumnMap.get(name); - } - - private static boolean areDuplicateLabIds(LabLookupTable labLookupTable) - { - Set labids = new HashSet<>(); - for (Integer id : labLookupTable.getExistingKeyMap().values()) - { - if (!labids.add(id)) - return true; // already there - } - return false; - } -} diff --git a/specimen/src/org/labkey/specimen/importer/SpecimenColumn.java b/specimen/src/org/labkey/specimen/importer/SpecimenColumn.java deleted file mode 100644 index 37d665a5b90..00000000000 --- a/specimen/src/org/labkey/specimen/importer/SpecimenColumn.java +++ /dev/null @@ -1,125 +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.importer; - -import org.labkey.api.util.TimeOnlyDate; - -import java.util.Collection; -import java.util.Collections; - -public class SpecimenColumn extends ImportableColumn -{ - private final TargetTable _targetTable; - - private String _fkTable; - private String _joinType; - private String _fkColumn; - private String _aggregateEventFunction; - private boolean _isKeyColumn = false; - - public SpecimenColumn(String tsvColumnName, Collection tsvColumnAliases, String dbColumnName, String databaseType, TargetTable eventColumn, boolean unique) - { - super(tsvColumnName, tsvColumnAliases, dbColumnName, databaseType, unique, false); - _targetTable = eventColumn; - } - - public SpecimenColumn(String tsvColumnName, String dbColumnName, String databaseType, TargetTable eventColumn, boolean unique) - { - this(tsvColumnName, Collections.emptyList(), dbColumnName, databaseType, eventColumn, unique); - } - - public SpecimenColumn(String tsvColumnName, String dbColumnName, String databaseType, boolean isKeyColumn, TargetTable eventColumn, boolean unique) - { - this(tsvColumnName, dbColumnName, databaseType, eventColumn, unique); - _isKeyColumn = isKeyColumn; - } - - public SpecimenColumn(String tsvColumnName, String dbColumnName, String databaseType, boolean isKeyColumn, TargetTable eventColumn) - { - this(tsvColumnName, dbColumnName, databaseType, eventColumn, false); - _isKeyColumn = isKeyColumn; - } - - public SpecimenColumn(String tsvColumnName, String dbColumnName, String databaseType, TargetTable eventColumn) - { - this(tsvColumnName, dbColumnName, databaseType, eventColumn, false); - } - - public SpecimenColumn(String tsvColumnName, Collection tsvColumnAliases, String dbColumnName, String databaseType, TargetTable eventColumn) - { - this(tsvColumnName, tsvColumnAliases, dbColumnName, databaseType, eventColumn, false); - } - - public SpecimenColumn(String tsvColumnName, String dbColumnName, String databaseType, TargetTable eventColumn, String aggregateEventFunction) - { - this(tsvColumnName, dbColumnName, databaseType, eventColumn, false); - _aggregateEventFunction = aggregateEventFunction; - } - - public SpecimenColumn(String tsvColumnName, String dbColumnName, String databaseType, - TargetTable eventColumn, String fkTable, String fkColumn, String joinType) - { - this(tsvColumnName, dbColumnName, databaseType, eventColumn, false); - _fkColumn = fkColumn; - _fkTable = fkTable; - _joinType = joinType; - } - - public TargetTable getTargetTable() - { - return _targetTable; - } - - public String getFkColumn() - { - return _fkColumn; - } - - public String getFkTable() - { - return _fkTable; - } - - public String getJoinType() - { - return _joinType; - } - - public String getDbType() - { - return _dbType; - } - - public String getAggregateEventFunction() - { - return _aggregateEventFunction; - } - - public boolean isKeyColumn() - { - return _isKeyColumn; - } - - public String getFkTableAlias() - { - return getDbColumnName() + "Lookup"; - } - - public boolean isDateType() - { - return getDbType() != null && (getDbType().equals("DATETIME") || getDbType().equals("TIMESTAMP")) && !getJavaClass().equals(TimeOnlyDate.class); - } -} diff --git a/specimen/src/org/labkey/specimen/importer/SpecimenImportFile.java b/specimen/src/org/labkey/specimen/importer/SpecimenImportFile.java deleted file mode 100644 index bf9dc95c15f..00000000000 --- a/specimen/src/org/labkey/specimen/importer/SpecimenImportFile.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2013-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.importer; - -import org.labkey.api.reader.DataLoader; - -import java.io.IOException; - -public interface SpecimenImportFile -{ - SpecimenTableType getTableType(); - DataLoader getDataLoader() throws IOException; -} diff --git a/specimen/src/org/labkey/specimen/importer/SpecimenImporter.java b/specimen/src/org/labkey/specimen/importer/SpecimenImporter.java deleted file mode 100644 index 24d0d6fdd1d..00000000000 --- a/specimen/src/org/labkey/specimen/importer/SpecimenImporter.java +++ /dev/null @@ -1,3096 +0,0 @@ -/* - * Copyright (c) 2008-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.importer; - -import org.apache.commons.lang3.StringUtils; -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.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.labkey.api.collections.CaseInsensitiveHashMap; -import org.labkey.api.collections.CaseInsensitiveHashSet; -import org.labkey.api.collections.IntHashMap; -import org.labkey.api.data.BaseColumnInfo; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.Container; -import org.labkey.api.data.DatabaseIdentifier; -import org.labkey.api.data.DbSchema; -import org.labkey.api.data.DbScope; -import org.labkey.api.data.DbSequence; -import org.labkey.api.data.DbSequenceManager; -import org.labkey.api.data.JdbcType; -import org.labkey.api.data.Parameter; -import org.labkey.api.data.Results; -import org.labkey.api.data.ResultsImpl; -import org.labkey.api.data.RuntimeSQLException; -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.TableResultSet; -import org.labkey.api.data.TableSelector; -import org.labkey.api.data.TempTableInfo; -import org.labkey.api.data.UpdateableTableInfo; -import org.labkey.api.data.dialect.SqlDialect; -import org.labkey.api.dataiterator.DataIterator; -import org.labkey.api.dataiterator.DataIteratorBuilder; -import org.labkey.api.dataiterator.DataIteratorContext; -import org.labkey.api.dataiterator.DataIteratorUtil; -import org.labkey.api.dataiterator.ImportProgress; -import org.labkey.api.dataiterator.ListofMapsDataIterator; -import org.labkey.api.dataiterator.LoggingDataIterator; -import org.labkey.api.dataiterator.MapDataIterator; -import org.labkey.api.dataiterator.Pump; -import org.labkey.api.dataiterator.SimpleTranslator; -import org.labkey.api.dataiterator.StandardDataIteratorBuilder; -import org.labkey.api.dataiterator.TableInsertDataIteratorBuilder; -import org.labkey.api.exceptions.OptimisticConflictException; -import org.labkey.api.exp.Lsid; -import org.labkey.api.exp.api.ExpSampleType; -import org.labkey.api.exp.api.ExperimentService; -import org.labkey.api.exp.api.SampleTypeService; -import org.labkey.api.iterator.MarkableIterator; -import org.labkey.api.pipeline.PipelineJob; -import org.labkey.api.query.DefaultSchema; -import org.labkey.api.query.FieldKey; -import org.labkey.api.query.QueryUpdateService; -import org.labkey.api.query.ValidationException; -import org.labkey.api.reader.ColumnDescriptor; -import org.labkey.api.reader.DataLoader; -import org.labkey.api.reader.Readers; -import org.labkey.api.security.User; -import org.labkey.api.specimen.SpecimenEvent; -import org.labkey.api.specimen.SpecimenEventDateComparator; -import org.labkey.api.specimen.SpecimenEventManager; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.specimen.Vial; -import org.labkey.api.specimen.importer.EventVialRollup; -import org.labkey.api.specimen.importer.RollupInstance; -import org.labkey.api.specimen.location.LocationCache; -import org.labkey.api.specimen.location.LocationManager; -import org.labkey.api.specimen.model.SpecimenComment; -import org.labkey.api.study.Location; -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.StudyUtils; -import org.labkey.api.study.TimepointType; -import org.labkey.api.study.importer.ImportHelperService; -import org.labkey.api.study.importer.ImportHelperService.ParticipantIdTranslator; -import org.labkey.api.study.importer.ImportHelperService.SequenceNumTranslator; -import org.labkey.api.study.importer.SimpleStudyImportContext; -import org.labkey.api.study.model.VisitService; -import org.labkey.api.test.TestWhen; -import org.labkey.api.util.DateUtil; -import org.labkey.api.util.GUID; -import org.labkey.api.util.HeartBeat; -import org.labkey.api.util.JunitUtil; -import org.labkey.api.util.MultiPhaseCPUTimer; -import org.labkey.api.util.MultiPhaseCPUTimer.InvocationTimer.Order; -import org.labkey.api.util.Pair; -import org.labkey.api.util.ResultSetUtil; -import org.labkey.api.util.StringUtilsLabKey; -import org.labkey.api.util.TestContext; -import org.labkey.api.writer.VirtualFile; -import org.labkey.specimen.SpecimenColumns; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.SpecimenRequestManager; -import org.labkey.specimen.SpecimenTableManager; -import org.labkey.specimen.settings.SettingsManager; - -import java.io.BufferedReader; -import java.io.IOException; -import java.sql.SQLException; -import java.sql.Timestamp; -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.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.Callable; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import static org.labkey.specimen.SpecimenColumns.DRAW_TIMESTAMP; -import static org.labkey.specimen.SpecimenColumns.GLOBAL_UNIQUE_ID; -import static org.labkey.specimen.SpecimenColumns.GLOBAL_UNIQUE_ID_TSV_COL; -import static org.labkey.specimen.SpecimenColumns.LAB_ID; -import static org.labkey.specimen.SpecimenColumns.LAB_RECEIPT_DATE; -import static org.labkey.specimen.SpecimenColumns.SHIP_DATE; -import static org.labkey.specimen.SpecimenColumns.SITE_COLUMNS; -import static org.labkey.specimen.SpecimenColumns.SPEC_NUMBER_TSV_COL; -import static org.labkey.specimen.SpecimenColumns.STORAGE_DATE; -import static org.labkey.specimen.SpecimenColumns.VISIT_COL; -import static org.labkey.specimen.SpecimenColumns.VISIT_VALUE; - -/** - * User: brittp - * Date: Mar 13, 2006 - * Time: 2:18:48 - */ -@SuppressWarnings({"ConstantConditions"}) -public class SpecimenImporter extends SpecimenTableManager -{ - private enum ImportPhases {UpdateCommentSpecimenHashes, MarkOrphanedRequestVials, SetLockedInRequest, VialUpdatePreLoopPrep, - GetVialBatch, GetDateOrderedEvents, GetSpecimenComments, GetProcessingLocationId, GetFirstProcessedBy, GetCurrentLocationId, - CalculateLocation, GetLastEvent, DetermineUpdateVial, SetUpdateParameters, HandleComments, UpdateVials, UpdateComments, - UpdateSpecimenProcessingInfo, UpdateRequestability, UpdateVialCounts, ResyncStudy, SetLastSpecimenLoad, DropTempTable, - UpdateAllStatistics, CommitTransaction, ClearCaches, PopulateMaterials, PopulateSpecimens, PopulateVials, PopulateSpecimenEvents, - PopulateTempTable, PopulateLabs, SpecimenTypes, DeleteOldData, PrepareQcComments, NotifyChanged} - - private static final MultiPhaseCPUTimer TIMER = new MultiPhaseCPUTimer<>(ImportPhases.class, ImportPhases.values()); - - private static class SpecimenLoadInfo - { - private final TempTableInfo _tempTableInfo; - private final String _tempTableName; - private final List _availableColumns; - private final int _rowCount; - private final Container _container; - private final User _user; - private final DbSchema _schema; - - public SpecimenLoadInfo(User user, Container container, DbSchema schema, List availableColumns, int rowCount, TempTableInfo tempTableInfo) - { - _user = user; - _schema = schema; - _container = container; - _availableColumns = availableColumns; - _rowCount = rowCount; - _tempTableName = tempTableInfo.getSelectName(); - _tempTableInfo = tempTableInfo; - } - - // Number of rows inserted into the temp table - public int getRowCount() - { - return _rowCount; - } - - public List getAvailableColumns() - { - return _availableColumns; - } - - public String getTempTableName() - { - return _tempTableName; - } - - public Container getContainer() - { - return _container; - } - - public User getUser() - { - return _user; - } - - public DbSchema getSchema() - { - return _schema; - } - } - - private static final String GENERAL_JOB_STATUS_MSG = "PROCESSING SPECIMENS"; - - private static final SpecimenColumn DRAW_DATE = new SpecimenColumn("", "DrawDate", "DATE", TargetTable.SPECIMENS); - private static final SpecimenColumn DRAW_TIME = new SpecimenColumn("", "DrawTime", "TIME", TargetTable.SPECIMENS); - - private List _specimenCols; - private List _vialCols; - private List _vialEventCols; - private SQLFragment _specimenColsSql; - private SQLFragment _vialColsSql; - private SQLFragment _vialEventColsSql; - private Logger _logger; - private PipelineJob _job; - - protected int _generateGlobalUniqueIds = 0; - - private static final int SQL_BATCH_SIZE = 100; - - protected final SpecimenTableType _labsTableType; - protected final SpecimenTableType _additivesTableType; - protected final SpecimenTableType _derivativesTableType; - protected final SpecimenTableType _primaryTypesTableType; - protected final SpecimenTableType _specimensTableType; - - public @Nullable SpecimenTableType getForName(String name) - { - if (_labsTableType.getName().equalsIgnoreCase(name)) return _labsTableType; - if (_additivesTableType.getName().equalsIgnoreCase(name)) return _additivesTableType; - if (_derivativesTableType.getName().equalsIgnoreCase(name)) return _derivativesTableType; - if (_primaryTypesTableType.getName().equalsIgnoreCase(name)) return _primaryTypesTableType; - if ("specimens".equalsIgnoreCase(name)) return _specimensTableType; - return null; - } - - private MultiPhaseCPUTimer.InvocationTimer _iTimer; - - /** - * Constructor - * @param container import location - * @param user user whose permissions will be checked during import - */ - public SpecimenImporter(Container container, User user) - { - super(container, user); - - // The following could move to SpecimenTableManager, but they're only used by the importers - _specimensTableType = new SpecimenTableType("specimens", "study.Specimen", getSpecimenColumns()); - _labsTableType = new SpecimenTableType("labs", - SpecimenSchema.get().getTableInfoLocation(getContainer()).getSelectName(), SITE_COLUMNS); - _additivesTableType = new SpecimenTableType("additives", - SpecimenSchema.get().getTableInfoSpecimenAdditive(getContainer()).getSelectName(), SpecimenColumns.ADDITIVE_COLUMNS); - _derivativesTableType = new SpecimenTableType("derivatives", - SpecimenSchema.get().getTableInfoSpecimenDerivative(getContainer()).getSelectName(), SpecimenColumns.DERIVATIVE_COLUMNS); - _primaryTypesTableType = new SpecimenTableType("primary_types", - SpecimenSchema.get().getTableInfoSpecimenPrimaryType(getContainer()).getSelectName(), SpecimenColumns.PRIMARYTYPE_COLUMNS); - } - - private void resyncStudy(boolean syncParticipantVisit, boolean failForUndefinedVisits) throws ValidationException - { - TableInfo tableParticipant = SpecimenSchema.get().getTableInfoParticipant(); - TableInfo tableSpecimen = getTableInfoSpecimen(); - - executeSQL(tableParticipant.getSchema(), "INSERT INTO " + tableParticipant.getSelectName() + " (Container, ParticipantId)\n" + - "SELECT DISTINCT ?, ptid AS ParticipantId\n" + - "FROM " + tableSpecimen.getSelectName() + "\n" + - "WHERE ptid IS NOT NULL AND " + - "ptid NOT IN (SELECT ParticipantId FROM " + tableParticipant.getSelectName() + " WHERE Container = ?)", getContainer(), getContainer()); - - if (syncParticipantVisit) - { - Study study = StudyService.get().getStudy(getContainer()); - info("Updating study-wide subject/visit information..."); - ValidationException errors = VisitService.get().updateParticipantVisitsWithCohortUpdate(study, getUser(), failForUndefinedVisits, _logger); - if (errors.hasErrors()) - throw errors; - info("Subject/visit update complete."); - } - - info("Updating locations in use..."); - LocationManager.updateLocationTableInUse(getTableInfoLocation(), getContainer()); - } - - private void updateAllStatistics() - { - updateStatistics(ExperimentService.get().getTinfoMaterial()); - updateStatistics(getTableInfoSpecimen()); - updateStatistics(getTableInfoVial()); - updateStatistics(getTableInfoSpecimenEvent()); - } - - private boolean updateStatistics(TableInfo tinfo) - { - info("Updating statistics for " + tinfo + "..."); - boolean updated = tinfo.getSqlDialect().updateStatistics(tinfo); - if (updated) - info("Statistics update " + tinfo + " complete."); - else - info("Statistics update not supported for this database type."); - return updated; - } - - public void process(VirtualFile specimensDir, boolean merge, SimpleStudyImportContext ctx, @Nullable PipelineJob job, boolean syncParticipantVisit) - throws IOException, ValidationException - { - Map sifMap = populateFileMap(specimensDir, new HashMap<>()); - Study study = StudyService.get().getStudy(getContainer()); - process(sifMap, merge, ctx.getLogger(), job, syncParticipantVisit, false, ctx.isFailForUndefinedVisits() || study.isFailForUndefinedTimepoints()); - } - - protected void process(Map sifMap, boolean merge, Logger logger, @Nullable PipelineJob job, - boolean syncParticipantVisit, boolean editingSpecimens) - throws IOException, ValidationException - { - Study study = StudyService.get().getStudy(getContainer()); - process(sifMap, merge, logger, job, syncParticipantVisit, editingSpecimens, study.isFailForUndefinedTimepoints()); - } - - private void process(Map sifMap, boolean merge, Logger logger, @Nullable PipelineJob job, - boolean syncParticipantVisit, boolean editingSpecimens, boolean failForUndefinedVisits) - throws IOException, ValidationException - { - DbSchema schema = SpecimenSchema.get().getSchema(); - _logger = logger; - _job = job; - - DbScope scope = schema.getScope(); - boolean commitSuccessfully = false; - try (DbScope.Transaction transaction = scope.ensureTransaction()) - { - _iTimer = TIMER.getInvocationTimer(); - - setStatus(GENERAL_JOB_STATUS_MSG); - _iTimer.setPhase(ImportPhases.PopulateLabs); - if (null != sifMap.get(_labsTableType)) - { - try - { - mergeTable(schema, sifMap.get(_labsTableType), getTableInfoLocation(), true, true); - } - finally - { - LocationCache.clear(getContainer()); - } - } - - _iTimer.setPhase(ImportPhases.SpecimenTypes); - if (merge) - { - if (null != sifMap.get(_additivesTableType)) - mergeTable(schema, sifMap.get(_additivesTableType), getTableInfoAdditive(), false, true); - if (null != sifMap.get(_derivativesTableType)) - mergeTable(schema, sifMap.get(_derivativesTableType), getTableInfoDerivative(), false, true); - if (null != sifMap.get(_primaryTypesTableType)) - mergeTable(schema, sifMap.get(_primaryTypesTableType), getTableInfoPrimaryType(), false, true); - } - else - { - if (null != sifMap.get(_additivesTableType)) - replaceTable(schema, sifMap.get(_additivesTableType), getTableInfoAdditive(), false, true); - if (null != sifMap.get(_derivativesTableType)) - replaceTable(schema, sifMap.get(_derivativesTableType), getTableInfoDerivative(), false, true); - if (null != sifMap.get(_primaryTypesTableType)) - replaceTable(schema, sifMap.get(_primaryTypesTableType), getTableInfoPrimaryType(), false, true); - } - - // Specimen temp table must be populated AFTER the types tables have been reloaded, since the SpecimenHash - // calculated in the temp table relies on the new RowIds for the types: - setStatus(GENERAL_JOB_STATUS_MSG + " (temp table)"); - _iTimer.setPhase(ImportPhases.PopulateTempTable); - SpecimenImportFile specimenFile = sifMap.get(_specimensTableType); - SpecimenLoadInfo loadInfo = populateTempSpecimensTable(specimenFile, merge); - - // NOTE: if no rows were loaded in the temp table, don't remove existing materials/specimens/vials/events. - if (loadInfo.getRowCount() > 0) - populateSpecimenTables(loadInfo, merge); - else - info("Specimens: 0 rows found in input"); - - if (merge) - { - // Delete any orphaned specimen rows without vials - _iTimer.setPhase(ImportPhases.DeleteOldData); - executeSQL(SpecimenSchema.get().getSchema(), "DELETE FROM " + getTableInfoSpecimen().getSelectName() + - " WHERE RowId NOT IN (SELECT SpecimenId FROM " + getTableInfoVial().getSelectName() + ")"); - } - - // No need to setPhase() here... method sets timer phases immediately - updateCalculatedSpecimenData(merge, editingSpecimens); - - setStatus(GENERAL_JOB_STATUS_MSG + " (update study)"); - _iTimer.setPhase(ImportPhases.ResyncStudy); - resyncStudy(syncParticipantVisit, failForUndefinedVisits); - - ensureNotCanceled(); - _iTimer.setPhase(ImportPhases.SetLastSpecimenLoad); - // Set LastSpecimenLoad to now... we'll check this before snapshot study specimen refresh - StudyService.get().setLastSpecimenLoad( StudyService.get().getStudy(getContainer()), getUser(), new Date()); - - _iTimer.setPhase(ImportPhases.DropTempTable); - - // Drop the temp table within the transaction; otherwise, we may get a different connection object, - // where the table is no longer available. Note that this means that the temp table will stick around - // if an exception is thrown during loading, but this is probably okay- the DB will clean it up eventually. - loadInfo._tempTableInfo.delete(); - - _iTimer.setPhase(ImportPhases.UpdateAllStatistics); - updateAllStatistics(); - - // notify listeners that specimens have changed in this container - setStatus(GENERAL_JOB_STATUS_MSG); - _iTimer.setPhase(ImportPhases.NotifyChanged); - SpecimenService.get().fireSpecimensChanged(getContainer(), getUser(), _logger); - - _iTimer.setPhase(ImportPhases.CommitTransaction); - transaction.commit(); - commitSuccessfully = true; - } - finally - { - try - { - if (commitSuccessfully) - { - _iTimer.setPhase(ImportPhases.ClearCaches); - StudyInternalService.get().clearCaches(getContainer()); - SpecimenRequestManager.get().clearCaches(getContainer()); - - info(_iTimer.getTimings("Timings for each phase of this import are listed below:", Order.HighToLow, "|")); - } - } - finally - { - TIMER.releaseInvocationTimer(_iTimer); - } - } - } - - private SpecimenLoadInfo populateTempSpecimensTable(SpecimenImportFile file, boolean merge) throws IOException, ValidationException - { - TempTablesHolder tempTablesHolder = createTempTable(); - - Pair, Integer> pair = populateTempTable(tempTablesHolder, file, merge); - List columns = pair.first; - Integer rowCount = pair.second; - - tempTablesHolder.getCreateIndexes().run(); - - if (tempTablesHolder.getSelectInsertTempTableInfo().isTracking()) // If no specimens we will not have created this table - tempTablesHolder.getSelectInsertTempTableInfo().delete(); - - return new SpecimenLoadInfo(getUser(), getContainer(), DbSchema.getTemp(), columns, rowCount, tempTablesHolder.getTempTableInfo()); - } - - private void populateSpecimenTables(SpecimenLoadInfo info, boolean merge) throws ValidationException - { - setStatus(GENERAL_JOB_STATUS_MSG + " (populate tables)"); - _iTimer.setPhase(ImportPhases.DeleteOldData); - if (!merge) - { -// SimpleFilter containerFilter = SimpleFilter.createContainerFilter(info.getContainer()); - info("Deleting old data from SpecimenEvent, Vial and Specimen tables..."); - SQLFragment sql = new SQLFragment("TRUNCATE ") - .append(getTableInfoSpecimenEvent().getSelectName()) - .append(", ") - .append(getTableInfoVial().getSelectName()) - .append(", ") - .append(getTableInfoSpecimen().getSelectName()); - - executeSQL(getTableInfoSpecimen().getSchema(), sql); - info("Complete."); - } - - boolean seenVisitValue = false; - for (SpecimenColumn col : info.getAvailableColumns()) - { - if (VISIT_VALUE.getDbColumnName().equalsIgnoreCase(col.getDbColumnName())) - { - seenVisitValue = true; - break; - } - } - - ensureNotCanceled(); - _iTimer.setPhase(ImportPhases.PopulateMaterials); - populateMaterials(info, merge); - ensureNotCanceled(); - _iTimer.setPhase(ImportPhases.PopulateSpecimens); - populateSpecimens(info, merge, seenVisitValue); - ensureNotCanceled(); - _iTimer.setPhase(ImportPhases.PopulateVials); - populateVials(info, merge, seenVisitValue); - ensureNotCanceled(); - _iTimer.setPhase(ImportPhases.PopulateSpecimenEvents); - populateSpecimenEvents(info, merge); - } - - private Set getConflictingEventColumns(List events) - { - if (events.size() <= 1) - return Collections.emptySet(); - Set conflicts = new HashSet<>(); - - for (SpecimenColumn col : _specimenColumns) - { - if (col.getAggregateEventFunction() == null && col.getTargetTable() == TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS) - { - // lower the case of the first character: - String propName = col.getDbColumnName().substring(0, 1).toLowerCase() + col.getDbColumnName().substring(1); - for (int i = 0; i < events.size() - 1; i++) - { - SpecimenEvent event = events.get(i); - SpecimenEvent nextEvent = events.get(i + 1); - Object currentValue = event.get(propName); - Object nextValue = nextEvent.get(propName); - if (!Objects.equals(currentValue, nextValue)) - { - if (propName.equalsIgnoreCase("drawtimestamp")) - { - Object currentDateOnlyValue = DateUtil.getDateOnly((Date) currentValue); - Object nextDateOnlyValue = DateUtil.getDateOnly((Date) nextValue); - if (!Objects.equals(currentDateOnlyValue, nextDateOnlyValue)) - conflicts.add(DRAW_DATE.getDbColumnName()); - Object currentTimeOnlyValue = DateUtil.getTimeOnly((Date) currentValue); - Object nextTimeOnlyValue = DateUtil.getTimeOnly((Date) nextValue); - if (!Objects.equals(currentTimeOnlyValue, nextTimeOnlyValue)) - conflicts.add(DRAW_TIME.getDbColumnName()); - } - else - { - conflicts.add(col.getDbColumnName()); - } - } - } - } - } - - return conflicts; - } - - private void clearConflictingVialColumns(Vial vial, Set conflicts) - { - SQLFragment sql = new SQLFragment() - .append("UPDATE ") - .append(getTableInfoVial().getSelectName()) - .append(" SET\n "); - - boolean hasConflict = false; - String sep = ""; - for (SpecimenColumn col : _specimenColumns) - { - if (col.getAggregateEventFunction() == null && col.getTargetTable().isVials() && !col.isUnique()) - { - if (conflicts.contains(col.getDbColumnName())) - { - hasConflict = true; - sql - .append(sep) - .append(col.getDbColumnName()).append(" = NULL"); - sep = ",\n "; - } - } - } - - if (!hasConflict) - return; - - sql - .append("\nWHERE GlobalUniqueId = ?") - .add(vial.getGlobalUniqueId()); - - new SqlExecutor(SpecimenSchema.get().getSchema()).execute(sql); - } - - private void updateCommentSpecimenHashes() - { - TableInfo commentTable = SpecimenSchema.get().getTableInfoSpecimenComment(); - String commentTableSelectName = commentTable.getSelectName(); - TableInfo vialTable = getTableInfoVial(); - String vialTableSelectName = vialTable.getSelectName(); - - SQLFragment sql = new SQLFragment() - .append("UPDATE ") - .append(commentTableSelectName) - .append(" SET SpecimenHash = (\n") - .append("SELECT SpecimenHash FROM ") - .append(vialTableSelectName).append(" WHERE ") - .append(vialTable.getColumn("GlobalUniqueId").getValueSql(vialTableSelectName)) - .append(" = ") - .append(commentTable.getColumn("GlobalUniqueId").getValueSql(commentTableSelectName)) - .append(")\nWHERE ") - .append(commentTable.getColumn("Container").getValueSql(commentTableSelectName)) - .append(" = ?") - .add(getContainer().getId()); - - info("Updating hash codes for existing comments..."); - new SqlExecutor(SpecimenSchema.get().getSchema()).execute(sql); - info("Complete."); - } - - private void prepareQCComments() - { - StringBuilder columnList = new StringBuilder(); - columnList.append("VialId"); - for (SpecimenColumn col : _specimenColumns) - { - if (col.getTargetTable() == TargetTable.SPECIMENS_AND_SPECIMEN_EVENTS && col.getAggregateEventFunction() == null) - { - columnList.append(",\n "); - columnList.append(col.getDbColumnName()); - } - } - - // find the global unique ID for those vials with conflicts: - TableInfo specimenEventTable = getTableInfoSpecimenEvent(); - SQLFragment conflictedGUIDs = new SQLFragment("SELECT GlobalUniqueId FROM ") - .append(getTableInfoVial(), "vial") - .append(" WHERE RowId IN (\n") - .append("SELECT VialId FROM\n") - .append("(SELECT DISTINCT\n") - .append(columnList).append("\nFROM ") - .append(specimenEventTable.getSelectName()) - .append("\nWHERE Obsolete = ") - .append(specimenEventTable.getSqlDialect().getBooleanFALSE()) - .append("\nGROUP BY\n") - .append(columnList) - .append(") ") - .append("AS DupCheckView\nGROUP BY VialId HAVING Count(VialId) > 1") - .append("\n)"); - - // Delete comments that were holding QC state (and nothing more) for vials that do not currently have any conflicts - SQLFragment deleteClearedVials = new SQLFragment("DELETE FROM study.SpecimenComment WHERE Container = ? ") - .add(getContainer().getId()) - .append("AND Comment IS NULL AND QualityControlFlag = ? ") - .add(Boolean.TRUE) - .append("AND QualityControlFlagForced = ? ") - .add(Boolean.FALSE) - .append("AND GlobalUniqueId NOT IN (") - .append(conflictedGUIDs) - .append(")"); - info("Clearing QC flags for vials that no longer have history conflicts..."); - new SqlExecutor(SpecimenSchema.get().getSchema()).execute(deleteClearedVials); - info("Complete."); - - - // Insert placeholder comments for newly discovered QC problems; SpecimenHash will be updated within updateCalculatedSpecimenData, so this - // doesn't have to be set here. - SQLFragment insertPlaceholderQCComments = new SQLFragment("INSERT INTO study.SpecimenComment ") - .append("(GlobalUniqueId, Container, QualityControlFlag, QualityControlFlagForced, Created, CreatedBy, Modified, ModifiedBy) ") - .append("SELECT GlobalUniqueId, ?, ?, ?, ?, ?, ?, ? ") - .add(getContainer().getId()) - .add(Boolean.TRUE) - .add(Boolean.FALSE) - .add(new Date()) - .add(getUser().getUserId()) - .add(new Date()) - .add(getUser().getUserId()) - .append(" FROM (\n").append(conflictedGUIDs).append(") ConflictedVials\n") - .append("WHERE GlobalUniqueId NOT IN ") - .append("(SELECT GlobalUniqueId FROM study.SpecimenComment WHERE Container = ?)") - .add(getContainer().getId()); - info("Setting QC flags for vials that have new history conflicts..."); - new SqlExecutor(SpecimenSchema.get().getSchema()).execute(insertPlaceholderQCComments); - info("Complete."); - } - - private void markOrphanedRequestVials() - { - // Mark those global unique IDs that are in requests but are no longer found in the vial table: - TableInfo vialTable = getTableInfoVial(); - String vialTableSelectName = vialTable.getSelectName(); - SQLFragment orphanMarkerSql = new SQLFragment() - .append("UPDATE study.SampleRequestSpecimen SET Orphaned = ? WHERE RowId IN (\n") - .add(Boolean.TRUE) - .append("\tSELECT study.SampleRequestSpecimen.RowId FROM study.SampleRequestSpecimen\n") - .append("\tLEFT OUTER JOIN ") - .append(vialTableSelectName) - .append(" ON\n\t\t") - .append(vialTable.getColumn("GlobalUniqueId").getValueSql(vialTableSelectName)) - .append(" = study.SampleRequestSpecimen.SpecimenGlobalUniqueId\n") - .append("\tWHERE ") - .append(vialTable.getColumn("GlobalUniqueId").getValueSql(vialTableSelectName)) - .append(" IS NULL AND\n") - .append("\t\tstudy.SampleRequestSpecimen.Container = ?)") - .add(getContainer().getId()); - info("Marking requested vials that have been orphaned..."); - - SqlExecutor executor = new SqlExecutor(SpecimenSchema.get().getSchema()); - executor.execute(orphanMarkerSql); - info("Complete."); - - // un-mark those global unique IDs that were previously marked as orphaned but are now found in the vial table: - SQLFragment deorphanMarkerSql = new SQLFragment() - .append("UPDATE study.SampleRequestSpecimen SET Orphaned = ? WHERE RowId IN (\n") - .add(Boolean.FALSE) - .append("\tSELECT study.SampleRequestSpecimen.RowId FROM study.SampleRequestSpecimen\n") - .append("\tLEFT OUTER JOIN ") - .append(vialTableSelectName) - .append(" ON\n\t\t") - .append(vialTable.getColumn("GlobalUniqueId").getValueSql(vialTableSelectName)) - .append(" = study.SampleRequestSpecimen.SpecimenGlobalUniqueId\n") - .append("\tWHERE ") - .append(vialTable.getColumn("GlobalUniqueId").getValueSql(vialTableSelectName)) - .append(" IS NOT NULL AND\n") - .append("\t\tstudy.SampleRequestSpecimen.Orphaned = ? AND\n") - .add(Boolean.TRUE) - .append("\t\tstudy.SampleRequestSpecimen.Container = ?)") - .add(getContainer().getId()); - info("Marking requested vials that have been de-orphaned..."); - executor.execute(deorphanMarkerSql); - info("Complete."); - } - - private void setLockedInRequestStatus() - { - TableInfo vialTable = getTableInfoVial(); - SqlDialect dialect = vialTable.getSqlDialect(); - String vialTableSelectName = vialTable.getSelectName(); - SQLFragment lockedInRequestSql = new SQLFragment("UPDATE ").append(vialTableSelectName).append( - " SET LockedInRequest = ").appendValue(Boolean.TRUE, dialect).append(" WHERE RowId IN (SELECT ").append(vialTable.getColumn("RowId").getValueSql(vialTableSelectName)) - .append(" FROM ").append(vialTableSelectName).append(", study.LockedSpecimens " + - "WHERE study.LockedSpecimens.Container = ").appendValue(getContainer(), dialect).append(" AND ") - .append(vialTable.getColumn("GlobalUniqueId").getValueSql(vialTableSelectName)).append(" = study.LockedSpecimens.GlobalUniqueId)"); - - info("Setting Specimen Locked in Request status..."); - new SqlExecutor(SpecimenSchema.get().getSchema()).execute(lockedInRequestSql); - info("Complete."); - } - - private void updateSpecimenProcessingInfo() - { - TableInfo specimenTable = getTableInfoSpecimen(); - String specimenTableSelectName = specimenTable.getSelectName(); - TableInfo vialTable = getTableInfoVial(); - String vialTableSelectName = vialTable.getSelectName(); - SQLFragment sql = new SQLFragment("UPDATE ").append(specimenTableSelectName).append(" SET ProcessingLocation = (\n" + - "\tSELECT MAX(ProcessingLocation) AS ProcessingLocation FROM \n" + - "\t\t(SELECT DISTINCT SpecimenId, ProcessingLocation FROM ").append(vialTableSelectName).append( - " WHERE SpecimenId = ").append(specimenTable.getColumn("RowId").getValueSql(specimenTableSelectName)).append(") Locations\n" + - "\tGROUP BY SpecimenId\n" + - "\tHAVING COUNT(ProcessingLocation) = 1\n" + - ")"); - info("Updating processing locations on the specimen table..."); - new SqlExecutor(SpecimenSchema.get().getSchema()).execute(sql); - info("Complete."); - - sql = new SQLFragment("UPDATE ").append(specimenTableSelectName).append(" SET FirstProcessedByInitials = (\n" + - "\tSELECT MAX(FirstProcessedByInitials) AS FirstProcessedByInitials FROM \n" + - "\t\t(SELECT DISTINCT SpecimenId, FirstProcessedByInitials FROM ").append(vialTableSelectName).append( - " WHERE SpecimenId = ").append(specimenTable.getColumn("RowId").getValueSql(specimenTableSelectName)).append(") Locations\n" + - "\tGROUP BY SpecimenId\n" + - "\tHAVING COUNT(FirstProcessedByInitials) = 1\n" + - ")"); - info("Updating first processed by initials on the specimen table..."); - new SqlExecutor(SpecimenSchema.get().getSchema()).execute(sql); - info("Complete."); - } - - private static final int CURRENT_SITE_UPDATE_SIZE = 1000; - private static final int CURRENT_SITE_UPDATE_LOGGING_SIZE = 10000; // Can choose to log at a less frequent rate than the update batch size - - // UNDONE: add vials in-clause to only update data for rows that changed - private void updateCalculatedSpecimenData(final boolean merge, final boolean editingSpecimens) - { - setStatus(GENERAL_JOB_STATUS_MSG + " (update)"); - _iTimer.setPhase(ImportPhases.PrepareQcComments); - // delete unnecessary comments and create placeholders for newly discovered errors: - prepareQCComments(); - - _iTimer.setPhase(ImportPhases.UpdateCommentSpecimenHashes); - updateCommentSpecimenHashes(); - - _iTimer.setPhase(ImportPhases.MarkOrphanedRequestVials); - markOrphanedRequestVials(); - _iTimer.setPhase(ImportPhases.SetLockedInRequest); - setLockedInRequestStatus(); - - _iTimer.setPhase(ImportPhases.VialUpdatePreLoopPrep); - // clear caches before determining current sites: - SpecimenRequestManager.get().clearCaches(getContainer()); - final Map siteMap = new IntHashMap<>(); - - TableInfo vialTable = getTableInfoVial(); - SQLFragment vialPropertiesSql = new SQLFragment("UPDATE ").append(vialTable.getSelectName()) - .append(" SET CurrentLocation = CAST(? AS INTEGER), ProcessingLocation = CAST(? AS INTEGER), FirstProcessedByInitials = ?, AtRepository = ?, LatestComments = ?, LatestQualityComments = ? "); - - for (List> rollupList : getEventToVialRollups().values()) - { - for (RollupInstance rollup : rollupList) - { - String colName = rollup.first; - ColumnInfo column = vialTable.getColumn(colName); - if (null == column) - throw new IllegalStateException("Expected Vial table column to exist."); - vialPropertiesSql.append(", ").appendIdentifier(column.getSelectIdentifier()).append(" = ") - .append(JdbcType.VARCHAR.equals(column.getJdbcType()) ? "?" : "CAST(? AS " + vialTable.getSqlDialect().getSqlCastTypeName(column.getJdbcType()) + ")"); - } - } - - vialPropertiesSql.append(" WHERE RowId = ?"); - - _iTimer.setPhase(ImportPhases.HandleComments); - - TableInfo commentTable = SpecimenSchema.get().getTableInfoSpecimenComment(); - final String updateCommentSql = "UPDATE " + commentTable + " SET QualityControlComments = ? WHERE GlobalUniqueId = ?"; - - // Populate a GlobalUniqueId -> SpecimenEvent map containing all quality control vial comments in this container - final Map qcCommentMap = new HashMap<>(); - - SQLFragment selectCommentsSql = new SQLFragment() - .append("SELECT c.* FROM ") - .append(commentTable, "c") - .append(" INNER JOIN ") - .append(getTableInfoVial(), "v") - .append(" ON c.GlobalUniqueId = v.GlobalUniqueId WHERE Container = ? AND (QualityControlFlag = ? OR QualityControlFlagForced = ?)") - .add(getContainer()) - .add(true) - .add(true); - - new SqlSelector(SpecimenSchema.get().getSchema(), selectCommentsSql).forEach(SpecimenComment.class, comment -> qcCommentMap.put(comment.getGlobalUniqueId(), comment)); - -// if (!merge) -// new SpecimenTablesProvider(getContainer(), getUser(), null).dropTableIndices(SpecimenTablesProvider.VIAL_TABLENAME); - - // TODO: Select only required subset of Event and Vial columns? - _iTimer.setPhase(ImportPhases.GetDateOrderedEvents); - - TableSelector eventSelector = new TableSelector(getTableInfoSpecimenEvent(), new SimpleFilter(FieldKey.fromString("Obsolete"), false), new Sort("VialId")); - - try (Results eventResults = eventSelector.getResults(false)) - { - final MutableInt rowCount = new MutableInt(); - final MarkableIterator> eventIterator = new MarkableIterator<>(eventResults.iterator()); - - _iTimer.setPhase(ImportPhases.GetVialBatch); - TableSelector vialSelector = new TableSelector(getTableInfoVial(), null, new Sort("RowId")); - - vialSelector.forEachMapBatch(CURRENT_SITE_UPDATE_SIZE, vialBatch -> { - int count = rowCount.intValue(); - if (count % CURRENT_SITE_UPDATE_LOGGING_SIZE == 0) - info("Updating vial rows " + (count + 1) + " through " + (count + CURRENT_SITE_UPDATE_LOGGING_SIZE) + "."); - - setStatus(GENERAL_JOB_STATUS_MSG + " (update vials)"); - - final List vials = new ArrayList<>(CURRENT_SITE_UPDATE_SIZE); - - for (Map map : vialBatch) - vials.add(new Vial(getContainer(), map)); - - List> vialPropertiesParams = new ArrayList<>(CURRENT_SITE_UPDATE_SIZE); - List> commentParams = new ArrayList<>(); - - for (Vial vial : vials) - { - long vialId = vial.getRowId(); - - _iTimer.setPhase(ImportPhases.GetDateOrderedEvents); - List dateOrderedEvents = new ArrayList<>(); - while (eventIterator.hasNext()) - { - eventIterator.mark(); - Map map = eventIterator.next(); - - if (vialId == (Long) map.get("VialId")) - { - dateOrderedEvents.add(new SpecimenEvent(getContainer(), map)); - } - else - { - eventIterator.reset(); - break; - } - } - dateOrderedEvents.sort(SpecimenEventDateComparator.get()); - - _iTimer.setPhase(ImportPhases.GetProcessingLocationId); - Integer processingLocation = LocationManager.get().getProcessingLocationId(dateOrderedEvents); - _iTimer.setPhase(ImportPhases.GetFirstProcessedBy); - String firstProcessedByInitials = SpecimenManager.get().getFirstProcessedByInitials(dateOrderedEvents); - _iTimer.setPhase(ImportPhases.GetCurrentLocationId); - Integer currentLocation = LocationManager.get().getCurrentLocationId(dateOrderedEvents); - - _iTimer.setPhase(ImportPhases.CalculateLocation); - boolean atRepository = false; - - if (currentLocation != null) - { - Location location; - - if (!siteMap.containsKey(currentLocation)) - { - location = LocationManager.get().getLocation(getContainer(), currentLocation); - if (location != null) - siteMap.put(currentLocation, location); - } - else - { - location = siteMap.get(currentLocation); - } - - if (location != null) - atRepository = location.isRepository() != null && location.isRepository(); - } - - // All of the additional fields (deviationCodes, Concentration, Integrity, Yield, Ratio, QualityComments, Comments) always take the latest value - _iTimer.setPhase(ImportPhases.GetLastEvent); - SpecimenEvent lastEvent = SpecimenEventManager.get().getLastEvent(dateOrderedEvents); - if (null == lastEvent) - throw new IllegalStateException("There should always be at least 1 event."); - - _iTimer.setPhase(ImportPhases.DetermineUpdateVial); - boolean updateVial = false; - List params = new ArrayList<>(); - - if (!Objects.equals(currentLocation, vial.getCurrentLocation()) || - !Objects.equals(processingLocation, vial.getProcessingLocation()) || - !Objects.equals(firstProcessedByInitials, vial.getFirstProcessedByInitials()) || - atRepository != vial.isAtRepository() || - !Objects.equals(vial.getLatestComments(), lastEvent.getComments()) || - !Objects.equals(vial.getLatestQualityComments(), lastEvent.getQualityComments())) - { - updateVial = true; // Something is different - } - - if (!updateVial) - { - for (Map.Entry>> rollupEntry : getEventToVialRollups().entrySet()) - { - String eventColName = rollupEntry.getKey(); - ColumnInfo column = getTableInfoSpecimenEvent().getColumn(eventColName); - if (null == column) - throw new IllegalStateException("Expected Specimen Event table column to exist."); - var eventColSelectName = column.getSelectIdentifier(); - for (RollupInstance rollupItem : rollupEntry.getValue()) - { - String vialColName = rollupItem.first; - Object rollupResult = rollupItem.second.getRollupResult(dateOrderedEvents, eventColSelectName.getId(), - rollupItem.getFromType(), rollupItem.getToType()); - if (!Objects.equals(vial.get(vialColName), rollupResult)) - { - updateVial = true; // Something is different - break; - } - } - if (updateVial) - break; - } - } - - _iTimer.setPhase(ImportPhases.SetUpdateParameters); - if (updateVial) - { - // Something is different; update everything - params.add(currentLocation); - params.add(processingLocation); - params.add(firstProcessedByInitials); - params.add(atRepository); - params.add(lastEvent.getComments()); - params.add(lastEvent.getQualityComments()); - - for (Map.Entry>> rollupEntry : getEventToVialRollups().entrySet()) - { - String eventColName = rollupEntry.getKey(); - ColumnInfo column = getTableInfoSpecimenEvent().getColumn(eventColName); - if (null == column) - throw new IllegalStateException("Expected Specimen Event table column to exist."); - var eventColAlias = column.getAlias(); // Use alias since we're looking up in the rowMap - for (RollupInstance rollupItem : rollupEntry.getValue()) - { - Object rollupResult = rollupItem.second.getRollupResult(dateOrderedEvents, eventColAlias.getId(), - rollupItem.getFromType(), rollupItem.getToType()); - params.add(rollupResult); - } - } - - params.add(vial.getRowId()); - vialPropertiesParams.add(params); - } - - _iTimer.setPhase(ImportPhases.HandleComments); - SpecimenComment comment = qcCommentMap.get(vial.getGlobalUniqueId()); - - if (comment != null) - { - // if we have a comment, it may be because we're in a bad QC state. If so, we should update - // the reason for the QC problem. - String message = null; - - Set conflicts = getConflictingEventColumns(dateOrderedEvents); - - if (!conflicts.isEmpty()) - { - // Null out conflicting Vial columns - if (merge) - { - // NOTE: in checkForConflictingSpecimens() we check the imported specimen columns used - // to generate the specimen hash are not in conflict so we shouldn't need to clear any - // columns on the specimen table. Vial columns are not part of the specimen hash and - // can safely be cleared without compromising the specimen hash. - clearConflictingVialColumns(vial, conflicts); - } - - String sep = ""; - message = "Conflicts found: "; - for (String conflict : conflicts) - { - message += sep + conflict; - sep = ", "; - } - } - - commentParams.add(Arrays.asList(message, vial.getGlobalUniqueId())); - } - } - - _iTimer.setPhase(ImportPhases.UpdateVials); - if (!vialPropertiesParams.isEmpty()) - { - assert vialPropertiesSql.getParams().isEmpty(); - Table.batchExecute(SpecimenSchema.get().getSchema(), vialPropertiesSql.getRawSQL(), vialPropertiesParams); - } - - _iTimer.setPhase(ImportPhases.UpdateComments); - if (!commentParams.isEmpty()) - Table.batchExecute(SpecimenSchema.get().getSchema(), updateCommentSql, commentParams); - - rowCount.add(CURRENT_SITE_UPDATE_SIZE); - _iTimer.setPhase(ImportPhases.GetVialBatch); - }); - } - catch (SQLException e) - { - throw new RuntimeSQLException(e); - } - - // finally, after all other data has been updated, we can update our cached specimen counts and processing locations: - setStatus(GENERAL_JOB_STATUS_MSG + " (update counts)"); - _iTimer.setPhase(ImportPhases.UpdateSpecimenProcessingInfo); - updateSpecimenProcessingInfo(); - - _iTimer.setPhase(ImportPhases.UpdateRequestability); - try - { - RequestabilityManager.getInstance().updateRequestability(getContainer(), getUser(), false, editingSpecimens, _logger); - } - catch (RequestabilityManager.InvalidRuleException e) - { - throw new IllegalStateException("One or more requestability rules is invalid. Please remove or correct the invalid rule.", e); - } - - _iTimer.setPhase(ImportPhases.UpdateVialCounts); - info("Updating cached vial counts..."); - - SpecimenRequestManager.get().updateVialCounts(getContainer(), getUser()); - - info("Vial count update complete."); - } - - private Map populateFileMap(VirtualFile dir, Map fileNameMap) throws IOException - { - for (String dirName : dir.listDirs()) - { - populateFileMap(dir.getDir(dirName), fileNameMap); - } - - for (String fileName : dir.list()) - { - if (!fileName.toLowerCase().endsWith(".tsv")) - continue; - - try (BufferedReader reader = Readers.getReader(dir.getInputStream(fileName))) - { - String line = reader.readLine(); - if (null == line) - continue; - line = StringUtils.trimToEmpty(line); - if (!line.startsWith("#")) - throw new IllegalStateException("Import files are expected to start with a comment indicating table name"); - - String canonicalName = line.substring(1).trim().toLowerCase(); - SpecimenTableType type = getForName(canonicalName); - - if (null != type) - fileNameMap.put(type, getSpecimenImportFile(dir, fileName, type)); - } - } - - return fileNameMap; - } - - // TODO: Pass in merge (or import strategy)? - private SpecimenImportFile getSpecimenImportFile(VirtualFile dir, String fileName, SpecimenTableType type) - { - return new FileSystemSpecimenImportFile(dir, fileName, type); - } - - private void info(String message) - { - if (_logger != null) - _logger.info(message); - } - - private void debug(CharSequence message) - { - //noinspection PointlessBooleanExpression - if (DEBUG && _logger != null) - _logger.debug(message); - } - - private static String _currentStatus = GENERAL_JOB_STATUS_MSG; - private void setStatus(@Nullable String status) - { - if (null != _job) - { - _currentStatus = status; - _job.setStatus(_currentStatus); - } - } - - private void ensureNotCanceled() - { - if (null != _job) - _job.setStatus(_currentStatus); // Will throw if job has been canceled - } - - private List getSpecimenCols(List availableColumns) - { - if (_specimenCols == null) - { - List cols = new ArrayList<>(availableColumns.size()); - for (SpecimenColumn col : availableColumns) - { - if (col.getTargetTable().isSpecimens()) - cols.add(col); - } - _specimenCols = cols; - } - return _specimenCols; - } - - private SQLFragment getSpecimenColsSql(List availableColumns, boolean seenVisitValue) - { - if (_specimenColsSql == null) - { - String sep = ""; - SQLFragment cols = new SQLFragment(); - for (SpecimenColumn col : getSpecimenCols(availableColumns)) - { - cols.append(sep).appendIdentifier(col.getLegalDbColumnName(getSqlDialect())); - sep = ",\n "; - } - if (!seenVisitValue) - cols.append(sep).append(VISIT_VALUE.getDbColumnName()); - _specimenColsSql = cols; - } - return _specimenColsSql; - } - - private List getVialCols(List availableColumns) - { - if (_vialCols == null) - { - List cols = new ArrayList<>(availableColumns.size()); - for (SpecimenColumn col : availableColumns) - { - if (col.getTargetTable().isVials()) - cols.add(col); - } - _vialCols = cols; - } - return _vialCols; - } - - private SQLFragment getVialColsSql(List availableColumns) - { - if (_vialColsSql == null) - { - String sep = ""; - SQLFragment cols = new SQLFragment(); - for (SpecimenColumn col : getVialCols(availableColumns)) - { - cols.append(sep).appendIdentifier(col.getLegalDbColumnName(getSqlDialect())); - sep = ",\n "; - } - _vialColsSql = cols; - } - return _vialColsSql; - } - - private List getSpecimenEventCols(List availableColumns) - { - if (_vialEventCols == null) - { - List cols = new ArrayList<>(availableColumns.size()); - for (SpecimenColumn col : availableColumns) - { - if (col.getTargetTable().isEvents()) - cols.add(col); - } - _vialEventCols = cols; - } - return _vialEventCols; - } - - private SQLFragment getSpecimenEventColsSql(List availableColumns) - { - if (_vialEventColsSql == null) - { - String sep = ""; - SQLFragment cols = new SQLFragment(); - for (SpecimenColumn col : getSpecimenEventCols(availableColumns)) - { - cols.append(sep).appendIdentifier(col.getLegalDbColumnName(getSqlDialect())); - sep = ",\n "; - } - _vialEventColsSql = cols; - } - return _vialEventColsSql; - } - - private void populateMaterials(SpecimenLoadInfo info, boolean merge) - { - String columnName = null; - - for (SpecimenColumn specimenColumn : info.getAvailableColumns()) - { - if (GLOBAL_UNIQUE_ID_TSV_COL.equals(specimenColumn.getPrimaryTsvColumnName())) - { - columnName = specimenColumn.getDbColumnName(); - break; - } - } - - if (columnName == null) - { - for (SpecimenColumn specimenColumn : info.getAvailableColumns()) - { - if (SPEC_NUMBER_TSV_COL.equals(specimenColumn.getPrimaryTsvColumnName())) - { - columnName = specimenColumn.getDbColumnName(); - break; - } - } - } - - if (columnName == null) - { - throw new IllegalStateException("Could not find a unique specimen identifier column. Either \"" + GLOBAL_UNIQUE_ID_TSV_COL - + "\" or \"" + SPEC_NUMBER_TSV_COL + "\" must be present in the set of specimen columns."); - } - - String insertObjectSQL = "INSERT INTO exp.Object (ObjectURI, Container) \n" + - "SELECT DISTINCT T.LSID AS ObjectURI, ? AS Container\n" + - "FROM " + info.getTempTableName() + " T LEFT OUTER JOIN exp.Object O ON T.LSID = O.ObjectURI\n" + - "WHERE O.ObjectURI IS NULL\n"; - - String countMaterialToInsertSQL = "SELECT DISTINCT T.LSID\n" + - "FROM " + info.getTempTableName() + " T LEFT OUTER JOIN exp.Material M ON T.LSID = M.LSID\n" + - "WHERE M.LSID IS NULL\n"; - - String insertMaterialSQL = "INSERT INTO exp.Material (RowId, LSID, rootMaterialRowId, Name, ObjectId, Container, CpasType, Created) \n" + - "SELECT ? + (ROW_NUMBER() OVER (ORDER BY ObjectId)), LSID, ? + (ROW_NUMBER() OVER (ORDER BY ObjectId)), Name, ObjectId, Container, CpasType, Created FROM\n" + - "(SELECT DISTINCT T.LSID, T." + columnName + " AS Name, (SELECT ObjectId FROM exp.Object O where O.ObjectURI=T.LSID) AS ObjectId, ? AS Container, ? AS CpasType, CAST(? AS " + getSqlDialect().getDefaultDateTimeDataType()+ ") AS Created\n" + - " FROM " + info.getTempTableName() + " T LEFT OUTER JOIN exp.Material M ON T.LSID = M.LSID\n" + - " WHERE M.LSID IS NULL) X\n"; - - /* NOTE: Not really necessary to delete and recreate the object rows - String deleteObjectSQL = "DELETE FROM exp.Object WHERE ObjectURI IN (SELECT M.LSID FROM exp.Material M\n" + - "LEFT OUTER JOIN " + info.getTempTableName() + " T ON M.LSID = T.LSID\n" + - "LEFT OUTER JOIN exp.MaterialInput MI ON M.RowId = MI.MaterialId\n" + - "WHERE T.LSID IS NULL\n" + - "AND MI.MaterialId IS NULL\n" + - "AND M.CpasType IN (?, '" + StudyService.SPECIMEN_NAMESPACE_PREFIX + "') \n" + - "AND M.Container = ?)"; - */ - - String deleteMaterialSQL = "DELETE FROM exp.Material WHERE RowId IN (SELECT M.RowId FROM exp.Material M\n" + - "LEFT OUTER JOIN " + info.getTempTableName() + " T ON M.LSID = T.LSID\n" + - "LEFT OUTER JOIN exp.MaterialInput MI ON M.RowId = MI.MaterialId\n" + - "WHERE T.LSID IS NULL\n" + - "AND MI.MaterialId IS NULL\n" + - "AND M.CpasType IN (?, '" + StudyService.SPECIMEN_NAMESPACE_PREFIX + "') \n" + - "AND M.Container = ?)"; - - String prefix = new Lsid(StudyService.SPECIMEN_NAMESPACE_PREFIX, "Folder-" + info.getContainer().getRowId(), "").toString(); - String cpasType; - ExpSampleType sampleType = SampleTypeService.get().getSampleType(info.getContainer(), SpecimenService.SAMPLE_TYPE_NAME); - - if (sampleType == null) - { - ExpSampleType source = SampleTypeService.get().createSampleType(); - source.setContainer(info.getContainer()); - source.setMaterialLSIDPrefix(prefix); - source.setName(SpecimenService.SAMPLE_TYPE_NAME); - source.setLSID(SampleTypeService.get().getSampleTypeLsid(SpecimenService.SAMPLE_TYPE_NAME, info.getContainer()).toString()); - source.setDescription("Study specimens for " + info.getContainer().getPath()); - source.save(null); - cpasType = source.getLSID(); - } - else - { - cpasType = sampleType.getLSID(); - } - - var createdTimestamp = new Parameter.TypedValue(new Timestamp(System.currentTimeMillis()), JdbcType.TIMESTAMP); - - int affected; - if (!merge) - { - info("exp.Material: Deleting entries for removed specimens..."); - SQLFragment deleteFragment = new SQLFragment(deleteMaterialSQL, cpasType, info.getContainer().getId()); - if (DEBUG) - logSQLFragment(deleteFragment); - affected = executeSQL(info.getSchema(), deleteFragment); - if (affected >= 0) - info("exp.Material: " + affected + " rows removed."); - } - - // NOTE: No need to update existing Materials when merging -- just insert any new materials not found. - info("exp.Material: Inserting new entries from temp table..."); - SQLFragment insertObjectFragment = new SQLFragment(insertObjectSQL, info.getContainer().getId()); - if (DEBUG) - logSQLFragment(insertObjectFragment); - executeSQL(info.getSchema(), insertObjectFragment); - - long count = new SqlSelector(info.getSchema(), countMaterialToInsertSQL).getRowCount(); - if (count > 0) - { - // reserve rowids for this insert - // NOTE QuerySchema exp.Materials (plural) has dbsequence info, but DbSchema exp.Material (singular) does not - TableInfo material = DefaultSchema.get(info.getUser(), info.getContainer(),"exp").getTable("Materials", null); - ColumnInfo rowId = material.getColumn("RowId"); - DbSequence seq = DbSequenceManager.getPreallocatingSequence(rowId.getDbSequenceContainer(null), material.getDbSequenceName(rowId.getName()), 0, 1000); - - /* HACK, we don't do this anywhere else, but we need to get a block of continuous IDS to make this work */ - // row_number() starts at 1 so subtract 1 - long start = DbSequenceManager.reserveSequentialBlock(seq, count); - start -= 1; - - SQLFragment insertMaterialFragment = new SQLFragment(insertMaterialSQL, start, start, info.getContainer().getId(), cpasType, createdTimestamp); - if (DEBUG) - logSQLFragment(insertMaterialFragment); - affected = executeSQL(info.getSchema(), insertMaterialFragment); - if (affected >= 0) - info("exp.Material: " + affected + " rows inserted."); - } - - info("exp.Material: Update complete."); - } - - private SQLFragment getSpecimenEventTempTableColumns(SpecimenLoadInfo info) - { - SQLFragment columnList = new SQLFragment(); - String prefix = ""; - for (SpecimenColumn col : getSpecimenEventCols(info.getAvailableColumns())) - { - columnList.append(prefix); - prefix = ", "; - columnList.append("\n ").append(info.getTempTableName()).append(".").appendIdentifier(col.getLegalDbColumnName(getSqlDialect())); - } - return columnList; - } - - // NOTE: In merge case, we've already checked the specimen hash columns are not in conflict. - private void appendConflictResolvingSQL(SqlDialect dialect, SQLFragment sql, SpecimenColumn col, String tempTableName, - @Nullable SpecimenColumn castColumn) - { - // If castColumn no null, then we still count col, but then cast col's value to castColumn's type and name it castColumn's name - SQLFragment selectCol = new SQLFragment(tempTableName).append(".").appendIdentifier(col.getLegalDbColumnName(getSqlDialect())); - - if (col.getAggregateEventFunction() != null) - { - sql.append(col.getAggregateEventFunction()).append("(").append(selectCol).append(")"); - } - else - { - sql.append("CASE WHEN"); - if (col.getJavaClass().equals(Boolean.class)) - { - // gross nested calls to cast the boolean to an int, get its min, then cast back to a boolean. - // this is needed because most aggregates don't work on boolean values. - sql.append(" COUNT(DISTINCT ").append(selectCol).append(") = 1 THEN "); - sql.append("CAST(MIN(CAST(").append(selectCol).append(" AS INTEGER)) AS ").append(dialect.getBooleanDataType()).append(")"); - } - else - { - if (null != castColumn) - { - sql.append(" COUNT(DISTINCT ").append(tempTableName).append(".").appendIdentifier(castColumn.getLegalDbColumnName(getSqlDialect())).append(") = 1 THEN "); - sql.append("CAST(MIN(").append(selectCol).append(") AS ").append(castColumn.getDbType()).append(")"); - } - else - { - sql.append(" COUNT(DISTINCT ").append(selectCol).append(") = 1 THEN "); - sql.append("MIN(").append(selectCol).append(")"); - } - } - sql.append(" ELSE NULL END"); - } - sql.append(" AS "); - - if (null != castColumn) - sql.appendIdentifier(castColumn.getLegalDbColumnName(getSqlDialect())); - else - sql.appendIdentifier(col.getLegalDbColumnName(getSqlDialect())); - } - - - private void populateSpecimens(SpecimenLoadInfo info, boolean merge, boolean seenVisitValue) throws ValidationException - { - String participantSequenceNumExpr = StudyUtils.getParticipantSequenceNumExpr(info._schema, "PTID", "VisitValue"); - - SQLFragment insertSelectSql = new SQLFragment() - .append("SELECT ") - .append(participantSequenceNumExpr) - .append(" AS ParticipantSequenceNum") - .append(", SpecimenHash, ") - .append(DRAW_DATE.getDbColumnName()) - .append(", ") - .append(DRAW_TIME.getDbColumnName()) - .append(", ") - .append(getSpecimenColsSql(info.getAvailableColumns(), seenVisitValue)) - .append(" FROM (\n") - .append(getVialListFromTempTableSql(info, true, seenVisitValue)) - .append(") VialList\n") - .append("GROUP BY ") - .append("SpecimenHash, ") - .append(getSpecimenColsSql(info.getAvailableColumns(), seenVisitValue)) - .append(", ") - .append(DRAW_DATE.getDbColumnName()) - .append(", ") - .append(DRAW_TIME.getDbColumnName()); - - TableInfo specimenTable = getTableInfoSpecimen(); - String specimenTableSelectName = specimenTable.getSelectName(); - if (merge) - { - // Create list of specimen columns, including unique columns not found in SPECIMEN_COLUMNS. - Set cols = new LinkedHashSet<>(); - cols.add(new SpecimenColumn("SpecimenHash", "SpecimenHash", "VARCHAR(256)", TargetTable.SPECIMENS, true)); - cols.add(new SpecimenColumn("ParticipantSequenceNum", "ParticipantSequenceNum", "VARCHAR(200)", TargetTable.SPECIMENS, false)); - cols.add(DRAW_DATE); - cols.add(DRAW_TIME); - cols.addAll(getSpecimenCols(info.getAvailableColumns())); - - // Insert or update the specimens from in the temp table - try (TableResultSet rs = new SqlSelector(info.getSchema(), insertSelectSql).getResultSet()) - { - if (VERBOSE_DEBUG) - ResultSetUtil.logData(rs, _logger); - DataIteratorBuilder dib = new DataIteratorBuilder.Wrapper(new ResultsImpl(rs)); - mergeTable(info.getSchema(), specimenTableSelectName, specimenTable, cols, dib, false, false); - } - catch (SQLException e) - { - throw new RuntimeSQLException(e); - } - } - else - { - // Insert all specimens from in the temp table. - SQLFragment insertSql = new SQLFragment() - .append("INSERT INTO ") - .append(specimenTableSelectName) - .append("\n(") - .append("ParticipantSequenceNum, SpecimenHash, ") - .append(DRAW_DATE.getDbColumnName()) - .append(", ") - .append(DRAW_TIME.getDbColumnName()) - .append(", ") - .append(getSpecimenColsSql(info.getAvailableColumns(), seenVisitValue)) - .append(")\n") - .append(insertSelectSql); - - if (DEBUG) - logSQLFragment(insertSql); - - info("Specimens: Inserting new rows..."); - executeSQL(info.getSchema(), insertSql); - info("Specimens: Insert complete."); - } - } - - private SQLFragment getVialListFromTempTableSql(SpecimenLoadInfo info, boolean forSpecimenTable, boolean seenVisitValue) - { - String prefix = ""; - SQLFragment vialListSql = new SQLFragment() - .append("SELECT "); - if (!forSpecimenTable) - { - vialListSql - .append(info.getTempTableName()) - .append(".LSID AS LSID"); - prefix = ",\n "; - } - vialListSql - .append(prefix) - .append("SpecimenHash"); - prefix = ",\n "; - for (SpecimenColumn col : info.getAvailableColumns()) - { - if ((col.getTargetTable().isVials() || col.getTargetTable().isSpecimens()) && - (!forSpecimenTable || !GLOBAL_UNIQUE_ID.getDbColumnName().equalsIgnoreCase(col.getDbColumnName()))) - { - vialListSql.append(prefix); - appendConflictResolvingSQL(info.getSchema().getSqlDialect(), vialListSql, col, info.getTempTableName(), null); - } - } - if (!seenVisitValue) - { - vialListSql - .append(prefix) - .append("0.0 AS ") - .append(VISIT_VALUE.getDbColumnName()); - } - - // DrawDate and DrawTime are a little different; - // we need to do the conflict count on DrawTimeStamp and then cast to Date or Time - vialListSql.append(prefix); - appendConflictResolvingSQL(info.getSchema().getSqlDialect(), vialListSql, DRAW_TIMESTAMP, info.getTempTableName(), DRAW_DATE); - vialListSql.append(prefix); - appendConflictResolvingSQL(info.getSchema().getSqlDialect(), vialListSql, DRAW_TIMESTAMP, info.getTempTableName(), DRAW_TIME); - - vialListSql - .append("\nFROM ") - .append(info.getTempTableName()) - .append("\nGROUP BY\n"); - if (!forSpecimenTable) - vialListSql.append(info.getTempTableName()).append(".LSID,\n "); - vialListSql.append(info.getTempTableName()).append(".SpecimenHash"); - if (!forSpecimenTable) - vialListSql.append(",\n ").append(info.getTempTableName()).append(".GlobalUniqueId"); - - return vialListSql; - } - - private void populateVials(SpecimenLoadInfo info, boolean merge, boolean seenVisitValue) throws ValidationException - { - TableInfo specimenTable = getTableInfoSpecimen(); - String specimenTableSelectName = specimenTable.getSelectName(); - TableInfo vialTable = getTableInfoVial(); - String vialTableSelectName = vialTable.getSelectName(); - - String prefix = ",\n "; - SQLFragment insertSelectSql = new SQLFragment() - .append("SELECT exp.Material.RowId") - .append(prefix) - .append(specimenTable.getColumn("RowId").getValueSql(specimenTableSelectName)) - .append(" AS SpecimenId") - .append(prefix) - .append(specimenTable.getColumn("SpecimenHash").getValueSql(specimenTableSelectName)) - .append(prefix) - // Set a default value of true for the 'Available' column: - .append("? AS Available") - .add(Boolean.TRUE); - - for (SpecimenColumn col : getVialCols(info.getAvailableColumns())) - insertSelectSql.append(prefix).append("VialList.").appendIdentifier(col.getLegalDbColumnName(getSqlDialect())); - - insertSelectSql - .append(" FROM (").append(getVialListFromTempTableSql(info, false, seenVisitValue)) - .append(") VialList") - // join to material: - .append("\n JOIN exp.Material ON (") - .append("VialList.LSID = exp.Material.LSID") - .append(" AND exp.Material.Container = ?)") - .add(info.getContainer().getId()) - // join to specimen: - .append("\n JOIN ") - .append(specimenTableSelectName) - .append(" ON ") - .append(specimenTable.getColumn("SpecimenHash").getValueSql(specimenTableSelectName)) - .append(" = VialList.SpecimenHash"); - - if (merge) - { - // Create list of vial columns, including unique columns not found in SPECIMEN_COLUMNS. - Set cols = new LinkedHashSet<>(); - // NOTE: study.Vial.RowId is actually an FK to exp.Material.RowId - cols.add(GLOBAL_UNIQUE_ID); - cols.add(new SpecimenColumn("RowId", "RowId", "INT NOT NULL", TargetTable.VIALS, false)); - cols.add(new SpecimenColumn("SpecimenId", "SpecimenId", "INT NOT NULL", TargetTable.VIALS, false)); - cols.add(new SpecimenColumn("SpecimenHash", "SpecimenHash", "VARCHAR(256)", TargetTable.VIALS, false)); - cols.add(new SpecimenColumn("Available", "Available", ImportTypes.BOOLEAN_TYPE, TargetTable.VIALS, false)); - cols.addAll(getVialCols(info.getAvailableColumns())); - - // Insert or update the vials from in the temp table. - try (TableResultSet rs = new SqlSelector(info.getSchema(), insertSelectSql).getResultSet()) - { - if (VERBOSE_DEBUG) - ResultSetUtil.logData(rs, _logger); - DataIteratorBuilder dib = new DataIteratorBuilder.Wrapper(new ResultsImpl(rs)); - mergeTable(info.getSchema(), vialTableSelectName, vialTable, cols, dib, false, false); - } - catch (SQLException e) - { - throw new RuntimeSQLException(e); - } - } - else - { - // Insert all vials from in the temp table. - SQLFragment insertSql = new SQLFragment() - .append("INSERT INTO ") - .append(vialTableSelectName) - .append("\n(RowId, SpecimenId, SpecimenHash, Available, ") - .append(getVialColsSql(info.getAvailableColumns())) - .append(")\n") - .append(insertSelectSql); - - if (DEBUG) - logSQLFragment(insertSql); - - info("Vials: Inserting new rows..."); - executeSQL(info.getSchema(), insertSql); - info("Vials: Insert complete."); - } - } - - private void logSQLFragment(SQLFragment sql) - { - info(sql.getSQL()); - info("Params: "); - for (Object param : sql.getParams()) - info(param.toString()); - } - - private void populateSpecimenEvents(SpecimenLoadInfo info, boolean merge) throws ValidationException - { - TableInfo specimenEventTable = getTableInfoSpecimenEvent(); - String specimenEventTableSelectName = specimenEventTable.getSelectName(); - TableInfo vialTable = getTableInfoVial(); - String vialTableSelectName = vialTable.getSelectName(); - - SQLFragment insertSelectSql = new SQLFragment() - .append("SELECT ") - .append(vialTable.getColumn("RowId").getValueSql(vialTableSelectName)) - .append(" AS VialId, \n") - .append(getSpecimenEventTempTableColumns(info)) - .append(" FROM ") - .append(info.getTempTableName()) - .append("\nJOIN ") - .append(vialTableSelectName) - .append(" ON ") - .append(info.getTempTableName()) - .append(".GlobalUniqueId = ") - .append(vialTable.getColumn("GlobalUniqueId").getValueSql(vialTableSelectName)); - - if (merge) - { - // Create list of vial columns, including unique columns not found in SPECIMEN_COLUMNS. - // Events are special in that we want to merge based on a pseudo-unique set of columns: - // Container, VialId (vial.GlobalUniqueId), LabId, StorageDate, ShipDate, LabReceiptDate - // We need to always add these extra columns, even if they aren't in the list of available columns. - Set cols = new LinkedHashSet<>(); - cols.add(new SpecimenColumn("VialId", "VialId", "INT NOT NULL", TargetTable.SPECIMEN_EVENTS, true)); - cols.add(LAB_ID); - cols.add(SHIP_DATE); - cols.add(STORAGE_DATE); - cols.add(LAB_RECEIPT_DATE); - - cols.addAll(getSpecimenEventCols(info.getAvailableColumns())); - - // Insert or update the vials from in the temp table. - try (TableResultSet rs = new SqlSelector(info.getSchema(), insertSelectSql).getResultSet()) - { - if (VERBOSE_DEBUG) - ResultSetUtil.logData(rs, _logger); - DataIteratorBuilder dib = new DataIteratorBuilder.Wrapper(new ResultsImpl(rs)); - mergeTable(info.getSchema(), specimenEventTableSelectName, specimenEventTable, cols, dib, false, false); - } - catch (SQLException e) - { - throw new RuntimeSQLException(e); - } - } - else - { - // Insert all events from the temp table - SQLFragment insertSql = new SQLFragment() - .append("INSERT INTO ") - .append(specimenEventTableSelectName) - .append("\n") - .append("(VialId, ") - .append(getSpecimenEventColsSql(info.getAvailableColumns())) - .append(")\n") - .append(insertSelectSql); - - if (DEBUG) - logSQLFragment(insertSql); - - info("Specimen Events: Inserting new rows."); - executeSQL(info.getSchema(), insertSql); - info("Specimen Events: Insert complete."); - } - } - - private interface ComputedColumn - { - String getName(); - Object getValue(Map row) throws ValidationException; - } - - private static class EntityIdComputedColumn implements ComputedColumn - { - @Override - public String getName() { return "EntityId"; } - @Override - public Object getValue(Map row) { return GUID.makeGUID(); } - } - - private Pair, Integer> mergeTable( - DbSchema schema, String tableName, @Nullable TableInfo target, - Collection potentialColumns, DataIteratorBuilder values, boolean addEntityId, boolean hasContainerColumn) - throws ValidationException - { - ComputedColumn entityIdCol = null; - if (addEntityId) - { - entityIdCol = new EntityIdComputedColumn(); - } - - return mergeTable(schema, tableName, target, potentialColumns, values, entityIdCol, hasContainerColumn); - } - - private void mergeTable(DbSchema schema, SpecimenImportFile file, TableInfo target, boolean addEntityId, boolean hasContainerColumn) - throws ValidationException, IOException - { - SpecimenTableType type = file.getTableType(); - - ComputedColumn entityIdCol = null; - - if (addEntityId) - { - entityIdCol = new EntityIdComputedColumn(); - } - - try (DataLoader loader = loadTsv(file)) - { - mergeTable(schema, type.getTableName(), target, type.getColumns(), loader, entityIdCol, hasContainerColumn); - } - } - - /** - * Insert or update rows on the target table using the unique columns of potentialColumns - * to identify the existing row. - * - * NOTE: The idCol is used only during insert -- the value won't be updated if the row already exists. - * - * @param schema The dbschema. - * @param idCol The computed column. - * @return A pair of the columns actually found in the data values and a total row count. - */ - private Pair, Integer> mergeTable( - DbSchema schema, String tableName, @Nullable TableInfo target, - Collection potentialColumns, DataIteratorBuilder values, - ComputedColumn idCol, boolean hasContainerColumn) - throws ValidationException - { - // tests SpecimenTest, LuminexUploadAndCopyTest, VaccineProtocolTest, FlowSpecimenTest, SpecimenImportTest, CreateVialsTest, ViabilityTest - if (values == null) - { - info(tableName + ": No rows to merge"); - return new Pair<>(Collections.emptyList(), 0); - } - - if (null == target) - { - target = schema.getTable(tableName.substring(tableName.indexOf('.') + 1)); - if (null == target) - throw new IllegalArgumentException("tablename: " + tableName); - } - - // get the iter 'early' so we can look at the columns - - DataIteratorContext dix = new DataIteratorContext(); - dix.setInsertOption(QueryUpdateService.InsertOption.MERGE); - DataIterator iter = values.getDataIterator(dix); - - CaseInsensitiveHashSet tsvColumnNames = new CaseInsensitiveHashSet(); - for (int i=1 ; i<= iter.getColumnCount() ; i++) - tsvColumnNames.add(iter.getColumnInfo(i).getName()); - - List availableColumns = new ArrayList<>(); - CaseInsensitiveHashSet skipColumns = new CaseInsensitiveHashSet(target.getColumnNameSet()); - CaseInsensitiveHashSet keyColumns = new CaseInsensitiveHashSet(); - - CaseInsensitiveHashSet dontUpdate = new CaseInsensitiveHashSet(); - if (null != idCol) - dontUpdate.add(idCol.getName()); - dontUpdate.add("entityid"); - skipColumns.remove("entityid"); - - // NOTE entityid is handled by DataIterator so ignore EntityIdComputedColumn - if (idCol instanceof EntityIdComputedColumn) - idCol = null; - - for (T column : potentialColumns) - { - if (tsvColumnNames.contains(column.getPrimaryTsvColumnName()) || tsvColumnNames.contains(column.getDbColumnName())) - { - availableColumns.add(column); - skipColumns.remove(column.getDbColumnName()); - } - } - - for (T col : availableColumns) - { - if (col.isUnique()) - keyColumns.add(col.getDbColumnName()); - } - if (hasContainerColumn) - { - keyColumns.add("Container"); - skipColumns.remove("Container"); - } - if (idCol != null) - skipColumns.remove(idCol.getName()); - if (keyColumns.isEmpty()) - keyColumns = null; - - DataIteratorBuilder specimenIter = new SpecimenImportBuilder(new DataIteratorBuilder.Wrapper(iter), potentialColumns, Collections.singletonList(idCol)); - DataIteratorBuilder std = StandardDataIteratorBuilder.forInsert(target, specimenIter, getContainer(), getUser(), dix); - DataIteratorBuilder tableIter = new TableInsertDataIteratorBuilder(std, target, getContainer()) - .setKeyColumns(keyColumns) - .setAddlSkipColumns(skipColumns) - .setDontUpdate(dontUpdate); - - assert !_specimensTableType.getTableName().equalsIgnoreCase(tableName); - info(tableName + ": Starting merge of data..."); - - Pump pump = new Pump(tableIter, dix); - pump.run(); - if (dix.getErrors().hasErrors()) - throw dix.getErrors().getLastRowError(); - int rowCount = pump.getRowCount(); - - info(tableName + "updated or inserted " + rowCount + " rows of data"); - return new Pair<>(availableColumns, rowCount); - } - - private void replaceTable(DbSchema schema, SpecimenImportFile file, TableInfo target, boolean addEntityId, boolean hasContainerColumn) - throws IOException, ValidationException - { - ComputedColumn entityIdCol = null; - if (addEntityId) - { - entityIdCol = new EntityIdComputedColumn(); - } - - replaceTable(schema, file, file.getTableType().getTableName(), target, false, hasContainerColumn, null, null, entityIdCol); - } - - /** - * Deletes the target table and inserts new rows. - * - * @param schema The dbschema - * @param file SpecimenImportFile - * @param tableName Fully qualified table name, e.g., "study.Vials" - * @param generateGlobaluniqueIds Generate globalUniqueIds if any needed - * @param drawTime DrawTime column or null - * @return A pair of the columns actually found in the data values and a total row count. - */ - public Pair, Integer> replaceTable( - DbSchema schema, SpecimenImportFile file, String tableName, @Nullable TableInfo target, - boolean generateGlobaluniqueIds, boolean hasContainerColumn, ComputedColumn drawDate, ComputedColumn drawTime, - ComputedColumn... computedColumnsAddl) - throws IOException, ValidationException - { - if (file == null) - { - info(tableName + ": No rows to replace"); - return new Pair<>(Collections.emptyList(), 0); - } - - ensureNotCanceled(); - info(tableName + ": Starting replacement of all data..."); - - assert !_specimensTableType.getTableName().equalsIgnoreCase(tableName); - if (hasContainerColumn) - executeSQL(schema, "DELETE FROM " + tableName + " WHERE Container = ?", getContainer().getId()); - else - executeSQL(schema, "DELETE FROM " + tableName); - - ArrayList computedColumns = new ArrayList<>(); - if (null != drawDate) - computedColumns.add(drawDate); - if (null != drawTime) - computedColumns.add(drawTime); - computedColumns.addAll(Arrays.asList(computedColumnsAddl)); - - final List newUniqueIds = (generateGlobaluniqueIds && _generateGlobalUniqueIds > 0) ? - getValidGlobalUniqueIds(_generateGlobalUniqueIds) : null; - - if (null != newUniqueIds) - { - computedColumns.add(new ComputedColumn() - { - int idCount = 0; - - @Override - public String getName() - { - return GLOBAL_UNIQUE_ID_TSV_COL; - } - - @Override - public Object getValue(Map row) - { - Object uniqueid = row.get(GLOBAL_UNIQUE_ID_TSV_COL); - if (null == uniqueid) - uniqueid = newUniqueIds.get(idCount++); - return uniqueid; - } - }); - } - - int rowCount; - ColumnDescriptor[] tsvColumns; - - if (null == target) - { - String dbname = tableName; - if (dbname.startsWith(schema.getName() + ".")) - dbname = dbname.substring(schema.getName().length() + 1); - target = schema.getTable(dbname); - } - if (null == target) - throw new IllegalStateException("Could not resolve table: " + tableName); - - DataIteratorContext dix = new DataIteratorContext(); - dix.setInsertOption(QueryUpdateService.InsertOption.IMPORT); - DataLoader tsv = loadTsv(file); - tsvColumns = tsv.getColumns(); - -/* // DEBUG: Dump data - StringBuilder infoCol = new StringBuilder(""); - for (ColumnDescriptor cd : tsvColumns) - infoCol.append(cd.getColumnName() + ", "); - info(infoCol.toString()); - String[][] lines = tsv.getFirstNLines(15); - for (String[] line : lines) - { - StringBuilder infoRow = new StringBuilder(""); - for (String item : line) - infoRow.append(item + ", "); - info(infoRow.toString()); - } - info(""); -*/ - // CONSIDER turn off data conversion - //for (ColumnDescriptor cd : tsvColumns) cd.clazz = String.class; - // CONSIDER use AsyncDataIterator - //DataIteratorBuilder asyncIn = new AsyncDataIterator.Builder(tsv); - DataIteratorBuilder asyncIn = tsv; - DataIteratorBuilder specimenWrapped = new SpecimenImportBuilder(asyncIn, file.getTableType().getColumns(), computedColumns); - DataIteratorBuilder standardEtl = StandardDataIteratorBuilder.forInsert(target, specimenWrapped, getContainer(), getUser(), dix); - DataIteratorBuilder persist = ((UpdateableTableInfo)target).persistRows(standardEtl, dix); - Pump pump = new Pump(persist, dix); - pump.setProgress(new ImportProgress() - { - long heartBeat = HeartBeat.currentTimeMillis(); - - @Override - public void setTotalRows(int rows) - { - } - - @Override - public void setCurrentRow(int currentRow) - { - if (0 == currentRow % SQL_BATCH_SIZE) - { - if (0 == currentRow % (SQL_BATCH_SIZE*100)) - info(currentRow + " rows loaded..."); - long hb = HeartBeat.currentTimeMillis(); - if (hb == heartBeat) - return; - ensureNotCanceled(); - heartBeat = hb; - } - } - }); - pump.run(); - if (dix.getErrors().hasErrors()) - { - var last = dix.getErrors().getLastRowError(); - var vex = new ValidationException(last.getMessage() + " (File: " + file.getTableType().getName() + ")") - { - @Override - public synchronized Throwable getCause() - { - return last; - } - }; - } - rowCount = pump.getRowCount(); - - info(tableName + ": Replaced all data with " + rowCount + " new rows."); - - // Note: this duplicates the logic in SpecimenImportIterator (just below). Keep these code paths in sync. - Map importMap = (Map)createImportMap(file.getTableType().getColumns()); - final var seen = new HashSet(); - List availableColumns = Arrays.stream(tsvColumns).map(tsv2 -> importMap.get(tsv2.getColumnName())) - .filter(Objects::nonNull) - .filter(cd -> seen.add(cd.getPrimaryTsvColumnName())) - .collect(Collectors.toList()); - return new Pair<>(availableColumns, rowCount); - } - - static Map createImportMap(Collection importColumns) - { - var map = new CaseInsensitiveHashMap(); - for (var c : importColumns) - map.put(c.getDbColumnName(), c); - for (var c : importColumns) - c.getImportAliases().forEach(n -> map.put(n, c)); - for (var c : importColumns) - if (StringUtils.isNotBlank(c.getPrimaryTsvColumnName())) - map.put(c.getPrimaryTsvColumnName(), c); - return map; - } - - - private class SpecimenImportBuilder implements DataIteratorBuilder - { - private final DataIteratorBuilder dib; - private final Collection importColumns; - private final List computedColumns; - - SpecimenImportBuilder(DataIteratorBuilder in, Collection importColumns, List computedColumns) - { - dib = in; - this.importColumns = importColumns; - this.computedColumns = computedColumns; - } - - @Override - public DataIterator getDataIterator(final DataIteratorContext context) - { - DataIterator in = dib.getDataIterator(context); - DataIterator aliased = LoggingDataIterator.wrap(createAliasDataIterator(this, in, context)); - return LoggingDataIterator.wrap(new SpecimenImportIterator(this, DataIteratorUtil.wrapMap(aliased, false), context)); - } - } - - // TODO We should consider trying to let the Standard DataIterator "import alias" replace some of this ImportableColumn behavior - // TODO that might let us switch SpecimenImportBuilder to after StandardDataIteratorBuilder instead of before - // TODO Also, this would allow us to _not_ have TsvLoader do type conversion. see loadTsv(). - DataIterator createAliasDataIterator(SpecimenImportBuilder sib, DataIterator in, DataIteratorContext context) - { - var importMap = createImportMap(sib.importColumns); - ArrayList names = new ArrayList<>(in.getColumnCount()+1); - names.add(null); - for (int i=1 ; i<=in.getColumnCount() ; i++) - { - ImportableColumn c = importMap.get(in.getColumnInfo(i).getName()); - String alias = c == null ? null : c.getPrimaryTsvColumnName(); - if (StringUtils.isBlank(alias)) - alias = null; - names.add(alias); - } - return AliasDataIterator.wrap(in, context, names); - } - - public static class AliasDataIterator extends SimpleTranslator - { - static DataIterator wrap(DataIterator in, DataIteratorContext context, List names) - { - boolean hasAlias = false; - for (int i=1 ; i<=in.getColumnCount() ; i++) - { - String name = i < names.size() ? names.get(i) : null; - hasAlias |= name != null && !name.equals(in.getColumnInfo(i).getName()); - } - if (!hasAlias) - return in; - return new AliasDataIterator(in, context, names); - } - - /** names should be one based so they match the column indexes */ - AliasDataIterator(DataIterator in, DataIteratorContext context, List names) - { - super(in, context); - - for (int i=1 ; i<=in.getColumnCount() ; i++) - { - String name = i < names.size() ? names.get(i) : null; - if (null != name) - addColumn(name, i); - else - addColumn(i); - } - } - } - - - // TODO StandardDataIteratorBuilder should be enforcing max length - private class SpecimenImportIterator extends SimpleTranslator - { - private Map _rowMap; - - SpecimenImportIterator(SpecimenImportBuilder sib, MapDataIterator in, DataIteratorContext context) - { - super(in, context); - - SqlDialect d = DbSchema.getTemp().getSqlDialect(); - - CaseInsensitiveHashSet tsvColumnNames = new CaseInsensitiveHashSet(); - for (int i=1 ; i<= in.getColumnCount() ; i++) - tsvColumnNames.add(in.getColumnInfo(i).getName()); - - // deal with computedColumns that might mask importColumns - CaseInsensitiveHashSet seen = new CaseInsensitiveHashSet(); - - for (final ComputedColumn cc : sib.computedColumns) - { - if (null != cc && seen.add(cc.getName())) - { - ColumnInfo col = new BaseColumnInfo(cc.getName(), JdbcType.OTHER); - Callable call = () -> { - Object computedValue = cc.getValue(_rowMap); - if (computedValue instanceof Parameter.TypedValue) - return ((Parameter.TypedValue) computedValue).getJdbcParameterValue(); - else - return computedValue; - }; - addColumn(col, call); - } - } - - for (final ImportableColumn ic : sib.importColumns) - { - if (seen.add(ic.getLegalDbColumnName(d).getId())) - { - String boundInputColumnName = null; - if (tsvColumnNames.contains(ic.getPrimaryTsvColumnName())) - boundInputColumnName = ic.getPrimaryTsvColumnName(); - final String name = boundInputColumnName; - ColumnInfo col = new BaseColumnInfo(ic.getLegalDbColumnName(d).getId(), ic.getJdbcType()); - Supplier call = () -> { - Object ret = null; - if (null != name) - ret = _rowMap.get(name); - if (null == ret) - ret = ic.getDefaultValue(); - - if (ic.getMaxSize() >= 0 && ret instanceof String) - { - if (((String)ret).length() > ic.getMaxSize()) - { - @SuppressWarnings("ThrowableNotThrown") - var rowError = getRowError(); - rowError.addFieldError(ic.getPrimaryTsvColumnName(), "Value \"" + ret + "\" is too long for column " + - ic.getDbColumnName() + ". The maximum allowable length is " + ic.getMaxSize() + "."); - } - } - - return ret; - }; - addColumn(col, call); - } - } - } - - @Override - protected void processNextInput() - { - _rowMap = ((MapDataIterator)getInput()).getMap(); - } - } - - private DataLoader loadTsv(@NotNull SpecimenImportFile importFile) throws IOException - { - assert null != importFile; - - SpecimenTableType type = importFile.getTableType(); - String tableName = type.getTableName(); - - info(tableName + ": Parsing data file for table..."); - - var expectedColumns = createImportMap(type.getColumns()); - - DataLoader loader = importFile.getDataLoader(); - - for (ColumnDescriptor column : loader.getColumns()) - { - var expectedColumn = expectedColumns.get(column.name.toLowerCase()); - - if (expectedColumn != null) - { - column.clazz = expectedColumn.getJavaClass(); - if (VISIT_COL.equals(column.name)) - column.clazz = String.class; - } - else - { - column.load = false; - } - } - - return loader; - } - - private Pair, Integer> populateTempTable(TempTablesHolder tempTablesHolder, SpecimenImportFile file, boolean merge) - throws IOException, ValidationException - { - info("Populating specimen temp table..."); - TempTableInfo tempTableInfo = tempTablesHolder.getTempTableInfo(); - int rowCount; - List loadedColumns = new ArrayList<>(); - - ComputedColumn lsidCol = new ComputedColumn() - { - @Override - public String getName() { return "LSID"; } - @Override - public Object getValue(Map row) throws ValidationException - { - String id = (String) row.get(GLOBAL_UNIQUE_ID_TSV_COL); - if (id == null) - id = (String) row.get(SPEC_NUMBER_TSV_COL); - - if (id == null) - { - throw new ValidationException("GlobalUniqueId is required but was not supplied"); - } - - Lsid lsid = SpecimenService.get().getSpecimenMaterialLsid(getContainer(), id); - return lsid.toString(); - } - }; - - // remove VISIT_COL since that's a computed column - // 1) should that be removed from SPECIMEN_COLUMNS? - // 2) convert this to DataIterator? - SpecimenColumn _visitCol = null; - SpecimenColumn _participantIdCol = null; - for (SpecimenColumn sc : _specimenColumns) - { - if ("VisitValue".equals(sc.getDbColumnName())) - _visitCol = sc; - else if ("Ptid".equals(sc.getDbColumnName())) - _participantIdCol = sc; - } - - final Study study = StudyService.get().getStudy(getContainer()); - final ImportHelperService ihs = ImportHelperService.get(); - final SequenceNumTranslator snt = ihs.getSequenceNumTranslator(study); - final ParticipantIdTranslator pit = ihs.getParticipantIdTranslator(study, getUser()); - final SpecimenColumn visitCol = _visitCol; - final SpecimenColumn dateCol = DRAW_TIMESTAMP; - final SpecimenColumn participantIdCol = _participantIdCol; - final Parameter.TypedValue nullDouble = Parameter.nullParameter(JdbcType.DOUBLE); - - ComputedColumn computedParticipantIdCol = new ComputedColumn() - { - @Override - public String getName() - { - return participantIdCol.getDbColumnName(); - } - - @Override - public Object getValue(Map row) throws ValidationException - { - Object p = SpecimenImporter.this.getValue(participantIdCol, row); - return pit.translateParticipantId(p); - } - }; - - ComputedColumn sequencenumCol = new ComputedColumn() - { - @Override - public String getName() - { - return visitCol.getDbColumnName(); - } - - @Override - public Object getValue(Map row) throws ValidationException - { - Object s = SpecimenImporter.this.getValue(visitCol, row); - Object d = SpecimenImporter.this.getValue(dateCol, row); - Double sequencenum = snt.translateSequenceNum(s, d); -// if (sequencenum == null) -// throw new org.apache.commons.beanutils.ConversionException("No visit_value provided: visit_value=" + String.valueOf(s) + " draw_timestamp=" + String.valueOf(d)); - if (null == sequencenum) - return nullDouble; - return sequencenum; - } - }; - - ComputedColumn drawDateCol = new ComputedColumn() - { - @Override - public String getName() - { - return "DrawDate"; - } - - @Override - public Object getValue(Map row) - { - Object d = SpecimenImporter.this.getValue(dateCol, row); - return DateUtil.getDateOnly((Date) d); - } - }; - - ComputedColumn drawTimeCol = new ComputedColumn() - { - @Override - public String getName() - { - return "DrawTime"; - } - - @Override - public Object getValue(Map row) - { - Object d = SpecimenImporter.this.getValue(dateCol, row); - return DateUtil.getTimeOnly((Date)d); - } - }; - - Pair, Integer> pair = new Pair<>(null, 0); - boolean success = true; - final int MAX_TRYS = 3; - for (int tryCount = 0; tryCount < MAX_TRYS; tryCount += 1) - { - try - { - pair = replaceTable(tempTableInfo.getSchema(), file, tempTableInfo.getSelectName(), tempTableInfo, true, false, drawDateCol, drawTimeCol, - lsidCol, sequencenumCol, computedParticipantIdCol); - - loadedColumns = pair.first; - rowCount = pair.second; - - if (rowCount == 0) - { - info("Found no specimen columns to import. Temp table will not be loaded."); - return pair; - } - - remapTempTableLookupIndexes(tempTableInfo.getSchema(), tempTableInfo.getSelectName(), loadedColumns); - - updateTempTableVisits(tempTableInfo.getSchema(), tempTableInfo.getSelectName()); - - if (merge) - { - checkForConflictingSpecimens(tempTableInfo.getSchema(), tempTableInfo.getSelectName(), loadedColumns); - } - } - catch (OptimisticConflictException e) - { - if (tryCount + 1 < MAX_TRYS) - success = false; // Try again - else - throw e; - } - if (success) - break; - } - - updateTempTableSpecimenHash(tempTablesHolder, loadedColumns); - - info("Specimen temp table populated."); - return pair; - } - - protected void remapTempTableLookupIndexes(DbSchema schema, String tempTable, List loadedColumns) - { - String sep = ""; - SQLFragment remapExternalIdsSql = new SQLFragment("UPDATE ").append(tempTable).append(" SET "); - for (SpecimenColumn col : loadedColumns) - { - if (col.getFkTable() != null) - { - remapExternalIdsSql.append(sep).appendIdentifier(col.getLegalDbColumnName(getSqlDialect())).append(" = (SELECT RowId FROM ") - .append(getTableInfoFromFkTableName(col.getFkTable()).getSelectName()).append(" ").append(col.getFkTableAlias()) - .append(" WHERE ").append("(").append(tempTable).append(".") - .appendIdentifier(col.getLegalDbColumnName(getSqlDialect())).append(" = ").append(col.getFkTableAlias()).append(".").append(col.getFkColumn()) - .append("))"); - sep = ",\n\t"; - } - } - - info("Remapping lookup indexes in temp table..."); - if (DEBUG) - info(remapExternalIdsSql.toDebugString()); - if (!sep.isBlank()) - executeSQL(schema, remapExternalIdsSql); - info("Update complete."); - } - - private void updateTempTableVisits(DbSchema schema, String tempTable) - { - Study study = StudyService.get().getStudy(getContainer()); - if (study.getTimepointType() != TimepointType.VISIT) - { - info("Updating visit values to match draw timestamps (date-based studies only)..."); - SQLFragment visitValueSql = new SQLFragment() - .append("UPDATE ") - .append(tempTable) - .append(" SET VisitValue = (") - .append(StudyUtils.sequenceNumFromDateSQL("DrawTimestamp")) - .append(")"); - if (DEBUG) - info(visitValueSql.toDebugString()); - executeSQL(schema, visitValueSql); - info("Update complete."); - } - } - - protected void checkForConflictingSpecimens(DbSchema schema, String tempTable, List loadedColumns) - { - if (!SettingsManager.get().getRepositorySettings(getContainer()).isSpecimenDataEditable()) - { - info("Checking for conflicting specimens before merging..."); - - // Columns used in the specimen hash - SQLFragment hashCols = new SQLFragment(); - for (SpecimenColumn col : loadedColumns) - { - if (col.getTargetTable().isSpecimens() && col.getAggregateEventFunction() == null) - { - hashCols.append(",\n\t"); - hashCols.appendIdentifier(col.getLegalDbColumnName(getSqlDialect())); - } - } - hashCols.append("\n"); - - SQLFragment existingEvents = new SQLFragment("SELECT GlobalUniqueId") - .append(hashCols) - .append("FROM ") - .append(getTableInfoVial(), "Vial") - .append("\n") - .append("JOIN ").append(getTableInfoSpecimen(), "Specimen") - .append("\n") - .append("ON Vial.SpecimenId = Specimen.RowId\n") - .append("WHERE Vial.GlobalUniqueId IN (SELECT GlobalUniqueId FROM ") - .append(tempTable) - .append(")\n"); - - SQLFragment tempTableEvents = new SQLFragment("SELECT GlobalUniqueId") - .append(hashCols) - .append("FROM ") - .append(tempTable); - - // "UNION ALL" the temp and the existing tables and group by columns used in the specimen hash - SQLFragment allEventsByHashCols = new SQLFragment("SELECT COUNT(*) AS Group_Count, * FROM (\n") - .append("(\n").append(existingEvents).append("\n)\n") - .append("UNION ALL /* SpecimenImporter.checkForConflictingSpecimens() */\n") - .append("(\n").append(tempTableEvents).append("\n)\n") - .append(") U\n") - .append("GROUP BY GlobalUniqueId") - .append(hashCols); - - Map>> rowsByGUID = new HashMap<>(); - Set duplicateGUIDs = new TreeSet<>(); - - Map[] allEventsByHashColsResults = new SqlSelector(schema, allEventsByHashCols).getMapArray(); - - for (Map row : allEventsByHashColsResults) - { - String guid = (String)row.get("GlobalUniqueId"); - if (guid != null) - { - if (rowsByGUID.containsKey(guid)) - { - // Found a duplicate - List> dups = rowsByGUID.get(guid); - dups.add(row); - duplicateGUIDs.add(guid); - } - else - { - rowsByGUID.put(guid, new ArrayList<>(Arrays.asList(row))); - } - } - } - - if (duplicateGUIDs.isEmpty()) - { - info("No conflicting specimens found"); - } - else - { - StringBuilder sb = new StringBuilder(); - for (String guid : duplicateGUIDs) - { - List> dups = rowsByGUID.get(guid); - if (dups != null && !dups.isEmpty()) - { - if (!sb.isEmpty()) - sb.append("\n"); - sb.append("Conflicting specimens found for GlobalUniqueId '").append(guid).append("':\n"); - - for (Map row : dups) - { - // CONSIDER: if we want to be really fancy, we could diff the columns to find the conflicting value. - for (SpecimenColumn col : loadedColumns) - if (col.getTargetTable().isSpecimens() && col.getAggregateEventFunction() == null) - sb.append(" ").append(col.getDbColumnName()).append("=").append(row.get(col.getDbColumnName())).append("\n"); - sb.append("\n"); - } - } - } - - _logger.error(sb.toString()); - - // If conflicts are found, stop the import. - throw new IllegalStateException(sb.toString()); - } - } - else - { - // Check if any incoming vial is already present in the vial table; this is not allowed - info("Checking for conflicting specimens in editable repsoitory..."); - SQLFragment sql = new SQLFragment("SELECT COUNT(*) FROM ").append(getTableInfoVial().getSelectName()); - sql.append(" WHERE GlobalUniqueId IN " + "(SELECT GlobalUniqueId FROM "); - sql.append(tempTable).append(")"); - ArrayList counts = new SqlSelector(schema, sql).getArrayList(Integer.class); - if (1 != counts.size()) - { - throw new IllegalStateException("Expected one and only one count of rows."); - } - else if (0 != counts.getFirst() && _generateGlobalUniqueIds > 0) - { - // We were trying to generate globalUniqueIds - throw new OptimisticConflictException("Attempt to generate global unique ids failed.", null, 0); - } - else if (0 != counts.getFirst()) - { - throw new IllegalStateException("With an editable specimen repository, importing may not reference any existing specimen. " + - counts.getFirst() + " imported specimen events refer to existing specimens.") ; - } - info("No conflicting specimens found"); - } - } - - private void updateTempTableSpecimenHash(TempTablesHolder tempTablesHolder, List loadedColumns) - { - DbSchema schema = tempTablesHolder.getTempTableInfo().getSchema(); - String tempTableName = tempTablesHolder.getTempTableInfo().getSelectName(); - String selectInsertTempTableName = tempTablesHolder.getSelectInsertTempTableInfo().getSelectName(); - - // NOTE: In merge case, we've already checked the specimen hash columns are not in conflict. - SQLFragment conflictResolvingSubselect = new SQLFragment("SELECT GlobalUniqueId"); - for (SpecimenColumn col : loadedColumns) - { - if (col.getTargetTable().isSpecimens()) - { - conflictResolvingSubselect.append(",\n\t"); - SQLFragment selectCol = new SQLFragment(tempTableName).append(".").appendIdentifier(col.getLegalDbColumnName(getSqlDialect())); - - if (col.getAggregateEventFunction() != null) - conflictResolvingSubselect.append(col.getAggregateEventFunction()).append("(").append(selectCol).append(")"); - else - { - SQLFragment singletonAggregate; - if (col.getJavaClass().equals(Boolean.class)) - { - // gross nested calls to cast the boolean to an int, get its min, then cast back to a boolean. - // this is needed because most aggregates don't work on boolean values. - singletonAggregate = new SQLFragment("CAST(MIN(CAST(").append(selectCol).append(" AS INTEGER)) AS ").append(schema.getSqlDialect().getBooleanDataType()).append(")"); - } - else - { - singletonAggregate = new SQLFragment("MIN(").append(selectCol).append(")"); - } - conflictResolvingSubselect.append("CASE WHEN"); - conflictResolvingSubselect.append(" COUNT(DISTINCT ").append(selectCol).append(") = 1 THEN "); - conflictResolvingSubselect.append(singletonAggregate); - conflictResolvingSubselect.append(" ELSE NULL END"); - } - conflictResolvingSubselect.append(" AS ").appendIdentifier(col.getLegalDbColumnName(getSqlDialect())); - } - } - conflictResolvingSubselect.append("\nFROM ").append(tempTableName).append("\nGROUP BY GlobalUniqueId"); - - SQLFragment updateHashSql = new SQLFragment("SELECT ("); - makeUpdateSpecimenHashSql(schema, getContainer(), loadedColumns, "InnerTable", updateHashSql); - updateHashSql.append(") AS SpecimenHash, ") - .append("InnerTable.GlobalUniqueId") - .append("\n\tINTO ") - .append(selectInsertTempTableName) - .append("\n\tFROM (") - .append(conflictResolvingSubselect) - .append(") InnerTable"); - - info("Calculating specimen hash values into second temp table..."); - if (DEBUG) - info(updateHashSql.toDebugString()); - executeSQL(schema, updateHashSql); - info("Done calculating specimen hash values."); - tempTablesHolder.getSelectInsertTempTableInfo().track(); // We've now created the second temp table - - SQLFragment setSpecimenHashSql = new SQLFragment("UPDATE ") - .append(tempTableName) - .append(" SET SpecimenHash = InnerTable.SpecimenHash\nFROM ") - .append(selectInsertTempTableName) - .append(" InnerTable\nWHERE ") - .append(tempTableName) - .append(".GlobalUniqueId = InnerTable.GlobalUniqueId"); - - info("Updating specimen hash values in temp table..."); - if (DEBUG) - info(setSpecimenHashSql.toDebugString()); - executeSQL(schema, setSpecimenHashSql); - info("Update complete."); - info("Temp table populated."); - } - - public static void makeUpdateSpecimenHashSql(DbSchema schema, Container container, List loadedColumns, String innerTable, SQLFragment updateHashSql) - { - ArrayList hash = new ArrayList<>(); - hash.add(new SQLFragment("?").add("Fld-" + container.getRowId())); - - Map loadedColumnMap = new HashMap<>(); - loadedColumns.forEach(col -> loadedColumnMap.put(col.getPrimaryTsvColumnName(), col)); - final String strType = schema.getSqlDialect().getSqlCastTypeName(JdbcType.VARCHAR); - SpecimenColumns.BASE_SPECIMEN_COLUMNS.forEach(col -> { - if (col.getTargetTable().isSpecimens()) - { - hash.add(new SQLFragment("'~'")); - if (loadedColumnMap.isEmpty() || loadedColumnMap.containsKey(col.getPrimaryTsvColumnName())) - { - SQLFragment columnNameFragment = new SQLFragment().appendIdentifier(innerTable).append(".").appendIdentifier(col.getLegalDbColumnName(schema.getSqlDialect())); - hash.add(new SQLFragment(" COALESCE(CAST(").append(columnNameFragment).append(" AS ").append(strType).append("),'')")); - } - } - }); - - updateHashSql.append(schema.getSqlDialect().concatenate(hash.toArray(new SQLFragment[0]))); - } - - private Object getValue(ImportableColumn col, Map tsvRow) - { - Object value = null; - if (tsvRow.containsKey(col.getPrimaryTsvColumnName())) - value = tsvRow.get(col.getPrimaryTsvColumnName()); - else if (tsvRow.containsKey(col.getDbColumnName())) - value = tsvRow.get(col.getDbColumnName()); - return value; - } - - private static final boolean DEBUG = false; - private static final boolean VERBOSE_DEBUG = false; - - private static class TempTablesHolder - { - private final TempTableInfo _tempTableInfo; // main specimen temp table - private final TempTableInfo _selectInsertTempTableInfo; // temp table used to Select Insert into while populating main temp table - private final Runnable _createIndexes; - - public TempTablesHolder(TempTableInfo tempTableInfo, TempTableInfo selectInsertTempTableInfo, Runnable createIndexes) - { - _tempTableInfo = tempTableInfo; - _selectInsertTempTableInfo = selectInsertTempTableInfo; - _createIndexes = createIndexes; - } - - public TempTableInfo getTempTableInfo() { - return _tempTableInfo; - } - - public TempTableInfo getSelectInsertTempTableInfo() { - return _selectInsertTempTableInfo; - } - - public Runnable getCreateIndexes() { - return _createIndexes; - } - } - - private TempTablesHolder createTempTable() - { - info("Creating temp table to hold archive data..."); - SqlDialect dialect = DbSchema.getTemp().getSqlDialect(); - - SQLFragment sql = new SQLFragment(); - String uniquifier = StringUtilsLabKey.getUniquifier(9); - - ArrayList columns = new ArrayList<>(); - - String strType = dialect.getSqlTypeName(JdbcType.VARCHAR); - - sql.append("\n(\n RowId ").append(dialect.getUniqueIdentType()).append(", "); - columns.add(new BaseColumnInfo("RowId", JdbcType.INTEGER, 0, false)); - columns.getFirst().setAutoIncrement(true); - - sql.append("LSID ").append(strType).append("(300) NOT NULL, "); - columns.add(new BaseColumnInfo("LSID", JdbcType.VARCHAR, 300, false)); - - sql.append("SpecimenHash ").append(strType).append("(300), "); - columns.add(new BaseColumnInfo("SpecimenHash", JdbcType.VARCHAR, 300, true)); - - sql.append(DRAW_DATE.getDbColumnName()).append(" ").append(DRAW_DATE.getDbType()).append(", "); - columns.add(new BaseColumnInfo(DRAW_DATE.getDbColumnName(), DRAW_DATE.getJdbcType(), 0, true)); - - sql.append(DRAW_TIME.getDbColumnName()).append(" ").append(DRAW_TIME.getDbType()); - columns.add(new BaseColumnInfo(DRAW_TIME.getDbColumnName(), DRAW_TIME.getJdbcType(), 0, true)); - - for (SpecimenColumn col : _specimenColumns) - { - DatabaseIdentifier name = col.getLegalDbColumnName(getSqlDialect()); - sql.append(",\n ").appendIdentifier(name).append(" ").append(col.getDbType()); - BaseColumnInfo colInfo = new BaseColumnInfo(name.getId(), col.getJdbcType(), col.getMaxSize(), true); - Collection aliases = col.getImportAliases(); - if (!aliases.isEmpty()) - colInfo.setImportAliasesSet(new HashSet<>(aliases)); - columns.add(colInfo); - } - sql.append("\n)"); - - TempTableInfo tempTableInfo = new TempTableInfo("SpecimenUpload", (List)(List)columns, Arrays.asList("RowId")); - final String fullTableName = tempTableInfo.getSelectName(); - - sql.insert(0, "CREATE TABLE " + fullTableName + " "); - executeSQL(DbSchema.getTemp(), sql); - tempTableInfo.track(); - - // globalUniquId - final String globalUniqueIdIndexSql = "CREATE INDEX IX_SpecimenUpload" + uniquifier + "_GlobalUniqueId ON " + fullTableName + "(GlobalUniqueId)"; - if (DEBUG) - info(globalUniqueIdIndexSql); - executeSQL(DbSchema.getTemp(), globalUniqueIdIndexSql); - - // We'll Insert Into this one with the calculated specimenHashes and then Update the temp table from there - ArrayList columns2 = new ArrayList<>(); - columns2.add(new BaseColumnInfo(GLOBAL_UNIQUE_ID.getDbColumnName(), GLOBAL_UNIQUE_ID.getJdbcType(), GLOBAL_UNIQUE_ID.getMaxSize(), true)); - columns2.add(new BaseColumnInfo("SpecimenHash", JdbcType.VARCHAR, 300, true)); - TempTableInfo selectInsertTempTableInfo = new TempTableInfo("SpecimenUpload2", columns2, Collections.singletonList("RowId")); - - final String rowIdIndexSql = "CREATE INDEX IX_SpecimenUpload" + uniquifier + "_RowId ON " + fullTableName + "(RowId)"; - final String lsidIndexSql = "CREATE INDEX IX_SpecimenUpload" + uniquifier + "_LSID ON " + fullTableName + "(LSID)"; - final String hashIndexSql = "CREATE INDEX IX_SpecimenUpload" + uniquifier + "_SpecimenHash ON " + fullTableName + "(SpecimenHash)"; - - // delay remaining indexes - Runnable createIndexes = () -> { - if (DEBUG) - { - info(rowIdIndexSql); - info(lsidIndexSql); - info(hashIndexSql); - } - executeSQL(DbSchema.getTemp(), rowIdIndexSql); - executeSQL(DbSchema.getTemp(), lsidIndexSql); - executeSQL(DbSchema.getTemp(), hashIndexSql); - info("Created indexes on table " + fullTableName); - }; - - info("Created temporary table " + fullTableName); - return new TempTablesHolder(tempTableInfo, selectInsertTempTableInfo, createIndexes); - } - - - private static final String SPECIMEN_SEQUENCE_NAME = "org.labkey.study.samples"; - - private List getValidGlobalUniqueIds(int count) - { - List uniqueIds = new ArrayList<>(); - DbSequence sequence = DbSequenceManager.get(getContainer(), SPECIMEN_SEQUENCE_NAME); - sequence.ensureMinimum(70000); - - Sort sort = new Sort(); - sort.appendSortColumn(FieldKey.fromString("GlobalUniqueId"), Sort.SortDirection.DESC, false); - Set columns = Collections.singleton("GlobalUniqueId"); - Set currentIds = new HashSet<>(new TableSelector(getTableInfoVial(), columns, null, sort).getArrayList(String.class)); - - for (int i = 0; i < count; i += 1) - { - while (true) - { - String id = String.valueOf(sequence.next()); - - if (!currentIds.contains(id)) - { - uniqueIds.add(id); - break; - } - } - } - - return uniqueIds; - } - - - @TestWhen(TestWhen.When.BVT) - public static class TestCase extends Assert - { - private static final String TABLE = "SpecimenImporterTest"; - - private TempTableInfo _simpleTable; - - @Before - public void createTable() - { - List columns = new ArrayList<>(); - columns.add(new BaseColumnInfo("Container", JdbcType.GUID, 0, false)); - columns.add(new BaseColumnInfo("id", JdbcType.VARCHAR, 0, false)); - columns.add(new BaseColumnInfo("s", JdbcType.VARCHAR, 30, true)); - columns.getLast().setKeyField(true); - columns.add(new BaseColumnInfo("i", JdbcType.INTEGER, 0, true)); - columns.add(new BaseColumnInfo("entityid", JdbcType.GUID, 0, false)); - _simpleTable = new TempTableInfo(TABLE, (List)(List)columns, Arrays.asList("s")); - - new SqlExecutor(_simpleTable.getSchema()).execute("CREATE TABLE " + _simpleTable.getSelectName() + - "(Container VARCHAR(255) NOT NULL, id VARCHAR(10) NOT NULL, s VARCHAR(32), i INTEGER, entityid VARCHAR(36))"); - _simpleTable.track(); - } - - @After - public void dropTable() - { - if (null != _simpleTable) - _simpleTable.delete(); - } - - private TableResultSet selectValues() - { - return new SqlSelector(_simpleTable.getSchema(), "SELECT Container, id, s, i, entityid FROM " + _simpleTable + " ORDER BY id").getResultSet(); - } - - private Map row(String s, Integer i) - { - Map map = new CaseInsensitiveHashMap<>(); - map.put("s", s); - map.put("i", i); - return map; - } - - @Test - public void mergeTest() throws Exception - { - Container c = JunitUtil.getTestContainer(); - - Collection cols = List.of( - new ImportableColumn("s", "s", "VARCHAR(32)", true), - new ImportableColumn("i", "i", "INTEGER", false) - ); - - ListofMapsDataIterator values = new ListofMapsDataIterator( - new LinkedHashSet<>(List.of("s","i")), - List.of( - row("Bob", 100), - row("Sally", 200), - row(null, 300)) - ); - - SpecimenImporter importer = new SpecimenImporter(c, null); // TODO: don't have user here - final Integer[] counter = new Integer[] { 0 }; - ComputedColumn idCol = new ComputedColumn() - { - @Override - public String getName() { return "id"; } - @Override - public Object getValue(Map row) - { - return String.valueOf(++counter[0]); - } - }; - - // Insert rows - Pair, Integer> pair = importer.mergeTable(_simpleTable.getSchema(), _simpleTable.getSelectName(), _simpleTable, cols, values, idCol, true); - assertNotNull(pair); - assertEquals(pair.first.size(), cols.size()); - assertEquals(3, pair.second.intValue()); - assertEquals(3, counter[0].intValue()); - - - String bobGUID, sallyGUID, nullGUID, jimmyGUID; - int jimmyID; - - try (TableResultSet rs = selectValues()) - { - Iterator> iter = rs.iterator(); - Map row0 = iter.next(); - Map row1 = iter.next(); - Map row2 = iter.next(); - assertFalse(iter.hasNext()); - - assertEquals("Bob", row0.get("s")); - assertEquals(100, row0.get("i")); - assertEquals("1", row0.get("id")); - bobGUID = (String)row0.get("entityid"); -// assertNotNull(bobGUID); - - assertEquals("Sally", row1.get("s")); - assertEquals(200, row1.get("i")); - assertEquals("2", row1.get("id")); - sallyGUID = (String)row1.get("entityid"); -// assertNotNull(sallyGUID); - - assertNull(row2.get("s")); - assertEquals(300, row2.get("i")); - assertEquals("3", row2.get("id")); - nullGUID = (String)row2.get("entityid"); -// assertNotNull(nullGUID); - } - - // Add one new row, update one existing row. - values = new ListofMapsDataIterator( - new LinkedHashSet<>(List.of("s","i")), - List.of( - row("Bob", 105), - row(null, 305), - row("Jimmy", 405) - ) - ); - - - pair = importer.mergeTable(_simpleTable.getSchema(), _simpleTable.getSelectName(), _simpleTable, cols, values, idCol, true); - assertEquals(pair.first.size(), cols.size()); - assertEquals(3, pair.second.intValue()); -// assertEquals(4, counter[0].intValue()); - - try (TableResultSet rs = selectValues()) - { - Iterator> iter = rs.iterator(); - Map row0 = iter.next(); - Map row1 = iter.next(); - Map row2 = iter.next(); - Map row3 = iter.next(); - assertFalse(iter.hasNext()); - - assertEquals("Bob", row0.get("s")); - assertEquals(105, row0.get("i")); - assertEquals("1", row0.get("id")); - assertEquals(bobGUID, row0.get("entityid")); - - assertEquals("Sally", row1.get("s")); - assertEquals(200, row1.get("i")); - assertEquals("2", row1.get("id")); - assertEquals(sallyGUID, row1.get("entityid")); - - assertNull(row2.get("s")); - assertEquals(305, row2.get("i")); - assertEquals("3", row2.get("id")); - assertEquals(nullGUID, row2.get("entityid")); - - assertEquals("Jimmy", row3.get("s")); - assertEquals(405, row3.get("i")); - - jimmyID = Integer.valueOf((String) row3.get("id")); - assertTrue(4 <= jimmyID); - jimmyGUID = (String)row3.get("entityid"); - - // HMM, the original mergeTable() fails this check (non DataIteratyor) - // assertNotNull(jimmyGUID); - } - - // let's really mix things up and try updating using a column that's not marked as the PK - - Collection colsAlternate = List.of( - new ImportableColumn("s", "s", "VARCHAR(32)", false), - new ImportableColumn("i", "i", "INTEGER", true) - ); - - values = new ListofMapsDataIterator( - new LinkedHashSet<>(List.of("s","i")), - List.of( - row("John", 405) - ) - ); - - pair = importer.mergeTable(_simpleTable.getSchema(), _simpleTable.getSelectName(), _simpleTable, colsAlternate, values, idCol, true); - assertEquals(pair.first.size(), cols.size()); - assertEquals(1, pair.second.intValue()); - - try (TableResultSet rs = selectValues()) - { - Iterator> iter = rs.iterator(); - Map row0 = iter.next(); - Map row1 = iter.next(); - Map row2 = iter.next(); - Map row3 = iter.next(); - assertFalse(iter.hasNext()); - - assertEquals("John", row3.get("s")); - assertEquals(405, row3.get("i")); - assertEquals(jimmyID, (int)Integer.valueOf((String)row3.get("id"))); - assertEquals(jimmyGUID, row3.get("entityid")); - } - } - - @Test - public void tempTableConsistencyTest() - { - Container c = JunitUtil.getTestContainer(); - DbSchema schema = SpecimenSchema.get().getSchema(); - User user = TestContext.get().getUser(); - - // Provisioned specimen tables need to be created in this order - TableInfo specimenTableInfo = SpecimenSchema.get().getTableInfoSpecimen(c, user); - TableInfo vialTableInfo = SpecimenSchema.get().getTableInfoVial(c, user); - TableInfo specimenEventTableInfo = SpecimenSchema.get().getTableInfoSpecimenEvent(c, user); - SpecimenImporter importer = new SpecimenImporter(c, user); - - for (SpecimenColumn specimenColumn : importer._specimenColumns) - { - TargetTable targetTable = specimenColumn.getTargetTable(); - List tableNames = targetTable.getTableNames(); - for (String tableName : tableNames) - { - TableInfo tableInfo = null; - if ("SpecimenEvent".equalsIgnoreCase(tableName)) - tableInfo = specimenEventTableInfo; - else if ("Specimen".equalsIgnoreCase(tableName)) - tableInfo = specimenTableInfo; - else if ("Vial".equalsIgnoreCase(tableName)) - tableInfo = vialTableInfo; - if (null != tableInfo) - checkConsistency(tableInfo, tableName, specimenColumn); - } - } - for (ImportableColumn importableColumn : SpecimenColumns.ADDITIVE_COLUMNS) - { - checkConsistency(schema, "SpecimenAdditive", importableColumn); - } - for (ImportableColumn importableColumn : SpecimenColumns.DERIVATIVE_COLUMNS) - { - checkConsistency(schema, "SpecimenDerivative", importableColumn); - } - for (ImportableColumn importableColumn : SpecimenColumns.PRIMARYTYPE_COLUMNS) - { - checkConsistency(schema, "SpecimenPrimaryType", importableColumn); - } - for (ImportableColumn importableColumn : SITE_COLUMNS) - { - checkConsistency(schema, "Site", importableColumn); - } - } - - private void checkConsistency(DbSchema schema, String tableName, ImportableColumn importableColumn) - { - TableInfo tableInfo = schema.getTable(tableName); - checkConsistency(tableInfo, tableName, importableColumn); - } - - private void checkConsistency(TableInfo tableInfo, String tableName, ImportableColumn importableColumn) - { - String columnName = importableColumn.getDbColumnName(); - ColumnInfo columnInfo = tableInfo.getColumn(columnName); - JdbcType jdbcType = columnInfo.getJdbcType(); - - if (jdbcType == JdbcType.VARCHAR) - { - assert importableColumn.getJdbcType() == JdbcType.VARCHAR: - "Column '" + columnName + "' in table '" + tableName + "' has inconsistent types in SQL and importer: varchar vs " + importableColumn.getJdbcType().name(); - assert columnInfo.getScale() == importableColumn.getMaxSize() : - "Column '" + columnName + "' in table '" + tableName + "' has inconsistent varchar lengths in importer and SQL: " + importableColumn.getMaxSize() + " vs " + columnInfo.getScale(); - } - assert jdbcType == importableColumn.getJdbcType() || - (importableColumn.getJdbcType() == JdbcType.DOUBLE && (jdbcType == JdbcType.REAL || jdbcType == JdbcType.DECIMAL)) : - "Column '" + columnName + "' in table '" + tableName + "' has inconsistent types in SQL and importer: " + columnInfo.getJdbcType() + " vs " + importableColumn.getJdbcType(); - } - } - - private int executeSQL(DbSchema schema, CharSequence sql, Object... params) - { - return executeSQL(schema, new SQLFragment(sql, params)); - } - - private int executeSQL(DbSchema schema, SQLFragment sql) - { - debug(sql); - return new SqlExecutor(schema).execute(sql); - } -} diff --git a/specimen/src/org/labkey/specimen/importer/SpecimenSchemaImporter.java b/specimen/src/org/labkey/specimen/importer/SpecimenSchemaImporter.java deleted file mode 100644 index 0589905c082..00000000000 --- a/specimen/src/org/labkey/specimen/importer/SpecimenSchemaImporter.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (c) 2014-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.importer; - -import org.apache.xmlbeans.XmlObject; -import org.jetbrains.annotations.Nullable; -import org.labkey.api.admin.ImportException; -import org.labkey.api.admin.InvalidFileException; -import org.labkey.api.collections.CaseInsensitiveHashMap; -import org.labkey.api.data.Container; -import org.labkey.api.exp.DomainURIFactory; -import org.labkey.api.exp.ImportTypesHelper; -import org.labkey.api.exp.OntologyManager; -import org.labkey.api.exp.OntologyManager.ImportPropertyDescriptorsList; -import org.labkey.api.exp.PropertyDescriptor; -import org.labkey.api.exp.property.Domain; -import org.labkey.api.exp.property.DomainProperty; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.specimen.importer.ImportTemplate; -import org.labkey.api.specimen.model.SpecimenTablesProvider; -import org.labkey.api.study.SpecimenTablesTemplate; -import org.labkey.api.study.importer.SimpleStudyImportContext; -import org.labkey.api.study.importer.SimpleStudyImporter; -import org.labkey.api.util.Pair; -import org.labkey.api.util.XmlBeansUtil; -import org.labkey.api.util.XmlValidationException; -import org.labkey.api.writer.VirtualFile; -import org.labkey.data.xml.TableType; -import org.labkey.data.xml.TablesDocument; -import org.labkey.data.xml.TablesType; -import org.labkey.specimen.writer.SpecimenArchiveDataTypes; -import org.labkey.study.xml.StudyDocument; -import org.springframework.validation.BindException; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * Created by klum on 2/7/14. - */ -public class SpecimenSchemaImporter implements SimpleStudyImporter -{ - private SpecimenTablesTemplate _previousTablesTemplate; - - @Override - public Timing getTiming() - { - return Timing.Early; - } - - @Override - public void preHandling(SimpleStudyImportContext ctx) throws ImportException - { - // if specimen schemas will be imported, don't create optional specimen table fields - if (containsSchemasToImport(ctx)) - _previousTablesTemplate = SpecimenSchema.get().setSpecimenTablesTemplates(new ImportTemplate()); - } - - @Override - public void postHandling(SimpleStudyImportContext ctx) - { - if (_previousTablesTemplate != null) - SpecimenSchema.get().setSpecimenTablesTemplates(_previousTablesTemplate); - } - - @Override - public String getDescription() - { - return "specimen schemas"; - } - - @Override - public String getDataType() - { - return SpecimenArchiveDataTypes.SPECIMENS; - } - - private boolean containsSchemasToImport(SimpleStudyImportContext ctx) throws ImportException - { - StudyDocument.Study.Specimens specimens = ctx.getXml().getSpecimens(); - if (null != specimens && null != specimens.getDir()) - { - try - { - VirtualFile specimenDir = ctx.getRoot().getDir(specimens.getDir()); - // Second check fails for the publish study case, since we don't create a zipped archive inside of the - // MemoryVirtualFile. During specimen migration, it was convenient to move calling of this method to - // process(), but that meant the check started to be called (and failed) in the publish case... which - // meant no custom columns. - if (null != specimenDir) // && null != specimens.getFile()) - { - XmlObject schemaXml = specimenDir.getXmlBean(SpecimenArchiveDataTypes.SCHEMA_FILENAME); - return (schemaXml instanceof TablesDocument); - } - } - catch (IOException e) - { - throw new ImportException(e.getMessage()); - } - } - return false; - } - - @Nullable - public static VirtualFile getSpecimenFolder(SimpleStudyImportContext ctx) throws ImportException - { - StudyDocument.Study.Specimens specimens = ctx.getXml().getSpecimens(); - if (null != specimens && null != specimens.getDir()) - { - return ctx.getRoot().getDir(specimens.getDir()); - } - return null; - } - - @Override - public void process(SimpleStudyImportContext ctx, VirtualFile studyDir, BindException errors) throws Exception - { - if (!ctx.isDataTypeSelected(getDataType())) - return; - - VirtualFile specimenDir = getSpecimenFolder(ctx); - - if (!isValidForImportArchive(ctx, specimenDir)) - return; - - if (!containsSchemasToImport(ctx)) - return; - - TablesDocument tablesDoc; - try - { - XmlObject schemaXml = specimenDir.getXmlBean(SpecimenArchiveDataTypes.SCHEMA_FILENAME); - if (schemaXml instanceof TablesDocument) - { - tablesDoc = (TablesDocument)schemaXml; - XmlBeansUtil.validateXmlDocument(tablesDoc, SpecimenArchiveDataTypes.SCHEMA_FILENAME); - } - else - { - ctx.getLogger().info("No specimen metadata file found to import"); - return; - } - } - catch (XmlValidationException e) - { - throw new InvalidFileException(SpecimenArchiveDataTypes.SCHEMA_FILENAME, e); - } - - TablesType tablesXml = tablesDoc.getTables(); - SpecimenTablesProvider tablesProvider = new SpecimenTablesProvider(ctx.getContainer(), ctx.getUser(), new ImportTemplate()); - - for (TableType tableXml : tablesXml.getTableArray()) - { - final String tableName = tableXml.getTableName(); - - // get the domain of the table we are updating - final Domain domain = tablesProvider.getDomain(tableName, false, true); - final Container container = ctx.getContainer(); - - if (domain != null) - { - ImportTypesHelper importHelper = new ImportTypesHelper(tableXml, "TableName", tableName); - List propErrors = new ArrayList<>(); - - // Create a map of existing properties - Map current = new CaseInsensitiveHashMap<>(); - for (DomainProperty dp : domain.getProperties()) - { - PropertyDescriptor pd = dp.getPropertyDescriptor(); - current.put(pd.getName(), pd); - } - - DomainURIFactory factory = name -> new Pair<>(domain.getTypeURI(), container); - - ImportPropertyDescriptorsList pds = importHelper.getImportPropertyDescriptors(factory, propErrors, container); - if (!propErrors.isEmpty()) - throw new ImportException("Unable to get an instance of TablesDocument from " + SpecimenArchiveDataTypes.SCHEMA_FILENAME); - - boolean isDirty = false; - for (OntologyManager.ImportPropertyDescriptor ipd : pds.properties) - { - if (!current.containsKey(ipd.pd.getName())) - { - DomainProperty domainProperty = domain.addPropertyOfPropertyDescriptor(ipd.pd); - ipd.validators.forEach(domainProperty::addValidator); - domainProperty.setConditionalFormats(ipd.formats); - isDirty = true; - } - } - - if (isDirty) - domain.save(ctx.getUser()); - } - } - } - - @Override - public boolean isValidForImportArchive(SimpleStudyImportContext ctx, VirtualFile root) - { - try - { - return root != null && root.getXmlBean(SpecimenArchiveDataTypes.SCHEMA_FILENAME) != null; - } - catch (IOException e) - { - return false; - } - } -} diff --git a/specimen/src/org/labkey/specimen/importer/SpecimenSettingsImporter.java b/specimen/src/org/labkey/specimen/importer/SpecimenSettingsImporter.java deleted file mode 100644 index 70024499bea..00000000000 --- a/specimen/src/org/labkey/specimen/importer/SpecimenSettingsImporter.java +++ /dev/null @@ -1,481 +0,0 @@ -/* - * Copyright (c) 2013-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.importer; - -import org.labkey.api.admin.ImportException; -import org.labkey.api.attachments.AttachmentService; -import org.labkey.api.data.Container; -import org.labkey.api.security.GroupManager; -import org.labkey.api.security.SecurityManager; -import org.labkey.api.specimen.location.LocationCache; -import org.labkey.api.specimen.location.LocationImpl; -import org.labkey.specimen.settings.DisplaySettings; -import org.labkey.specimen.settings.RepositorySettings; -import org.labkey.specimen.settings.StatusSettings; -import org.labkey.api.study.StudyService; -import org.labkey.api.study.importer.SimpleStudyImportContext; -import org.labkey.api.study.importer.SimpleStudyImporter; -import org.labkey.api.writer.VirtualFile; -import org.labkey.security.xml.GroupType; -import org.labkey.specimen.SpecimenRequestManager; -import org.labkey.specimen.SpecimenRequestManager.SpecimenRequestInput; -import org.labkey.specimen.SpecimenRequestStatus; -import org.labkey.specimen.actions.ManageReqsBean; -import org.labkey.specimen.model.SpecimenRequestActor; -import org.labkey.specimen.requirements.RequirementType; -import org.labkey.specimen.requirements.SpecimenRequestRequirement; -import org.labkey.specimen.requirements.SpecimenRequestRequirementProvider; -import org.labkey.specimen.requirements.SpecimenRequestRequirementType; -import org.labkey.specimen.settings.RequestNotificationSettings; -import org.labkey.specimen.settings.SettingsManager; -import org.labkey.specimen.writer.SpecimenArchiveDataTypes; -import org.labkey.study.xml.DefaultRequirementType; -import org.labkey.study.xml.DefaultRequirementsType; -import org.labkey.study.xml.SpecimenRepositoryType; -import org.labkey.study.xml.SpecimenSettingsType; -import org.labkey.study.xml.SpecimensDocument; -import org.labkey.study.xml.StudyDocument; -import org.springframework.validation.BindException; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class SpecimenSettingsImporter implements SimpleStudyImporter -{ - @Override - public Timing getTiming() - { - return Timing.Late; // Can't set up specimen request actors until the locations have been created - } - - @Override - public void preHandling(SimpleStudyImportContext ctx) - { - } - - @Override - public void postHandling(SimpleStudyImportContext ctx) - { - } - - @Override - public String getDescription() - { - return "specimen settings"; - } - - @Override - public String getDataType() - { - return SpecimenArchiveDataTypes.SPECIMEN_SETTINGS; - } - - @Override - public void process(SimpleStudyImportContext ctx, VirtualFile studyDir, BindException errors) throws SQLException, ImportException, IOException - { - if (!ctx.isDataTypeSelected(getDataType())) - return; - - if (isValidForImportArchive(ctx, studyDir)) - { - StudyDocument.Study.Specimens xmlSettings = ctx.getXml().getSpecimens(); - - if (xmlSettings.getSettings() != null) - { - ctx.getLogger().info("Loading {}", getDescription()); - - // Import specimen settings from specimen_settings.xml doc - VirtualFile settingsDir = studyDir; - if (xmlSettings.getDir() != null) - settingsDir = studyDir.getDir(xmlSettings.getDir()); - - SpecimensDocument specimensDoc = (SpecimensDocument)settingsDir.getXmlBean(xmlSettings.getSettings()); - importSettings(ctx, specimensDoc.getSpecimens()); - - ctx.getLogger().info("Done importing {}", getDescription()); - } - } - } - - @Override - public boolean isValidForImportArchive(SimpleStudyImportContext ctx, VirtualFile root) throws ImportException - { - return ctx.getXml() != null && ctx.getXml().getSpecimens() != null; - } - - // Import specimen settings from specimen_settings.xml - private void importSettings(SimpleStudyImportContext ctx, SpecimenSettingsType xmlSettings) throws SQLException - { - Container c = ctx.getContainer(); - RepositorySettings reposSettings = SettingsManager.get().getRepositorySettings(c); - - // webpart groupings - SpecimenSettingsType.WebPartGroupings xmlWebPartGroupings = xmlSettings.getWebPartGroupings(); - if (null != xmlWebPartGroupings) - { - SpecimenSettingsType.WebPartGroupings.Grouping[] xmlGroupings = xmlWebPartGroupings.getGroupingArray(); - if (null != xmlGroupings) - { - ArrayList groupings = new ArrayList<>(2); - for (SpecimenSettingsType.WebPartGroupings.Grouping xmlGrouping : xmlGroupings) - { - String[] groupBys = xmlGrouping.getGroupByArray(); - groupings.add(groupBys); - } - reposSettings.setSpecimenWebPartGroupings(groupings); - } - } - - // repository type - SpecimenRepositoryType.Enum repositoryType = xmlSettings.getRepositoryType(); - boolean simple = (SpecimenRepositoryType.STANDARD == repositoryType); - reposSettings.setSimple(simple); - if (xmlSettings.isSetEnableRequests()) - reposSettings.setEnableRequests(xmlSettings.getEnableRequests()); - if (xmlSettings.isSetEditableRepository()) - reposSettings.setSpecimenDataEditable(xmlSettings.getEditableRepository()); - org.labkey.specimen.settings.SettingsManager.get().saveRepositorySettings(c, reposSettings); - - // location types - SpecimenSettingsType.LocationTypes xmlLocationTypes = xmlSettings.getLocationTypes(); - if (null != xmlLocationTypes) - { - Boolean repo = xmlLocationTypes.isSetRepository() && xmlLocationTypes.getRepository().isSetAllowRequests() ? xmlLocationTypes.getRepository().getAllowRequests() : null; - Boolean clinic = xmlLocationTypes.isSetClinic() && xmlLocationTypes.getClinic().isSetAllowRequests() ? xmlLocationTypes.getClinic().getAllowRequests() : null; - Boolean sal = xmlLocationTypes.isSetSiteAffiliatedLab() && xmlLocationTypes.getSiteAffiliatedLab().isSetAllowRequests() ? xmlLocationTypes.getSiteAffiliatedLab().getAllowRequests() : null; - Boolean endpoint = xmlLocationTypes.isSetEndpointLab() && xmlLocationTypes.getEndpointLab().isSetAllowRequests() ? xmlLocationTypes.getEndpointLab().getAllowRequests() : null; - - StudyService.get().saveLocationSettings(ctx.getStudy(), ctx.getUser(), repo, clinic, sal, endpoint); - } - - importRequestStatuses(ctx, xmlSettings); - importRequestActors(ctx, xmlSettings); - importDefaultRequirements(ctx, xmlSettings); - importDisplaySettings(ctx, xmlSettings); - importRequestForm(ctx, xmlSettings); - importNotifications(ctx, xmlSettings); - importRequestabilityRules(ctx, xmlSettings); - } - - private void importRequestStatuses(SimpleStudyImportContext ctx, SpecimenSettingsType xmlSettings) - { - SpecimenSettingsType.RequestStatuses xmlRequestStatuses = xmlSettings.getRequestStatuses(); - if (xmlRequestStatuses != null) - { - SpecimenSettingsType.RequestStatuses.Status[] xmlStatusArray = xmlRequestStatuses.getStatusArray(); - if (xmlStatusArray.length > 0) - { - // remove any existing not in-use, non-system statuses for this container before importing the new ones - Set inUseStatusIds = SpecimenRequestManager.get().getRequestStatusIdsInUse(ctx.getContainer()); - List inUseStatusLabels = new ArrayList<>(); - for (SpecimenRequestStatus existingStatus : SpecimenRequestManager.get().getRequestStatuses(ctx.getContainer(), ctx.getUser())) - { - if (!existingStatus.isSystemStatus() && !inUseStatusIds.contains(existingStatus.getRowId())) - SpecimenRequestManager.get().deleteRequestStatus(existingStatus); - else - inUseStatusLabels.add(existingStatus.getLabel()); - } - - // create new request statuses from the xml settings file - for (int i = 0; i < xmlStatusArray.length; i++) - { - String newStatusLabel = xmlStatusArray[i].isSetLabel() && xmlStatusArray[i].getLabel() != null && !xmlStatusArray[i].getLabel().isEmpty() ? xmlStatusArray[i].getLabel() : null; - if (inUseStatusLabels.contains(newStatusLabel)) - { - ctx.getLogger().warn("Skipping request status that matches an existing status label: {}", newStatusLabel); - } - else if (newStatusLabel != null) - { - SpecimenRequestStatus newStatus = new SpecimenRequestStatus(); - newStatus.setContainer(ctx.getContainer()); - newStatus.setLabel(newStatusLabel); - newStatus.setSortOrder(i+1); - if (xmlStatusArray[i].isSetFinalState()) - newStatus.setFinalState(xmlStatusArray[i].getFinalState()); - if (xmlStatusArray[i].isSetLockSpecimens()) - newStatus.setSpecimensLocked(xmlStatusArray[i].getLockSpecimens()); - SpecimenRequestManager.get().createRequestStatus(ctx.getUser(), newStatus); - } - else - { - ctx.getLogger().warn("Skipping request status that does not have a label."); - } - } - } - if (xmlRequestStatuses.isSetMultipleSearch()) - { - StatusSettings settings = SettingsManager.get().getStatusSettings(ctx.getContainer()); - if (settings.isUseShoppingCart() != xmlRequestStatuses.getMultipleSearch()) - { - settings.setUseShoppingCart(xmlRequestStatuses.getMultipleSearch()); - org.labkey.specimen.settings.SettingsManager.get().saveStatusSettings(ctx.getContainer(), settings); - } - } - } - } - - private void importRequestActors(SimpleStudyImportContext ctx, SpecimenSettingsType xmlSettings) - { - SpecimenSettingsType.RequestActors xmlRequestActors = xmlSettings.getRequestActors(); - if (xmlRequestActors != null) - { - SpecimenSettingsType.RequestActors.Actor[] xmlActorArray = xmlRequestActors.getActorArray(); - if (xmlActorArray.length > 0) - { - // remove any existing not in-use actors - // note: this will also remove all groups and members for that actor - Set inUseActorIds = SpecimenRequestRequirementProvider.get().getActorsInUseSet(ctx.getContainer()); - Map inUseActors = new HashMap<>(); - for (SpecimenRequestActor existingActor : SpecimenRequestRequirementProvider.get().getActors(ctx.getContainer())) - { - if (!inUseActorIds.contains(existingActor.getRowId())) - existingActor.delete(ctx.getUser()); - else - inUseActors.put(existingActor.getLabel(), existingActor); - } - - // create new request actors from the xml settings file - for (int i = 0; i < xmlActorArray.length; i++) - { - String newActorLabel = xmlActorArray[i].isSetLabel() && xmlActorArray[i].getLabel() != null && !xmlActorArray[i].getLabel().isEmpty() ? xmlActorArray[i].getLabel() : null; - if (newActorLabel != null) - { - // create new request actor if label does not match existing in-use actor label - SpecimenRequestActor actor; - if (!inUseActors.containsKey(newActorLabel)) - { - actor = new SpecimenRequestActor(); - actor.setContainer(ctx.getContainer()); - actor.setLabel(newActorLabel); - actor.setSortOrder(i); - actor.setPerSite(SpecimenSettingsType.RequestActors.Actor.Type.LOCATION.equals(xmlActorArray[i].getType())); // default is per study - actor.create(ctx.getUser()); - } - else - { - actor = inUseActors.get(newActorLabel); - } - - if (xmlActorArray[i].getGroups() != null) - { - for (GroupType newActorGroup : xmlActorArray[i].getGroups().getGroupArray()) - { - // verify that the location exists in this study, for the per site actor type - LocationImpl location = null; - if (actor.isPerSite()) - { - location = LocationCache.getForLabel(ctx.getContainer(), newActorGroup.getName()); - - if (location == null) - { - ctx.getLogger().warn("Request actor group not created for \"{}, {}\". Could not find matching study location.", actor.getLabel(), newActorGroup.getName()); - continue; - } - } - - // note: currently, request actor groups only have users as members (no groups in groups) - Integer newGroupId = actor.getGroupId(location, true); - GroupManager.importGroupMembers(SecurityManager.getGroup(newGroupId), newActorGroup, ctx.getLogger(), ctx.getContainer()); - } - } - } - else - { - ctx.getLogger().warn("Skipping request actor that does not have a label."); - } - } - } - } - } - - private void importDefaultRequirements(SimpleStudyImportContext ctx, SpecimenSettingsType xmlSettings) - { - SpecimenSettingsType.DefaultRequirements xmlDefRequirements = xmlSettings.getDefaultRequirements(); - if (xmlDefRequirements != null) - { - // remove existing default requirements for this container, full replacement - ManageReqsBean existingDefaultReqBeans = new ManageReqsBean(ctx.getUser(), ctx.getContainer()); - List existingDefaultReqs = new ArrayList<>(); - existingDefaultReqs.addAll(Arrays.asList(existingDefaultReqBeans.getOriginatorRequirements())); - existingDefaultReqs.addAll(Arrays.asList(existingDefaultReqBeans.getProviderRequirements())); - existingDefaultReqs.addAll(Arrays.asList(existingDefaultReqBeans.getReceiverRequirements())); - existingDefaultReqs.addAll(Arrays.asList(existingDefaultReqBeans.getGeneralRequirements())); - for (SpecimenRequestRequirement existingReq : existingDefaultReqs) - { - try - { - SpecimenRequestManager.get().deleteRequestRequirement(ctx.getUser(), existingReq, false); - } - catch(AttachmentService.DuplicateFilenameException ignored) - {} // no op, this would only occur with deleteRequestRequirement when createEvent is true - } - - for (DefaultRequirementsType xmlReq : xmlDefRequirements.getOriginatingLabArray()) - createDefaultRequirement(xmlReq, ctx, SpecimenRequestRequirementType.ORIGINATING_SITE); - for (DefaultRequirementsType xmlReq : xmlDefRequirements.getProvidingLabArray()) - createDefaultRequirement(xmlReq, ctx, SpecimenRequestRequirementType.PROVIDING_SITE); - for (DefaultRequirementsType xmlReq : xmlDefRequirements.getReceivingLabArray()) - createDefaultRequirement(xmlReq, ctx, SpecimenRequestRequirementType.RECEIVING_SITE ); - for (DefaultRequirementsType xmlReq : xmlDefRequirements.getGeneralArray()) - createDefaultRequirement(xmlReq, ctx, SpecimenRequestRequirementType.NON_SITE_BASED); - } - } - - private void createDefaultRequirement(DefaultRequirementsType xmlReqs, SimpleStudyImportContext ctx, RequirementType type) - { - if (xmlReqs != null) - { - for (DefaultRequirementType xmlReq : xmlReqs.getRequirementArray()) - { - List matchingActors = SpecimenRequestRequirementProvider.get().getActorsByLabel(ctx.getContainer(), xmlReq.getActor()); - if (!matchingActors.isEmpty()) - { - SpecimenRequestRequirement requirement = new SpecimenRequestRequirement(); - requirement.setContainer(ctx.getContainer()); - requirement.setActorId(matchingActors.getFirst().getRowId()); - requirement.setDescription(xmlReq.getDescription()); - requirement.setRequestId(-1); - SpecimenRequestRequirementProvider.get().createDefaultRequirement(ctx.getUser(), requirement, type); - } - else - { - ctx.getLogger().warn("Could not find matching actor with label: {}", xmlReq.getActor()); - } - } - } - } - - private void importDisplaySettings(SimpleStudyImportContext ctx, SpecimenSettingsType xmlSettings) - { - ctx.getLogger().info("Importing specimen display settings"); - SpecimenSettingsType.DisplaySettings xmlDisplay = xmlSettings.getDisplaySettings(); - if (xmlDisplay != null) - { - DisplaySettings display = new DisplaySettings(); - SpecimenSettingsType.DisplaySettings.CommentsAndQC commentsAndQC = xmlDisplay.getCommentsAndQC(); - if (commentsAndQC != null) - { - display.setDefaultToCommentsMode(commentsAndQC.getDefaultToCommentsMode()); - display.setEnableManualQCFlagging(commentsAndQC.getEnableManualQCFlagging()); - } - SpecimenSettingsType.DisplaySettings.LowSpecimenWarnings warnings = xmlDisplay.getLowSpecimenWarnings(); - if (warnings != null) - { - display.setLastVial(warnings.getLastVial()); - display.setZeroVials(warnings.getZeroVials()); - } - - org.labkey.specimen.settings.SettingsManager.get().saveDisplaySettings(ctx.getContainer(), display); - } - } - - private void importRequestForm(SimpleStudyImportContext ctx, SpecimenSettingsType xmlSettings) throws SQLException - { - ctx.getLogger().info("Importing specimen request forms"); - // try to merge with any existing request forms, even though there doesn't seem to be the notion of a duplicate value - Set currentInputs = new HashSet<>(); - List inputs = new ArrayList<>(); - for (SpecimenRequestInput input : SpecimenRequestManager.get().getNewSpecimenRequestInputs(ctx.getContainer(), false)) - { - inputs.add(input); - currentInputs.add(input.getTitle()); - } - - SpecimenSettingsType.RequestForms xmlForms = xmlSettings.getRequestForms(); - if (xmlForms != null) - { - SpecimenSettingsType.RequestForms.Form[] formArray = xmlForms.getFormArray(); - if (formArray != null && formArray.length > 0) - { - for (SpecimenSettingsType.RequestForms.Form form : formArray) - { - if (!currentInputs.contains(form.getTitle())) - { - SpecimenRequestInput input = new SpecimenRequestInput( - form.getTitle(), - form.getHelpText(), - form.getDisplayOrder(), - form.getMultiLine(), - form.getRequired(), - form.getRememberSiteValue() - ); - - inputs.add(input); - } - else - ctx.getLogger().info("There is currently a form with the same title: {}, skipping this from import", form.getTitle()); - } - inputs.sort(Comparator.comparingInt(SpecimenRequestInput::getDisplayOrder)); - SpecimenRequestManager.get().saveNewSpecimenRequestInputs(ctx.getContainer(), inputs.toArray(new SpecimenRequestInput[0])); - } - } - } - - private void importNotifications(SimpleStudyImportContext ctx, SpecimenSettingsType xmlSettings) - { - ctx.getLogger().info("Importing specimen notification settings"); - SpecimenSettingsType.Notifications xmlNotifications = xmlSettings.getNotifications(); - if (xmlNotifications != null) - { - RequestNotificationSettings notifications = new RequestNotificationSettings(); - - if (xmlNotifications.getReplyTo() != null) - notifications.setReplyTo(xmlNotifications.getReplyTo()); - if (xmlNotifications.getSubjectSuffix() != null) - notifications.setSubjectSuffix(xmlNotifications.getSubjectSuffix()); - notifications.setNewRequestNotifyCheckbox(xmlNotifications.getNewRequestNotifyCheckbox()); - if (xmlNotifications.getNewRequestNotify() != null) - notifications.setNewRequestNotify(xmlNotifications.getNewRequestNotify()); - notifications.setCcCheckbox(xmlNotifications.getCcCheckbox()); - if (xmlNotifications.getCc() != null) - notifications.setCc(xmlNotifications.getCc()); - if (xmlNotifications.getDefaultEmailNotify() != null) - notifications.setDefaultEmailNotify(xmlNotifications.getDefaultEmailNotify()); - if (xmlNotifications.getSpecimensAttachment() != null) - notifications.setSpecimensAttachment(xmlNotifications.getSpecimensAttachment()); - - org.labkey.specimen.settings.SettingsManager.get().saveRequestNotificationSettings(ctx.getContainer(), notifications); - } - } - - private void importRequestabilityRules(SimpleStudyImportContext ctx, SpecimenSettingsType xmlSettings) - { - ctx.getLogger().info("Importing specimen requestability rules"); - SpecimenSettingsType.RequestabilityRules xmlRules = xmlSettings.getRequestabilityRules(); - if (xmlRules != null) - { - List rules = new ArrayList<>(); - SpecimenSettingsType.RequestabilityRules.Rule[] xmlRuleArray = xmlRules.getRuleArray(); - if (xmlRuleArray != null && xmlRuleArray.length > 0) - { - for (SpecimenSettingsType.RequestabilityRules.Rule rule : xmlRuleArray) - { - RequestabilityManager.RuleType type = RequestabilityManager.RuleType.valueOf(rule.getType()); - rules.add(type.createRule(ctx.getContainer(), rule.getRuleData())); - } - RequestabilityManager.getInstance().saveRules(ctx.getContainer(), ctx.getUser(), rules); - } - } - } -} diff --git a/specimen/src/org/labkey/specimen/importer/SpecimenTableType.java b/specimen/src/org/labkey/specimen/importer/SpecimenTableType.java deleted file mode 100644 index 5d519e91b9c..00000000000 --- a/specimen/src/org/labkey/specimen/importer/SpecimenTableType.java +++ /dev/null @@ -1,47 +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.importer; - -import java.util.Collection; - -public class SpecimenTableType -{ - private final String _name; - private final String _tableName; - private final Collection _columns; - - public SpecimenTableType(String name, String tableName, Collection columns) - { - _name = name; - _tableName = tableName; - _columns = columns; - } - - public String getName() - { - return _name; - } - - public String getTableName() - { - return _tableName; - } - - public Collection getColumns() - { - return _columns; - } -} diff --git a/specimen/src/org/labkey/specimen/importer/TargetTable.java b/specimen/src/org/labkey/specimen/importer/TargetTable.java deleted file mode 100644 index 60dd5c4ba16..00000000000 --- a/specimen/src/org/labkey/specimen/importer/TargetTable.java +++ /dev/null @@ -1,173 +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.importer; - -import java.util.ArrayList; -import java.util.List; - -public enum TargetTable -{ - SPECIMEN_EVENTS - { - @Override - public List getTableNames() - { - List names = new ArrayList<>(1); - names.add("SpecimenEvent"); - return names; - } - - @Override - public boolean isEvents() - { - return true; - } - - @Override - public boolean isSpecimens() - { - return false; - } - - @Override - public boolean isVials() - { - return false; - } - }, - SPECIMENS - { - @Override - public List getTableNames() - { - List names = new ArrayList<>(1); - names.add("Specimen"); - return names; - } - - @Override - public boolean isEvents() - { - return false; - } - - @Override - public boolean isSpecimens() - { - return true; - } - - @Override - public boolean isVials() - { - return false; - } - }, - VIALS - { - @Override - public List getTableNames() - { - List names = new ArrayList<>(1); - names.add("Vial"); - return names; - } - - @Override - public boolean isEvents() - { - return false; - } - - @Override - public boolean isSpecimens() - { - return false; - } - - @Override - public boolean isVials() - { - return true; - } - }, - SPECIMENS_AND_SPECIMEN_EVENTS - { - @Override - public List getTableNames() - { - List names = new ArrayList<>(1); - names.add("Specimen"); - names.add("SpecimenEvent"); - return names; - } - - @Override - public boolean isEvents() - { - return true; - } - - @Override - public boolean isSpecimens() - { - return true; - } - - @Override - public boolean isVials() - { - return false; - } - }, - VIALS_AND_SPECIMEN_EVENTS - { - @Override - public List getTableNames() - { - List names = new ArrayList<>(1); - names.add("Vial"); - names.add("SpecimenEvent"); - return names; - } - - @Override - public boolean isEvents() - { - return true; - } - - @Override - public boolean isSpecimens() - { - return false; - } - - @Override - public boolean isVials() - { - return true; - } - }; - - public abstract boolean isEvents(); - - public abstract boolean isVials(); - - public abstract boolean isSpecimens(); - - public abstract List getTableNames(); -} diff --git a/specimen/src/org/labkey/specimen/model/AdditiveType.java b/specimen/src/org/labkey/specimen/model/AdditiveType.java deleted file mode 100644 index 42940f0a9dd..00000000000 --- a/specimen/src/org/labkey/specimen/model/AdditiveType.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2009-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.model; - -import org.labkey.api.data.Container; -import org.labkey.api.specimen.model.AdditiveTypeDomainKind; -import org.labkey.api.study.AbstractStudyCachable; - -import java.util.Map; - -public class AdditiveType extends AbstractStudyCachable -{ - private long _rowId; // serial NOT NULL, - private Container _container; // entityid NOT NULL, - private String _ldmsAdditiveCode; // character varying(5), - private String _labwareAdditiveCode; // character varying(5), - private String _additive; // character varying(100), - private int _externalId; // integer NOT NULL default 0 - - public AdditiveType(Container container, Map map) - { - super(); - _rowId = (int) map.get(AdditiveTypeDomainKind.ROWID); - _container = container; - _ldmsAdditiveCode = (String) map.get(AdditiveTypeDomainKind.LDMSADDITIVECODE); - _labwareAdditiveCode = (String) map.get(AdditiveTypeDomainKind.LABWAREADDITIVECODE); - _additive = (String) map.get(AdditiveTypeDomainKind.ADDITIVE); - _externalId = (int) map.get(AdditiveTypeDomainKind.EXTERNALID); - } - - @Override - public Long getPrimaryKey() - { - return _rowId; - } - - public long getRowId() - { - return _rowId; - } - - public void setRowId(long rowId) - { - _rowId = rowId; - } - - public int getExternalId() - { - return _externalId; - } - - public void setExternalId(int externalId) - { - _externalId = externalId; - } - - @Override - public Container getContainer() - { - return _container; - } - - @Override - public void setContainer(Container container) - { - _container = container; - } - - public String getLdmsAdditiveCode() - { - return _ldmsAdditiveCode; - } - - public void setLdmsAdditiveCode(String ldmsAdditiveCode) - { - _ldmsAdditiveCode = ldmsAdditiveCode; - } - - public String getLabwareAdditiveCode() - { - return _labwareAdditiveCode; - } - - public void setLabwareAdditiveCode(String labwareAdditiveCode) - { - _labwareAdditiveCode = labwareAdditiveCode; - } - - public String getAdditive() - { - return _additive; - } - - public void setAdditive(String additive) - { - _additive = additive; - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/model/DerivativeType.java b/specimen/src/org/labkey/specimen/model/DerivativeType.java deleted file mode 100644 index e55f641833e..00000000000 --- a/specimen/src/org/labkey/specimen/model/DerivativeType.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2009-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.model; - -import org.labkey.api.data.Container; -import org.labkey.api.specimen.model.DerivativeTypeDomainKind; -import org.labkey.api.study.AbstractStudyCachable; - -import java.util.Map; - -public class DerivativeType extends AbstractStudyCachable -{ - private long _rowId; // serial NOT NULL, - private Container _container; // entityid NOT NULL, - private String _ldmsDerivativeCode; // character varying(5), - private String _labwareDerivativeCode; // character varying(5), - private String _derivative; // character varying(100), - private int _externalId; // integer NOT NULL default 0 - - public DerivativeType(Container container, Map map) - { - super(); - _rowId = (int) map.get(DerivativeTypeDomainKind.ROWID); - _container = container; - _ldmsDerivativeCode = (String) map.get(DerivativeTypeDomainKind.LDMSDERIVATIVECODE); - _labwareDerivativeCode = (String) map.get(DerivativeTypeDomainKind.LABWAREDERIVATIVECODE); - _derivative = (String) map.get(DerivativeTypeDomainKind.DERIVATIVE); - _externalId = (int) map.get(DerivativeTypeDomainKind.EXTERNALID); - } - - @Override - public Long getPrimaryKey() - { - return _rowId; - } - - public long getRowId() - { - return _rowId; - } - - public void setRowId(long rowId) - { - _rowId = rowId; - } - - public int getExternalId() - { - return _externalId; - } - - public void setExternalId(int externalId) - { - _externalId = externalId; - } - - @Override - public Container getContainer() - { - return _container; - } - - @Override - public void setContainer(Container container) - { - _container = container; - } - - public String getLdmsDerivativeCode() - { - return _ldmsDerivativeCode; - } - - public void setLdmsDerivativeCode(String ldmsDerivativeCode) - { - _ldmsDerivativeCode = ldmsDerivativeCode; - } - - public String getLabwareDerivativeCode() - { - return _labwareDerivativeCode; - } - - public void setLabwareDerivativeCode(String labwareDerivativeCode) - { - _labwareDerivativeCode = labwareDerivativeCode; - } - - public String getDerivative() - { - return _derivative; - } - - public void setDerivative(String derivative) - { - _derivative = derivative; - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/model/ExtendedSpecimenRequestView.java b/specimen/src/org/labkey/specimen/model/ExtendedSpecimenRequestView.java deleted file mode 100644 index 96a9afcaa9a..00000000000 --- a/specimen/src/org/labkey/specimen/model/ExtendedSpecimenRequestView.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2013-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.model; - -import org.labkey.api.data.Entity; - -/** - * User: Nick Arnold - * Date: 3/13/13 - */ -public class ExtendedSpecimenRequestView extends Entity -{ - private boolean _active; - private String _body; - - public static ExtendedSpecimenRequestView createView(String body) - { - ExtendedSpecimenRequestView view = new ExtendedSpecimenRequestView(); - view.setBody(body); - view.setActive(true); - return view; - } - - public String getBody() - { - return _body; - } - - public void setBody(String body) - { - _body = body; - } - - public boolean isActive() - { - return _active; - } - - public void setActive(boolean active) - { - _active = active; - } -} diff --git a/specimen/src/org/labkey/specimen/model/PrimaryType.java b/specimen/src/org/labkey/specimen/model/PrimaryType.java deleted file mode 100644 index a23e91e5108..00000000000 --- a/specimen/src/org/labkey/specimen/model/PrimaryType.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2009-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.model; - -import org.labkey.api.data.Container; -import org.labkey.api.specimen.model.PrimaryTypeDomainKind; -import org.labkey.api.study.AbstractStudyCachable; - -import java.util.Map; - -public class PrimaryType extends AbstractStudyCachable -{ - private long _rowId; // serial NOT NULL, - private Container _container; // entityid NOT NULL, - private String _primaryTypeLDMSCode; // character varying(5), - private String _primaryTypeLabwareCode; // character varying(5), - private String _primaryType; // character varying(100), - private int _externalId; // integer NOT NULL default 0 - - public PrimaryType(Container container, Map map) - { - super(); - _rowId = (int) map.get(PrimaryTypeDomainKind.ROWID); - _container = container; - _primaryTypeLDMSCode = (String) map.get(PrimaryTypeDomainKind.PRIMARYLDMSCODE); - _primaryTypeLabwareCode = (String) map.get(PrimaryTypeDomainKind.PRIMARYLABWARECODE); - _primaryType = (String) map.get(PrimaryTypeDomainKind.PRIMARYTYPE); - _externalId = (int) map.get(PrimaryTypeDomainKind.EXTERNALID); - } - - @Override - public Long getPrimaryKey() - { - return _rowId; - } - - public long getRowId() - { - return _rowId; - } - - public void setRowId(long rowId) - { - _rowId = rowId; - } - - public int getExternalId() - { - return _externalId; - } - - public void setExternalId(int externalId) - { - _externalId = externalId; - } - - @Override - public Container getContainer() - { - return _container; - } - - @Override - public void setContainer(Container container) - { - _container = container; - } - - public String getPrimaryTypeLDMSCode() - { - return _primaryTypeLDMSCode; - } - - public void setPrimaryTypeLDMSCode(String primaryTypeLDMSCode) - { - _primaryTypeLDMSCode = primaryTypeLDMSCode; - } - - public String getPrimaryTypeLabwareCode() - { - return _primaryTypeLabwareCode; - } - - public void setPrimaryTypeLabwareCode(String primaryTypeLabwareCode) - { - _primaryTypeLabwareCode = primaryTypeLabwareCode; - } - - public String getPrimaryType() - { - return _primaryType; - } - - public void setPrimaryType(String primaryType) - { - _primaryType = primaryType; - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/model/SpecimenRequestActor.java b/specimen/src/org/labkey/specimen/model/SpecimenRequestActor.java deleted file mode 100644 index b0a2d6a52d4..00000000000 --- a/specimen/src/org/labkey/specimen/model/SpecimenRequestActor.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2008-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.model; - -import org.labkey.api.data.Container; -import org.labkey.api.data.TableInfo; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.study.StudyCachable; -import org.labkey.api.util.MemTracker; -import org.labkey.specimen.requirements.DefaultActor; - -public class SpecimenRequestActor extends DefaultActor implements StudyCachable, Cloneable -{ - private int _rowId; - private Integer _sortOrder; - private Container _container; - private String _label; - private boolean _perSite; - private boolean _mutable = true; - - public SpecimenRequestActor() - { - MemTracker.getInstance().put(this); - } - - protected void verifyMutability() - { - if (!_mutable) - throw new IllegalStateException("Cached objects are immutable; createMutable must be called first."); - } - - public boolean isMutable() - { - return _mutable; - } - - protected void unlock() - { - _mutable = true; - } - - @Override - public void lock() - { - _mutable = false; - } - - @Override - public SpecimenRequestActor createMutable() - { - try - { - SpecimenRequestActor obj = (SpecimenRequestActor) clone(); - obj.unlock(); - return obj; - } - catch (CloneNotSupportedException e) - { - throw new RuntimeException(e); - } - } - - @Override - public Container getContainer() - { - return _container; - } - - public void setContainer(Container container) - { - verifyMutability(); - _container = container; - } - - public String getLabel() - { - return _label; - } - - public void setLabel(String label) - { - verifyMutability(); - _label = label; - } - - @Override - public Integer getPrimaryKey() - { - return getRowId(); - } - - public int getRowId() - { - return _rowId; - } - - public void setRowId(int rowId) - { - verifyMutability(); - _rowId = rowId; - } - - public Integer getSortOrder() - { - return _sortOrder; - } - - public void setSortOrder(Integer sortOrder) - { - verifyMutability(); - _sortOrder = sortOrder; - } - - public boolean isPerSite() - { - return _perSite; - } - - public void setPerSite(boolean perSite) - { - verifyMutability(); - _perSite = perSite; - } - - @Override - public String getGroupName() - { - return getLabel(); - } - - - @Override - protected TableInfo getTableInfo() - { - return SpecimenSchema.get().getTableInfoSampleRequestActor(); - } -} diff --git a/specimen/src/org/labkey/specimen/model/SpecimenRequestEvent.java b/specimen/src/org/labkey/specimen/model/SpecimenRequestEvent.java deleted file mode 100644 index 06d7a6ce28c..00000000000 --- a/specimen/src/org/labkey/specimen/model/SpecimenRequestEvent.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2008-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.model; - -import org.jetbrains.annotations.NotNull; -import org.labkey.api.attachments.AttachmentParent; -import org.labkey.api.attachments.AttachmentParentType; -import org.labkey.api.data.Container; -import org.labkey.api.study.AbstractStudyCachable; - -import java.util.Date; -import java.util.Objects; - -public class SpecimenRequestEvent extends AbstractStudyCachable implements AttachmentParent -{ - private int _rowId; - private String _entityId; - private int _createdBy; - private long _created; - private Container _container; - private int _requestId; - private Integer _requirementId; - private String _comments; - private String _entryType; - - public String getComments() - { - return _comments; - } - - public void setComments(String comments) - { - verifyMutability(); - _comments = comments; - } - - @Override - public Container getContainer() - { - return _container; - } - - @Override - public String getContainerId() - { - return _container.getId(); - } - - @Override - public void setContainer(Container container) - { - verifyMutability(); - _container = container; - } - - public Date getCreated() - { - return new Date(_created); - } - - public void setCreated(Date created) - { - verifyMutability(); - _created = created.getTime(); - } - - public int getCreatedBy() - { - return _createdBy; - } - - public void setCreatedBy(int createdBy) - { - verifyMutability(); - _createdBy = createdBy; - } - - @Override - public String getEntityId() - { - return _entityId; - } - - public void setEntityId(String entityId) - { - verifyMutability(); - _entityId = entityId; - } - - public String getEntryType() - { - return _entryType; - } - - public void setEntryType(String entryType) - { - verifyMutability(); - _entryType = entryType; - } - - public int getRequestId() - { - return _requestId; - } - - public void setRequestId(int requestId) - { - verifyMutability(); - _requestId = requestId; - } - - @Override - public Integer getPrimaryKey() - { - return getRowId(); - } - - public int getRowId() - { - return _rowId; - } - - public void setRowId(int rowId) - { - verifyMutability(); - _rowId = rowId; - } - - public Integer getRequirementId() - { - return _requirementId; - } - - public void setRequirementId(Integer requirementId) - { - verifyMutability(); - _requirementId = requirementId; - } - - @Override - public @NotNull AttachmentParentType getAttachmentParentType() - { - return SpecimenRequestEventType.get(); - } - - @Override - public boolean equals(Object o) - { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - SpecimenRequestEvent that = (SpecimenRequestEvent) o; - return _rowId == that._rowId && _createdBy == that._createdBy && _created == that._created && _requestId == that._requestId && Objects.equals(_entityId, that._entityId) && Objects.equals(_container, that._container) && Objects.equals(_requirementId, that._requirementId) && Objects.equals(_comments, that._comments) && Objects.equals(_entryType, that._entryType); - } - - @Override - public int hashCode() - { - return Objects.hash(_rowId, _entityId, _createdBy, _created, _container, _requestId, _requirementId, _comments, _entryType); - } -} diff --git a/specimen/src/org/labkey/specimen/model/SpecimenRequestEventType.java b/specimen/src/org/labkey/specimen/model/SpecimenRequestEventType.java deleted file mode 100644 index 44465008630..00000000000 --- a/specimen/src/org/labkey/specimen/model/SpecimenRequestEventType.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2017-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.model; - -import org.jetbrains.annotations.NotNull; -import org.labkey.api.attachments.AttachmentParentType; -import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.TableInfo; -import org.labkey.api.specimen.SpecimenSchema; - -public class SpecimenRequestEventType implements AttachmentParentType -{ - private static final SpecimenRequestEventType INSTANCE = new SpecimenRequestEventType(); - - public static SpecimenRequestEventType get() - { - return INSTANCE; - } - - private SpecimenRequestEventType() - { - } - - @Override - public @NotNull String getUniqueName() - { - return "SpecimenRequest"; - } - - @Override - public @NotNull SQLFragment getSelectEntityIdAndDescriptionSql() - { - TableInfo table = SpecimenSchema.get().getTableInfoSampleRequestEvent(); - return new SQLFragment("SELECT EntityId, ") - .append(table.getSqlDialect().concatenate("'RowId:'", "CAST(RowId AS VARCHAR)")) - .append(" AS Description FROM ") - .append(table, "sre"); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/model/SpecimenTypeSummary.java b/specimen/src/org/labkey/specimen/model/SpecimenTypeSummary.java deleted file mode 100644 index b53e1825689..00000000000 --- a/specimen/src/org/labkey/specimen/model/SpecimenTypeSummary.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright (c) 2008-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.model; - -import org.labkey.api.data.Container; -import org.labkey.api.study.SpecimenUrls; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.view.ActionURL; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -public class SpecimenTypeSummary -{ - public static abstract class TypeCount - { - private final Container _container; - private final TypeCount _parent; - private final String _label; - private final Integer _id; - - private int _vialCount = 0; - - private TypeCount(Container container, SpecimenTypeSummary.TypeCount parent, String label, Integer id) - { - _container = container; - _parent = parent; - _label = label; - _id = id; - } - - public int getVialCount() - { - return _vialCount; - } - - public void setVialCount(int vialCount) - { - _vialCount = vialCount; - } - - public String getLabel() - { - return _label; - } - - public Integer getId() - { - return _id; - } - - public TypeCount getParent() - { - return _parent; - } - - public String geDisplayLabel() - { - StringBuilder label = new StringBuilder(); - buildDisplayLabel(label); - return label.toString(); - } - - private void buildDisplayLabel(StringBuilder label) - { - if (_parent != null) - { - _parent.buildDisplayLabel(label); - label.append(", "); - } - String currentLabel = getLabel(); - label.append(currentLabel != null ? currentLabel : "[unknown]"); - } - - public ActionURL getURL() - { - ActionURL url = PageFlowUtil.urlProvider(SpecimenUrls.class).getSpecimensURL(_container, true); - addFilterParameters("SpecimenDetail", url); - return url; - } - - private void addFilterParameters(String dataRegionName, ActionURL url) - { - url.addParameter(dataRegionName + "." + getSpecimenViewFilterColumn() + "~eq", getLabel()); - if (_parent != null) - _parent.addFilterParameters(dataRegionName, url); - } - - public abstract List getChildren(); - - public abstract String getSpecimenViewFilterColumn(); - } - - private class PrimaryTypeCount extends TypeCount - { - private PrimaryTypeCount(Container container, String label, Integer id) - { - super(container, null, label, id); - } - - @Override - public List getChildren() - { - return getDerivatives(this); - } - - @Override - public String getSpecimenViewFilterColumn() - { - return "PrimaryType/Description"; - } - } - - private class DerivativeTypeCount extends TypeCount - { - private final PrimaryTypeCount _parent; - - private DerivativeTypeCount(Container container, SpecimenTypeSummary.PrimaryTypeCount parent, String label, Integer id) - { - super(container, parent, label, id); - _parent = parent; - } - - @Override - public List getChildren() - { - return getAdditives(_parent, this); - } - - @Override - public String getSpecimenViewFilterColumn() - { - return "DerivativeType/Description"; - } - } - - private class AdditiveTypeCount extends TypeCount - { - private AdditiveTypeCount(Container container, SpecimenTypeSummary.DerivativeTypeCount parent, String label, Integer id) - { - super(container, parent, label, id); - } - - @Override - public List getChildren() - { - return Collections.emptyList(); - } - - @Override - public String getSpecimenViewFilterColumn() - { - return "AdditiveType/Description"; - } - } - - private final Container _container; - private final SpecimenTypeSummaryRow[] _rows; - - public SpecimenTypeSummary(Container container, SpecimenTypeSummaryRow[] rows) - { - _container = container; - _rows = rows; - } - - private boolean safeEqual(Object first, Object second) - { - if (first == null && second == null) - return true; - if (first == null || second == null) - return false; - return first.equals(second); - } - - public List getPrimaryTypes() - { - List counts = new ArrayList<>(); - PrimaryTypeCount current = null; - for (SpecimenTypeSummaryRow row : _rows) - { - if (current == null || !safeEqual(row.getPrimaryType(), current.getLabel())) - { - current = new PrimaryTypeCount(_container, row.getPrimaryType(), row.getPrimaryTypeId()); - counts.add(current); - } - current.setVialCount(current.getVialCount() + row.getVialCount().intValue()); - } - return counts; - } - - public List getDerivatives() - { - return getDerivatives(null); - } - - public List getDerivatives(PrimaryTypeCount primaryType) - { - Map counts = new TreeMap<>(); - DerivativeTypeCount current; - for (SpecimenTypeSummaryRow row : _rows) - { - if (primaryType == null || safeEqual(primaryType.getLabel(), row.getPrimaryType())) - { - String key = row.getDerivative() + "/" + row.getDerivative(); - current = counts.get(key); - if (current == null) - { - current = new DerivativeTypeCount(_container, primaryType, row.getDerivative(), row.getDerivativeTypeId()); - counts.put(key, current); - } - current.setVialCount(current.getVialCount() + row.getVialCount().intValue()); - } - } - return new ArrayList<>(counts.values()); - } - - public List getAdditives() - { - return getAdditives(null); - } - - public List getAdditives(DerivativeTypeCount derivativeType) - { - return getAdditives(null, derivativeType); - } - - public List getAdditives(PrimaryTypeCount primaryType, DerivativeTypeCount derivativeType) - { - Map counts = new TreeMap<>(); - AdditiveTypeCount current; - for (SpecimenTypeSummaryRow row : _rows) - { - if ((derivativeType == null || safeEqual(derivativeType.getLabel(), row.getDerivative())) && - (primaryType == null || safeEqual(primaryType.getLabel(), row.getPrimaryType()))) - { - String key = row.getAdditive() + "/" + row.getAdditive(); - current = counts.get(key); - if (current == null) - { - current = new AdditiveTypeCount(_container, derivativeType, row.getAdditive(), row.getAdditiveTypeId()); - counts.put(key, current); - } - current.setVialCount(current.getVialCount() + row.getVialCount().intValue()); - } - } - if (counts.size() == 1) - { - String label = counts.values().iterator().next().getLabel(); - if (label == null || label.isEmpty() || "None".equalsIgnoreCase(label)) - return Collections.emptyList(); - } - return new ArrayList<>(counts.values()); - } -} diff --git a/specimen/src/org/labkey/specimen/model/SpecimenTypeSummaryRow.java b/specimen/src/org/labkey/specimen/model/SpecimenTypeSummaryRow.java deleted file mode 100644 index 8902b4c84d5..00000000000 --- a/specimen/src/org/labkey/specimen/model/SpecimenTypeSummaryRow.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2008-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.model; - -public class SpecimenTypeSummaryRow -{ - private String _primaryType; - private Integer _primaryTypeId; - private String _derivative; - private Integer _derivativeTypeId; - private String _additive; - private Integer _additiveTypeId; - private Integer _vialCount; - - public String getPrimaryType() - { - return _primaryType; - } - - public void setPrimaryType(String primaryType) - { - _primaryType = primaryType; - } - - public String getDerivative() - { - return _derivative; - } - - public void setDerivative(String derivative) - { - _derivative = derivative; - } - - public String getAdditive() - { - return _additive; - } - - public void setAdditive(String additive) - { - _additive = additive; - } - - public Integer getVialCount() - { - return _vialCount != null ? _vialCount : Integer.valueOf(0); - } - - public void setVialCount(Integer vialCount) - { - _vialCount = vialCount; - } - - public Integer getPrimaryTypeId() - { - return _primaryTypeId; - } - - public void setPrimaryTypeId(Integer primaryTypeId) - { - _primaryTypeId = primaryTypeId; - } - - public Integer getDerivativeTypeId() - { - return _derivativeTypeId; - } - - public void setDerivativeTypeId(Integer derivativeTypeId) - { - _derivativeTypeId = derivativeTypeId; - } - - public Integer getAdditiveTypeId() - { - return _additiveTypeId; - } - - public void setAdditiveTypeId(Integer additiveTypeId) - { - _additiveTypeId = additiveTypeId; - } -} diff --git a/specimen/src/org/labkey/specimen/notifications/ActorNotificationRecipientSet.java b/specimen/src/org/labkey/specimen/notifications/ActorNotificationRecipientSet.java deleted file mode 100644 index 7fa97a66c93..00000000000 --- a/specimen/src/org/labkey/specimen/notifications/ActorNotificationRecipientSet.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2008-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.notifications; - -import org.labkey.api.data.Container; -import org.labkey.api.security.User; -import org.labkey.api.specimen.location.LocationImpl; -import org.labkey.api.specimen.location.LocationManager; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.HtmlStringBuilder; -import org.labkey.api.util.LinkBuilder; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.util.SafeToRender; -import org.labkey.api.view.ActionURL; -import org.labkey.specimen.actions.ShowGroupMembersAction; -import org.labkey.specimen.model.SpecimenRequestActor; -import org.labkey.specimen.requirements.SpecimenRequestRequirementProvider; - -/** - * User: brittp - * Date: May 4, 2007 - * Time: 3:36:19 PM - */ -public class ActorNotificationRecipientSet extends NotificationRecipientSet -{ - private final SpecimenRequestActor _actor; - private final LocationImpl _location; - - public ActorNotificationRecipientSet(SpecimenRequestActor actor, LocationImpl location) - { - super(); - _actor = actor; - _location = location; - - User[] users = actor.getMembers(location); - String[] addresses = new String[users.length]; - boolean[] addressInactive = new boolean[users.length]; - for (int i = 0; i < users.length; i++) - { - addresses[i] = users[i].getEmail(); - addressInactive[i] = !users[i].isActive(); - } - - setEmailAddresses(addresses, addressInactive); - } - - public SpecimenRequestActor getActor() - { - return _actor; - } - - public LocationImpl getLocation() - { - return _location; - } - - @Override - public String getShortRecipientDescription() - { - StringBuilder shortDesc = new StringBuilder(); - shortDesc.append(getActor().getLabel()); - if (getLocation() != null) - shortDesc.append(", ").append(getLocation().getDisplayName()); - return shortDesc.toString(); - } - - @Override - public String getLongRecipientDescription() - { - String emailList = getShortRecipientDescription() + ": " + - getEmailAddressesAsString(", "); - return emailList; - } - - public String getFormValue() - { - return getActor().getRowId() + "," + (getLocation() != null ? getLocation().getRowId() : "-1"); - } - - public static ActorNotificationRecipientSet getFromFormValue(Container container, String formValue) - { - String[] ids = formValue.split(","); - int actorId = Integer.parseInt(ids[0]); - int locationId = Integer.parseInt(ids[1]); - SpecimenRequestActor actor = SpecimenRequestRequirementProvider.get().getActor(container, actorId); - LocationImpl location = locationId >= 0 ? LocationManager.get().getLocation(container, locationId) : null; - return new ActorNotificationRecipientSet(actor, location); - } - - private HtmlString getConfigureEmailsLinkHTML(ActionURL currentUrl) - { - ActionURL url = ShowGroupMembersAction.getShowGroupMembersURL( - getActor().getContainer(), - getActor().getRowId(), - getLocation() != null ? getLocation().getRowId() : null, - currentUrl - ); - return LinkBuilder.labkeyLink("Configure Addresses", url).getHtmlString(); - } - - public SafeToRender getHtmlDescriptionAndLink(boolean hasEmailAddresses, ActionURL currentUrl) - { - HtmlStringBuilder builder = HtmlStringBuilder.of(getShortRecipientDescription()); - if (hasEmailAddresses) - { - builder.append(PageFlowUtil.popupHelp( - HtmlStringBuilder.of(HtmlString.unsafe(getEmailAddressesAsString("
") + "
")) - .append(getConfigureEmailsLinkHTML(currentUrl)).getHtmlString(), "Group Members")); - } - else - builder.append(" ").append(getConfigureEmailsLinkHTML(currentUrl)); - return builder; - } -} diff --git a/specimen/src/org/labkey/specimen/notifications/DefaultRequestNotification.java b/specimen/src/org/labkey/specimen/notifications/DefaultRequestNotification.java deleted file mode 100644 index cc13757db0d..00000000000 --- a/specimen/src/org/labkey/specimen/notifications/DefaultRequestNotification.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2008-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.notifications; - -import org.jetbrains.annotations.NotNull; -import org.labkey.api.attachments.Attachment; -import org.labkey.api.attachments.AttachmentFile; -import org.labkey.api.attachments.AttachmentService; -import org.labkey.api.attachments.ByteArrayAttachmentFile; -import org.labkey.api.data.ColumnHeaderType; -import org.labkey.api.data.ExcelWriter; -import org.labkey.api.data.TSVWriter; -import org.labkey.api.specimen.Vial; -import org.labkey.specimen.query.SpecimenQueryView; -import org.labkey.api.view.ViewContext; -import org.labkey.specimen.model.SpecimenRequestEvent; -import org.labkey.specimen.requirements.SpecimenRequest; -import org.labkey.specimen.requirements.SpecimenRequestRequirement; -import org.labkey.specimen.settings.RequestNotificationSettings; -import org.labkey.specimen.settings.SettingsManager; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - -public class DefaultRequestNotification -{ - protected List _recipients; - protected SpecimenRequest _request; - protected String _eventSummary; - protected SpecimenRequestRequirement _specimenRequestRequirement; - protected String _comments; - protected SpecimenRequestEvent _event; - - public DefaultRequestNotification(SpecimenRequest request, List recipients, String eventSummary, - SpecimenRequestEvent event, String comments, SpecimenRequestRequirement specimenRequestRequirement, - ViewContext context) throws Exception - { - _request = request; - _recipients = recipients; - _eventSummary = eventSummary; - _comments = comments; - _specimenRequestRequirement = specimenRequestRequirement; - _event = event; - addSpecimenListFileIfNeeded(context); - } - - public final String getSpecimenListHTML(ViewContext context) throws SQLException, IOException - { - List vials = getSpecimenList(); - if (vials != null && !vials.isEmpty()) - { - SpecimenQueryView view = SpecimenQueryView.createView(context, vials, SpecimenQueryView.ViewType.VIALS_EMAIL); - view.setDisableLowVialIndicators(true); - view.getSettings().setMaxRows(-1); - return view.getSimpleHtmlTable(); - } - return null; - } - - private void addSpecimenListFileIfNeeded(ViewContext context) throws Exception - { - RequestNotificationSettings settings = SettingsManager.get().getRequestNotificationSettings(_request.getContainer()); - if (RequestNotificationSettings.SpecimensAttachmentEnum.ExcelAttachment == settings.getSpecimensAttachmentEnum() || - RequestNotificationSettings.SpecimensAttachmentEnum.TextAttachment == settings.getSpecimensAttachmentEnum()) - { - final ByteArrayAttachmentFile specimenListFile; - List vials = getSpecimenList(); - if (vials != null && !vials.isEmpty()) - { - SpecimenQueryView view = SpecimenQueryView.createView(context, vials, SpecimenQueryView.ViewType.VIALS_EMAIL); - view.getSettings().setMaxRows(-1); - view.setDisableLowVialIndicators(true); - if (RequestNotificationSettings.SpecimensAttachmentEnum.ExcelAttachment == settings.getSpecimensAttachmentEnum()) - specimenListFile = view.exportToExcelFile(ExcelWriter.ExcelDocumentType.xls, null, null, false); - else - specimenListFile = view.exportToTsvFile(TSVWriter.DELIM.TAB, TSVWriter.QUOTE.DOUBLE, ColumnHeaderType.Caption, null, null, false); - if (null != specimenListFile) - { - List attachments = new ArrayList<>(); - attachments.add(specimenListFile); - AttachmentService.get().addAttachments(_event, attachments, context.getUser()); - } - } - } - } - - protected List getSpecimenList() - { - return _request.getVials(); - } - - public SpecimenRequestEvent getEvent() - { - return _event; - } - - public @NotNull List getAttachments() - { - return AttachmentService.get().getAttachments(_event); - } - - public String getComments() - { - return _comments; - } - - public SpecimenRequestRequirement getRequirement() - { - return _specimenRequestRequirement; - } - - final public List getRecipients() - { - return _recipients; - } - - final public SpecimenRequest getSpecimenRequest() - { - return _request; - } - - final public String getEventSummary() - { - return _eventSummary; - } -} diff --git a/specimen/src/org/labkey/specimen/notifications/NotificationRecipientSet.java b/specimen/src/org/labkey/specimen/notifications/NotificationRecipientSet.java deleted file mode 100644 index 67683db788b..00000000000 --- a/specimen/src/org/labkey/specimen/notifications/NotificationRecipientSet.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2008-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.notifications; - -import jakarta.mail.Address; - -/** - * User: brittp - * Date: May 4, 2007 - * Time: 3:35:26 PM - */ -public class NotificationRecipientSet -{ - private String[] _addresses; - private boolean[] _addressInactiveBits; - private int _countInactiveEmailAddresses = 0; - - public NotificationRecipientSet(Address[] addresses) - { - _addresses = new String[addresses.length]; - _addressInactiveBits = new boolean[addresses.length]; // all false by default - int i = 0; - for (Address address : addresses) - _addresses[i++] = address.toString(); - } - - protected NotificationRecipientSet() - { - } - - public String[] getAllEmailAddresses() - { - return _addresses; - } - - public String[] getEmailAddresses(boolean includeInactiveUsers) - { - if (0 == _countInactiveEmailAddresses || includeInactiveUsers) - return _addresses; - String[] addresses = new String[_addresses.length - _countInactiveEmailAddresses]; - int k = 0; - for (int i = 0; i < _addresses.length; i += 1) - { - if (!_addressInactiveBits[i]) - { - addresses[k] = _addresses[i]; - k += 0; - } - } - return addresses; - } - - protected void setEmailAddresses(String[] addresses, boolean[] inactiveBits) - { - _addresses = addresses; - assert (addresses.length == inactiveBits.length); - _addressInactiveBits = inactiveBits; - - for (boolean inactive : _addressInactiveBits) - if (inactive) - _countInactiveEmailAddresses += 1; - } - - public String getShortRecipientDescription() - { - StringBuilder builder = new StringBuilder(); - boolean first = true; - for (String email : _addresses) - { - if (!first) - builder.append(", "); - builder.append(email); - first = false; - } - return builder.toString(); - } - - public String getLongRecipientDescription() - { - return getShortRecipientDescription(); - } - - public String getEmailAddressesAsString(String separator) - { - StringBuilder emailList = new StringBuilder(); - assert (null != _addresses && null != _addressInactiveBits && _addresses.length == _addressInactiveBits.length); - boolean first = true; - for (int i = 0; i < _addresses.length; i += 1) - { - if (!first) - emailList.append(separator); - if (_addressInactiveBits[i]) - emailList.append(""); - emailList.append(_addresses[i]); - if (_addressInactiveBits[i]) - emailList.append(""); - first = false; - } - return emailList.toString(); - } - - public boolean hasInactiveEmailAddress() - { - return _countInactiveEmailAddresses > 0; - } -} diff --git a/specimen/src/org/labkey/specimen/pipeline/FileAnalysisSpecimenTask.java b/specimen/src/org/labkey/specimen/pipeline/FileAnalysisSpecimenTask.java deleted file mode 100644 index 5427de2966c..00000000000 --- a/specimen/src/org/labkey/specimen/pipeline/FileAnalysisSpecimenTask.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2020-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.pipeline; - -import org.apache.commons.lang3.BooleanUtils; -import org.apache.tika.detect.DefaultDetector; -import org.apache.tika.io.TikaInputStream; -import org.apache.tika.metadata.Metadata; -import org.apache.tika.mime.MediaType; -import org.jetbrains.annotations.Nullable; -import org.labkey.api.admin.ImportException; -import org.labkey.api.admin.PipelineJobLoggerGetter; -import org.labkey.api.pipeline.PipelineJob; -import org.labkey.api.pipeline.PipelineJobException; -import org.labkey.api.pipeline.file.FileAnalysisJobSupport; -import org.labkey.api.util.FileUtil; -import org.labkey.specimen.importer.AbstractSpecimenTask; -import org.labkey.specimen.importer.AbstractSpecimenTaskFactory; -import org.labkey.api.study.importer.SimpleStudyImportContext; -import org.labkey.api.util.DateUtil; -import org.labkey.api.writer.FileSystemFile; -import org.labkey.api.writer.VirtualFile; -import org.labkey.vfs.FileLike; - -import java.io.IOException; -import java.util.Date; -import java.util.List; - -// Registered by the specimen module (specimenContext.xml), so specimen module is always present when this code is invoked. -public class FileAnalysisSpecimenTask extends AbstractSpecimenTask -{ - public static final String MERGE_SPECIMEN = "mergeSpecimen"; - - public FileAnalysisSpecimenTask(FileAnalysisSpecimenTask.Factory factory, PipelineJob job) - { - super(factory, job); - } - - @Override - protected FileLike getSpecimenFile(PipelineJob job) - { - FileAnalysisJobSupport support = job.getJobSupport(FileAnalysisJobSupport.class); - List paths = support.getInputFiles(); - // there should only be a single file associated with this task - assert paths.size() == 1; - return paths.getFirst(); - } - - @Override - protected SimpleStudyImportContext getImportContext(PipelineJob job) - { - return new SimpleStudyImportContext(job.getUser(), job.getContainer(), null, null, new PipelineJobLoggerGetter(job), null); - } - - @Override - protected boolean isMerge() - { - FileAnalysisJobSupport support = getJob().getJobSupport(FileAnalysisJobSupport.class); - boolean isMerge = false; - - if (support.getParameters().containsKey(MERGE_SPECIMEN)) - isMerge = BooleanUtils.toBoolean(support.getParameters().get(MERGE_SPECIMEN)); - - getJob().getLogger().info("Specimen merge option is set to: {}", isMerge); - return isMerge; - } - - @Override - public AbstractSpecimenTask.ImportHelper getImportHelper() - { - return new ImportHelper(); - } - - private static class ImportHelper extends DefaultImportHelper - { - private FileLike _tempDir; - - @Override - public VirtualFile getSpecimenDir(PipelineJob job, SimpleStudyImportContext ctx, @Nullable FileLike inputFile) throws ImportException, PipelineJobException - { - if (inputFile != null) - { - try (TikaInputStream is = TikaInputStream.get(inputFile.openInputStream())) - { - // determine the type of file being imported - DefaultDetector detector = new DefaultDetector(); - MediaType type = detector.detect(is, new Metadata()); - - if (MediaType.APPLICATION_ZIP.equals(type)) - { - ctx.getLogger().info("Specimen archive detected"); - return super.getSpecimenDir(job, ctx, inputFile); - } - else if (MediaType.TEXT_PLAIN.equals(type)) - { - // this is a loose file that has already been extracted from the archive - // copy the single file to a temp directory and process it there - // - ctx.getLogger().info("Single specimen file detected, moving to a temp folder for processing."); - String tempDirName = DateUtil.formatDateTime(new Date(), "yyMMddHHmmssSSS"); - _tempDir = inputFile.getParent().resolveChild(tempDirName); - FileUtil.createDirectory(_tempDir); - FileUtil.copyFile(inputFile, _tempDir.resolveChild(inputFile.getName())); - - return new FileSystemFile(_tempDir); - } - else - throw new RuntimeException("Unknown specimen file type uploaded"); - } - catch (IOException e) - { - throw new RuntimeException(e); - } - } - return null; - } - - @Override - public void afterImport(SimpleStudyImportContext ctx) - { - super.afterImport(ctx); - - if (_tempDir != null) - delete(_tempDir, ctx); - } - } - - public static class Factory extends AbstractSpecimenTaskFactory - { - public Factory() - { - super(FileAnalysisSpecimenTask.class); - } - - @Override - public FileAnalysisSpecimenTask createTask(PipelineJob job) - { - return new FileAnalysisSpecimenTask(this, job); - } - } -} diff --git a/specimen/src/org/labkey/specimen/pipeline/SpecimenArchive.java b/specimen/src/org/labkey/specimen/pipeline/SpecimenArchive.java deleted file mode 100644 index 5688e35722e..00000000000 --- a/specimen/src/org/labkey/specimen/pipeline/SpecimenArchive.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2009-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.pipeline; - -import org.labkey.api.data.Container; -import org.labkey.api.study.SpecimenService; -import org.labkey.api.study.SpecimenTransform; -import org.labkey.vfs.FileLike; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Date; -import java.util.Enumeration; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -/* -* User: adam -* Date: Sep 1, 2009 -* Time: 2:32:53 PM -*/ -public class SpecimenArchive -{ - private final FileLike _definitionFile; - - public SpecimenArchive(FileLike definitionFile) - { - _definitionFile = definitionFile; - } - - public FileLike getDefinitionFile() - { - return _definitionFile; - } - - // Move to ZipUtil? - public List getEntryDescriptions(Container container) throws IOException - { - List entryList = new ArrayList<>(); - for (SpecimenTransform transform : SpecimenService.get().getSpecimenTransforms(container)) - { - if (transform.getFileType().isType(_definitionFile)) - { - entryList.add(new EntryDescription(_definitionFile.getName(), _definitionFile.getSize(), new Date(_definitionFile.getLastModified()))); - return entryList; - } - } - - // standard non-transformed specimen archive - try (ZipFile zip = new ZipFile(_definitionFile.toNioPathForRead().toFile())) - { - Enumeration entries = zip.entries(); - while (entries.hasMoreElements()) - { - ZipEntry entry = entries.nextElement(); - if (entry.isDirectory()) - continue; - entryList.add(new EntryDescription(entry.getName(), entry.getSize(), new Date(entry.getTime()))); - } - } - - return entryList; - } - - public static class EntryDescription - { - private final String _name; - private final long _size; - private final Date _date; - - public EntryDescription(String name, long size, Date date) - { - _name = name; - _size = size; - _date = date; - } - - public Date getDate() - { - return _date; - } - - public String getName() - { - return _name; - } - - public long getSize() - { - return _size; - } - } -} diff --git a/specimen/src/org/labkey/specimen/pipeline/SpecimenBatch.java b/specimen/src/org/labkey/specimen/pipeline/SpecimenBatch.java deleted file mode 100644 index 7bb40fe1fcb..00000000000 --- a/specimen/src/org/labkey/specimen/pipeline/SpecimenBatch.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2008-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.pipeline; - -import org.labkey.api.admin.PipelineJobLoggerGetter; -import org.labkey.api.pipeline.PipeRoot; -import org.labkey.api.pipeline.PipelineJobService; -import org.labkey.api.pipeline.TaskId; -import org.labkey.api.pipeline.TaskPipeline; -import org.labkey.api.portal.ProjectUrls; -import org.labkey.api.specimen.SpecimensPage; -import org.labkey.api.study.importer.SimpleStudyImportContext; -import org.labkey.api.study.pipeline.StudyBatch; -import org.labkey.api.util.FileUtil; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.ViewBackgroundInfo; -import org.labkey.vfs.FileLike; - -import java.io.Serializable; - -/** - * User: brittp - * Date: Mar 14, 2006 - * Time: 5:04:38 PM - */ - -// Pipeline job used for importing individual specimen archives (not as part of a study). -public class SpecimenBatch extends StudyBatch implements Serializable, SpecimenJobSupport -{ - private boolean _isMerge; - - // For serialization - protected SpecimenBatch() {} - - public SpecimenBatch(ViewBackgroundInfo info, FileLike definitionFile, PipeRoot root, boolean merge) - { - super(info, definitionFile, root); - _isMerge = merge; - } - - @Override - protected FileLike createLogFile() - { - return getPipeRoot().getLogDirectory(true).resolveChild(FileUtil.makeFileNameWithTimestamp(_definitionFile.getName(), "log")); - } - - @Override - public String getDescription() - { - String description = "Import specimens"; - if (_definitionFile != null) - description += ": " + _definitionFile.getName(); - return description; - } - - @Override - public ActionURL getStatusHref() - { - return PageFlowUtil.urlProvider(ProjectUrls.class).getBeginURL(getInfo().getContainer(), SpecimensPage.PAGE_ID); - } - - @Override - public FileLike getSpecimenArchivePath() - { - return _definitionFile; - } - - @Override - public boolean isMerge() - { - return _isMerge; - } - - @Override - public SimpleStudyImportContext getImportContext() - { - return new SimpleStudyImportContext(getUser(), getContainer(), null, null, new PipelineJobLoggerGetter(this), null); - } - - @Override - public TaskPipeline getTaskPipeline() - { - return PipelineJobService.get().getTaskPipeline(new TaskId(SpecimenBatch.class)); - } -} diff --git a/specimen/src/org/labkey/specimen/pipeline/SpecimenJobSupport.java b/specimen/src/org/labkey/specimen/pipeline/SpecimenJobSupport.java deleted file mode 100644 index 17c43c128d7..00000000000 --- a/specimen/src/org/labkey/specimen/pipeline/SpecimenJobSupport.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2009-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.pipeline; - -import org.labkey.api.study.importer.SimpleStudyImportContext; -import org.labkey.vfs.FileLike; - -/* -* User: adam -* Date: Sep 1, 2009 -* Time: 3:02:30 PM -*/ -public interface SpecimenJobSupport -{ - FileLike getSpecimenArchivePath(); - boolean isMerge(); - SimpleStudyImportContext getImportContext(); -} diff --git a/specimen/src/org/labkey/specimen/pipeline/SpecimenPipeline.java b/specimen/src/org/labkey/specimen/pipeline/SpecimenPipeline.java deleted file mode 100644 index ab1c81dd0d9..00000000000 --- a/specimen/src/org/labkey/specimen/pipeline/SpecimenPipeline.java +++ /dev/null @@ -1,72 +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.pipeline; - -import org.labkey.api.module.Module; -import org.labkey.api.pipeline.PipeRoot; -import org.labkey.api.pipeline.PipelineDirectory; -import org.labkey.api.pipeline.PipelineProvider; -import org.labkey.api.security.permissions.InsertPermission; -import org.labkey.specimen.importer.AbstractSpecimenTask; -import org.labkey.api.study.SpecimenService; -import org.labkey.api.study.SpecimenTransform; -import org.labkey.api.study.StudyService; -import org.labkey.api.view.ViewContext; -import org.labkey.specimen.actions.SpecimenController.ImportSpecimenDataAction; - -import java.io.File; - -public class SpecimenPipeline extends PipelineProvider -{ - public SpecimenPipeline(Module owningModule) - { - super("Specimen", owningModule); - } - - @Override - public void updateFileProperties(final ViewContext context, PipeRoot pr, PipelineDirectory directory, boolean includeAll) - { - if ( - !context.getContainer().hasPermission(context.getUser(), InsertPermission.class) || - context.getContainer().isDataspace() || // Cannot import specimens into Dataspace container - StudyService.get().getStudy(context.getContainer()) == null) - { - return; - } - - File[] files = directory.listFiles(new FileEntryFilter() - { - @Override - public boolean accept(File f) - { - if (AbstractSpecimenTask.ARCHIVE_FILE_TYPE.isType(f)) - return true; - else - { - for (SpecimenTransform transform : SpecimenService.get().getSpecimenTransforms(context.getContainer())) - { - if (transform.getFileType().isType(f)) - return true; - } - } - return false; - } - }); - - String actionId = createActionId(ImportSpecimenDataAction.class, "Import Specimen Data"); - addAction(actionId, ImportSpecimenDataAction.class, "Import Specimen Data", directory, files, true, false, includeAll); - } -} diff --git a/specimen/src/org/labkey/specimen/pipeline/SpecimenReloadJob.java b/specimen/src/org/labkey/specimen/pipeline/SpecimenReloadJob.java deleted file mode 100644 index 7c21cdc51e9..00000000000 --- a/specimen/src/org/labkey/specimen/pipeline/SpecimenReloadJob.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2014-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.pipeline; - -import org.labkey.api.pipeline.PipeRoot; -import org.labkey.api.pipeline.PipelineJobService; -import org.labkey.api.pipeline.TaskId; -import org.labkey.api.pipeline.TaskPipeline; -import org.labkey.api.study.SpecimenTransform; -import org.labkey.api.util.FileUtil; -import org.labkey.api.view.ViewBackgroundInfo; -import org.labkey.vfs.FileLike; - -import java.io.Serializable; - -/** - * Created by klum on 5/24/2014. - */ -public class SpecimenReloadJob extends SpecimenBatch implements Serializable, SpecimenReloadJobSupport -{ - private String _transformName; - private SpecimenTransform.ExternalImportConfig _importConfig; - - // For serialization - protected SpecimenReloadJob() {} - - public SpecimenReloadJob(ViewBackgroundInfo info, PipeRoot root, String transformName) - { - super(info, null, root, false); - - FileLike logFile = root.resolvePathToFileLike(FileUtil.makeFileNameWithTimestamp("specimen_reload", "log")); - setLogFile(logFile); - _transformName = transformName; - } - - @Override - public void setSpecimenArchive(FileLike archiveFile) - { - _definitionFile = archiveFile; - } - - @Override - public String getSpecimenTransform() - { - return _transformName; - } - - public void setExternalImportConfig(SpecimenTransform.ExternalImportConfig config) - { - _importConfig = config; - } - - @Override - public SpecimenTransform.ExternalImportConfig getExternalImportConfig() - { - return _importConfig; - } - - @Override - public TaskPipeline getTaskPipeline() - { - return PipelineJobService.get().getTaskPipeline(new TaskId(SpecimenReloadJob.class)); - } -} diff --git a/specimen/src/org/labkey/specimen/pipeline/SpecimenReloadJobSupport.java b/specimen/src/org/labkey/specimen/pipeline/SpecimenReloadJobSupport.java deleted file mode 100644 index d5d2affb5f2..00000000000 --- a/specimen/src/org/labkey/specimen/pipeline/SpecimenReloadJobSupport.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2014-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.pipeline; - -import org.labkey.api.study.SpecimenTransform; -import org.labkey.vfs.FileLike; - -/** - * Created by klum on 5/24/2014. - */ -public interface SpecimenReloadJobSupport extends SpecimenJobSupport -{ - void setSpecimenArchive(FileLike archiveFile); - - String getSpecimenTransform(); - - SpecimenTransform.ExternalImportConfig getExternalImportConfig(); -} diff --git a/specimen/src/org/labkey/specimen/pipeline/SpecimenReloadTask.java b/specimen/src/org/labkey/specimen/pipeline/SpecimenReloadTask.java deleted file mode 100644 index c2d4e7d0bf3..00000000000 --- a/specimen/src/org/labkey/specimen/pipeline/SpecimenReloadTask.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2014-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.pipeline; - -import org.jetbrains.annotations.NotNull; -import org.labkey.api.pipeline.AbstractTaskFactory; -import org.labkey.api.pipeline.AbstractTaskFactorySettings; -import org.labkey.api.pipeline.PipeRoot; -import org.labkey.api.pipeline.PipelineJob; -import org.labkey.api.pipeline.PipelineJobException; -import org.labkey.api.pipeline.PipelineService; -import org.labkey.api.pipeline.RecordedActionSet; -import org.labkey.api.study.SpecimenService; -import org.labkey.api.study.SpecimenTransform; -import org.labkey.api.util.FileType; -import org.labkey.api.util.FileUtil; -import org.labkey.vfs.FileLike; - -import java.util.Collections; -import java.util.List; - -/** - * Created by klum on 5/24/2014. - */ -public class SpecimenReloadTask extends PipelineJob.Task -{ - private SpecimenReloadTask(Factory factory, PipelineJob job) - { - super(factory, job); - } - - @NotNull - @Override - public RecordedActionSet run() throws PipelineJobException - { - PipelineJob job = getJob(); - SpecimenReloadJobSupport support = job.getJobSupport(SpecimenReloadJobSupport.class); - String specimenTransformName = support.getSpecimenTransform(); - SpecimenTransform transform = SpecimenService.get().getSpecimenTransform(specimenTransformName); - - if (transform != null) - { - PipeRoot root = PipelineService.get().findPipelineRoot(job.getContainer()); - if (root != null) - { - FileLike archive = root.getRootFileLike().resolveChild(FileUtil.makeFileNameWithTimestamp("specimen_reload", transform.getFileType().getDefaultSuffix())); - transform.importFromExternalSource(job, support.getExternalImportConfig(), archive); - - support.setSpecimenArchive(archive); - return new RecordedActionSet(); - } - } - - throw new PipelineJobException("Failed to locate a specimen transform implementation for the name: " + specimenTransformName); - } - - public static class Factory extends AbstractTaskFactory - { - public Factory() - { - super(SpecimenReloadTask.class); - } - - @Override - public PipelineJob.Task createTask(PipelineJob job) - { - return new SpecimenReloadTask(this, job); - } - - @Override - public List getInputTypes() - { - return Collections.emptyList(); - } - - @Override - public List getProtocolActionNames() - { - return Collections.emptyList(); - } - - @Override - public String getStatusName() - { - return "RELOAD SPECIMEN"; - } - - @Override - public boolean isJobComplete(PipelineJob job) - { - return false; - } - } -} diff --git a/specimen/src/org/labkey/specimen/pipeline/StandaloneSpecimenTask.java b/specimen/src/org/labkey/specimen/pipeline/StandaloneSpecimenTask.java deleted file mode 100644 index e76444eaa6b..00000000000 --- a/specimen/src/org/labkey/specimen/pipeline/StandaloneSpecimenTask.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2009-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.pipeline; - -import org.labkey.api.pipeline.PipelineJob; -import org.labkey.specimen.importer.AbstractSpecimenTask; -import org.labkey.specimen.importer.AbstractSpecimenTaskFactory; - -/* -* User: adam -* Date: Sep 1, 2009 -* Time: 4:37:31 PM -*/ - -// This task is used to import specimen archives directly via the pipeline ui. SpecimenBatch is the associated pipeline job. -// Registered by the specimen module (specimenContext.xml), so specimen module is always present when this code is invoked. -public class StandaloneSpecimenTask extends AbstractSpecimenTask -{ - private StandaloneSpecimenTask(Factory factory, PipelineJob job) - { - super(factory, job); - } - - public static class Factory extends AbstractSpecimenTaskFactory - { - public Factory() - { - super(StandaloneSpecimenTask.class); - } - - @Override - public PipelineJob.Task createTask(PipelineJob job) - { - return new StandaloneSpecimenTask(this, job); - } - } -} diff --git a/specimen/src/org/labkey/specimen/query/BaseSpecimenPivotTable.java b/specimen/src/org/labkey/specimen/query/BaseSpecimenPivotTable.java deleted file mode 100644 index eb9fdc2fd01..00000000000 --- a/specimen/src/org/labkey/specimen/query/BaseSpecimenPivotTable.java +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Copyright (c) 2012-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.query; - -import org.apache.logging.log4j.LogManager; -import org.jetbrains.annotations.NotNull; -import org.labkey.api.action.SpringActionController; -import org.labkey.api.collections.CaseInsensitiveHashSet; -import org.labkey.api.collections.IntHashMap; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.Container; -import org.labkey.api.data.ContainerFilter; -import org.labkey.api.data.MutableColumnInfo; -import org.labkey.api.data.PropertyManager; -import org.labkey.api.data.PropertyManager.WritablePropertyMap; -import org.labkey.api.data.TableInfo; -import org.labkey.api.query.AliasedColumn; -import org.labkey.api.query.FilteredTable; -import org.labkey.api.query.UserSchema; -import org.labkey.api.security.UserPrincipal; -import org.labkey.api.security.permissions.Permission; -import org.labkey.api.security.permissions.ReadPermission; -import org.labkey.api.specimen.location.LocationImpl; -import org.labkey.api.specimen.location.LocationManager; -import org.labkey.specimen.model.SpecimenTypeSummary; -import org.labkey.api.study.StudyService; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.model.PrimaryType; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * User: klum - * Date: Mar 14, 2012 - */ -public abstract class BaseSpecimenPivotTable extends FilteredTable -{ - protected static final String AGGREGATE_DELIM = "::"; - protected static final String TYPE_DELIM = "-"; - protected Set _wrappedColumnNames = new CaseInsensitiveHashSet(); - - protected static String getNormalName(String name) - { - return name.toLowerCase(); - } - - public class LegalCaseInsensitiveMap - { - // Map of names to normalized and legalized names - // If normalized name is duplicated, it is added to the Duplicated set - private final HashMap _nameToNormalName = new HashMap<>(); - private final HashSet _normalNameDuplicated = new HashSet<>(); - - public void putName(String key) - { - putName(key, -1); - } - - public void putName(String key, int rowId) - { - // If rowId >= 0, use it to distinguish otherwise identical keys (for site name) - if (key != null && (!_nameToNormalName.containsKey(key) || rowId >= 0)) - { - String legalName = ColumnInfo.legalNameFromName(key); - String normalName = BaseSpecimenPivotTable.getNormalName(legalName); - if (_nameToNormalName.containsValue(normalName)) - { - _normalNameDuplicated.add(normalName); - } - _nameToNormalName.put(key, normalName); - - // Note: all sites will have a non-negative rowId; it's possible a site name + rowId could be the same - // as a type name, but that's fine. A type name having the same id as a site name will cause no conflict. - - if (!_typeNameIdMapWrapper.containsKey(key, rowId)) - { - _typeNameIdMapWrapper.put(key, rowId); - } - } - } - - public String getNormalName(String key) - { - return _nameToNormalName.get(key); - } - - public boolean isDuplicated(String key) - { - return _normalNameDuplicated.contains(getNormalName(key)); - } - - /** - * use the row id to uniquify the column name, else just return the name - */ - public String getLabel(String label, int id) - { - if (label != null && isDuplicated(label)) - { - String idString = _typeNameIdMapWrapper.get(label, id); - int mappedId = (idString != null) ? Integer.valueOf(idString) : id; - return String.format("%s(%s)", getNormalName(label), mappedId); - } - else - return label; - } - } - - // For assigning unique ids to type names, so if there are legal/lowercase name conflicts we can create - // a unique id to ensure uniqueness in the queries we create - private static final String CATEGORY_NAME = "TypeNameToUniqueIdPropertyMap"; - - private static class PropertyMapWrapper - { - private Map _typeNameIdMap; - final private Container _container; - - public PropertyMapWrapper(Container container) - { - _container = container; - _typeNameIdMap = PropertyManager.getProperties(container, CATEGORY_NAME); - } - public boolean containsKey(String key, int rowId) - { - String keyWithId = getKeyWithId(key, rowId); - return _typeNameIdMap.containsKey(keyWithId); - } - public String get(String key, int rowId) - { - String keyWithId = getKeyWithId(key, rowId); - return _typeNameIdMap.get(keyWithId); - } - - public void put(String key, int rowId) - { - synchronized (_container) - { - WritablePropertyMap typeNameIdMapWritable = PropertyManager.getWritableProperties(_container, CATEGORY_NAME, true); - - String keyWithId = getKeyWithId(key, rowId); - if (!typeNameIdMapWritable.containsKey(keyWithId)) - { - int id = typeNameIdMapWritable.size() + 1; - String value = String.valueOf(id); - typeNameIdMapWritable.put(keyWithId, value); - // Questionable... but suppress for now - try (var ignored = SpringActionController.ignoreSqlUpdates()) - { - typeNameIdMapWritable.save(); - } - _typeNameIdMap = PropertyManager.getProperties(_container, CATEGORY_NAME); - } - } - } - - private String getKeyWithId(String key, int rowId) - { - return (rowId >= 0) ? key + rowId : key; - } - } - - private final PropertyMapWrapper _typeNameIdMapWrapper; - - public BaseSpecimenPivotTable(final TableInfo tinfo, final UserSchema schema) - { - super(tinfo, schema); - - LogManager.getLogger(BaseSpecimenPivotTable.class).debug("creating specimen pivot\nSCHEMA={} {}@{}\nTABLE={} {}@{}", schema.getName(), schema.getClass().getSimpleName(), System.identityHashCode(schema), tinfo.getName(), this.getClass().getSimpleName(), System.identityHashCode(this), new Throwable("stack trace")); - - addWrapColumn(_rootTable.getColumn(StudyService.get().getSubjectColumnName(getContainer()))); - addWrapColumn(_rootTable.getColumn(StudyService.get().getSubjectVisitColumnName(getContainer()))); - addWrapColumn(_rootTable.getColumn("Visit")); - - _typeNameIdMapWrapper = new PropertyMapWrapper(getContainer()); - } - - protected static class NameLabelPair - { - public String _name; - public String _label; - - public NameLabelPair(String name, String label) - { - _name = name; - _label = label; - } - } - - protected MutableColumnInfo wrapPivotColumn(ColumnInfo col, String descriptionFormat, NameLabelPair...parts) - { - // The parts._name should already be "Normal Legal Name" parts - StringBuilder name = new StringBuilder(); - StringBuilder label = new StringBuilder(); - String delim = ""; - String labelDelim = ""; - Object[] labelsForDescription = new String[parts.length]; - int i = 0; - for (NameLabelPair part : parts) - { - if (part != null && part._name != null) - { - name.append(delim).append(part._name); - label.append(labelDelim).append(part._label); - labelsForDescription[i] = part._label; - - delim = "_"; - labelDelim = ":"; - } - i += 1; - } - - // issue 21022 : duplicate primary or derivative type names results in duplicate wrapped column names - ensureUniqueWrappedName(name); - _wrappedColumnNames.add(name.toString()); - var colInfo = new AliasedColumn(this, name.toString(), col); // make lower case - colInfo.setLabel(label.toString()); - if (descriptionFormat != null) - colInfo.setDescription(String.format(descriptionFormat, labelsForDescription)); - - return addColumn(colInfo); - } - - private void ensureUniqueWrappedName(StringBuilder name) - { - int i = 1; - while (_wrappedColumnNames.contains(name.toString())) - { - name.append("_").append(i++); - } - } - - protected Map getPrimaryTypeMap(Container container) - { - Map typeMap = new IntHashMap<>(); - LegalCaseInsensitiveMap legalMap = new LegalCaseInsensitiveMap(); - SpecimenTypeSummary summary = SpecimenManager.get().getSpecimenTypeSummary(container, getUserSchema().getUser()); - List primaryTypes = summary.getPrimaryTypes(); - - for (SpecimenTypeSummary.TypeCount type : primaryTypes) - { - if (type.getId() != null) - { - legalMap.putName(type.getLabel()); - } - } - - for (SpecimenTypeSummary.TypeCount type : primaryTypes) - { - if (type.getId() != null) - typeMap.put(type.getId(), new NameLabelPair( - legalMap.getLabel(type.getLabel(), -1), type.getLabel())); - } - return typeMap; - } - - public Map getAllPrimaryTypesMap(Container container) - { - Map typeMap = new IntHashMap<>(); - LegalCaseInsensitiveMap legalMap = new LegalCaseInsensitiveMap(); - List primaryTypes = SpecimenManager.get().getPrimaryTypes(container); - - for (PrimaryType type : primaryTypes) - { - legalMap.putName(type.getPrimaryType()); - } - - for (PrimaryType type : primaryTypes) - { - typeMap.put((int)type.getRowId(), new NameLabelPair( - legalMap.getLabel(type.getPrimaryType(), -1), type.getPrimaryType())); - } - return typeMap; - } - - protected Map getDerivativeTypeMap(Container container) - { - Map typeMap = new IntHashMap<>(); - LegalCaseInsensitiveMap legalMap = new LegalCaseInsensitiveMap(); - SpecimenTypeSummary summary = SpecimenManager.get().getSpecimenTypeSummary(container, getUserSchema().getUser()); - List types = summary.getDerivatives(); - - for (SpecimenTypeSummary.TypeCount type : types) - { - if (type.getId() != null && type.getLabel() != null) - legalMap.putName(type.getLabel()); - } - - for (SpecimenTypeSummary.TypeCount type : types) - { - if (type.getId() != null && type.getLabel() != null) - typeMap.put(type.getId(), new NameLabelPair( - legalMap.getLabel(type.getLabel(), -1), type.getLabel())); - } - return typeMap; - } - - protected Map getSiteMap(Container container) - { - Map siteMap = new IntHashMap<>(); - LegalCaseInsensitiveMap legalMap = new LegalCaseInsensitiveMap(); - List locations = LocationManager.get().getLocations(container); - - for (LocationImpl location : locations) - { - legalMap.putName(location.getLabel(), location.getRowId()); - } - - for (LocationImpl location : locations) - { - siteMap.put(location.getRowId(), new NameLabelPair( - legalMap.getLabel(location.getLabel(), location.getRowId()), location.getLabel())); - } - return siteMap; - } - - /** Note we cache these pivot tables, so we can't go calling setContainerFilter() so - * return false==supportsContainerFilter() - * @return false - */ - @Override - public boolean supportsContainerFilter() - { - return false; - } - - @Override - protected void applyContainerFilter(ContainerFilter filter) - { - // Because these Pivot tables pull from tables that have already been filtered, don't apply any such filter here - } - - @Override - public boolean hasPermission(@NotNull UserPrincipal user, @NotNull Class perm) - { - return perm.equals(ReadPermission.class) && getContainer().hasPermission(user, perm); - } -} diff --git a/specimen/src/org/labkey/specimen/query/BaseSpecimenQueryView.java b/specimen/src/org/labkey/specimen/query/BaseSpecimenQueryView.java deleted file mode 100644 index 146a44ad6e5..00000000000 --- a/specimen/src/org/labkey/specimen/query/BaseSpecimenQueryView.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2008-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.query; - -import org.jetbrains.annotations.Nullable; -import org.labkey.api.data.ButtonBar; -import org.labkey.api.data.DataRegion; -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.data.Sort; -import org.labkey.api.query.QuerySettings; -import org.labkey.api.query.QueryView; -import org.labkey.api.query.UserSchema; -import org.labkey.api.study.CohortFilter; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.DataView; -import org.labkey.api.view.DisplayElement; - -import java.util.List; - -public abstract class BaseSpecimenQueryView extends QueryView -{ - protected final SimpleFilter _filter; - protected final Sort _sort; - protected @Nullable CohortFilter _cohortFilter; - private List _buttons; - - public BaseSpecimenQueryView(UserSchema schema, QuerySettings settings, SimpleFilter filter, Sort sort) - { - super(schema, settings, null); - _filter = filter; - _sort = sort; - } - - @Override - public DataView createDataView() - { - DataView view = super.createDataView(); - SimpleFilter filter = (SimpleFilter) view.getRenderContext().getBaseFilter(); - if (filter != null) - filter.addAllClauses(_filter); - else - filter = _filter; - - if (_cohortFilter != null) - _cohortFilter.addFilterCondition(view.getTable(), getContainer(), filter); - - view.getRenderContext().setBaseFilter(filter); - Sort sort = view.getRenderContext().getBaseSort(); - if (sort == null) - { - view.getRenderContext().setBaseSort(_sort); - } - else - { - sort.insertSort(_sort); - } - if (_buttons != null) - { - ButtonBar bbar = new ButtonBar(); - bbar.addAll(view.getDataRegion().getButtonBar(DataRegion.MODE_GRID).getList()); - bbar.addAll(_buttons); - view.getDataRegion().setShowRecordSelectors(true); - view.getDataRegion().setButtonBar(bbar); - } - view.getDataRegion().setButtonBarPosition(getButtonBarPosition()); - return view; - } - - protected DataRegion.ButtonBarPosition getButtonBarPosition() - { - return DataRegion.ButtonBarPosition.TOP; - } - - public void setButtons(List buttons) - { - _buttons = buttons; - } - - public ActionURL getBaseViewURL() - { - return urlBaseView(); - } -} diff --git a/specimen/src/org/labkey/specimen/query/SpecimenEventQueryView.java b/specimen/src/org/labkey/specimen/query/SpecimenEventQueryView.java deleted file mode 100644 index 09e25233a66..00000000000 --- a/specimen/src/org/labkey/specimen/query/SpecimenEventQueryView.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2008-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.query; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.data.Sort; -import org.labkey.api.query.FieldKey; -import org.labkey.api.query.QuerySettings; -import org.labkey.api.query.UserSchema; -import org.labkey.api.specimen.SpecimenQuerySchema; -import org.labkey.api.specimen.Vial; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.view.ViewContext; - -/** - * User: brittp - * Date: Jan 26, 2007 - * Time: 10:13:50 AM - */ -public class SpecimenEventQueryView extends BaseSpecimenQueryView -{ - protected SpecimenEventQueryView(UserSchema schema, QuerySettings settings, SimpleFilter filter, Sort sort) - { - super(schema, settings, filter, sort); - } - - public static SpecimenEventQueryView createView(ViewContext context, Vial vial) - { - Study study = StudyService.get().getStudy(context.getContainer()); - UserSchema schema = SpecimenQuerySchema.get(study, context.getUser()); - String queryName = "SpecimenEvent"; - QuerySettings qs = schema.getSettings(context, queryName, queryName); - SimpleFilter filter = new SimpleFilter(FieldKey.fromParts("VialId"), vial.getRowId()); - Sort sort = new Sort("-ShipDate"); - return new SpecimenEventQueryView(schema, qs, filter, sort); - } -} diff --git a/specimen/src/org/labkey/specimen/query/SpecimenPivotByDerivativeType.java b/specimen/src/org/labkey/specimen/query/SpecimenPivotByDerivativeType.java deleted file mode 100644 index a326aebbc6d..00000000000 --- a/specimen/src/org/labkey/specimen/query/SpecimenPivotByDerivativeType.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2012-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.query; - -import org.apache.commons.lang3.math.NumberUtils; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.Container; -import org.labkey.api.data.ContainerFilter; -import org.labkey.api.query.UserSchema; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; - -import java.util.Map; - -/** - * User: klum - * Date: Mar 9, 2012 - */ -public class SpecimenPivotByDerivativeType extends BaseSpecimenPivotTable -{ - public static final String PIVOT_BY_DERIVATIVE_TYPE = "Primary/Derivative Type Vial Counts"; - private static final String COLUMN_DESCRIPTION_FORMAT = "Number of vials of primary & derivative type %s/%s"; - - public SpecimenPivotByDerivativeType(final UserSchema schema, Study study, ContainerFilter cf) - { - super(SpecimenReportQuery.getPivotByDerivativeType(schema, study, cf), schema); - setName(PIVOT_BY_DERIVATIVE_TYPE); - setDescription("Contains up to one row of Specimen Primary/Derivative Type totals for each " + StudyService.get().getSubjectNounSingular(getContainer()) + - "/visit combination."); - - Container container = getContainer(); - Map primaryTypeMap = getPrimaryTypeMap(container); - Map derivativeTypeMap = getDerivativeTypeMap(container); - - for (ColumnInfo col : getRealTable().getColumns()) - { - // look for the primary/derivative pivot encoding - String[] parts = col.getName().split(AGGREGATE_DELIM); - - if (parts.length == 2) - { - String[] types = parts[0].split(TYPE_DELIM); - - if (types.length == 2) - { - int primaryId = NumberUtils.toInt(types[0]); - int derivativeId = NumberUtils.toInt(types[1]); - - if (primaryTypeMap.containsKey(primaryId) && derivativeTypeMap.containsKey(derivativeId)) - { - wrapPivotColumn(col, - COLUMN_DESCRIPTION_FORMAT, - primaryTypeMap.get(primaryId), - derivativeTypeMap.get(derivativeId), - new NameLabelPair(parts[1], parts[1]) - ); - } - } - } - } - setDefaultVisibleColumns(getDefaultVisibleColumns()); - addWrapColumn(_rootTable.getColumn("Container")); - } -} diff --git a/specimen/src/org/labkey/specimen/query/SpecimenPivotByPrimaryType.java b/specimen/src/org/labkey/specimen/query/SpecimenPivotByPrimaryType.java deleted file mode 100644 index ee540633c93..00000000000 --- a/specimen/src/org/labkey/specimen/query/SpecimenPivotByPrimaryType.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2012-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.query; - -import org.apache.commons.lang3.math.NumberUtils; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.Container; -import org.labkey.api.data.ContainerFilter; -import org.labkey.api.query.UserSchema; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; - -import java.util.Map; - -public class SpecimenPivotByPrimaryType extends BaseSpecimenPivotTable -{ - public static final String PIVOT_BY_PRIMARY_TYPE = "Primary Type Vial Counts"; - private static final String COLUMN_DESCRIPTION_FORMAT = "Number of vials of primary type %s"; - - public SpecimenPivotByPrimaryType(final UserSchema schema, Study study, ContainerFilter cf) - { - super(SpecimenReportQuery.getPivotByPrimaryType(schema, study, cf), schema); - Container container = getContainer(); - setName(PIVOT_BY_PRIMARY_TYPE); - setDescription("Contains up to one row of Specimen Primary Type totals for each " + StudyService.get().getSubjectNounSingular(container) + - "/visit combination."); - - Map primaryTypeMap = getPrimaryTypeMap(container); - Map allPrimaryTypes = getAllPrimaryTypesMap(container); - - for (ColumnInfo col : getRealTable().getColumns()) - { - // look for the primary/derivative pivot encoding - String[] parts = col.getName().split(AGGREGATE_DELIM); - - if (parts.length == 2) - { - int primaryId = NumberUtils.toInt(parts[0]); - - if (primaryTypeMap.containsKey(primaryId)) - { - wrapPivotColumn(col, COLUMN_DESCRIPTION_FORMAT, primaryTypeMap.get(primaryId), - new NameLabelPair(parts[1], parts[1])); - } - else if (allPrimaryTypes.containsKey(primaryId)) - { - var wrappedCol = wrapPivotColumn(col, COLUMN_DESCRIPTION_FORMAT, allPrimaryTypes.get(primaryId), - new NameLabelPair(parts[1], parts[1])); - - wrappedCol.setHidden(true); - } - } - } - - setDefaultVisibleColumns(getDefaultVisibleColumns()); - - addWrapColumn(_rootTable.getColumn("Container")); - } -} diff --git a/specimen/src/org/labkey/specimen/query/SpecimenPivotByRequestingLocation.java b/specimen/src/org/labkey/specimen/query/SpecimenPivotByRequestingLocation.java deleted file mode 100644 index e9fe4b162c1..00000000000 --- a/specimen/src/org/labkey/specimen/query/SpecimenPivotByRequestingLocation.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2012-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.query; - -import org.apache.commons.lang3.math.NumberUtils; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.Container; -import org.labkey.api.data.ContainerFilter; -import org.labkey.api.query.UserSchema; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; - -import java.util.Map; - -/** - * User: klum - * Date: Mar 14, 2012 - */ -public class SpecimenPivotByRequestingLocation extends BaseSpecimenPivotTable -{ - public static final String PIVOT_BY_REQUESTING_LOCATION = "Vial Counts by Requesting Location"; - private static final String COLUMN_DESCRIPTION_FORMAT = "Number of vials of primary & derivative type %s/%s requested by %s"; - - public SpecimenPivotByRequestingLocation(final UserSchema schema, Study study, ContainerFilter cf) - { - super(SpecimenReportQuery.getPivotByRequestingLocation(schema, study, cf), schema); - setName(PIVOT_BY_REQUESTING_LOCATION); - setDescription("Contains up to one row of Specimen Derivative Type totals by Requesting Location for each " + StudyService.get().getSubjectNounSingular(getContainer()) + - "/visit combination."); - - Container container = getContainer(); - Map primaryTypeMap = getPrimaryTypeMap(container); - Map derivativeTypeMap = getDerivativeTypeMap(container); - Map locationMap = getSiteMap(container); - - for (ColumnInfo col : getRealTable().getColumns()) - { - // look for the primary/derivative pivot encoding - String[] parts = col.getName().split(AGGREGATE_DELIM); - - if (parts.length == 2) - { - String[] types = parts[0].split(TYPE_DELIM); - - if (types.length == 3) - { - int primaryId = NumberUtils.toInt(types[0]); - int derivativeId = NumberUtils.toInt(types[1]); - int locationId = NumberUtils.toInt(types[2]); - - if (primaryTypeMap.containsKey(primaryId) && derivativeTypeMap.containsKey(derivativeId) && locationMap.containsKey(locationId)) - { - wrapPivotColumn(col, - COLUMN_DESCRIPTION_FORMAT, - primaryTypeMap.get(primaryId), - derivativeTypeMap.get(derivativeId), - locationMap.get(locationId) - ); - } - } - } - } - setDefaultVisibleColumns(getDefaultVisibleColumns()); - addWrapColumn(_rootTable.getColumn("Container")); - } -} diff --git a/specimen/src/org/labkey/specimen/query/SpecimenQueryView.java b/specimen/src/org/labkey/specimen/query/SpecimenQueryView.java deleted file mode 100644 index 8e23a2b5145..00000000000 --- a/specimen/src/org/labkey/specimen/query/SpecimenQueryView.java +++ /dev/null @@ -1,1038 +0,0 @@ -/* - * Copyright (c) 2008-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.query; - -import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.NotNull; -import org.labkey.api.collections.ResultSetRowMapFactory; -import org.labkey.api.data.AbstractTableInfo; -import org.labkey.api.data.Aggregate; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.Container; -import org.labkey.api.data.ContainerManager; -import org.labkey.api.data.DataRegion; -import org.labkey.api.data.DetailsColumn; -import org.labkey.api.data.DisplayColumn; -import org.labkey.api.data.MenuButton; -import org.labkey.api.data.RenderContext; -import org.labkey.api.data.Results; -import org.labkey.api.data.RuntimeSQLException; -import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.SimpleDisplayColumn; -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.data.Sort; -import org.labkey.api.data.SqlSelector; -import org.labkey.api.data.Table; -import org.labkey.api.data.TableInfo; -import org.labkey.api.query.DetailsURL; -import org.labkey.api.query.FieldKey; -import org.labkey.api.query.QueryParam; -import org.labkey.api.query.QuerySettings; -import org.labkey.api.query.QueryView; -import org.labkey.api.query.UserSchema; -import org.labkey.api.reports.report.ReportUrls; -import org.labkey.api.security.User; -import org.labkey.api.specimen.SpecimenQuerySchema; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.specimen.Vial; -import org.labkey.api.specimen.model.SpecimenTablesProvider; -import org.labkey.api.study.CohortFilter; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.study.StudyUtils; -import org.labkey.api.study.model.ParticipantDataset; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.HtmlStringBuilder; -import org.labkey.api.util.LinkBuilder; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.DataView; -import org.labkey.api.view.ViewContext; -import org.labkey.api.writer.HtmlWriter; -import org.labkey.specimen.actions.SpecimenController; -import org.labkey.specimen.security.permissions.EditSpecimenDataPermission; -import org.labkey.specimen.security.permissions.RequestSpecimensPermission; -import org.labkey.specimen.settings.DisplaySettings; -import org.labkey.specimen.settings.RepositorySettings; -import org.labkey.specimen.settings.SettingsManager; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static java.util.Objects.requireNonNull; - -public class SpecimenQueryView extends BaseSpecimenQueryView -{ - public enum Mode - { - COMMENTS, - REQUESTS, - DEFAULT - } - - private final boolean _requireSequenceNum; - - private static class SpecimenDataRegion extends DataRegion - { - private final Map _requiredColumns = new HashMap<>(); - - @Override - protected boolean isErrorRow(RenderContext ctx, int rowIndex) - { - return StudyUtils.isFieldTrue(ctx, "QualityControlFlag"); - } - - protected void addColumnIfMissing(Set currentColumns, String colName) - { - ColumnInfo col = getTable().getColumn(colName); - if (col == null) - throw new IllegalStateException("Failed to find expected column " + colName); - ColumnInfo foundCol = null; - for (Iterator it = currentColumns.iterator(); it.hasNext() && foundCol == null; ) - { - ColumnInfo current = it.next(); - if (colName.equalsIgnoreCase(current.getName())) - foundCol = current; - } - if (foundCol == null) - { - currentColumns.add(col); - foundCol = col; - } - _requiredColumns.put(colName, foundCol); - } - - @Override - public void addQueryColumns(Set columns) - { - for (String requiredColumn : getRequiredColumns()) - addColumnIfMissing(columns, requiredColumn); - } - - protected List getRequiredColumns() - { - List cols = new ArrayList<>(); - cols.add("QualityControlFlag"); - return cols; - } - - protected ColumnInfo getRequiredColumn(String columnName) - { - ColumnInfo col = _requiredColumns.get(columnName); - if (col == null) - throw new IllegalStateException("Failed to find expected column " + columnName); - return col; - } - - protected Object getRequiredColumnValue(RenderContext ctx, String columnName) - { - return getRequiredColumn(columnName).getValue(ctx); - } - } - - private static class VialRestrictedDataRegion extends SpecimenDataRegion - { - @Override - protected boolean isRecordSelectorEnabled(RenderContext ctx) - { - return isAvailable(ctx); - } - - @Override - protected List getRequiredColumns() - { - List required = super.getRequiredColumns(); - required.add("GlobalUniqueId"); - required.add("Available"); - required.add("AvailabilityReason"); - return required; - } - - @Override - protected String getRecordSelectorId(RenderContext ctx) - { - return "check_" + getRequiredColumnValue(ctx, "GlobalUniqueId"); - } - - @Override - protected void renderExtraRecordSelectorContent(RenderContext ctx, HtmlWriter out) - { - if (!isAvailable(ctx)) - { - var reasonCol = getRequiredColumn("AvailabilityReason"); - Object reason = reasonCol.getValue(ctx.getRow()); - - HtmlStringBuilder builder = HtmlStringBuilder.of(reason instanceof String r ? r : "Specimen Unavailable.") - .append(HtmlString.BR).append(HtmlString.BR) - .append("Click ") - .append(LinkBuilder.simpleLink("[history]", getHistoryLink(ctx))) - .append(" for more information."); - - out.write(PageFlowUtil.popupHelp(builder.getHtmlString(), "Specimen Unavailable")); - } - } - - private ActionURL getHistoryLink(RenderContext ctx) - { - String containerId = (String) ctx.getRow().get("Container"); - ActionURL url = getHistoryLinkURL(ctx.getViewContext(), containerId); - Long specimenId = (Long) ctx.getRow().get("RowId"); - - return url.addParameter("id", specimenId); - } - - private boolean isAvailable(RenderContext ctx) - { - return StudyUtils.isFieldTrue(ctx, getRequiredColumn("Available")); - } - } - - protected static ActionURL getHistoryLinkURL(ViewContext ctx, String containerId) - { - Container container = null != containerId ? ContainerManager.getForId(containerId) : ctx.getContainer(); - return new ActionURL(SpecimenController.SpecimenEventsAction.class, container).addReturnUrl(ctx.getActionURL()); - } - - private class SpecimenRestrictedDataRegion extends SpecimenDataRegion - { - @Override - protected boolean isRecordSelectorEnabled(RenderContext ctx) - { - return isAvailable(ctx); - } - - @Override - protected List getRequiredColumns() - { - List required = super.getRequiredColumns(); - required.add("SpecimenHash"); - required.add("AvailableCount"); - return required; - } - - @Override - protected String getRecordSelectorId(RenderContext ctx) - { - return "check_" + getRequiredColumnValue(ctx, "SpecimenHash"); - } - - @Override - protected void renderExtraRecordSelectorContent(RenderContext ctx, HtmlWriter out) - { - if (!isAvailable(ctx)) - { - out.write(PageFlowUtil.popupHelp(HtmlString.of("No vials of this primary specimen are available. All " + - "vials are either part of an active specimen request, locked by an administrator, or not " + - "currently held by a repository."), "Specimens Unavailable")); - } - } - - private boolean isAvailable(RenderContext ctx) - { - Integer count; - if (getRequiredColumnValue(ctx, "AvailableCount") != null) - { - count = ((Number) getRequiredColumnValue(ctx, "AvailableCount")).intValue(); - } - else - { - String hash = (String) getRequiredColumnValue(ctx, "SpecimenHash"); - count = getSampleCounts(ctx).get(hash); - } - return (count != null && count.intValue() > 0); - } - } - - private boolean _showHistoryLinks; - private boolean _disableLowVialIndicators; - private boolean _showRecordSelectors; - private boolean _restrictRecordSelectors = true; - private Map _hiddenFormFields; - // key of _availableSpecimenCounts is a specimen hash, which includes ptid, type, etc. - private Map _availableSpecimenCounts; - - private final boolean _participantVisitFiltered; - private final ViewType _viewType; - - - public enum ViewType - { - SUMMARY() - { - @Override - public String getQueryName() - { - return "SpecimenSummary"; - } - - @Override - public String getViewName() - { - return null; - } - - @Override - public boolean isVialView() - { - return false; - } - - @Override - public boolean isForExport() - { - return false; - } - }, - VIALS() - { - @Override - public String getQueryName() - { - return "SpecimenDetail"; - } - - @Override - public String getViewName() - { - return null; - } - - @Override - public boolean isVialView() - { - return true; - } - - @Override - public boolean isForExport() - { - return false; - } - }, - VIALS_EMAIL() - { - @Override - public String getQueryName() - { - return "SpecimenDetail"; - } - - @Override - public String getViewName() - { - return "SpecimenEmail"; - } - - @Override - public boolean isVialView() - { - return true; - } - - @Override - public boolean isForExport() - { - return true; - } - }; - - public abstract String getQueryName(); - - public abstract String getViewName(); - - public abstract boolean isVialView(); - - public abstract boolean isForExport(); - } - - protected SpecimenQueryView(UserSchema schema, QuerySettings settings, SimpleFilter filter, Sort sort, ViewType viewType, - boolean participantVisitFiltered, CohortFilter cohortFilter, boolean requireSequenceNum) - { - super(schema, settings, filter, sort); - _viewType = viewType; - _cohortFilter = cohortFilter; - _participantVisitFiltered = participantVisitFiltered; - _requireSequenceNum = requireSequenceNum; - - RepositorySettings repositorySettings = SettingsManager.get().getRepositorySettings(schema.getContainer()); - boolean isEditable = ViewType.VIALS == viewType && repositorySettings.isSpecimenDataEditable() && getContainer().hasPermission(getUser(), EditSpecimenDataPermission.class); - setShowUpdateColumn(isEditable); - - setShowInsertNewButton(isEditable); - setShowDeleteButton(isEditable); - setShowImportDataButton(false); - if (isEditable) - { - AbstractTableInfo tableInfo = (AbstractTableInfo) getTable(); - ActionURL updateUrl = new ActionURL(SpecimenController.UpdateSpecimenQueryRowAction.class, getContainer()); - updateUrl.addParameter("schemaName", "study"); - updateUrl.addParameter(QueryView.DATAREGIONNAME_DEFAULT + "." + QueryParam.queryName, tableInfo.getName()); - setUpdateURL(new DetailsURL(updateUrl, Collections.singletonMap("RowId", "RowId"))); - - // we want a DetailsURL-like string so clear the container - ActionURL insertActionURL = new ActionURL(SpecimenController.InsertSpecimenQueryRowAction.class, ContainerManager.getRoot()); - insertActionURL.addParameter("schemaName", "study"); - insertActionURL.addParameter(QueryView.DATAREGIONNAME_DEFAULT + "." + QueryParam.queryName, tableInfo.getName()); - - setInsertURL(insertActionURL.getLocalURIString(false)); - } - - setViewItemFilter((type, label) -> - StudyUtils.STUDY_CROSSTAB_REPORT_TYPE.equals(type) || DEFAULT_ITEM_FILTER.accept(type, label) - ); - } - - public static SpecimenQueryView createView(ViewContext context, ViewType viewType) - { - SimpleFilter filter = new SimpleFilter(); - return createView(context, filter, createDefaultSort(viewType), viewType, false, null, false); - } - - public static SpecimenQueryView createView(ViewContext context, QuerySettings settings, ViewType viewType) - { - SimpleFilter filter = new SimpleFilter(); - return createView(context, settings, filter, createDefaultSort(viewType), viewType, false, null, false); - } - - public static SpecimenQueryView createView(ViewContext context, ViewType viewType, CohortFilter cohortFilter) - { - SimpleFilter filter = new SimpleFilter(); - return createView(context, filter, createDefaultSort(viewType), viewType, false, cohortFilter, false); - } - - public static SpecimenQueryView createView(ViewContext context, List samples, ViewType viewType) - { - SimpleFilter filter = new SimpleFilter(); - addFilterClause(filter, samples, viewType); - return createView(context, filter, createDefaultSort(viewType), viewType, false, null, false); - } - - public static SpecimenQueryView createView(ViewContext context, @NotNull Collection participantDatasets, ViewType viewType) - { - SimpleFilter filter = new SimpleFilter(); - Study study = StudyService.get().getStudy(context.getContainer()); - addFilterClause(requireNonNull(study), filter, participantDatasets); - return createView(context, filter, createDefaultSort(viewType), viewType, true, null, true); - } - - public enum PARAMS - { - excludeRequestedBySite, - showCompleteRequestedByEnrollmentSite, - showCompleteRequestedBySite, - showCompleteRequestedOnly, - showNotCompleteRequestedOnly, - showNotRequestedOnly, - showRequestedByEnrollmentSite, - showRequestedBySite, - showRequestedOnly, - } - - private static boolean hasRequestFilterParam(ViewContext context, PARAMS param) - { - String paramValue = StringUtils.trimToNull((String) context.get(param.name())); - return null != paramValue && paramValue.equalsIgnoreCase("true"); - } - - private static void addOnlyPreviouslyRequestedFilter(ViewContext context, SimpleFilter filter) - { - String showRequestedBySite = StringUtils.trimToNull((String) context.get(PARAMS.showRequestedBySite.name())); - if (null != showRequestedBySite) - addOnlyPreviouslyRequestedClause(filter, context.getContainer(), Integer.parseInt(showRequestedBySite), false); - else - { - showRequestedBySite = StringUtils.trimToNull((String) context.get(PARAMS.showCompleteRequestedBySite.name())); - if (null != showRequestedBySite) - addOnlyPreviouslyRequestedClause(filter, context.getContainer(), Integer.parseInt(showRequestedBySite), true); - } - } - - private static void addEnrollmentSiteRequestFilter(ViewContext context, SimpleFilter filter) - { - String showRequestedByEnrollmentSite = StringUtils.trimToNull((String) context.get(PARAMS.showRequestedByEnrollmentSite.name())); - if (null != showRequestedByEnrollmentSite) - addPreviouslyRequestedEnrollmentClause(filter, context.getContainer(), context.getUser(), Integer.parseInt(showRequestedByEnrollmentSite), false); - else - { - showRequestedByEnrollmentSite = StringUtils.trimToNull((String) context.get(PARAMS.showCompleteRequestedByEnrollmentSite.name())); - if (null != showRequestedByEnrollmentSite) - addPreviouslyRequestedEnrollmentClause(filter, context.getContainer(), context.getUser(), Integer.parseInt(showRequestedByEnrollmentSite), true); - } - } - - private static void addRequestFilter(ViewContext context, SimpleFilter filter) - { - if (hasRequestFilterParam(context, PARAMS.showRequestedOnly)) - addPreviouslyRequestedClause(filter, context.getContainer(), true, false); - else if (hasRequestFilterParam(context, PARAMS.showCompleteRequestedOnly)) - addPreviouslyRequestedClause(filter, context.getContainer(), true, true); - else if (hasRequestFilterParam(context, PARAMS.showNotRequestedOnly)) - addPreviouslyRequestedClause(filter, context.getContainer(), false, false); - else if (hasRequestFilterParam(context, PARAMS.showNotCompleteRequestedOnly)) - addPreviouslyRequestedClause(filter, context.getContainer(), false, true); - } - - private static SpecimenQueryView createView(ViewContext context, SimpleFilter filter, Sort sort, ViewType viewType, - boolean participantVisitFiltered, CohortFilter cohortFilter, boolean requireSequenceNum) - { - Study study = StudyService.get().getStudy(context.getContainer()); - UserSchema schema = SpecimenQuerySchema.get(study, context.getUser()); - String queryName = viewType.getQueryName(); - QuerySettings qs = schema.getSettings(context, queryName, queryName); - return createView(context, qs, filter, sort, viewType, participantVisitFiltered, cohortFilter, requireSequenceNum); - } - - private static SpecimenQueryView createView(ViewContext context, QuerySettings qs, SimpleFilter filter, Sort sort, ViewType viewType, - boolean participantVisitFiltered, CohortFilter cohortFilter, boolean requireSequenceNum) - { - Study study = StudyService.get().getStudy(context.getContainer()); - UserSchema schema = SpecimenQuerySchema.get(study, context.getUser()); - String viewName = viewType.getViewName(); - if (qs.getViewName() == null && viewName != null) - qs.setViewName(viewName); - - String excludeRequested = StringUtils.trimToNull((String) context.get(PARAMS.excludeRequestedBySite.name())); - if (null != excludeRequested) - addNotPreviouslyRequestedClause(filter, context.getContainer(), Integer.parseInt(excludeRequested)); - addOnlyPreviouslyRequestedFilter(context, filter); - addEnrollmentSiteRequestFilter(context, filter); - addRequestFilter(context, filter); - return new SpecimenQueryView(schema, qs, filter, sort, viewType, participantVisitFiltered, cohortFilter, requireSequenceNum); - } - - public Map getSampleCounts(RenderContext ctx) - { - if (null == _availableSpecimenCounts) - try - { - _availableSpecimenCounts = new HashMap<>(); - ResultSet rs = ctx.getResults(); - Set specimenHashes = new HashSet<>(); - int originalRow = rs.getRow(); - rs.last(); - if (rs.getRow() - originalRow < 1000) - { - rs.absolute(originalRow); - if (rs.getRow() == 0) - { - rs.next(); - } - do - { - specimenHashes.add(rs.getString("SpecimenHash")); - if (specimenHashes.size() > 150) - { - _availableSpecimenCounts.putAll(getSpecimenCounts(ctx.getContainer(), specimenHashes)); - specimenHashes = new HashSet<>(); - } - } - while (rs.next()); - if (!specimenHashes.isEmpty()) - { - _availableSpecimenCounts.putAll(getSpecimenCounts(ctx.getContainer(), specimenHashes)); - } - } - else - { - _availableSpecimenCounts.putAll(getSpecimenCounts(ctx.getContainer(), null)); - } - rs.absolute(originalRow); - } - catch (SQLException e) - { - throw new RuntimeSQLException(e); - } - - return _availableSpecimenCounts; - } - - private Map getSpecimenCounts(Container container, Collection specimenHashes) - { - TableInfo tableInfoSpecimen = SpecimenSchema.get().getTableInfoSpecimen(container); - - SQLFragment extraClause = null; - if (specimenHashes != null) - { - extraClause = new SQLFragment(" WHERE SpecimenHash "); - tableInfoSpecimen.getSqlDialect().appendInClauseSql(extraClause, specimenHashes); - } - - final Map map = new HashMap<>(); - - SQLFragment sql = new SQLFragment("SELECT SpecimenHash, CAST(AvailableCount AS Integer) AS AvailableCount FROM "); - sql.append(tableInfoSpecimen.getFromSQL("")); - if (extraClause != null) - { - sql.append(extraClause); - } - new SqlSelector(SpecimenSchema.get().getSchema(), sql).forEach(rs -> { - String specimenHash = rs.getString("SpecimenHash"); - map.put(specimenHash, rs.getInt("AvailableCount")); - }); - - return map; - } - - private static Sort createDefaultSort(ViewType viewType) - { - if (viewType.isVialView()) - return new Sort("GlobalUniqueId"); - else - return new Sort(requireNonNull(StudyService.get()).getSubjectColumnName(getContextContainer()) + ",Visit,PrimaryType,DerivativeType,AdditiveType"); - } - - protected static SimpleFilter addFilterClause(SimpleFilter filter, List vials, ViewType viewType) - { - if (vials != null && !vials.isEmpty()) - { - StringBuilder whereClause = new StringBuilder(); - if (viewType.isVialView()) - whereClause.append("RowId IN ("); - else - whereClause.append("SpecimenHash IN ("); - for (int i = 0; i < vials.size(); i++) - { - if (viewType.isVialView()) - whereClause.append(vials.get(i).getRowId()); - else - whereClause.append(vials.get(i).getSpecimenHash()); - if (i < vials.size() - 1) - whereClause.append(", "); - } - whereClause.append(")"); - filter = filter.addWhereClause(whereClause.toString(), null); - } - return filter; - } - - protected static SimpleFilter addFilterClause(Study study, SimpleFilter filter, @NotNull Collection participantDatasets) - { - boolean visitBased = study.getTimepointType().isVisitBased(); - if (!participantDatasets.isEmpty()) - { - StringBuilder whereClause = new StringBuilder(); - List params = new ArrayList<>(); - String sep = ""; - for (ParticipantDataset pd : participantDatasets) - { - whereClause.append(sep); - whereClause.append("("); - if (visitBased && pd.getSequenceNum() != null) - { - whereClause.append("SequenceNum = ? AND "); - params.add(pd.getSequenceNum()); - } - else if (pd.getVisitDate() != null) - { - whereClause.append(SpecimenSchema.get().getSqlDialect().getDateTimeToDateCast("DrawTimestamp")); - whereClause.append(" = "); - whereClause.append(SpecimenSchema.get().getSqlDialect().getDateTimeToDateCast("?")).append(" AND "); - params.add(pd.getVisitDate()); - } - whereClause.append(requireNonNull(StudyService.get()).getSubjectColumnName(getContextContainer())).append(" = ?)"); - params.add(pd.getParticipantId()); - sep = " OR "; - } - filter = filter.addWhereClause(whereClause.toString(), params.toArray(new Object[0])); - } - return filter; - } - - protected static SimpleFilter addNotPreviouslyRequestedClause(SimpleFilter filter, Container container, int locationId) - { - TableInfo tableInfoVial = SpecimenSchema.get().getTableInfoVial(container); - SQLFragment sql = new SQLFragment("SpecimenHash NOT IN (" + - "SELECT v.SpecimenHash from study.SampleRequestSpecimen rs join study.SampleRequest r on rs.SamplerequestId=r.RowId " - + "join "); - sql.append(tableInfoVial.getFromSQL("v")).append(" on rs.SpecimenGlobalUniqueId=v.GlobalUniqueId " - + "join study.SamplerequestStatus status ON r.StatusId=status.RowId " - + "where r.DestinationSiteId=").appendValue(locationId).append(" AND status.SpecimensLocked=").append(tableInfoVial.getSqlDialect().getBooleanTRUE()).append(")"); - - assert(sql.getParams().isEmpty()); - - filter.addWhereClause(sql.getSQL(), null, FieldKey.fromParts("SpecimenHash")); - - return filter; - } - - protected static SimpleFilter addOnlyPreviouslyRequestedClause(SimpleFilter filter, Container container, int locationId, boolean completedRequestsOnly) - { - String sql = "GlobalUniqueId IN (" - + "SELECT rs.SpecimenGlobalUniqueId FROM study.SampleRequestSpecimen rs join study.SampleRequest r on rs.SamplerequestId=r.RowId " - + "join study.SamplerequestStatus status ON r.StatusId=status.RowId " - + "where r.DestinationSiteId=? AND rs.Container=? AND status.SpecimensLocked=?" + - (completedRequestsOnly ? " AND status.FinalState=?" : "") + ")"; - - Object[] params; - if (completedRequestsOnly) - params = new Object[]{locationId, container.getId(), Boolean.TRUE, Boolean.TRUE}; - else - params = new Object[]{locationId, container.getId(), Boolean.TRUE}; - - filter.addWhereClause(sql, params, FieldKey.fromParts("GlobalUniqueId")); - - return filter; - } - - protected static SimpleFilter addPreviouslyRequestedEnrollmentClause(SimpleFilter filter, Container container, User user, int locationId, boolean completedRequestsOnly) - { - SQLFragment sql = getBaseRequestedEnrollmentSql(container, user, completedRequestsOnly); - - if (locationId == -1) - { - sql.append("IS NULL)"); - } - else - { - sql.append("= ?)"); - sql.add(locationId); - } - - filter.addWhereClause(sql, FieldKey.fromParts("GlobalUniqueId")); - - return filter; - } - - public static SQLFragment getBaseRequestedEnrollmentSql(Container container, User user, boolean isCompleteRequestsOnly) - { - 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 baseSql = new SQLFragment("GlobalUniqueId IN (SELECT Specimen.GlobalUniqueId FROM "); - baseSql.append(tableInfoSpecimenDetail.getFromSQL(tableInfoAlias)).append(",\n" + - "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" + - " Request.StatusId = Status.RowId AND\n" + - " RequestSpecimen.SampleRequestId = Request.RowId AND\n" + - " RequestSpecimen.Container = Request.Container AND\n" + - " Specimen.Container = RequestSpecimen.Container AND\n" + - " Specimen.GlobalUniqueId = RequestSpecimen.SpecimenGlobalUniqueId AND\n" + - " Participant.Container = Specimen.Container AND\n" + - " Participant.ParticipantId = Specimen.Ptid AND\n" + - " Status.SpecimensLocked = ? AND\n" + - (isCompleteRequestsOnly ? " Status.FinalState = ? AND\n" : "") + - " Specimen.Container = ? AND\n" + - " Participant.EnrollmentSiteId "); - baseSql.add(Boolean.TRUE); - if (isCompleteRequestsOnly) - baseSql.add(Boolean.TRUE); - baseSql.add(container); - return baseSql; - } - - protected static SimpleFilter addPreviouslyRequestedClause(SimpleFilter filter, Container container, boolean showRequested, boolean completedRequestsOnly) - { - String sql = "GlobalUniqueId " + (showRequested ? "" : "NOT ") + "IN (" - + "SELECT rs.SpecimenGlobalUniqueId FROM study.SampleRequestSpecimen rs join study.SampleRequest r on rs.SamplerequestId=r.RowId " - + "join study.SamplerequestStatus status ON r.StatusId=status.RowId " - + "where rs.Container=? AND status.SpecimensLocked=?" + (completedRequestsOnly ? " AND status.FinalState=?" : "") + - ")"; - Object[] params; - if (completedRequestsOnly) - params = new Object[]{container.getId(), Boolean.TRUE, Boolean.TRUE}; - else - params = new Object[]{container.getId(), Boolean.TRUE}; - - filter.addWhereClause(sql, params, FieldKey.fromParts("GlobalUniqueId")); - - return filter; - } - - @Override - protected DataRegion createDataRegion() - { - SpecimenDataRegion rgn; - if (_viewType.isVialView()) - { - if (_showRecordSelectors && _restrictRecordSelectors) - rgn = new VialRestrictedDataRegion(); - else - rgn = new SpecimenDataRegion(); - rgn.setRecordSelectorValueColumns("RowId"); - - // NOTE: Setting here instead of in table ctor because if set when SpecimenDetail is sub-query, we have too many columns in sub-query (Dave) -// if (ViewType.VIALS.equals(_viewType)) -// ((BaseColumnInfo)getTable().getColumn("Container")).setRequired(true); - } - else - { - if (_showRecordSelectors && _restrictRecordSelectors) - rgn = new SpecimenRestrictedDataRegion(); - else - rgn = new SpecimenDataRegion(); - - rgn.setRecordSelectorValueColumns("SpecimenHash"); - if (_showRecordSelectors) - { - if (null == _hiddenFormFields) - _hiddenFormFields = new HashMap<>(); - _hiddenFormFields.put("fromGroupedView", Boolean.TRUE.toString()); - } - } - configureDataRegion(rgn); - rgn.prepareDisplayColumns(getViewContext().getContainer()); - return rgn; - } - - @Override - protected void configureDataRegion(DataRegion rgn) - { - super.configureDataRegion(rgn); - - DisplayColumn rowIdCol = rgn.getDisplayColumn("RowId"); - if (rowIdCol != null) - rowIdCol.setVisible(false); - DisplayColumn containerIdCol = rgn.getDisplayColumn("Container"); - if (containerIdCol != null) - containerIdCol.setVisible(false); - - rgn.setShowRecordSelectors(_showRecordSelectors); - - if (_hiddenFormFields != null) - { - for (Map.Entry param : _hiddenFormFields.entrySet()) - rgn.addHiddenFormField(param.getKey(), param.getValue()); - } - - rgn.addHiddenFormField("referrer", getViewContext().getActionURL().getLocalURIString()); - - if (_viewType.isVialView()) - { - addAggregateIfInDisplay("Volume", Aggregate.BaseType.SUM); - addAggregateIfInDisplay("GlobalUniqueId", Aggregate.BaseType.COUNT); - } - else - { - addAggregateIfInDisplay("TotalVolume", Aggregate.BaseType.SUM); - addAggregateIfInDisplay("LockedInRequestCount", Aggregate.BaseType.SUM); - addAggregateIfInDisplay("AtRepositoryCount", Aggregate.BaseType.SUM); - addAggregateIfInDisplay("VialCount", Aggregate.BaseType.SUM); - addAggregateIfInDisplay("AvailableVolume", Aggregate.BaseType.SUM); - addAggregateIfInDisplay("AvailableCount", Aggregate.BaseType.SUM); - } - } - - // Add the aggregate clause only if the aggregated column is going to be displayed - // (otherwise SQL is not happy) - private void addAggregateIfInDisplay(String colName, Aggregate.Type aggregateType) - { - ColumnInfo columnInfo = getTable().getColumn(colName); - if (columnInfo != null) - { - for (DisplayColumn displayColumn : super.getDisplayColumns()) - { - if (displayColumn instanceof DetailsColumn) - continue; - - if (columnInfo.equals(displayColumn.getColumnInfo())) - { - getSettings().addAggregates(new Aggregate(columnInfo, aggregateType)); - return; - } - } - } - } - - @Override - public List getDisplayColumns() - { - List cols = super.getDisplayColumns(); - - if (_viewType.isForExport()) - { - // Remove rowId column and update column, if present - DisplayColumn column = cols.getFirst(); - if (column.getName().equalsIgnoreCase("rowid") || column.getName().equalsIgnoreCase("update")) - cols.removeFirst(); - column = cols.getFirst(); - if (column.getName().equalsIgnoreCase("rowid") || column.getName().equalsIgnoreCase("update")) - cols.removeFirst(); - } - - if (_viewType.isVialView()) - { - if (_showHistoryLinks) - { - cols.addFirst(new SimpleDisplayColumn("[history]") - { - @Override - public String renderURL(RenderContext ctx) - { - String containerId = (String) ctx.getRow().get("Container"); - Long specimenId = (Long) ctx.getRow().get("RowId"); - ActionURL url = getHistoryLinkURL(ctx.getViewContext(), containerId).addParameter("id", specimenId.toString()).addParameter("selected", _participantVisitFiltered); - return url.toString(); - } - }); - } - } - - boolean oneVialIndicator = false; - boolean zeroVialIndicator = false; - if (!_disableLowVialIndicators) - { - DisplaySettings settings = SettingsManager.get().getDisplaySettings(getContainer()); - oneVialIndicator = settings.getLastVialEnum() == DisplaySettings.DisplayOption.ALL_USERS || - (settings.getLastVialEnum() == DisplaySettings.DisplayOption.ADMINS_ONLY && - getUser().hasRootAdminPermission()); - zeroVialIndicator = settings.getZeroVialsEnum() == DisplaySettings.DisplayOption.ALL_USERS || - (settings.getZeroVialsEnum() == DisplaySettings.DisplayOption.ADMINS_ONLY && - getUser().hasRootAdminPermission()); - } - RepositorySettings settings = SettingsManager.get().getRepositorySettings(getContainer()); - if (settings.isEnableRequests() && !_viewType.isForExport() && getViewContext().getContainer().hasPermission(getUser(), RequestSpecimensPermission.class)) - { - // Only add this column if we're using advanced specimen management and not exported to email or attachment - cols.addFirst(new SpecimenRequestDisplayColumn(this, getTable(), zeroVialIndicator, oneVialIndicator, - SettingsManager.get().isSpecimenShoppingCartEnabled(getContainer()) && _showRecordSelectors)); - } - - // this column is normally hidden but we need it on the select for any specimen filters - if (_requireSequenceNum) - { - ColumnInfo seqNumCol = getTable().getColumn("SequenceNum"); - if (seqNumCol != null) - cols.add(seqNumCol.getRenderer()); - } - - return cols; - } - - public boolean isShowingVials() - { - return _viewType.isVialView(); - } - - public boolean isShowHistoryLinks() - { - return _showHistoryLinks; - } - - public void addHiddenFormField(String name, String value) - { - if (_hiddenFormFields == null) - _hiddenFormFields = new HashMap<>(); - _hiddenFormFields.put(name, value); - } - - public boolean isDisableLowVialIndicators() - { - return _disableLowVialIndicators; - } - - public void setDisableLowVialIndicators(boolean disableLowVialIndicators) - { - _disableLowVialIndicators = disableLowVialIndicators; - } - - public boolean isShowRecordSelectors() - { - return _showRecordSelectors; - } - - public boolean isRestrictRecordSelectors() - { - return _restrictRecordSelectors; - } - - public void setRestrictRecordSelectors(boolean restrictRecordSelectors) - { - _restrictRecordSelectors = restrictRecordSelectors; - } - - public void setShowHistoryLinks(boolean showHistoryLinks) - { - _showHistoryLinks = showHistoryLinks; - } - - @Override - public void setShowRecordSelectors(boolean showRecordSelectors) - { - //assert !(showRecordSelectors && !_detailsView) : "Only details view may show record selectors"; - _showRecordSelectors = showRecordSelectors; - } - - public String getSimpleHtmlTable() throws SQLException - { - getSettings().setMaxRows(Table.ALL_ROWS); - DataView view = createDataView(); - DataRegion rgn = view.getDataRegion(); - RenderContext renderContext = view.getRenderContext(); - - try (Results results = rgn.getResults(renderContext)) - { - List allColumns = getExportColumns(rgn.getDisplayColumns()); - List columns = new ArrayList<>(); - for (DisplayColumn col : allColumns) - { - if (col.shouldRender(renderContext)) - columns.add(col); - } - StringBuilder builder = new StringBuilder(); - builder.append("\n \n"); - for (DisplayColumn col : columns) - { - String header = PageFlowUtil.filter(col.getCaption()); - header = header.replaceAll(" ", " "); - builder.append(" \n"); - } - builder.append(" \n"); - int row = 0; - - ResultSetRowMapFactory factory = ResultSetRowMapFactory.create(results); - - while (results.next()) - { - renderContext.setRow(factory.getRowMap(results)); - builder.append(" \n"); - builder.append(" \n"); - for (DisplayColumn col : columns) - { - HtmlString value = col.getFormattedHtml(renderContext); - builder.append(" \n"); - } - builder.append(" \n"); - } - builder.append("
 ").append(header).append("
").append(row).append("").append(value).append("
"); - return builder.toString(); - } - } - - @Override - public void addManageViewItems(MenuButton button, Map params) - { - ActionURL url = requireNonNull(PageFlowUtil.urlProvider(ReportUrls.class)).urlManageViews(getViewContext().getContainer()); - for (Map.Entry entry : params.entrySet()) - url.addParameter(entry.getKey(), entry.getValue()); - - button.addMenuItem("Manage Views", url); - } -} diff --git a/specimen/src/org/labkey/specimen/query/SpecimenReportQuery.java b/specimen/src/org/labkey/specimen/query/SpecimenReportQuery.java deleted file mode 100644 index 451a8ce243d..00000000000 --- a/specimen/src/org/labkey/specimen/query/SpecimenReportQuery.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (c) 2025-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.query; - -import org.labkey.api.data.Container; -import org.labkey.api.data.ContainerFilter; -import org.labkey.api.data.TableInfo; -import org.labkey.api.query.QueryDefinition; -import org.labkey.api.query.QueryException; -import org.labkey.api.query.QueryService; -import org.labkey.api.query.UserSchema; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; - -import java.util.ArrayList; -import java.util.List; - -public class SpecimenReportQuery -{ - public static final String PIVOT_BY_PRIMARY_TYPE = "SpecimenSummary_PivotByPrimaryType"; - public static final String PIVOT_BY_DERIVATIVE_TYPE = "SpecimenSummary_PivotByDerivativeType"; - public static final String PIVOT_BY_REQUESTING_LOCATION = "SpecimenSummary_PivotByRequestingLocation"; - - private static final String sql_pivotByPrimaryType = """ - SELECT - Container, - %s, - Visit, - %s, - PrimaryType, - SUM(VialCount) AS VialCount, - SUM(LockedInRequestCount) AS LockedInRequestCount, - SUM(AtRepositoryCount) AS AtRepositoryCount, - SUM(AvailableCount) AS AvailableCount, - SUM(ExpectedAvailableCount) AS ExpectedAvailableCount - - FROM SpecimenSummary - - GROUP BY Container, %s, Visit, %s, PrimaryType - - PIVOT VialCount, AvailableCount, AtRepositoryCount, LockedInRequestCount, ExpectedAvailableCount - BY PrimaryType - IN (SELECT RowId FROM SpecimenPrimaryType)"""; - - private static final String sql_pivotByDerivativeType = """ - SELECT - Container, - %s, - Visit, - %s, - PivotColumn, - SUM(VialCount) AS VialCount, - SUM(LockedInRequestCount) AS LockedInRequestCount, - SUM(AtRepositoryCount) AS AtRepositoryCount, - SUM(AvailableCount) AS AvailableCount, - SUM(ExpectedAvailableCount) AS ExpectedAvailableCount - - FROM (SELECT Container, %s, Visit, %s, ('' || CAST(PrimaryType AS VARCHAR) || '-' || CAST(DerivativeType AS VARCHAR)) AS PivotColumn, VialCount, LockedInRequestCount, AtRepositoryCount, AvailableCount, ExpectedAvailableCount FROM SpecimenSummary) X - - GROUP BY Container, %s, Visit, %s, PivotColumn - - PIVOT VialCount, AvailableCount, AtRepositoryCount, LockedInRequestCount, ExpectedAvailableCount - BY PivotColumn - IN (SELECT ('' || CAST(PrimaryType AS VARCHAR) || '-' || CAST(DerivativeType AS VARCHAR)) FROM (SELECT DISTINCT PrimaryType, DerivativeType FROM SpecimenSummary) X)"""; - - - private static final String sql_pivotRequestedByLocation = """ - SELECT\s - Container, Visit, %s, %s, PivotColumn, COUNT(*) AS RequestedVials - FROM - - (SELECT\s - Vial.Container,\s - Vial.Visit AS Visit,\s - Vial.%s,\s - Vial.%s,\s - '' || CAST(Vial.PrimaryType AS VARCHAR) || '-' || CAST(Vial.DerivativeType AS VARCHAR) || '-' || CAST(Request.Destination AS VARCHAR) AS PivotColumn, - Vial.DerivativeType,\s - Request.Destination\s - FROM VialRequest) X - - GROUP BY - Container, Visit, %s, %s, PivotColumn - PIVOT RequestedVials BY PivotColumn - """; -// UNDONE: do we want a custom IN query? -// IN (SELECT ...) - - - public static TableInfo getPivotByPrimaryType(UserSchema schema, Study study, ContainerFilter cf) - { - Container container = schema.getContainer(); - - if (study == null) - throw new IllegalStateException("A study does not exist for this folder"); - - String subjectCol = StudyService.get().getSubjectColumnName(container); - String visitCol = StudyService.get().getSubjectVisitColumnName(container); - - String query = String.format(sql_pivotByPrimaryType, subjectCol, visitCol, subjectCol, visitCol); - - QueryDefinition qdef = QueryService.get().createQueryDef(schema.getUser(), container, schema, PIVOT_BY_PRIMARY_TYPE); - qdef.setSql(query); - qdef.setIsHidden(true); - - List errors = new ArrayList<>(); - TableInfo tinfo = qdef.getTable(errors, true); - - if (!errors.isEmpty()) - { - StringBuilder sb = new StringBuilder(); - - for (QueryException qe : errors) - { - sb.append(qe.getMessage()).append('\n'); - } - throw new QueryException(sb.toString()); - } - return tinfo; - } - - public static TableInfo getPivotByDerivativeType(UserSchema schema, Study study, ContainerFilter cf) - { - Container container = schema.getContainer(); - - if (study == null) - throw new IllegalStateException("A study does not exist for this folder"); - - String subjectCol = StudyService.get().getSubjectColumnName(container); - String visitCol = StudyService.get().getSubjectVisitColumnName(container); - - String query = String.format(sql_pivotByDerivativeType, subjectCol, visitCol, subjectCol, visitCol, subjectCol, visitCol); - - QueryDefinition qdef = QueryService.get().createQueryDef(schema.getUser(), container, schema, PIVOT_BY_DERIVATIVE_TYPE); - qdef.setSql(query); - qdef.setIsHidden(true); - - List errors = new ArrayList<>(); - TableInfo tinfo = qdef.getTable(errors, true); - - if (!errors.isEmpty()) - { - StringBuilder sb = new StringBuilder(); - - for (QueryException qe : errors) - { - sb.append(qe.getMessage()).append('\n'); - } - throw new IllegalStateException(sb.toString()); - } - return tinfo; - } - - public static TableInfo getPivotByRequestingLocation(UserSchema schema, Study study, ContainerFilter cf) - { - Container container = schema.getContainer(); - - if (study == null) - throw new IllegalStateException("A study does not exist for this folder"); - - String subjectCol = StudyService.get().getSubjectColumnName(container); - String visitCol = StudyService.get().getSubjectVisitColumnName(container); - - String query = String.format(sql_pivotRequestedByLocation, subjectCol, visitCol, subjectCol, visitCol, subjectCol, visitCol); - - QueryDefinition qdef = QueryService.get().createQueryDef(schema.getUser(), container, schema.getSchemaPath(), PIVOT_BY_REQUESTING_LOCATION); - qdef.setSql(query); - qdef.setIsHidden(true); - - List errors = new ArrayList<>(); - qdef.setContainerFilter(cf); - TableInfo tinfo = qdef.getTable(errors, true); - - if (!errors.isEmpty()) - { - StringBuilder sb = new StringBuilder(); - - for (QueryException qe : errors) - { - sb.append(qe.getMessage()).append('\n'); - } - throw new IllegalStateException(sb.toString()); - } - return tinfo; - } -} diff --git a/specimen/src/org/labkey/specimen/query/SpecimenRequestDisplayColumn.java b/specimen/src/org/labkey/specimen/query/SpecimenRequestDisplayColumn.java deleted file mode 100644 index e299bcfb3c0..00000000000 --- a/specimen/src/org/labkey/specimen/query/SpecimenRequestDisplayColumn.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2009-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.query; - -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.RenderContext; -import org.labkey.api.data.SimpleDisplayColumn; -import org.labkey.api.data.TableInfo; -import org.labkey.api.study.StudyUtils; -import org.labkey.api.util.DOM; -import org.labkey.api.util.DOM.Renderable; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.util.PageFlowUtil.HelpPopupBuilder; -import org.labkey.api.writer.HtmlWriter; - -import java.util.Set; - -import static org.labkey.api.util.DOM.Attribute.id; -import static org.labkey.api.util.DOM.Attribute.src; -import static org.labkey.api.util.DOM.Attribute.style; -import static org.labkey.api.util.DOM.DIV; -import static org.labkey.api.util.DOM.IMG; -import static org.labkey.api.util.DOM.SPAN; -import static org.labkey.api.util.DOM.at; - -public class SpecimenRequestDisplayColumn extends SimpleDisplayColumn -{ - private final boolean _showOneVialIndicator; - private final boolean _showZeroVialIndicator; - private final TableInfo _table; - private final boolean _showCartLinks; - private final SpecimenQueryView _specimenQueryView; - - public SpecimenRequestDisplayColumn(SpecimenQueryView specimenQueryView, TableInfo table, boolean showOneVialIndicator, - boolean showZeroVialIndicator, boolean showCartLinks) - { - _specimenQueryView = specimenQueryView; - _showOneVialIndicator = showOneVialIndicator; - _showZeroVialIndicator = showZeroVialIndicator; - _table = table; - _showCartLinks = showCartLinks; - } - - @Override - public void renderGridCellContents(RenderContext ctx, HtmlWriter out) - { - String hash = (String) ctx.getRow().get("SpecimenHash"); - String globalUniqueId = (String) ctx.getRow().get("GlobalUniqueId"); - int c = -1; - if (ctx.getRow().get("AvailableCount") != null) - { - c = ((Number)ctx.getRow().get("AvailableCount")).intValue(); - } - else - { - Integer sampleCount = _specimenQueryView.getSampleCounts(ctx).get(hash); - if (sampleCount != null) - c = sampleCount.intValue(); - } - - int count = c; - boolean vialView = _specimenQueryView.isShowingVials(); - boolean available = (!vialView && count > 0) || (vialView && StudyUtils.isFieldTrue(ctx, "Available")); - boolean showCart = _showCartLinks && available; - - SPAN( - at(style,"text-align: center").at(showCart, id, (globalUniqueId != null ? globalUniqueId : hash)), - (Renderable) ret -> { - String script = null; - - if (showCart) - { - script = (globalUniqueId == null ? "requestByHash('" + hash + "'); return false;" - : "requestByGlobalUniqueId('" + globalUniqueId + "'); return false;"); - } - - if (_showOneVialIndicator && count == 1) - { - out.write(getVialCountHtml(ctx, IMG(at(src, ctx.getViewContext().getContextPath() + "/_images/one.png")), - "One Vial Available", HtmlString.of("Only one vial of this primary specimen is available."), script)); - } - else if (_showZeroVialIndicator && count == 0) - { - out.write(getVialCountHtml(ctx, IMG(at(src, ctx.getViewContext().getContextPath() + "/_images/zero.png")), - "Zero Vials Available", HtmlString.of("No vials of this primary specimen are currently available for request."), null)); - } - else - { - out.write(getVialCountHtml(ctx, DIV(at(style, "color:gray"), count), - count + " Vials Available", HtmlString.of(count + " vials of this primary specimen are currently available for new requests."), script)); - } - return ret; - } - ).appendTo(out); - } - - private HelpPopupBuilder getVialCountHtml(RenderContext ctx, Renderable cellHtml, String popupTitle, Renderable popupBody, String requestScript) - { - if (requestScript != null) - { - cellHtml = DOM.createHtmlFragment(cellHtml, IMG(at(src, ctx.getViewContext().getContextPath() + "/_images/cart.png"))); - popupBody = DOM.createHtmlFragment(popupBody, HtmlString.BR, HtmlString.BR, "Click the shopping cart icon to request this specimen."); - } - return PageFlowUtil.popupHelp(popupBody, popupTitle).link(cellHtml).script(requestScript); - } - - @Override - public void addQueryColumns(Set set) - { - set.add(_table.getColumn("SpecimenHash")); - // fix for https://cpas.fhcrc.org/Issues/home/issues/details.view?issueId=3116 - ColumnInfo atRepositoryColumn = _table.getColumn("AtRepository"); - if (atRepositoryColumn != null) - set.add(atRepositoryColumn); - ColumnInfo lockedInRequestColumn = _table.getColumn("LockedInRequest"); - if (lockedInRequestColumn != null) - set.add(lockedInRequestColumn); - ColumnInfo availableCountColumn = _table.getColumn("AvailableCount"); - if (availableCountColumn != null) - set.add(availableCountColumn); - ColumnInfo availableColumn = _table.getColumn("Available"); - if (availableColumn != null) - set.add(availableColumn); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/query/SpecimenRequestQueryView.java b/specimen/src/org/labkey/specimen/query/SpecimenRequestQueryView.java deleted file mode 100644 index 851d0c9b265..00000000000 --- a/specimen/src/org/labkey/specimen/query/SpecimenRequestQueryView.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (c) 2008-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.query; - -import org.jetbrains.annotations.Nullable; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.DataColumn; -import org.labkey.api.data.DataRegion; -import org.labkey.api.data.RenderContext; -import org.labkey.api.data.SimpleDisplayColumn; -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.data.Sort; -import org.labkey.api.query.FieldKey; -import org.labkey.api.query.QuerySettings; -import org.labkey.api.query.UserSchema; -import org.labkey.api.security.User; -import org.labkey.api.specimen.SpecimenQuerySchema; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.study.StudyUtils; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.NavTree; -import org.labkey.api.view.ViewContext; -import org.labkey.api.writer.HtmlWriter; -import org.labkey.specimen.SpecimenRequestManager; -import org.labkey.specimen.SpecimenRequestStatus; -import org.labkey.specimen.actions.SpecimenController.DeleteRequestAction; -import org.labkey.specimen.actions.SpecimenController.ManageRequestAction; -import org.labkey.specimen.actions.SpecimenController.SubmitRequestAction; -import org.labkey.specimen.security.permissions.ManageRequestsPermission; -import org.labkey.specimen.security.permissions.RequestSpecimensPermission; -import org.labkey.specimen.settings.SettingsManager; - -import java.util.Set; - -public class SpecimenRequestQueryView extends BaseSpecimenQueryView -{ - private NavTree[] _extraLinks; - private boolean _allowSortAndFilter = true; - private boolean _showOptionLinks = true; - - private static class RequestOptionDisplayColumn extends SimpleDisplayColumn - { - private final ColumnInfo _colCreatedBy; - private final ColumnInfo _colStatus; - private final boolean _showOptionLinks; - private final NavTree[] _extraLinks; - private final boolean _userIsSpecimenManager; - private final boolean _userCanRequest; - private final int _userId; - private final boolean _cartEnabled; - - private Integer _shoppingCartStatusRowId; - - public RequestOptionDisplayColumn(ViewContext context, boolean showOptionLinks, ColumnInfo colCreatedBy, ColumnInfo colStatus, NavTree... extraLinks) - { - _colStatus = colStatus; - _colCreatedBy = colCreatedBy; - User user = context.getUser(); - _userId = user.getUserId(); - _userIsSpecimenManager = context.getContainer().hasPermission(user, ManageRequestsPermission.class); - _userCanRequest = context.getContainer().hasPermission(user, RequestSpecimensPermission.class); - _showOptionLinks = showOptionLinks; - _extraLinks = extraLinks; - setTextAlign("right"); - setNoWrap(true); - setWidth("175em"); - _cartEnabled = SettingsManager.get().isSpecimenShoppingCartEnabled(context.getContainer()); - } - - @Override - public void addQueryColumns(Set set) - { - set.add(_colCreatedBy); - set.add(_colStatus); - } - - @Override - public void renderGridCellContents(RenderContext ctx, HtmlWriter out) - { - StringBuilder content = new StringBuilder(); - // Use a div to set spacing around the buttons- otherwise they overlap the edges of their data grid cells: - content.append("
"); - if (_extraLinks != null) - { - for (NavTree link : _extraLinks) - content.append(PageFlowUtil.button(link.getText()).href(link.getHref())).append(" "); - } - - if (_showOptionLinks) - { - if (_cartEnabled) - { - if (_shoppingCartStatusRowId == null) - { - SpecimenRequestStatus cartStatus = SpecimenRequestManager.get().getRequestShoppingCartStatus(ctx.getContainer(), - ctx.getViewContext().getUser()); - _shoppingCartStatusRowId = cartStatus.getRowId(); - } - - Integer statusId = (Integer) _colStatus.getValue(ctx); - Integer ownerId = (Integer) _colCreatedBy.getValue(ctx); - boolean canEdit = _userIsSpecimenManager || (_userCanRequest && ownerId == _userId); - - if (statusId.intValue() == _shoppingCartStatusRowId.intValue() && canEdit) - { - ActionURL submitUrl = new ActionURL(SubmitRequestAction.class, ctx.getContainer()).addParameter("id", "${requestId}"); - ActionURL cancelUrl = new ActionURL(DeleteRequestAction.class, ctx.getContainer()).addParameter("id", "${requestId}"); - - content.append(PageFlowUtil.button("Submit").href(submitUrl).usePost(StudyUtils.SUBMISSION_WARNING)); - content.append(PageFlowUtil.button("Cancel").href(cancelUrl).usePost(StudyUtils.CANCELLATION_WARNING)); - } - } - - ActionURL detailsUrl = new ActionURL(ManageRequestAction.class, ctx.getContainer()).addParameter("id", "${requestId}"); - - content.append(PageFlowUtil.button("Details").href(detailsUrl)); - } - content.append("
"); - setDisplayHtml(content.toString()); - super.renderGridCellContents(ctx, out); - } - - @Override - public String getName() - { - return "RequestOptions"; - } - } - - protected SpecimenRequestQueryView(UserSchema schema, QuerySettings settings, SimpleFilter filter, Sort sort, NavTree... extraLinks) - { - super(schema, settings, filter, sort); - _extraLinks = extraLinks; - } - - public static SpecimenRequestQueryView createView(ViewContext context) - { - return createView(context, null); - } - - public static SpecimenRequestQueryView createView(ViewContext context, @Nullable SimpleFilter filter) - { - Study study = StudyService.get().getStudy(context.getContainer()); - UserSchema schema = SpecimenQuerySchema.get(study, context.getUser()); - String queryName = "SpecimenRequest"; - QuerySettings qs = schema.getSettings(context, queryName, queryName); - - return new SpecimenRequestQueryView(schema, qs, addFilterClauses(filter), createDefaultSort()); - } - - private static Sort createDefaultSort() - { - return new Sort("-Created"); - } - - private static SimpleFilter addFilterClauses(@Nullable SimpleFilter filter) - { - if (filter == null) - filter = new SimpleFilter(); - filter.addCondition(FieldKey.fromParts("Hidden"), Boolean.FALSE); - return filter; - } - - @Override - protected DataRegion createDataRegion() - { - DataRegion rgn = super.createDataRegion(); - DataColumn commentsDC = (DataColumn) rgn.getDisplayColumn("Comments"); - if (commentsDC != null) - { - commentsDC.setWidth("300px"); - commentsDC.setPreserveNewlines(true); - } - - if (_showOptionLinks || (_extraLinks != null && _extraLinks.length > 0)) - { - RequestOptionDisplayColumn optionCol = - new RequestOptionDisplayColumn(getViewContext(), _showOptionLinks, getTable().getColumn("CreatedBy"), - getTable().getColumn("Status"), _extraLinks); - rgn.addDisplayColumn(0, optionCol); - } - rgn.setSortable(_allowSortAndFilter); - rgn.setShowFilters(_allowSortAndFilter); - return rgn; - } - - public void setExtraLinks(boolean showOptionLinks, NavTree... extraLinks) - { - _showOptionLinks = showOptionLinks; - _extraLinks = extraLinks; - } - - public void setAllowSortAndFilter(boolean allowSortAndFilter) - { - _allowSortAndFilter = allowSortAndFilter; - } - - public void setShowCustomizeLink(boolean showCustomizeLink) - { - getSettings().setAllowCustomizeView(showCustomizeLink); - } -} diff --git a/specimen/src/org/labkey/specimen/query/SpecimenUpdateService.java b/specimen/src/org/labkey/specimen/query/SpecimenUpdateService.java deleted file mode 100644 index 453c386350c..00000000000 --- a/specimen/src/org/labkey/specimen/query/SpecimenUpdateService.java +++ /dev/null @@ -1,549 +0,0 @@ -/* - * Copyright (c) 2013-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.query; - -import org.apache.commons.beanutils.converters.LongConverter; -import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.labkey.api.collections.LongHashMap; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.Container; -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.dataiterator.DataIteratorBuilder; -import org.labkey.api.dataiterator.DataIteratorContext; -import org.labkey.api.query.AbstractQueryUpdateService; -import org.labkey.api.query.BatchValidationException; -import org.labkey.api.query.FieldKey; -import org.labkey.api.query.InvalidKeyException; -import org.labkey.api.query.QueryService; -import org.labkey.api.query.QueryUpdateServiceException; -import org.labkey.api.query.ValidationException; -import org.labkey.api.security.User; -import org.labkey.api.security.permissions.DeletePermission; -import org.labkey.api.specimen.SpecimenEvent; -import org.labkey.api.specimen.SpecimenEventManager; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.specimen.Vial; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.util.UnexpectedException; -import org.labkey.api.view.UnauthorizedException; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.SpecimenRequestException; -import org.labkey.specimen.SpecimenRequestManager; -import org.labkey.specimen.importer.EditableSpecimenImporter; - -import java.io.IOException; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class SpecimenUpdateService extends AbstractQueryUpdateService -{ - Logger _logger = null; - - public SpecimenUpdateService(TableInfo queryTable) - { - super(queryTable); - } - - @Override - public int importRows(User user, Container container, DataIteratorBuilder rows, BatchValidationException errors, Map configParameters, @Nullable Map extraScriptContext) - { - if (null != configParameters) - { - Object o = configParameters.get(ConfigParameters.Logger); - if (o instanceof Logger) - _logger = (Logger)o; - } - DataIteratorContext context = getDataIteratorContext(errors, InsertOption.IMPORT, configParameters); - return _importRowsUsingInsertRows(user, container, rows.getDataIterator(context), errors, extraScriptContext); - } - - - @Override - public List> deleteRows(User user, Container container, List> keys, @Nullable Map configParameters, @Nullable Map extraScriptContext) - throws BatchValidationException - { - if (!hasPermission(user, DeletePermission.class)) - throw new UnauthorizedException("You do not have permission to delete data from this table."); - - BatchValidationException errors = new BatchValidationException(); - errors.setExtraContext(extraScriptContext); - getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.DELETE, null, true, errors, extraScriptContext); - - Set rowIds = new HashSet<>(keys.size()); - for (Map key : keys) - { - Object rowId = key.get("rowid"); - if (null != rowId) - rowIds.add(Long.class == rowId.getClass() ? (Long) rowId : (Integer) rowId); - else - throw new IllegalArgumentException("RowId not found for a row."); - } - List vials = SpecimenManager.get().getVials(container, user, rowIds); - if (vials.size() != keys.size()) - throw new IllegalStateException("Specimens should be same size as rows."); - - List> rows = new ArrayList<>(1); - try - { - for (Vial vial : vials) - checkDeletability(container, vial); - - for (Vial vial : vials) - SpecimenManager.get().deleteSpecimen(vial, false); - - SpecimenRequestManager.get().clearCaches(container); - - // Force recalculation of requestability and specimen table - EditableSpecimenImporter importer = new EditableSpecimenImporter(container, user, false); - importSpecimens(importer, rows, true); - } - catch (ValidationException e) - { - errors.addRowError(e); - throw errors; - } - catch (SpecimenRequestException e) - { - errors.addRowError(new ValidationException(e.getMessage())); - throw errors; - } - catch (IOException e) - { - throw new IllegalStateException(e.getMessage()); - } - - getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.DELETE, null, false, errors, extraScriptContext); - - addAuditEvent(user, container, QueryService.AuditAction.DELETE, configParameters, null, null, null); - - return new ArrayList<>(); - } - - @Override - protected Map deleteRow(User user, Container container, Map oldRow) - throws InvalidKeyException, ValidationException - { - long rowId = keyFromMap(oldRow); - Vial vial = SpecimenManager.get().getVial(container, user, rowId); - - if (null == vial) - throw new IllegalArgumentException("No specimen found for rowId: " + rowId); - - checkDeletability(container, vial); - SpecimenManager.get().deleteSpecimen(vial, true); - List> rows = new ArrayList<>(1); - - try - { - // Force recalculation of requestability and specimen table - EditableSpecimenImporter importer = new EditableSpecimenImporter(container, user, false); - importSpecimens(importer, rows, true); - } - catch (IOException e) - { - throw new IllegalStateException(e.getMessage()); - } - - return oldRow; - } - - @Override - @SuppressWarnings("unchecked") - protected Map getRow(User user, Container container, Map keys) - throws InvalidKeyException - { - Map row = new TableSelector(getQueryTable()).getObject(keyFromMap(keys), Map.class); - Map result = new HashMap<>(); - for (ColumnInfo column : getQueryTable().getColumns()) - if (row.containsKey(column.getName())) - result.put(column.getName(), row.get(column.getName())); - else - result.put(column.getName(), row.get(column.getName().toLowerCase())); - - return result; - } - - @Override - public List> insertRows(User user, Container container, List> rows, BatchValidationException errors, @Nullable Map configParameters, Map extraScriptContext) - { - Study study = StudyService.get().getStudy(container); - if (null == study) - throw new IllegalStateException("No study found."); - - boolean hasTableScript = getQueryTable().hasTriggers(container); - - errors.setExtraContext(extraScriptContext); - try - { - if (hasTableScript) - getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.INSERT, null, true, errors, extraScriptContext); - } - catch (BatchValidationException e) - { - return null; - } - - List> result = new ArrayList<>(rows.size()); - String subjectColumnName = study.getSubjectColumnName(); - - try - { - for (Map row : rows) - { - if (null == row.get(subjectColumnName) || null == row.get("SequenceNum")) - { - throw new ValidationException(subjectColumnName + " and Sequence Num are required."); - } - - if (!subjectColumnName.equalsIgnoreCase("participantid")) - { - row.put("participantid", row.get(subjectColumnName)); - row.remove(subjectColumnName); - } - - row.put("externalid", 1); - } - - EditableSpecimenImporter importer = new EditableSpecimenImporter(container, user, true); - importSpecimens(importer, rows, true); - } - catch (IOException e) - { - throw new IllegalStateException(e.getMessage()); - } - catch (IllegalStateException e) - { - errors.addRowError(new ValidationException(e.getMessage())); - } - catch (ValidationException e) - { - errors.addRowError(e); - } - - if (!errors.hasErrors()) - { -/* try - { - result = getRows(user, container, keys); - } - catch (InvalidKeyException e) - { - throw UnexpectedException.wrap(e); - } -*/ - } - - try - { - if (hasTableScript) - getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.INSERT, null, false, errors, extraScriptContext); - } - catch (BatchValidationException e) - { - - } - - return result; - } - - @Override - protected Map insertRow(User user, Container container, Map row) - throws ValidationException - { - Study study = StudyService.get().getStudy(container); - if (null == study) - throw new IllegalStateException("No study found."); - String subjectColumnName = study.getSubjectColumnName(); - - if (null == row.get(subjectColumnName) || null == row.get("SequenceNum")) - { - throw new ValidationException(subjectColumnName + " and Sequence Num are required."); - } - - if (!subjectColumnName.equalsIgnoreCase("participantid")) - { - row.put("participantid", row.get(subjectColumnName)); - row.remove(subjectColumnName); - } - - row.put("externalid", 1); - List> rows = new ArrayList<>(1); - rows.add(row); - - try - { - EditableSpecimenImporter importer = new EditableSpecimenImporter(container, user, true); - importSpecimens(importer, rows, true); - } - catch (IOException e) - { - throw new IllegalStateException(e.getMessage()); - } - catch (IllegalStateException e) - { - throw new ValidationException(e.getMessage()); - } - -/* - try - { - return getRow(user, container, keys.get(0)); - } - catch (InvalidKeyException e) - { - throw UnexpectedException.wrap(e); - } -*/ - return new HashMap<>(); - } - - @Override - public List> updateRows(User user, Container container, List> rows, List> oldKeys, BatchValidationException errors, @Nullable Map configParameters, Map extraScriptContext) - throws InvalidKeyException, BatchValidationException, QueryUpdateServiceException, SQLException - { - if (oldKeys != null && rows.size() != oldKeys.size()) - throw new IllegalArgumentException("rows and oldKeys are required to be the same length, but were " + rows.size() + " and " + oldKeys + " in length, respectively"); - - errors.setExtraContext(extraScriptContext); - getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.UPDATE, null, true, errors, extraScriptContext); - - Set rowIds = new HashSet<>(rows.size()); - Map> uniqueRows = new LongHashMap<>(rows.size()); - for (int i = 0; i < rows.size(); i++) - { - Map row = rows.get(i); - Map keys = oldKeys == null ? row : oldKeys.get(i); - long rowId = keyFromMap(keys); - try - { - checkDuplicateUpdate(rowId); - } - catch (ValidationException e) - { - throw new BatchValidationException(e); - } - rowIds.add(rowId); - uniqueRows.put(rowId, row); - } - Map vials = new LongHashMap<>(); - for (Vial vial : SpecimenManager.get().getVials(container, user, rowIds)) - { - vials.put(vial.getRowId(), vial); - } - if (vials.size() != uniqueRows.size()) - throw new IllegalStateException("Specimens should be same size as rows."); - - try - { - for (Vial vial : vials.values()) - checkEditability(container, vial); - } - catch (ValidationException e) - { - errors.addRowError(e); - throw errors; - } - catch (SpecimenRequestException e) - { - errors.addRowError(new ValidationException(e.getMessage())); - throw errors; - } - - EditableSpecimenImporter importer = new EditableSpecimenImporter(container, user, false); - List> newKeys = new ArrayList<>(uniqueRows.size()); - List> newRows = new ArrayList<>(uniqueRows.size()); - long externalId = SpecimenManager.get().getMaxExternalId(container) + 1; - for (Map.Entry> row : uniqueRows.entrySet()) - { - Vial vial = vials.get(row.getKey()); - Map rowMap = prepareRowMap(container, row.getValue(), vial, importer, externalId++); - newRows.add(rowMap); - - Map keys = new HashMap<>(); - keys.put("rowId", row.getKey()); - newKeys.add(keys); - } - - try - { - importSpecimens(importer, newRows, true); - } - catch (ValidationException e) - { - e.fillIn(getQueryTable().getPublicSchemaName(), getQueryTable().getName(), newRows.getFirst(), 0); - errors.addRowError(e); - throw errors; - } - catch (IOException e) - { - throw new IllegalStateException(e.getMessage()); - } - - getQueryTable().fireBatchTrigger(container, user, TableInfo.TriggerType.UPDATE, null, false, errors, extraScriptContext); - - addAuditEvent(user, container, QueryService.AuditAction.UPDATE, configParameters, rows, null, null); - - return getRows(user, container, newKeys); - } - - @Override - protected Map updateRow(User user, Container container, Map row, @NotNull Map oldRow, @Nullable Map configParameters) - throws InvalidKeyException, ValidationException - { - long rowId = oldRow != null ? keyFromMap(oldRow) : keyFromMap(row); - Vial vial = SpecimenManager.get().getVial(container, user, rowId); - if (vial == null) - throw new IllegalArgumentException("No specimen found for rowId: " + rowId); - - checkEditability(container, vial); - - EditableSpecimenImporter importer = new EditableSpecimenImporter(container, user, false); - long externalId = SpecimenManager.get().getMaxExternalId(container) + 1; - Map rowMap = prepareRowMap(container, row, vial, importer, externalId); - - // TODO: this is a hack to best deal with Requestable being Null until a better fix can be accomplished - if (null == oldRow || null == oldRow.get("requestable")) - { - Object obj = rowMap.get("requestable"); - if (null != obj && !(Boolean)obj) - rowMap.put("requestable", null); - } - - List> rows = new ArrayList<>(1); - rows.add(rowMap); - - try - { - importSpecimens(importer, rows, true); - } - catch (IOException e) - { - throw new IllegalStateException(e.getMessage()); - } - - // The rowId will not have changed - Map keys = new HashMap<>(); - keys.put("rowId", vial.getRowId()); - try - { - return getRow(user, container, keys); - } - catch (InvalidKeyException e) - { - throw UnexpectedException.wrap(e); - } - } - - private long keyFromMap(Map map) throws InvalidKeyException - { - if (null == map) - throw new InvalidKeyException("No values provided"); - - Object rowId = map.get("rowId"); - if (null == rowId) - rowId = map.get("RowId"); - if (null == rowId) - rowId = map.get("rowid"); - if (null == rowId) - throw new InvalidKeyException("No value provided for 'rowId' column"); - Long rowLong = new LongConverter(null).convert(Long.class, rowId); - if (null == rowLong) - throw new InvalidKeyException("Unable to convert rowId of '" + rowId + "' to a long"); - return rowLong; - } - - private Map prepareRowMap(Container container, Map row, Vial vial, - EditableSpecimenImporter importer, long externalId) - { - // Get last event so that our input considers all fields that were set; - // Then overwrite whatever is coming from the form - Map rowMap = getLastEventMap(container, vial); - for (Map.Entry entry : row.entrySet()) - { - String columnName = importer.getMappedColumnName(entry.getKey()); - rowMap.put(columnName, entry.getValue()); - } - - rowMap.put("globaluniqueid", vial.getGlobalUniqueId()); - rowMap.put("ExternalId", externalId); - return rowMap; - } - - private void importSpecimens(EditableSpecimenImporter importer, List> rows, - boolean merge) throws IOException, ValidationException - { - rows = importer.mapColumnNamesToTsvColumnNames(rows); - importer.process(rows, merge, _logger); - } - - private void checkEditability(Container container, Vial vial) throws ValidationException - { - SQLFragment sql = new SQLFragment("SELECT COUNT(FinalState) FROM " + SpecimenSchema.get().getTableInfoSampleRequestStatus() + - " WHERE FinalState = " + SpecimenSchema.get().getSqlDialect().getBooleanFALSE()); - sql.append(" AND RowId IN (") - .append("SELECT StatusId FROM " + SpecimenSchema.get().getTableInfoSampleRequest() + " WHERE RowId IN (") - .append("SELECT SampleRequestId FROM " + SpecimenSchema.get().getTableInfoSampleRequestSpecimen() + " WHERE Container = ? "); - sql.add(container.getId()); - sql.append(" AND SpecimenGlobalUniqueId = ?").add(vial.getGlobalUniqueId()); - sql.append(")) GROUP BY FinalState"); - - ArrayList counts = new SqlSelector(getQueryTable().getSchema(), sql).getArrayList(Integer.class); - if (counts.size() > 1) - throw new IllegalStateException("Expected one and only one count of rows."); - else if (!counts.isEmpty() && counts.getFirst() != 0) - throw new ValidationException("Specimen may not be edited when it's in a non-final request."); - } - - private void checkDeletability(Container container, Vial vial) throws ValidationException - { - SQLFragment sql = new SQLFragment("SELECT COUNT(*) FROM " + SpecimenSchema.get().getTableInfoSampleRequestSpecimen() + " WHERE Container = ? "); - sql.add(container.getId()); - sql.append(" AND SpecimenGlobalUniqueId = ?").add(vial.getGlobalUniqueId()); - - ArrayList counts = new SqlSelector(getQueryTable().getSchema(), sql).getArrayList(Integer.class); - if (counts.size() > 1) - throw new ValidationException("Expected one and only one count of rows."); - else if (!counts.isEmpty() && counts.getFirst() != 0) - throw new ValidationException("Specimen may not be deleted because it has been used in a request."); - } - - private static Map getLastEventMap(Container container, Vial vial) - { - List vials = Collections.singletonList(vial); - SpecimenEvent specimenEvent = SpecimenEventManager.get().getLastEvent(SpecimenManager.get().getSpecimenEvents(vials, false)); - if (null == specimenEvent) - throw new IllegalStateException("Expected at least one event for specimen."); - TableInfo tableInfoSpecimenEvent = SpecimenSchema.get().getTableInfoSpecimenEvent(container); - - SimpleFilter filter = new SimpleFilter(); - filter.addCondition(FieldKey.fromString("RowId"), specimenEvent.getRowId()); - return new TableSelector(tableInfoSpecimenEvent, filter, null).getMap(); - } -} - diff --git a/specimen/src/org/labkey/specimen/report/RequestSummaryByVisitType.java b/specimen/src/org/labkey/specimen/report/RequestSummaryByVisitType.java deleted file mode 100644 index 5ab88441598..00000000000 --- a/specimen/src/org/labkey/specimen/report/RequestSummaryByVisitType.java +++ /dev/null @@ -1,42 +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.report; - -public class RequestSummaryByVisitType extends SummaryByVisitType -{ - private Integer _destinationSiteId; - private String _siteLabel; - - public Integer getDestinationSiteId() - { - return _destinationSiteId; - } - - public void setDestinationSiteId(Integer destinationSiteId) - { - _destinationSiteId = destinationSiteId; - } - - public String getSiteLabel() - { - return _siteLabel; - } - - public void setSiteLabel(String siteLabel) - { - _siteLabel = siteLabel; - } -} diff --git a/specimen/src/org/labkey/specimen/report/SpecimenCountSummary.java b/specimen/src/org/labkey/specimen/report/SpecimenCountSummary.java deleted file mode 100644 index 96475aef05f..00000000000 --- a/specimen/src/org/labkey/specimen/report/SpecimenCountSummary.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2008-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.report; - -/** - * User: brittp - * Created: Jan 29, 2008 5:27:13 PM - */ -public class SpecimenCountSummary implements SpecimenReportCellData -{ - private Long _vialCount; - private Double _totalVolume; - private Integer _visit; - - @Override - public Long getVialCount() - { - return _vialCount; - } - - public void setVialCount(Long vialCount) - { - _vialCount = vialCount; - } - - @Override - public Double getTotalVolume() - { - return _totalVolume; - } - - public void setTotalVolume(Double totalVolume) - { - _totalVolume = totalVolume; - } - - @Override - public Integer getVisit() - { - return _visit; - } - - public void setVisit(Integer visit) - { - _visit = visit; - } -} diff --git a/specimen/src/org/labkey/specimen/report/SpecimenDetailQueryHelper.java b/specimen/src/org/labkey/specimen/report/SpecimenDetailQueryHelper.java deleted file mode 100644 index c9e368980f1..00000000000 --- a/specimen/src/org/labkey/specimen/report/SpecimenDetailQueryHelper.java +++ /dev/null @@ -1,49 +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.report; - -import org.labkey.api.data.SQLFragment; - -import java.util.Map; - -public class SpecimenDetailQueryHelper -{ - private final SQLFragment _viewSql; - private final String _typeGroupingColumns; - private final Map _aliasToTypePropertyMap; - - public SpecimenDetailQueryHelper(SQLFragment viewSql, String typeGroupingColumns, Map aliasToTypePropertyMap) - { - _viewSql = viewSql; - _typeGroupingColumns = typeGroupingColumns; - _aliasToTypePropertyMap = aliasToTypePropertyMap; - } - - public SQLFragment getViewSql() - { - return _viewSql; - } - - public String getTypeGroupingColumns() - { - return _typeGroupingColumns; - } - - public Map getAliasToTypePropertyMap() - { - return _aliasToTypePropertyMap; - } -} diff --git a/specimen/src/org/labkey/specimen/report/SpecimenReportCellData.java b/specimen/src/org/labkey/specimen/report/SpecimenReportCellData.java deleted file mode 100644 index e8f85f662cc..00000000000 --- a/specimen/src/org/labkey/specimen/report/SpecimenReportCellData.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2008-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.report; - -/** - * User: brittp - * Created: Jan 14, 2008 10:23:30 AM - */ -public interface SpecimenReportCellData -{ - Long getVialCount(); - Double getTotalVolume(); - Integer getVisit(); -} diff --git a/specimen/src/org/labkey/specimen/report/SpecimenReportExcelWriter.java b/specimen/src/org/labkey/specimen/report/SpecimenReportExcelWriter.java deleted file mode 100644 index 27710546704..00000000000 --- a/specimen/src/org/labkey/specimen/report/SpecimenReportExcelWriter.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2008-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.report; - -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.http.HttpServletResponse; -import jxl.Workbook; -import jxl.WorkbookSettings; -import jxl.write.Label; -import jxl.write.Number; -import jxl.write.WritableCell; -import jxl.write.WritableCellFormat; -import jxl.write.WritableFont; -import jxl.write.WritableSheet; -import jxl.write.WritableWorkbook; -import jxl.write.WriteException; -import org.labkey.api.data.ExcelWriter; -import org.labkey.api.study.Visit; -import org.labkey.api.util.ExceptionUtil; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/* - * User: brittp - * Date: May 20, 2008 - * Time: 9:34:36 AM - */ -public class SpecimenReportExcelWriter -{ - private final SpecimenVisitReportParameters _parameters; - - private WritableCellFormat _headerFormat; - - public SpecimenReportExcelWriter(SpecimenVisitReportParameters parameters) - { - _parameters = parameters; - } - - public void write(HttpServletResponse response) - { - WritableWorkbook workbook = null; - ServletOutputStream ostream = null; - try - { - WritableFont boldFont = new WritableFont(WritableFont.ARIAL, 10, WritableFont.BOLD); - _headerFormat = new WritableCellFormat(boldFont); - _headerFormat.setWrap(true); - _headerFormat.setVerticalAlignment(jxl.format.VerticalAlignment.TOP); - ostream = ExcelWriter.getOutputStream(response, _parameters.getLabel(), null, ExcelWriter.ExcelDocumentType.xls); - - WorkbookSettings settings = new WorkbookSettings(); - settings.setArrayGrowSize(300000); - workbook = Workbook.createWorkbook(ostream, settings); - - for (SpecimenVisitReport report : _parameters.getReports()) - writeReport(workbook, report); - } - catch (WriteException | IOException e) - { - throw new RuntimeException(e); - } - finally - { - if (workbook != null) - { - try - { - workbook.write(); - workbook.close(); - - // Flush the OutputStream - ostream.flush(); - // Finally, close the OutputStream - ostream.close(); - } - catch (WriteException | IOException e) - { - ExceptionUtil.logExceptionToMothership(null, e); - } - } - } - } - - private void writeReport(WritableWorkbook workbook, SpecimenVisitReport report) throws WriteException - { - String sheetName = ExcelWriter.cleanSheetName(report.getTitle()); - for (int i = 2; workbook.getSheet(sheetName) != null; i++) - sheetName = sheetName.substring(0, sheetName.length() - 1) + i; - - WritableSheet sheet = workbook.createSheet(sheetName, workbook.getNumberOfSheets()); - - List visits = new ArrayList(report.getVisits()); - - // Merge cells at top of sheet and write the headers - // One or more embedded tabs split a header into equally spaced columns - sheet.mergeCells(0, 0, 10, 0); - WritableCell cell = new Label(0, 0, _parameters.getLabel() + ": " + report.getTitle()); - sheet.addCell(cell); - - for (int i = 0; i < visits.size(); i++) - sheet.addCell(new Label(i + report.getLabelDepth(), 1, visits.get(i).getDisplayString(), _headerFormat)); - - boolean numericData = report.isNumericData(); - Collection rows = (Collection) report.getRows(); - if (rows.isEmpty()) - sheet.addCell(new Label(report.getLabelDepth() + 1, 2, "No data to show")); - else - { - int rowIndex = 2; - for (SpecimenVisitReport.Row rowData : rows) - { - int maxRowHeight = rowData.getMaxExcelRowHeight(visits); - int columnIndex = 0; - - for (SpecimenReportTitle titleElement : rowData.getTitleHierarchy()) - sheet.addCell(new Label(columnIndex++, rowIndex, titleElement.getDisplayValue())); - - for (Visit visit : visits) - { - String[] valueSet = rowData.getCellExcelText(visit); - if (valueSet != null && valueSet.length > 0) - { - for (int i = 0; i < valueSet.length; i++) - { - String value = valueSet[i]; - sheet.addCell(numericData ? - new Number(columnIndex, rowIndex + i, Double.parseDouble(value)) : - new Label(columnIndex, rowIndex + i, value)); - } - } - columnIndex++; - } - rowIndex += maxRowHeight; - } - } - sheet.getSettings().setVerticalFreeze(2); - sheet.getSettings().setHorizontalFreeze(report.getLabelDepth()); - } -} diff --git a/specimen/src/org/labkey/specimen/report/SpecimenReportManager.java b/specimen/src/org/labkey/specimen/report/SpecimenReportManager.java deleted file mode 100644 index 3e127c7d346..00000000000 --- a/specimen/src/org/labkey/specimen/report/SpecimenReportManager.java +++ /dev/null @@ -1,389 +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.report; - -import org.apache.commons.beanutils.PropertyUtils; -import org.apache.logging.log4j.LogManager; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.Container; -import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.data.SqlSelector; -import org.labkey.api.data.Table; -import org.labkey.api.data.TableInfo; -import org.labkey.api.data.dialect.SqlDialect; -import org.labkey.api.query.CustomView; -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.SpecimenQuerySchema; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.specimen.model.SpecimenTablesProvider; -import org.labkey.api.study.CohortFilter; -import org.labkey.api.study.StudyService; -import org.labkey.api.view.ActionURL; - -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - -public class SpecimenReportManager -{ - private final static SpecimenReportManager INSTANCE = new SpecimenReportManager(); - - private SpecimenReportManager() - { - } - - public static SpecimenReportManager get() - { - return INSTANCE; - } - - private SpecimenDetailQueryHelper getSpecimenDetailQueryHelper(Container container, User user, - CustomView baseView, SimpleFilter specimenDetailFilter, - SpecimenTypeLevel level) - { - UserSchema schema = SpecimenQuerySchema.get(StudyService.get().getStudy(container), user); - TableInfo tinfo = schema.getTable(SpecimenTablesProvider.SPECIMEN_DETAIL_TABLE_NAME); - - Map aliasToTypeProperty = new LinkedHashMap<>(); - - Collection columns = new HashSet<>(); - if (baseView != null) - { - // copy our saved view filter into our SimpleFilter via an ActionURL (yuck...) - ActionURL url = new ActionURL(); - baseView.applyFilterAndSortToURL(url, "mockDataRegion"); - specimenDetailFilter.addUrlFilters(url, "mockDataRegion"); - } - - // Build a list fo FieldKeys for all the columns that we must select, - // regardless of whether they're in the selected specimen view. We need to ask the view which - // columns are required in case there's a saved filter on a column outside the primary table: - columns.add(FieldKey.fromParts("Container")); - columns.add(FieldKey.fromParts("Visit")); - columns.add(FieldKey.fromParts("SequenceNum")); - columns.add(FieldKey.fromParts("LockedInRequest")); - columns.add(FieldKey.fromParts("GlobalUniqueId")); - columns.add(FieldKey.fromParts(StudyService.get().getSubjectColumnName(container))); - if (StudyService.get().showCohorts(container, schema.getUser())) - columns.add(FieldKey.fromParts("CollectionCohort")); - columns.add(FieldKey.fromParts("Volume")); - if (level != null) - { - for (SpecimenTypeBeanProperty typeProperty : level.getGroupingColumns()) - columns.add(typeProperty.getTypeKey()); - } - - // turn our fieldkeys into columns: - Map colMap = QueryService.get().getColumns(tinfo, columns); - Set unresolvedColumns = new HashSet<>(); - Collection cols = new ArrayList<>(colMap.values()); - cols = QueryService.get().ensureRequiredColumns(tinfo, cols, specimenDetailFilter, null, unresolvedColumns); - if (!unresolvedColumns.isEmpty()) - throw new IllegalStateException("Unable to resolve column(s): " + unresolvedColumns); - // generate our select SQL: - SQLFragment viewSql = new SQLFragment(); - viewSql.appendComment("", tinfo.getSqlDialect()); - viewSql.append(Table.getSelectSQL(tinfo, cols, specimenDetailFilter, null)); - viewSql.appendComment("", tinfo.getSqlDialect()); - - // save off the aliases for our grouping columns, so we can group by them later: - String groupingColSql = null; - if (level != null) - { - StringBuilder builder = new StringBuilder(); - String sep = ""; - for (SpecimenTypeBeanProperty typeProperty : level.getGroupingColumns()) - { - ColumnInfo col = colMap.get(typeProperty.getTypeKey()); - builder.append(sep).append(col.getAlias().getId()); - sep = ", "; - aliasToTypeProperty.put(col.getAlias().getId(), typeProperty); - } - groupingColSql = builder.toString(); - } - return new SpecimenDetailQueryHelper(viewSql, groupingColSql, aliasToTypeProperty); - } - - public SummaryByVisitType[] getSpecimenSummaryByVisitType(Container container, User user, SimpleFilter specimenDetailFilter, - boolean includeParticipantGroups, SpecimenTypeLevel level, CustomView baseView) - { - if (specimenDetailFilter == null) - specimenDetailFilter = new SimpleFilter(); - else - { - SimpleFilter clone = new SimpleFilter(); - clone.addAllClauses(specimenDetailFilter); - specimenDetailFilter = clone; - } - - final SpecimenDetailQueryHelper viewSqlHelper = getSpecimenDetailQueryHelper(container, user, baseView, specimenDetailFilter, level); - - String perPtidSpecimenSQL = "\t-- Inner SELECT gets the number of vials per participant/visit/type:\n" + - "\tSELECT InnerView.Container, InnerView.Visit, " + viewSqlHelper.getTypeGroupingColumns() + ",\n" + - "\tInnerView." + StudyService.get().getSubjectColumnName(container) + ", COUNT(*) AS VialCount, SUM(InnerView.Volume) AS PtidVolume \n" + - "FROM (\n" + viewSqlHelper.getViewSql().getSQL() + "\n) InnerView\n" + - "\tGROUP BY InnerView.Container, InnerView." + StudyService.get().getSubjectColumnName(container) + - ", InnerView.Visit, " + viewSqlHelper.getTypeGroupingColumns() + "\n"; - - SQLFragment sql = new SQLFragment("-- Outer grouping allows us to count participants AND sum vial counts:\n" + - "SELECT VialData.Visit AS Visit, " + viewSqlHelper.getTypeGroupingColumns() + ", COUNT(*) as ParticipantCount, \n" + - "SUM(VialData.VialCount) AS VialCount, SUM(VialData.PtidVolume) AS TotalVolume FROM \n" + - "(\n" + perPtidSpecimenSQL + ") AS VialData\n" + - "GROUP BY Visit, " + viewSqlHelper.getTypeGroupingColumns() + "\n" + - "ORDER BY " + viewSqlHelper.getTypeGroupingColumns() + ", Visit"); - sql.addAll(viewSqlHelper.getViewSql().getParamsArray()); - - final List ret = new ArrayList<>(); - - new SqlSelector(SpecimenSchema.get().getSchema(), sql).forEach(rs -> { - SummaryByVisitType summary = new SummaryByVisitType(); - if (rs.getObject("Visit") != null) - summary.setVisit(rs.getInt("Visit")); - summary.setTotalVolume(rs.getDouble("TotalVolume")); - long vialCount = rs.getLong("VialCount"); - summary.setVialCount(vialCount); - long participantCount = rs.getLong("ParticipantCount"); - summary.setParticipantCount(participantCount); - - for (Map.Entry typeProperty : viewSqlHelper.getAliasToTypePropertyMap().entrySet()) - { - String value = rs.getString(typeProperty.getKey()); - try - { - PropertyUtils.setProperty(summary, typeProperty.getValue().getBeanProperty(), value); - } - catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) - { - throw new RuntimeException(e); - } - } - ret.add(summary); - }); - - SummaryByVisitType[] summaries = ret.toArray(new SummaryByVisitType[0]); - - if (includeParticipantGroups) - setSummaryParticipantGroups(perPtidSpecimenSQL, viewSqlHelper.getViewSql().getParamsArray(), - viewSqlHelper.getAliasToTypePropertyMap(), summaries, StudyService.get().getSubjectColumnName(container), "Visit"); - - return summaries; - } - - private void setSummaryParticipantGroups(String sql, Object[] paramArray, final Map aliasToTypeProperty, - SummaryByVisitType[] summaries, final String ptidColumnName, final String visitValueColumnName) - { - SQLFragment fragment = new SQLFragment(sql); - fragment.addAll(paramArray); - - final Map> cellToPtidSet = new HashMap<>(); - - new SqlSelector(SpecimenSchema.get().getSchema(), fragment).forEach(rs -> { - String ptid = rs.getString(ptidColumnName); - Integer visit = rs.getInt(visitValueColumnName); - String primaryType = null; - String derivative = null; - String additive = null; - - for (Map.Entry entry : aliasToTypeProperty.entrySet()) - { - switch (entry.getValue().getLevel()) - { - case PrimaryType: - primaryType = rs.getString(entry.getKey()); - break; - case Derivative: - derivative = rs.getString(entry.getKey()); - break; - case Additive: - additive = rs.getString(entry.getKey()); - break; - } - } - - String key = getPtidListKey(visit, primaryType, derivative, additive); - - Set ptids = cellToPtidSet.computeIfAbsent(key, k -> new TreeSet<>()); - ptids.add(ptid != null ? ptid : "[unknown]"); - }); - - for (SummaryByVisitType summary : summaries) - { - Integer visit = summary.getVisit(); - String key = getPtidListKey(visit, summary.getPrimaryType(), summary.getDerivative(), summary.getAdditive()); - Set ptids = cellToPtidSet.get(key); - summary.setParticipantIds(ptids); - } - } - - private String getPtidListKey(Integer visit, String primaryType, String derivativeType, String additiveType) - { - return visit + "/" + primaryType + "/" + - (derivativeType != null ? derivativeType : "all") + - (additiveType != null ? additiveType : "all"); - } - - public RequestSummaryByVisitType[] getRequestSummaryBySite(Container container, User user, SimpleFilter specimenDetailFilter, boolean includeParticipantGroups, SpecimenTypeLevel level, CustomView baseView, boolean completeRequestsOnly) - { - if (specimenDetailFilter == null) - { - specimenDetailFilter = new SimpleFilter(); - } - else - { - SimpleFilter clone = new SimpleFilter(); - clone.addAllClauses(specimenDetailFilter); - specimenDetailFilter = clone; - } - - final SpecimenDetailQueryHelper sqlHelper = getSpecimenDetailQueryHelper(container, user, baseView, specimenDetailFilter, level); - - TableInfo locationTableInfo = SpecimenSchema.get().getTableInfoLocation(container); - String subjectCol = StudyService.get().getSubjectColumnName(container); - String sql = "SELECT Specimen.Container,\n" + - "Specimen." + subjectCol + ",\n" + - "Request.DestinationSiteId,\n" + - "Site.Label AS SiteLabel,\n" + - "Visit AS Visit,\n" + - sqlHelper.getTypeGroupingColumns() + ", COUNT(*) AS VialCount, SUM(Volume) AS TotalVolume\n" + - "FROM (" + sqlHelper.getViewSql().getSQL() + ") AS Specimen\n" + - "JOIN study.SampleRequestSpecimen AS RequestSpecimen ON \n" + - "\tSpecimen.GlobalUniqueId = RequestSpecimen.SpecimenGlobalUniqueId AND\n" + - "\tSpecimen.Container = RequestSpecimen.Container\n" + - "JOIN study.SampleRequest AS Request ON\n" + - "\tRequestSpecimen.SampleRequestId = Request.RowId AND\n" + - "\tRequestSpecimen.Container = Request.Container\n" + - "JOIN " + locationTableInfo.getSelectName() + " AS Site ON\n" + - "\tSite.Container = Request.Container AND\n" + - "\tSite.RowId = Request.DestinationSiteId\n" + - "JOIN study.SampleRequestStatus AS Status ON\n" + - "\tStatus.Container = Request.Container AND\n" + - "\tStatus.RowId = Request.StatusId and Status.SpecimensLocked = ?\n" + - (completeRequestsOnly ? "\tAND Status.FinalState = ?\n" : "") + - "GROUP BY Specimen.Container, Specimen." + subjectCol + ", Site.Label, DestinationSiteId, " + sqlHelper.getTypeGroupingColumns() + ", Visit\n" + - "ORDER BY Specimen.Container, Specimen." + subjectCol + ", Site.Label, DestinationSiteId, " + sqlHelper.getTypeGroupingColumns() + ", Visit"; - - Object[] params = new Object[sqlHelper.getViewSql().getParamsArray().length + 1 + (completeRequestsOnly ? 1 : 0)]; - System.arraycopy(sqlHelper.getViewSql().getParamsArray(), 0, params, 0, sqlHelper.getViewSql().getParamsArray().length); - params[params.length - 1] = Boolean.TRUE; - if (completeRequestsOnly) - params[params.length - 2] = Boolean.TRUE; - - SqlDialect d = SpecimenSchema.get().getSqlDialect(); - SQLFragment fragment = new SQLFragment(); - fragment.appendComment("",d); - fragment.append(sql); - fragment.appendComment("", d); - fragment.addAll(params); - - final List ret = new ArrayList<>(); - - new SqlSelector(SpecimenSchema.get().getSchema(), fragment).forEach(rs -> { - RequestSummaryByVisitType summary = new RequestSummaryByVisitType(); - summary.setDestinationSiteId(rs.getInt("DestinationSiteId")); - summary.setSiteLabel(rs.getString("SiteLabel")); - summary.setVisit(rs.getInt("Visit")); - summary.setTotalVolume(rs.getDouble("TotalVolume")); - long vialCount = rs.getLong("VialCount"); - summary.setVialCount(vialCount); - - for (Map.Entry typeProperty : sqlHelper.getAliasToTypePropertyMap().entrySet()) - { - String value = rs.getString(typeProperty.getKey()); - - try - { - PropertyUtils.setProperty(summary, typeProperty.getValue().getBeanProperty(), value); - } - catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) - { - LogManager.getLogger(SpecimenReportManager.class).error(e); - } - } - ret.add(summary); - }); - - RequestSummaryByVisitType[] summaries = ret.toArray(new RequestSummaryByVisitType[0]); - - if (includeParticipantGroups) - setSummaryParticipantGroups(sql, params, null, summaries, subjectCol, "Visit"); - - return summaries; - } - public Collection getParticipantSummaryByVisitType(Container container, User user, - SimpleFilter specimenDetailFilter, CustomView baseView, CohortFilter.Type cohortType) - { - if (specimenDetailFilter == null) - specimenDetailFilter = new SimpleFilter(); - else - { - SimpleFilter clone = new SimpleFilter(); - clone.addAllClauses(specimenDetailFilter); - specimenDetailFilter = clone; - } - SpecimenDetailQueryHelper sqlHelper = getSpecimenDetailQueryHelper(container, user, baseView, specimenDetailFilter, null); - String subjectCol = StudyService.get().getSubjectColumnName(container); - SQLFragment cohortJoinClause = switch (cohortType) - { - case DATA_COLLECTION -> new SQLFragment( - "LEFT OUTER JOIN study.ParticipantVisit ON\n " + - "\tSpecimenQuery.SequenceNum = study.ParticipantVisit.SequenceNum AND\n" + - "\tSpecimenQuery." + subjectCol + " = study.ParticipantVisit.ParticipantId AND\n" + - "\tSpecimenQuery.Container = study.ParticipantVisit.Container\n" + - "LEFT OUTER JOIN study.Cohort ON \n" + - "\tstudy.ParticipantVisit.CohortId = study.Cohort.RowId AND\n" + - "\tstudy.ParticipantVisit.Container = study.Cohort.Container\n" - ); - case PTID_CURRENT -> new SQLFragment( - """ - LEFT OUTER JOIN study.Cohort ON\s - \tstudy.Participant.CurrentCohortId = study.Cohort.RowId AND - \tstudy.Participant.Container = study.Cohort.Container - """ - ); - case PTID_INITIAL -> new SQLFragment( - """ - LEFT OUTER JOIN study.Cohort ON\s - \tstudy.Participant.InitialCohortId = study.Cohort.RowId AND - \tstudy.Participant.Container = study.Cohort.Container - """ - ); - }; - - SQLFragment ptidSpecimenSQL = new SQLFragment(); - SqlDialect d = SpecimenSchema.get().getSqlDialect(); - ptidSpecimenSQL.appendComment("", d); - ptidSpecimenSQL.append("SELECT SpecimenQuery.Visit AS Visit, SpecimenQuery.").append(subjectCol).append(" AS ParticipantId,\n") - .append("COUNT(*) AS VialCount, study.Cohort.Label AS Cohort, SUM(SpecimenQuery.Volume) AS TotalVolume\n").append("FROM ("); - ptidSpecimenSQL.append(sqlHelper.getViewSql()); - ptidSpecimenSQL.append(") AS SpecimenQuery\n" + "LEFT OUTER JOIN study.Participant ON\n" + "\tSpecimenQuery.").append(subjectCol).append(" = study.Participant.ParticipantId AND\n").append("\tSpecimenQuery.Container = study.Participant.Container\n"); - ptidSpecimenSQL.append(cohortJoinClause); - ptidSpecimenSQL.append("GROUP BY study.Cohort.Label, SpecimenQuery.").append(subjectCol).append(", Visit\n").append("ORDER BY study.Cohort.Label, SpecimenQuery.").append(subjectCol).append(", Visit"); - ptidSpecimenSQL.appendComment("", d); - - return new SqlSelector(SpecimenSchema.get().getSchema(), ptidSpecimenSQL).getCollection(SummaryByVisitParticipant.class); - } -} diff --git a/specimen/src/org/labkey/specimen/report/SpecimenReportTitle.java b/specimen/src/org/labkey/specimen/report/SpecimenReportTitle.java deleted file mode 100644 index 3809f05aadc..00000000000 --- a/specimen/src/org/labkey/specimen/report/SpecimenReportTitle.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2011-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.report; - -import org.apache.commons.lang3.StringUtils; - -/** - * User: adam - * Date: 12/12/11 - * Time: 1:40 PM - */ - -// Some specimen reports coalesce title columns vertically instead of repeating the same information in every row. This -// is a problem in demo mode, where all PTIDs of the same length resolve to the same display value. This class was added -// to distinguish "value" (the underlying value we use to coalesce) from "display value" (what we display to the user). -public class SpecimenReportTitle -{ - private final String _value; - private final String _displayValue; - - // Simple case -- value and display value are the same - public SpecimenReportTitle(String value) - { - this(value, value); - } - - public SpecimenReportTitle(String value, String displayValue) - { - _value = StringUtils.trimToEmpty(value); - _displayValue = StringUtils.trimToEmpty(displayValue); - } - - public String getValue() - { - return _value; - } - - public String getDisplayValue() - { - return _displayValue; - } -} diff --git a/specimen/src/org/labkey/specimen/report/SpecimenTypeBeanProperty.java b/specimen/src/org/labkey/specimen/report/SpecimenTypeBeanProperty.java deleted file mode 100644 index 5a4e01a8a02..00000000000 --- a/specimen/src/org/labkey/specimen/report/SpecimenTypeBeanProperty.java +++ /dev/null @@ -1,47 +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.report; - -import org.labkey.api.query.FieldKey; - -public class SpecimenTypeBeanProperty -{ - private final FieldKey _typeKey; - private final String _beanProperty; - private final SpecimenTypeLevel _level; - - public SpecimenTypeBeanProperty(FieldKey typeKey, String beanProperty, SpecimenTypeLevel level) - { - _typeKey = typeKey; - _beanProperty = beanProperty; - _level = level; - } - - public FieldKey getTypeKey() - { - return _typeKey; - } - - public String getBeanProperty() - { - return _beanProperty; - } - - public SpecimenTypeLevel getLevel() - { - return _level; - } -} diff --git a/specimen/src/org/labkey/specimen/report/SpecimenTypeLevel.java b/specimen/src/org/labkey/specimen/report/SpecimenTypeLevel.java deleted file mode 100644 index cfcb78451b2..00000000000 --- a/specimen/src/org/labkey/specimen/report/SpecimenTypeLevel.java +++ /dev/null @@ -1,98 +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.report; - -import org.labkey.api.query.FieldKey; -import org.labkey.api.util.SafeToRenderEnum; - -import java.util.ArrayList; -import java.util.List; - -public enum SpecimenTypeLevel implements SafeToRenderEnum -{ - PrimaryType() - { - @Override - public List getGroupingColumns() - { - List list = new ArrayList<>(); - list.add(new SpecimenTypeBeanProperty(FieldKey.fromParts("PrimaryType", "Description"), "primaryType", this)); - return list; - } - - @Override - public String[] getTitleHierarchy(SummaryByVisitType summary) - { - return new String[]{summary.getPrimaryType()}; - } - - @Override - public String getLabel() - { - return "Primary Type"; - } - }, - Derivative() - { - @Override - public List getGroupingColumns() - { - List parent = SpecimenTypeLevel.PrimaryType.getGroupingColumns(); - parent.add(new SpecimenTypeBeanProperty(FieldKey.fromParts("DerivativeType", "Description"), "derivative", this)); - return parent; - } - - @Override - public String[] getTitleHierarchy(SummaryByVisitType summary) - { - return new String[]{summary.getPrimaryType(), summary.getDerivative()}; - } - - @Override - public String getLabel() - { - return "Derivative"; - } - }, - Additive() - { - @Override - public List getGroupingColumns() - { - List parent = SpecimenTypeLevel.Derivative.getGroupingColumns(); - parent.add(new SpecimenTypeBeanProperty(FieldKey.fromParts("AdditiveType", "Description"), "additive", this)); - return parent; - } - - @Override - public String[] getTitleHierarchy(SummaryByVisitType summary) - { - return new String[]{summary.getPrimaryType(), summary.getDerivative(), summary.getAdditive()}; - } - - @Override - public String getLabel() - { - return "Additive"; - } - }; - - public abstract String[] getTitleHierarchy(SummaryByVisitType summary); - - public abstract List getGroupingColumns(); - - public abstract String getLabel(); -} diff --git a/specimen/src/org/labkey/specimen/report/SpecimenTypeVisitReport.java b/specimen/src/org/labkey/specimen/report/SpecimenTypeVisitReport.java deleted file mode 100644 index 111273372cd..00000000000 --- a/specimen/src/org/labkey/specimen/report/SpecimenTypeVisitReport.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2008-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.report; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.study.Visit; -import org.labkey.api.util.DemoMode; -import org.labkey.api.util.Formats; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.view.ActionURL; -import org.labkey.specimen.actions.SpecimenController; -import org.labkey.specimen.actions.SpecimenReportActions; - -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; -import java.util.TreeMap; - -public class SpecimenTypeVisitReport extends SpecimenVisitReport -{ - public SpecimenTypeVisitReport(String titlePrefix, Collection visits, SimpleFilter filter, SpecimenVisitReportParameters parameters) - { - super(titlePrefix, visits, filter, parameters); - } - - @Override - public Collection createRows() - { - SpecimenTypeLevel level = getTypeLevelEnum(); - SummaryByVisitType[] countSummary = SpecimenReportManager.get().getSpecimenSummaryByVisitType(_container, getUser(), _filter, isViewPtidList(), level, getBaseCustomView()); - Map rows = new TreeMap<>(); - - for (SummaryByVisitType count : countSummary) - { - String key = count.getPrimaryType() + "/" + - (count.getDerivative() != null ? count.getDerivative() : "All") + "/" + - (count.getAdditive() != null ? count.getAdditive() : "All"); - Row row = rows.get(key); - if (row == null) - { - String[] titleHierarchy = level.getTitleHierarchy(count); - row = new Row(titleHierarchy); - rows.put(key, row); - } - setVisitAsNonEmpty(count.getVisit()); - row.add(count); - } - - return rows.values(); - } - - private String getCellSummaryText(SummaryByVisitType summary) - { - StringBuilder summaryString = new StringBuilder(); - if (isViewVialCount()) - summaryString.append(summary.getVialCount()); - if (isViewParticipantCount()) - { - if (!summaryString.isEmpty()) - summaryString.append("/"); - summaryString.append(summary.getParticipantCount()); - } - if (isViewVolume()) - { - if (!summaryString.isEmpty()) - summaryString.append("/"); - //summaryString.append(summary.getTotalVolume()); - summaryString.append(Formats.f2.format(summary.getTotalVolume())); - } - return summaryString.toString(); - } - - @Override - protected String[] getCellExcelText(Visit visit, SummaryByVisitType summary) - { - if (summary == null || summary.getVialCount() == null) - return new String[] {}; - String summaryText = getCellSummaryText(summary); - boolean hasSummaryText = summaryText != null && !summaryText.isEmpty(); - int ptidCount = isViewPtidList() ? summary.getParticipantIds().size() : 0; - String[] strArray = new String[ptidCount + (hasSummaryText ? 1 : 0)]; - if (hasSummaryText) - strArray[0] = summaryText; - if (ptidCount > 0) - { - int currentIndex = (hasSummaryText ? 1 : 0); - for (String s : summary.getParticipantIds()) - strArray[currentIndex++] = s; - } - return strArray; - } - - @Override - protected String getCellHtml(Visit visit, SummaryByVisitType summary) - { - if (summary == null || summary.getVialCount() == null) - return " "; - ActionURL link = SpecimenController.getSpecimensURL(_container, true); - link = updateURLFilterParameter(link, "SpecimenDetail.Visit/SequenceNumMin", visit.getSequenceNumMin()); - - link = updateURLFilterParameter(link, "SpecimenDetail.PrimaryType/Description", summary.getPrimaryType()); - SpecimenTypeLevel level = getTypeLevelEnum(); - if (level == SpecimenTypeLevel.Derivative || level == SpecimenTypeLevel.Additive) - link = updateURLFilterParameter(link, "SpecimenDetail.DerivativeType/Description", summary.getDerivative()); - if (level == SpecimenTypeLevel.Additive) - link = updateURLFilterParameter(link, "SpecimenDetail.AdditiveType/Description", summary.getAdditive()); - - String linkHtml = link.getLocalURIString(); - if (_filter != null) - linkHtml += "&" + getFilterQueryString(visit, summary); - String summaryString = getCellSummaryText(summary); - StringBuilder cellHtml = new StringBuilder(); - if (!summaryString.isEmpty()) - { - cellHtml.append(""); - cellHtml.append(summaryString).append(""); - } - - if (isViewPtidList()) - { - if (!cellHtml.isEmpty()) - cellHtml.append("
"); - if (summary.getParticipantIds() != null) - { - for (Iterator it = summary.getParticipantIds().iterator(); it.hasNext();) - { - String participantId = it.next(); - ActionURL url = new ActionURL(SpecimenReportActions.TypeParticipantReportAction.class, _container); - url.addParameter("participantId", participantId); - url.addParameter(SpecimenVisitReportParameters.PARAMS.typeLevel, getTypeLevelEnum().name()); - url.addParameter(SpecimenVisitReportParameters.PARAMS.statusFilterName, getStatusFilterName()); - url.addParameter(SpecimenVisitReportParameters.PARAMS.viewVialCount, isViewVialCount()); - url.addParameter(SpecimenVisitReportParameters.PARAMS.viewParticipantCount, isViewParticipantCount()); - url.addParameter(SpecimenVisitReportParameters.PARAMS.viewVolume, isViewVolume()); - if (getBaseCustomViewName() != null && !getBaseCustomViewName().isEmpty() && - !SpecimenVisitReportParameters.DEFAULT_VIEW_ID.equals(getBaseCustomViewName())) - { - url.addParameter(SpecimenVisitReportParameters.PARAMS.baseCustomViewName, getBaseCustomViewName()); - } - cellHtml.append(""); - cellHtml.append(PageFlowUtil.filter(DemoMode.id(participantId, getContainer(), getUser()))); - cellHtml.append(""); - if (it.hasNext()) - cellHtml.append("
"); - } - } - } - return cellHtml.toString(); - } -} diff --git a/specimen/src/org/labkey/specimen/report/SpecimenVisitReport.java b/specimen/src/org/labkey/specimen/report/SpecimenVisitReport.java deleted file mode 100644 index 52530500e3f..00000000000 --- a/specimen/src/org/labkey/specimen/report/SpecimenVisitReport.java +++ /dev/null @@ -1,427 +0,0 @@ -/* - * Copyright (c) 2008-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.report; - -import org.labkey.api.collections.IntHashMap; -import org.labkey.api.data.Container; -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.query.CustomView; -import org.labkey.api.query.FieldKey; -import org.labkey.api.query.QueryDefinition; -import org.labkey.api.query.QueryService; -import org.labkey.api.query.UserSchema; -import org.labkey.api.security.User; -import org.labkey.api.specimen.SpecimenQuerySchema; -import org.labkey.api.study.CohortFilter; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.study.Visit; -import org.labkey.api.study.model.ParticipantGroup; -import org.labkey.api.study.model.ParticipantGroupService; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.NotFoundException; -import org.labkey.specimen.query.SpecimenQueryView; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * User: brittp - * Created: Jan 14, 2008 10:14:50 AM - */ -public abstract class SpecimenVisitReport -{ - private final Collection _visits; - private final SpecimenVisitReportParameters _parameters; - private final boolean _viewParticipantCount; - private final boolean _viewVolume; - private final boolean _viewPtidList; - private final boolean _viewVialCount; - private final String _title; - private final Map _nonEmptyColumns = new IntHashMap<>(); - - protected final Container _container; - protected final SimpleFilter _filter; - - private Collection _rows; - private List _nonEmptyVisits; - - public SpecimenVisitReport(String titlePrefix, Collection visits, SimpleFilter filter, SpecimenVisitReportParameters parameters) - { - _visits = visits; - _filter = filter; - _parameters = parameters; - _container = parameters.getContainer(); - _viewParticipantCount = parameters.isViewParticipantCount(); - _viewVolume = parameters.isViewVolume(); - _viewPtidList = parameters.isViewPtidList(); - // We'll show the vial count if it's explicitly requested or if nothing else has been selected: - _viewVialCount = parameters.isViewVialCount() || (!_viewParticipantCount && !_viewVolume && !_viewPtidList); - _title = titlePrefix + getTitleSuffix(); - } - - public Collection getRows() - { - if (_rows == null) - _rows = createRows(); - return _rows; - } - - protected abstract Collection createRows(); - - public String getTitle() - { - return _title; - } - - protected ActionURL updateURLFilterParameter(ActionURL url, String columnBase, Object value) - { - url.deleteParameter(columnBase + "~eq"); - url.deleteParameter(columnBase + "~isblank"); - if (value == null) - url.addParameter(columnBase + "~isblank", null); - else - url.addParameter(columnBase + "~eq", value.toString()); - return url; - } - - public Collection getVisits() - { - // ensure rows and non-empty columns have been generated - getRows(); - if (!_parameters.isHideEmptyColumns()) - return _visits; - else - { - if (_nonEmptyVisits == null) - { - List visits = new ArrayList<>(); - for (Visit visit : _visits) - { - if (_nonEmptyColumns.containsKey(visit.getId())) - visits.add(visit); - } - _nonEmptyVisits = new ArrayList<>(visits); - } - return _nonEmptyVisits; - } - } - - protected void setVisitAsNonEmpty(Integer visit) - { - if (!_nonEmptyColumns.containsKey(visit)) - _nonEmptyColumns.put(visit, visit); - } - - protected abstract String getCellHtml(Visit visit, CELLDATA summary); - - protected abstract String[] getCellExcelText(Visit visit, CELLDATA summary); - - public boolean isNumericData() - { - int valuesDisplayed =(_parameters.isViewVialCount() ? 1 : 0) + - (_parameters.isViewParticipantCount() ? 1 : 0) + - (_parameters.isViewVolume() ? 1 : 0); - return (valuesDisplayed == 1 && !_parameters.isViewPtidList()); - } - - private String getTitleSuffix() - { - StringBuilder suffixBuilder = new StringBuilder(); - if (isViewVialCount()) - suffixBuilder.append(" (Vial Count"); - if (isViewVolume()) - { - suffixBuilder.append(!suffixBuilder.isEmpty() ? "/" : " ("); - suffixBuilder.append("Total Volume"); - } - if (isViewParticipantCount()) - { - suffixBuilder.append(!suffixBuilder.isEmpty() ? "/" : " ("); - suffixBuilder.append(PageFlowUtil.filter(StudyService.get().getSubjectNounSingular(getContainer()))).append(" Count"); - } - if (isViewPtidList()) - { - suffixBuilder.append(!suffixBuilder.isEmpty() ? ", " : " ("); - suffixBuilder.append(PageFlowUtil.filter(StudyService.get().getSubjectColumnName(getContainer()))).append(" Group"); - } - suffixBuilder.append(")"); - return suffixBuilder.toString(); - } - - private SimpleFilter geBaseViewFilter() - { - SimpleFilter fullFilter = new SimpleFilter(); - fullFilter.addAllClauses(_filter); - // This is a terrible hack to deal with the fact that the some SpecimenDetail columns have been aliased - // in the query view. As a result, we need to use the view's column name for filtering - // at the database layer, and then map this column name for use in a query view filter parameter: - fullFilter = replaceFilterParameterName(fullFilter, "PrimaryTypeId", FieldKey.fromParts("PrimaryType", "RowId").toString()); - fullFilter = replaceFilterParameterName(fullFilter, "DerivativeTypeId", FieldKey.fromParts("DerivativeType", "RowId").toString()); - fullFilter = replaceFilterParameterName(fullFilter, "AdditiveTypeId", FieldKey.fromParts("AdditiveType", "RowId").toString()); - fullFilter = replaceFilterParameterName(fullFilter, "ptid", StudyService.get().getSubjectColumnName(getContainer())); - return fullFilter; - } - - protected void addCohortURLFilter(Study study, ActionURL url) - { - if (_parameters.getCohortFilter() != null) - _parameters.getCohortFilter().addURLParameters(study, url, null); - } - - protected String getFilterQueryString(Visit visit, CELLDATA summary) - { - ActionURL url = new ActionURL(); - if (_parameters.getBaseCustomViewName() != null && !_parameters.getBaseCustomViewName().isEmpty()) - { - if (!SpecimenVisitReportParameters.DEFAULT_VIEW_ID.equals(_parameters.getBaseCustomViewName())) - url.addParameter("SpecimenDetail.viewName", _parameters.getBaseCustomViewName()); - } - else - url.addParameter("SpecimenDetail.ignoreFilter", "1"); - - - Study study = StudyService.get().getStudy(visit.getContainer()); - if (null != study) - addCohortURLFilter(study, url); - - if (_parameters.getParticipantGroupFilter() >= 0) - { - ParticipantGroup filterGroup = ParticipantGroupService.get().getParticipantGroup(getContainer(), getUser(), _parameters.getParticipantGroupFilter()); - if (filterGroup != null) - filterGroup.addURLFilter(url, getContainer(), "SpecimenDetail"); - } - - switch (_parameters.getStatusFilter()) - { - case ALL: - case AVAILABLE: - case UNAVAILABLE: - case REQUESTED_INPROCESS: - case NOT_REQUESTED_INPROCESS: - // We don't need to add any special URL parameter here: these filters are just based on columns - // in SpecimenDetail, so the query string should be populated correctly due to geBaseViewFilter().toQueryString(). - break; - case NOT_REQUESTED_COMPLETE: - url.addParameter(SpecimenQueryView.PARAMS.showNotCompleteRequestedOnly, "true"); - break; - case REQUESTED_COMPLETE: - url.addParameter(SpecimenQueryView.PARAMS.showCompleteRequestedOnly, "true"); - break; - } - - String baseParams = geBaseViewFilter().toQueryString("SpecimenDetail"); - if (baseParams != null && !baseParams.isEmpty()) - return baseParams + "&" + url.getQueryString(); - else - return url.getQueryString(); - } - - public int getLabelDepth() - { - Collection rows = getRows(); - if (rows.isEmpty()) - return 0; - return rows.iterator().next().getTitleHierarchy().length; - } - - private static SpecimenReportTitle[] getSpecimenReportTitle(String[] stringHierarchy) - { - SpecimenReportTitle[] titleHierarchy = new SpecimenReportTitle[stringHierarchy.length]; - - for (int i = 0; i < titleHierarchy.length; i++) - titleHierarchy[i] = new SpecimenReportTitle(stringHierarchy[i]); - - return titleHierarchy; - } - - public class Row - { - private final SpecimenReportTitle[] _titleHierarchy; - private final Map _visitData = new IntHashMap<>(); - - public Row(String[] stringHierarchy) - { - this(getSpecimenReportTitle(stringHierarchy)); - } - - public Row(SpecimenReportTitle[] titleHierarchy) - { - _titleHierarchy = titleHierarchy; - } - - public String getCellHtml(Visit visit) - { - CELLDATA summary = _visitData.get(visit.getId()); - return SpecimenVisitReport.this.getCellHtml(visit, summary); - } - - public String[] getCellExcelText(Visit visit) - { - CELLDATA summary = _visitData.get(visit.getId()); - return SpecimenVisitReport.this.getCellExcelText(visit, summary); - } - - public int getMaxExcelRowHeight(List visits) - { - int max = 1; - for (Visit visit : visits) - { - int currentHeight = getCellExcelText(visit).length; - if (currentHeight > max) - max = currentHeight; - } - return max; - } - - public SpecimenReportTitle[] getTitleHierarchy() - { - return _titleHierarchy; - } - - public void add(CELLDATA summary) - { - _visitData.put(summary.getVisit(), summary); - } - } - - protected SimpleFilter replaceFilterParameterName(SimpleFilter filter, String oldKey, String newKey) - { - FieldKey oldFieldKey = FieldKey.fromString(oldKey); - for (SimpleFilter.FilterClause clause : filter.getClauses()) - { - if (clause.getFieldKeys().size() == 1 && oldFieldKey.equals(clause.getFieldKeys().getFirst())) - { - if (clause.getParamVals().length > 1) - throw new UnsupportedOperationException("Only single filters are supported on column " + newKey); - filter.deleteConditions(oldFieldKey); - if (clause.getParamVals().length != 1) - throw new UnsupportedOperationException("Reports that provide custom SQL filters must override getFilterQueryString."); - filter.addCondition(newKey, clause.getParamVals()[0]); - return filter; - } - } - return filter; - } - - protected boolean hasContent(String[] strArray) - { - if (strArray == null || strArray.length == 0) - return false; - for (String str : strArray) - { - if (str != null && !str.isEmpty()) - return true; - } - return false; - } - - protected String buildCellHtml(Visit visit, CELLDATA summary, String linkHtml) - { - String[] summaryString = getCellExcelText(visit, summary); - StringBuilder cellHtml = new StringBuilder(); - if (hasContent(summaryString)) - { - if (linkHtml != null) - cellHtml.append(""); - boolean first = true; - for (String str : summaryString) - { - if (str != null && !str.isEmpty()) - { - if (!first) - cellHtml.append("
"); - first = false; - cellHtml.append(str); - } - } - if (linkHtml != null) - cellHtml.append("
"); - } - return cellHtml.toString(); - } - - protected Container getContainer() - { - return _parameters.getContainer(); - } - - protected User getUser() - { - return _parameters.getUser(); - } - - protected boolean isViewVialCount() - { - return _viewVialCount; - } - - protected boolean isViewParticipantCount() - { - return _viewParticipantCount; - } - - protected boolean isViewVolume() - { - return _viewVolume; - } - - protected boolean isViewPtidList() - { - return _viewPtidList; - } - - protected String getBaseCustomViewName() - { - return _parameters.getBaseCustomViewName(); - } - - protected SpecimenTypeLevel getTypeLevelEnum() - { - return _parameters.getTypeLevelEnum(); - } - - protected String getStatusFilterName() - { - return _parameters.getStatusFilterName(); - } - - protected CohortFilter getCohortFilter() - { - if (StudyService.get().showCohorts(_container, getUser())) - return _parameters.getCohortFilter(); - return null; - } - - protected CustomView getBaseCustomView() - { - if (_parameters.getBaseCustomViewName() == null) - return null; - UserSchema schema = SpecimenQuerySchema.get(StudyService.get().getStudy(_container), _parameters.getUser()); - QueryDefinition def = QueryService.get().createQueryDefForTable(schema, "SpecimenDetail"); - String customViewName = _parameters.getBaseCustomViewName(); - if (SpecimenVisitReportParameters.DEFAULT_VIEW_ID.equals(customViewName)) - customViewName = null; - CustomView view = def.getCustomView(_parameters.getUser(), _parameters.getViewContext().getRequest(), customViewName); - if (view == null) - throw new NotFoundException("Custom view " + _parameters.getBaseCustomViewName() + " was not found. It may have been deleted by another user."); - - return view; - } -} diff --git a/specimen/src/org/labkey/specimen/report/SpecimenVisitReportAction.java b/specimen/src/org/labkey/specimen/report/SpecimenVisitReportAction.java deleted file mode 100644 index 5e0ca7087ed..00000000000 --- a/specimen/src/org/labkey/specimen/report/SpecimenVisitReportAction.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.report; - -import org.labkey.api.action.SimpleViewAction; -import org.labkey.api.data.Container; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.JspView; -import org.labkey.api.view.NavTree; -import org.labkey.api.view.VBox; -import org.labkey.specimen.actions.ReportConfigurationBean; -import org.labkey.specimen.actions.SpecimenController; -import org.labkey.specimen.actions.SpecimenController.AutoReportListAction; -import org.springframework.validation.BindException; -import org.springframework.web.servlet.ModelAndView; - -public abstract class SpecimenVisitReportAction extends SimpleViewAction -{ - private FormType _form; - - public SpecimenVisitReportAction(Class beanClass) - { - super(beanClass); - } - - @Override - public ModelAndView getView(FormType specimenVisitReportForm, BindException errors) - { - _form = specimenVisitReportForm; - if (specimenVisitReportForm.isExcelExport()) - { - SpecimenReportExcelWriter writer = new SpecimenReportExcelWriter(specimenVisitReportForm); - writer.write(getViewContext().getResponse()); - return null; - } - else - { - JspView reportView = new JspView<>("/org/labkey/specimen/view/specimenVisitReport.jsp", specimenVisitReportForm); - reportView.setIsWebPart(false); - if (this.isPrint()) - { - return reportView; - } - else - { - // Need unique id only in webpart case - ReportConfigurationBean bean = new ReportConfigurationBean(specimenVisitReportForm, false, 0); - return new VBox(new JspView<>("/org/labkey/specimen/view/autoReportList.jsp", bean), reportView); - } - } - } - - @Override - public void addNavTrail(NavTree root) - { - Container c = getContainer(); - ActionURL overviewURL = new ActionURL(SpecimenController.OverviewAction.class, c); - root.addChild("Specimen Overview", overviewURL); - root.addChild("Available Reports", new ActionURL(AutoReportListAction.class, c)); - root.addChild("Specimen Report: " + _form.getLabel()); - } -} diff --git a/specimen/src/org/labkey/specimen/report/SpecimenVisitReportParameters.java b/specimen/src/org/labkey/specimen/report/SpecimenVisitReportParameters.java deleted file mode 100644 index f49ecfcfd73..00000000000 --- a/specimen/src/org/labkey/specimen/report/SpecimenVisitReportParameters.java +++ /dev/null @@ -1,560 +0,0 @@ -/* - * Copyright (c) 2008-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.report; - -import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.jsp.taglib.AutoCompleteTextTag; -import org.labkey.api.query.CustomView; -import org.labkey.api.query.FieldKey; -import org.labkey.api.security.User; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.specimen.location.LocationImpl; -import org.labkey.api.study.Cohort; -import org.labkey.api.study.CohortFilter; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.study.model.CohortService; -import org.labkey.api.study.model.ParticipantGroup; -import org.labkey.api.study.model.ParticipantGroupService; -import org.labkey.api.util.DemoMode; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.util.Pair; -import org.labkey.api.util.SafeToRenderEnum; -import org.labkey.api.util.OptionBuilder; -import org.labkey.api.util.SelectBuilder; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.UnauthorizedException; -import org.labkey.api.view.ViewForm; -import org.labkey.specimen.actions.SpecimenController.CompleteSpecimenAction; -import org.labkey.specimen.settings.SettingsManager; - -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.labkey.api.util.HtmlString.unsafe; - -/** - * User: brittp - * Created: Jan 14, 2008 12:26:17 PM - */ -public abstract class SpecimenVisitReportParameters extends ViewForm -{ - public static final String DEFAULT_VIEW_ID = "~default~"; - - public enum PARAMS - { - statusFilterName, - viewVialCount, - viewParticipantCount, - viewVolume, - baseCustomViewName, - viewPtidList, - typeLevel, - hideEmptyColumns - } - - public enum Status implements SafeToRenderEnum - { - ALL("All vials"), - AVAILABLE("Available vials"), - UNAVAILABLE("Unavailable vials"), - REQUESTED_INPROCESS("Vials in any request (including in-process)"), - NOT_REQUESTED_INPROCESS("Vials not in in any request"), - REQUESTED_COMPLETE("Vials in completed requests"), - NOT_REQUESTED_COMPLETE("Vials not in completed requests"); - - private final String _caption; - - Status(String caption) - { - _caption = caption; - } - - public String getCaption() - { - return _caption; - } - } - - private CohortFilter _cohortFilter; - private Status _statusFilter = Status.ALL; - private String _baseCustomViewName; - private boolean _viewVialCount = false; - private boolean _viewParticipantCount = false; - private boolean _viewVolume = false; - private boolean _viewPtidList = false; - private boolean _hideEmptyColumns; - private boolean _excelExport; - private int _participantGroupFilter = -1; - private List _reports; - private SpecimenTypeLevel _typeLevel = SpecimenTypeLevel.Derivative; - - public SpecimenVisitReportParameters() - { - _cohortFilter = CohortService.get().getFromURL(getViewContext().getContainer(), getViewContext().getUser(), getViewContext().getActionURL()); - } - - public String getTypeLevel() - { - return _typeLevel.name(); - } - - public void setTypeLevel(String typeLevel) - { - _typeLevel = SpecimenTypeLevel.valueOf(typeLevel); - } - - public SpecimenTypeLevel getTypeLevelEnum() - { - return _typeLevel; - } - - public CohortFilter getCohortFilter() - { - return _cohortFilter; - } - - public Cohort getCohort() - { - return _cohortFilter != null ? _cohortFilter.getCohort(getContainer(), getUser()) : null; - } - - public void setCohortFilter(CohortFilter cohortFilter) - { - if (StudyService.get().showCohorts(getContainer(), getUser())) - _cohortFilter = cohortFilter; - } - - public List> getAdditionalFormInputHtml(User user) - { - return Collections.emptyList(); - } - - public String getStatusFilterName() - { - return _statusFilter.name(); - } - - public void setStatusFilterName(String statusFilter) - { - _statusFilter = Status.valueOf(statusFilter); - } - - public Status getStatusFilter() - { - return _statusFilter; - } - - public boolean isViewVialCount() - { - return _viewVialCount; - } - - public void setViewVialCount(boolean viewVialCount) - { - _viewVialCount = viewVialCount; - } - - public boolean isViewParticipantCount() - { - return _viewParticipantCount; - } - - public void setViewParticipantCount(boolean viewParticipantCount) - { - _viewParticipantCount = viewParticipantCount; - } - - public boolean isViewVolume() - { - return _viewVolume; - } - - public void setViewVolume(boolean viewVolume) - { - _viewVolume = viewVolume; - } - - public boolean isViewPtidList() - { - return _viewPtidList; - } - - public void setViewPtidList(boolean viewPtidList) - { - _viewPtidList = viewPtidList; - } - - public boolean isHideEmptyColumns() - { - return _hideEmptyColumns; - } - - public void setHideEmptyColumns(boolean hideEmptyColumns) - { - _hideEmptyColumns = hideEmptyColumns; - } - - public boolean isExcelExport() - { - return _excelExport; - } - - public void setExcelExport(boolean excelExport) - { - _excelExport = excelExport; - } - - public String getBaseCustomViewName() - { - return _baseCustomViewName; - } - - public void setBaseCustomViewName(String baseCustomViewName) - { - _baseCustomViewName = baseCustomViewName; - } - - protected void addBaseFilters(SimpleFilter filter) - { - if (allowsAvailabilityFilter() && getStatusFilter() != null) - addAvailabilityFilter(filter, getStatusFilter()); - - if (allowsCohortFilter() && getCohortFilter() != null) - addCohortFilter(filter, getCohortFilter()); - - if (allowsParticipantGroupFilter() && getParticipantGroupFilter() >= 0) - addParticipantGroupFilter(filter, getParticipantGroupFilter()); - } - - public static final String COMPLETED_REQUESTS_FILTER_SQL = - "SELECT SpecimenGlobalUniqueId FROM study.SampleRequestSpecimen\n" + - "\tJOIN study.SampleRequest ON study.SampleRequestSpecimen.SampleRequestId = study.SampleRequest.RowId\n" + - "\tJOIN study.SampleRequestStatus ON study.SampleRequest.StatusId = study.SampleRequestStatus.RowId\n" + - "\tWHERE study.SampleRequestStatus.SpecimensLocked = ? AND study.SampleRequestStatus.FinalState = ?\n" + - "\tAND study.SampleRequest.Container = ?"; - - protected void addAvailabilityFilter(SimpleFilter filter, Status status) - { - switch (status) - { - case ALL: - break; - case AVAILABLE: - filter.addCondition(FieldKey.fromParts("Available"), Boolean.TRUE); - break; - case UNAVAILABLE: - filter.addCondition(FieldKey.fromParts("Available"), Boolean.FALSE); - break; - case REQUESTED_INPROCESS: - filter.addCondition(FieldKey.fromParts("LockedInRequest"), Boolean.TRUE); - break; - case NOT_REQUESTED_INPROCESS: - filter.addCondition(FieldKey.fromParts("LockedInRequest"), Boolean.FALSE); - break; - case REQUESTED_COMPLETE: - filter.addWhereClause("GlobalUniqueId IN (\n" + COMPLETED_REQUESTS_FILTER_SQL + ")", - new Object[] { Boolean.TRUE, Boolean.TRUE, getContainer().getId()}); - break; - case NOT_REQUESTED_COMPLETE: - filter.addWhereClause("GlobalUniqueId NOT IN (\n" + COMPLETED_REQUESTS_FILTER_SQL + ")", - new Object[] { Boolean.TRUE, Boolean.TRUE, getContainer().getId()}); - break; - } - } - - protected void addParticipantGroupFilter(SimpleFilter filter, int ptidListId) - { - ParticipantGroup group = ParticipantGroupService.get().getParticipantGroup(getContainer(), getUser(), ptidListId); - if (group != null) - { - SQLFragment sql = new SQLFragment("(") - .appendIdentifier(StudyService.get().getSubjectColumnName(getContainer())) - .append(" IN (SELECT ") - .append("ParticipantId FROM ") - .append(SpecimenSchema.get().getTableInfoParticipantGroupMap()) - .append(" WHERE GroupId = ?))") - .add(ptidListId); - filter.addWhereClause(sql); - } - } - - protected void addCohortFilter(SimpleFilter filter, CohortFilter cohortFilter) - { - if (!StudyService.get().showCohorts(getContainer(), getUser())) - throw new UnauthorizedException("User does not have permission to view cohort information"); - - if (cohortFilter == CohortService.get().getUnassignedCohortFilter()) - { - SQLFragment whereClause = new SQLFragment("(") - .appendIdentifier(StudyService.get().getSubjectColumnName(getContainer())) - .append(" IN\n" + "(SELECT ParticipantId FROM study.participant WHERE CurrentCohortId IS NULL AND Container = ?)" + " OR (") - .appendIdentifier(StudyService.get().getSubjectColumnName(getContainer())) - .append(" NOT IN (SELECT ParticipantId FROM study.participant WHERE Container = ?)))") - .add(getContainer()) - .add(getContainer()); - - filter.addWhereClause(whereClause); - } - else if (cohortFilter != null) - { - Cohort cohort = cohortFilter.getCohort(getContainer(),getUser()); - int cohortId = null==cohort ? -1 : cohort.getRowId(); - - switch (cohortFilter.getType()) - { - case DATA_COLLECTION: - filter.addWhereClause(new SQLFragment("CollectionCohort = ? AND Container = ?") - .add(cohortId) - .add(getContainer()) - ); - break; - case PTID_CURRENT: - filter.addWhereClause(new SQLFragment() - .appendIdentifier(StudyService.get().getSubjectColumnName(getContainer())) - .append(" IN\n(SELECT ParticipantId FROM study.participant WHERE CurrentCohortId = ? AND Container = ?)") - .add(cohortId) - .add(getContainer()) - ); - break; - case PTID_INITIAL: - filter.addWhereClause(new SQLFragment() - .appendIdentifier(StudyService.get().getSubjectColumnName(getContainer())) - .append(" IN\n(SELECT ParticipantId FROM study.participant WHERE InitialCohortId = ? AND Container = ?)") - .add(cohortId).add(getContainer()) - ); - break; - } - } - } - - protected Pair getEnrollmentSitePicker(String inputName, Set locations, Integer selectedSiteId) - { - SelectBuilder sb = new SelectBuilder() - .name(inputName) - .addOption(new OptionBuilder() - .value("") - .label("All enrollment locations") - .build()); - - for (LocationImpl location : locations) - { - String label = "Unassigned enrollment location"; - String value = "-1"; - int currentSelectedSite = -1; - - if (location != null) - { - label = location.getLabel(); - value = Integer.toString(location.getRowId()); - currentSelectedSite = location.getRowId(); - } - - boolean selected = selectedSiteId != null && selectedSiteId == currentSelectedSite; - OptionBuilder ob = new OptionBuilder() - .value(value) - .label(label) - .selected(selected); - - sb.addOption(ob.build()); - } - - return new Pair<>("Enrollment site", unsafe(sb.toString())); - } - - public HtmlString getCustomViewPicker(Map specimenDetailViews) - { - SelectBuilder sb = new SelectBuilder() - .name("baseCustomViewName") - .addOption(new OptionBuilder() - .value("") - .label("Base report on all vials") - .build()); - - for (Map.Entry viewEntry : specimenDetailViews.entrySet()) - { - OptionBuilder ob = new OptionBuilder(); - String name = viewEntry.getKey(); - CustomView view = viewEntry.getValue(); - - if (view.getFilterAndSort() != null) - { - String viewName = DEFAULT_VIEW_ID; - String label = "Base on default view (filtered)"; - - if (name != null) - { - viewName = view.getName(); - label = "Base on view: " + view.getName(); - } - - boolean selected = _baseCustomViewName != null && _baseCustomViewName.equals(viewName); - sb.addOption(ob - .value(viewName) - .label(label) - .selected(selected) - .build()); - } - } - - return unsafe(sb.toString()); - } - - private String _allString = null; - protected String getAllString() - { - if (_allString == null) - _allString = "All " + PageFlowUtil.filter(StudyService.get().getSubjectNounPlural(getContainer())) + " (Large Report)"; - return _allString; - } - - public boolean isAllSubjectsOption(String subject) - { - if (subject == null) - return false; - String allString = getAllString().toLowerCase(); - subject = subject.toLowerCase(); - if (subject.length() > allString.length()) - return false; - return allString.startsWith(subject); - } - - protected Pair getParticipantPicker(String inputName, String selectedParticipantId) - { - Study study = StudyService.get().getStudy(getContainer()); - SelectBuilder builder = new SelectBuilder(); - - String allString = getAllString(); - Collection participantIds = StudyService.get().getParticipantIds(study, getUser()); - HtmlString participantPickerValues; - if (participantIds.size() <= 200) - { - // select the previously selected option or the first non-all option. We don't want to select 'all participants' - // by default, since these reports are extremely expensive to generate. - builder.name(inputName) - .addOption(new OptionBuilder() - .value(allString) - .label(allString) - .selected(isAllSubjectsOption(selectedParticipantId)) - .build()); - - boolean first = true; - for (String participantId : participantIds) - { - boolean isSelected = (selectedParticipantId != null && selectedParticipantId.equals(participantId)) || - (selectedParticipantId == null && first); - first = false; - - builder.addOption(new OptionBuilder() - .value(participantId) - .label(DemoMode.id(participantId, getContainer(), getUser())) - .selected(isSelected) - .build()); - } - participantPickerValues = unsafe(builder.toString()); - } - else - { - ActionURL completionUrl = new ActionURL(CompleteSpecimenAction.class, getContainer()).addParameter("type", "ParticipantId"); - String initValue = selectedParticipantId != null ? selectedParticipantId : participantIds.iterator().next(); - - StringWriter writer = new StringWriter(); - AutoCompleteTextTag tag = new AutoCompleteTextTag() - { - @Override - protected Writer getWriter() - { - return writer; - } - }; - tag.setUrl(completionUrl); - tag.setName(inputName); - tag.setValue(initValue); - - try - { - tag.doTag(); - } - catch (IOException e) - { - throw new RuntimeException(e); - } - - participantPickerValues = unsafe(writer.toString()); - } - - return new Pair<>(StudyService.get().getSubjectColumnName(getContainer()), participantPickerValues); - } - - protected abstract List createReports(); - - public abstract String getLabel(); - - /** Internal, stable, unique name used to identify this report type in webpart properties */ - public abstract String getReportType(); - - public boolean allowsCohortFilter() - { - return StudyService.get().showCohorts(getContainer(), getUser()); - } - - public boolean allowsAvailabilityFilter() - { - return SettingsManager.get().getRepositorySettings(getContainer()).isEnableRequests(); - } - - public boolean allowsParticipantAggregates() - { - return true; - } - - public boolean allowsCustomViewFilter() - { - return true; - } - - public boolean allowsParticipantGroupFilter() - { - return true; - } - - public int getParticipantGroupFilter() - { - return _participantGroupFilter; - } - - public void setParticipantGroupFilter(int participantGroupFilter) - { - _participantGroupFilter = participantGroupFilter; - } - - public abstract Class getAction(); - - public List getReports() - { - if (_reports == null) - _reports = createReports(); - return _reports; - } -} diff --git a/specimen/src/org/labkey/specimen/report/SummaryByVisitParticipant.java b/specimen/src/org/labkey/specimen/report/SummaryByVisitParticipant.java deleted file mode 100644 index 209fc33dc90..00000000000 --- a/specimen/src/org/labkey/specimen/report/SummaryByVisitParticipant.java +++ /dev/null @@ -1,42 +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.report; - -public class SummaryByVisitParticipant extends SpecimenCountSummary -{ - private String _participantId; - private String _cohort; - - public String getParticipantId() - { - return _participantId; - } - - public void setParticipantId(String participantId) - { - _participantId = participantId; - } - - public String getCohort() - { - return _cohort; - } - - public void setCohort(String cohort) - { - _cohort = cohort; - } -} diff --git a/specimen/src/org/labkey/specimen/report/SummaryByVisitType.java b/specimen/src/org/labkey/specimen/report/SummaryByVisitType.java deleted file mode 100644 index 03ae564146a..00000000000 --- a/specimen/src/org/labkey/specimen/report/SummaryByVisitType.java +++ /dev/null @@ -1,77 +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.report; - -import java.util.Set; - -public class SummaryByVisitType extends SpecimenCountSummary -{ - private String _primaryType; - private String _derivative; - private String _additive; - private Long _participantCount; - private Set _participantIds; - - public String getPrimaryType() - { - return _primaryType; - } - - public void setPrimaryType(String primaryType) - { - _primaryType = primaryType; - } - - public String getDerivative() - { - return _derivative; - } - - public void setDerivative(String derivative) - { - _derivative = derivative; - } - - public Long getParticipantCount() - { - return _participantCount; - } - - public void setParticipantCount(Long participantCount) - { - _participantCount = participantCount; - } - - public Set getParticipantIds() - { - return _participantIds; - } - - public void setParticipantIds(Set participantIds) - { - _participantIds = participantIds; - } - - public String getAdditive() - { - return _additive; - } - - public void setAdditive(String additive) - { - _additive = additive; - } -} diff --git a/specimen/src/org/labkey/specimen/report/participant/ParticipantSiteReport.java b/specimen/src/org/labkey/specimen/report/participant/ParticipantSiteReport.java deleted file mode 100644 index 46f6b8eb1f8..00000000000 --- a/specimen/src/org/labkey/specimen/report/participant/ParticipantSiteReport.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2008-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.report.participant; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.study.Visit; -import org.labkey.specimen.report.SpecimenVisitReportParameters; - -import java.util.List; - -/** - * User: brittp - * Created: Feb 4, 2008 3:48:04 PM - */ -public class ParticipantSiteReport extends ParticipantVisitReport -{ - public ParticipantSiteReport(String title, List visits, SimpleFilter filter, SpecimenVisitReportParameters parameters) - { - super(title, visits, filter, parameters); - } - - @Override - protected SimpleFilter replaceFilterParameterName(SimpleFilter filter, String oldKey, String newKey) - { - // this report adds a strange filter to 'ptid' that we want to just drop when it comes - // time to generate our link URLs: - if (!"ptid".equals(oldKey)) - return super.replaceFilterParameterName(filter, oldKey, newKey); - else - return filter; - } -} diff --git a/specimen/src/org/labkey/specimen/report/participant/ParticipantSiteReportFactory.java b/specimen/src/org/labkey/specimen/report/participant/ParticipantSiteReportFactory.java deleted file mode 100644 index 0402fbf3abd..00000000000 --- a/specimen/src/org/labkey/specimen/report/participant/ParticipantSiteReportFactory.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2008-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.report.participant; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.query.FieldKey; -import org.labkey.api.security.User; -import org.labkey.api.specimen.location.LocationImpl; -import org.labkey.api.specimen.location.LocationManager; -import org.labkey.api.study.StudyService; -import org.labkey.api.study.Visit; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.Pair; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.actions.SpecimenReportActions; -import org.labkey.specimen.report.SpecimenVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportAction; -import org.labkey.specimen.report.SpecimenVisitReportParameters; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -/** - * User: brittp - * Created: Jan 30, 2008 3:40:07 PM - */ -public class ParticipantSiteReportFactory extends SpecimenVisitReportParameters -{ - private Integer _enrollmentSiteId; - - @Override - protected List createReports() - { - List visits = SpecimenManager.get().getVisitsWithSpecimens(getContainer(), getUser(), getCohort()); - List reports = new ArrayList<>(); - Set enrollmentLocations; - if (_enrollmentSiteId == null) - { - enrollmentLocations = SpecimenManager.get().getEnrollmentSitesWithSpecimens(getContainer(), getUser()); - // add null to the set so we can search for ptid without an enrollment site: - enrollmentLocations.add(null); - } - else if (_enrollmentSiteId == -1) - { - enrollmentLocations = Collections.singleton(null); - } - else - { - enrollmentLocations = Collections.singleton(LocationManager.get().getLocation(getContainer(), _enrollmentSiteId)); - } - - for (LocationImpl location : enrollmentLocations) - { - String label = location != null ? location.getLabel() : "[No enrollment location assigned]"; - SimpleFilter filter = new SimpleFilter(); - addBaseFilters(filter); - if (location != null) - { - filter.addWhereClause("" + StudyService.get().getSubjectColumnName(getContainer()) + " IN (SELECT ParticipantId FROM study.Participant " + - "WHERE EnrollmentSiteId = ? AND Container = ?)", new Object[] { location.getRowId(), getContainer().getId() }, FieldKey.fromParts(StudyService.get().getSubjectColumnName(getContainer()))); - } - else - { - filter.addWhereClause(StudyService.get().getSubjectColumnName(getContainer()) + " IN (SELECT ParticipantId FROM study.Participant " + - "WHERE EnrollmentSiteId IS NULL AND Container = ?)", new Object[] { getContainer().getId() }, FieldKey.fromParts(StudyService.get().getSubjectColumnName(getContainer()))); - } - reports.add(new ParticipantSiteReport(label, visits, filter, this)); - } - return reports; - } - - @Override - public boolean allowsParticipantAggregates() - { - return false; - } - - @Override - public List> getAdditionalFormInputHtml(User user) - { - List> inputs = new ArrayList<>(super.getAdditionalFormInputHtml(user)); - Set locations = SpecimenManager.get().getEnrollmentSitesWithSpecimens(getContainer(), getUser()); - // add null to the set so we can search for ptid without an enrollment site: - locations.add(null); - inputs.add(getEnrollmentSitePicker("enrollmentSiteId", locations, _enrollmentSiteId)); - return inputs; - } - - public Integer getEnrollmentSiteId() - { - return _enrollmentSiteId; - } - - public void setEnrollmentSiteId(Integer enrollmentSiteId) - { - _enrollmentSiteId = enrollmentSiteId; - } - - @Override - public String getLabel() - { - return StudyService.get().getSubjectNounSingular(getContainer()) + " by Enrollment Location"; - } - - @Override - public String getReportType() - { - return "ParticipantByEnrollmentLocation"; - } - - @Override - public Class getAction() - { - return SpecimenReportActions.ParticipantSiteReportAction.class; - } -} diff --git a/specimen/src/org/labkey/specimen/report/participant/ParticipantSummaryReportFactory.java b/specimen/src/org/labkey/specimen/report/participant/ParticipantSummaryReportFactory.java deleted file mode 100644 index 056fea6cc93..00000000000 --- a/specimen/src/org/labkey/specimen/report/participant/ParticipantSummaryReportFactory.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2008-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.report.participant; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.study.StudyService; -import org.labkey.api.study.Visit; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.actions.SpecimenReportActions; -import org.labkey.specimen.report.SpecimenVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportAction; -import org.labkey.specimen.report.SpecimenVisitReportParameters; - -import java.util.Collections; -import java.util.List; - -/** - * User: brittp - * Created: Jan 29, 2008 5:56:06 PM - */ -public class ParticipantSummaryReportFactory extends SpecimenVisitReportParameters -{ - @Override - protected List createReports() - { - List visits = SpecimenManager.get().getVisitsWithSpecimens(getContainer(), getUser(), getCohort()); - SimpleFilter filter = new SimpleFilter(); - addBaseFilters(filter); - ParticipantVisitReport report = new ParticipantVisitReport("Summary", visits, filter, this); - return Collections.singletonList(report); - } - - @Override - public String getLabel() - { - return StudyService.get().getSubjectNounSingular(getContainer()) + " Summary"; - } - - @Override - public String getReportType() - { - return "ParticipantSummary"; - } - - @Override - public boolean allowsParticipantAggregates() - { - return false; - } - - @Override - public Class getAction() - { - return SpecimenReportActions.ParticipantSummaryReportAction.class; - } -} diff --git a/specimen/src/org/labkey/specimen/report/participant/ParticipantTypeReportFactory.java b/specimen/src/org/labkey/specimen/report/participant/ParticipantTypeReportFactory.java deleted file mode 100644 index 9b4bd0a4f3a..00000000000 --- a/specimen/src/org/labkey/specimen/report/participant/ParticipantTypeReportFactory.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) 2008-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.report.participant; - -import org.labkey.api.data.CompareType; -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.security.User; -import org.labkey.api.study.StudyService; -import org.labkey.api.study.Visit; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.OptionBuilder; -import org.labkey.api.util.Pair; -import org.labkey.api.util.SelectBuilder; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.actions.SpecimenReportActions; -import org.labkey.specimen.model.SpecimenTypeSummary; -import org.labkey.specimen.report.SpecimenVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportAction; -import org.labkey.specimen.report.SpecimenVisitReportParameters; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static org.labkey.api.util.HtmlString.unsafe; - -public class ParticipantTypeReportFactory extends SpecimenVisitReportParameters -{ - private String _selectedType; - - @Override - protected List createReports() - { - List types = getSelectedTypes(); - List visits = SpecimenManager.get().getVisitsWithSpecimens(getContainer(), getUser(), getCohort()); - List reports = new ArrayList<>(); - for (SpecimenTypeSummary.TypeCount type : types) - { - String displayLabel = type.geDisplayLabel(); - SimpleFilter filter = new SimpleFilter(); - addBaseFilters(filter); - while (type != null) - { - if (type.getLabel() != null) - filter.addCondition(type.getSpecimenViewFilterColumn(), type.getLabel()); - else - filter.addCondition(type.getSpecimenViewFilterColumn(), type.getLabel(), CompareType.ISBLANK); - type = type.getParent(); - } - reports.add(new ParticipantVisitReport(displayLabel, visits, filter, this)); - } - return reports; - } - - @Override - public boolean allowsParticipantAggregates() - { - return false; - } - - @Override - public List> getAdditionalFormInputHtml(User user) - { - return Collections.singletonList(getSpecimenTypePicker()); - } - - private void appendOptions(List types, SelectBuilder builder, String parentId, String selectedId, int indent) - { - for (SpecimenTypeSummary.TypeCount type : types) - { - String spacing = generateSpacing(indent); - String label = getLabel(type); - HtmlString encodedLabel = unsafe(spacing + HtmlString.of(label)); - - String id = parentId != null ? parentId + TYPE_COMPONENT_SEPARATOR + label : label; - builder.addOption(new OptionBuilder() - .value(id) - .label(encodedLabel) - .selected(id.equals(selectedId)) - .build()); - - appendOptions(type.getChildren(), builder, id, selectedId, indent + 1); - } - } - - private String generateSpacing(int indent) - { - String spacing = ""; - for (int i = 0; i < indent * 5; i++) - { - spacing = spacing.concat(HtmlString.NBSP.toString()); - } - return spacing; - } - - public String getSelectedType() - { - return _selectedType; - } - - public void setSelectedType(String selectedType) - { - _selectedType = selectedType; - } - - private static final String ALL_PRIMARY_TYPES_FORM_VALUE = "allPrimary"; - private static final String ALL_DERIVATIVE_TYPES_FORM_VALUE = "allDerivative"; - private static final String ALL_ADDITIVE_TYPES_FORM_VALUE = "allAdditive"; - private static final String TYPE_COMPONENT_SEPARATOR = "~#~"; - - protected List getSelectedTypes() - { - SpecimenTypeSummary summary = SpecimenManager.get().getSpecimenTypeSummary(getContainer(), getUser()); - if (_selectedType == null || _selectedType.equals(ALL_PRIMARY_TYPES_FORM_VALUE)) - return summary.getPrimaryTypes(); - if (_selectedType.equals(ALL_DERIVATIVE_TYPES_FORM_VALUE)) - return summary.getDerivatives(); - if (_selectedType.equals(ALL_ADDITIVE_TYPES_FORM_VALUE)) - return summary.getAdditives(); - String[] typeLabels = _selectedType.split(TYPE_COMPONENT_SEPARATOR); - - List types = summary.getPrimaryTypes(); - SpecimenTypeSummary.TypeCount selected = null; - for (String typeLabel : typeLabels) - { - selected = getTypeCountByLabel(types, typeLabel); - if (selected != null) - types = selected.getChildren(); - } - - // issue 13510: default to showing one report per primary type (if no type match found in list) - return selected == null ? summary.getPrimaryTypes() : Collections.singletonList(selected); - } - - private String getLabel(SpecimenTypeSummary.TypeCount type) - { - if (type.getLabel() == null || type.getLabel().isEmpty()) - return "[unknown]"; - else - return type.getLabel(); - - } - - private SpecimenTypeSummary.TypeCount getTypeCountByLabel(List counts, String label) - { - boolean unknown = "[unknown]".equals(label); - for (SpecimenTypeSummary.TypeCount count : counts) - { - if (getLabel(count).equals(label) || (unknown && (count.getLabel() == null || count.getLabel().isEmpty()))) - return count; - } - return null; - } - - protected Pair getSpecimenTypePicker() - { - SelectBuilder select = new SelectBuilder(); - SpecimenTypeSummary summary = SpecimenManager.get().getSpecimenTypeSummary(getContainer(), getUser()); - select.name("selectedType"); - - select.addOption(new OptionBuilder() - .value(ALL_PRIMARY_TYPES_FORM_VALUE) - .label("One report per primary type") - .selected(ALL_PRIMARY_TYPES_FORM_VALUE.equals(_selectedType)) - .build()); - - select.addOption(new OptionBuilder() - .value(ALL_DERIVATIVE_TYPES_FORM_VALUE) - .label("One report per derivative type") - .selected(ALL_DERIVATIVE_TYPES_FORM_VALUE.equals(_selectedType)) - .build()); - - select.addOption(new OptionBuilder() - .value(ALL_ADDITIVE_TYPES_FORM_VALUE) - .label("One report per additive type") - .selected(ALL_ADDITIVE_TYPES_FORM_VALUE.equals(_selectedType)) - .build()); - - appendOptions(summary.getPrimaryTypes(), select, null, _selectedType, 0); - return new Pair<>("Specimen type", unsafe(select.toString())); - } - - @Override - public String getLabel() - { - return StudyService.get().getSubjectNounSingular(getContainer()) + " by Specimen Type"; - } - - @Override - public String getReportType() - { - return "ParticipantByType"; - } - - @Override - public Class getAction() - { - return SpecimenReportActions.ParticipantTypeReportAction.class; - } -} diff --git a/specimen/src/org/labkey/specimen/report/participant/ParticipantVisitReport.java b/specimen/src/org/labkey/specimen/report/participant/ParticipantVisitReport.java deleted file mode 100644 index 6a9cabe69c0..00000000000 --- a/specimen/src/org/labkey/specimen/report/participant/ParticipantVisitReport.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2008-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.report.participant; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.study.CohortFilter; -import org.labkey.api.study.StudyService; -import org.labkey.api.study.Visit; -import org.labkey.api.util.DemoMode; -import org.labkey.api.view.ActionURL; -import org.labkey.specimen.actions.SpecimenController; -import org.labkey.specimen.report.SpecimenReportManager; -import org.labkey.specimen.report.SpecimenReportTitle; -import org.labkey.specimen.report.SpecimenVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportParameters; -import org.labkey.specimen.report.SummaryByVisitParticipant; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -/** - * User: brittp - * Created: Jan 29, 2008 4:52:26 PM - */ -public class ParticipantVisitReport extends SpecimenVisitReport -{ - private final boolean _showCohorts; - - public ParticipantVisitReport(String titlePrefix, List visits, SimpleFilter filter, SpecimenVisitReportParameters parameters) - { - super(titlePrefix, visits, filter, parameters); - _showCohorts = StudyService.get().showCohorts(_container, getUser()); - } - - @Override - public Collection createRows() - { - CohortFilter.Type cohortType = getCohortFilter() != null ? getCohortFilter().getType() : CohortFilter.Type.DATA_COLLECTION; - Collection countSummary = - SpecimenReportManager.get().getParticipantSummaryByVisitType(_container, getUser(), _filter, getBaseCustomView(), cohortType); - Map rows = new TreeMap<>(); - for (SummaryByVisitParticipant count : countSummary) - { - String cohort = _showCohorts ? count.getCohort() : "[cohort blinded]"; - if (cohort == null || cohort.isEmpty()) - cohort = "[No cohort assigned]"; - String key = cohort + "/" + count.getParticipantId(); - Row row = rows.get(key); - if (row == null) - { - String ptid = count.getParticipantId(); - SpecimenReportTitle ptidTitle = new SpecimenReportTitle(ptid, DemoMode.id(ptid, getContainer(), getUser())); - SpecimenReportTitle[] titleHierarchy; - if (_showCohorts) - titleHierarchy = new SpecimenReportTitle[] {new SpecimenReportTitle(cohort), ptidTitle}; - else - titleHierarchy = new SpecimenReportTitle[] {ptidTitle}; - row = new Row(titleHierarchy); - rows.put(key, row); - } - setVisitAsNonEmpty(count.getVisit()); - row.add(count); - } - return rows.values(); - } - - @Override - public int getLabelDepth() - { - return _showCohorts ? 2 : 1; - } - - @Override - protected String[] getCellExcelText(Visit visit, SummaryByVisitParticipant summary) - { - if (summary == null || summary.getVialCount() == null) - return new String[] {}; - - StringBuilder summaryString = new StringBuilder(); - if (isViewVialCount()) - summaryString.append(summary.getVialCount()); - if (isViewVolume()) - { - if (!summaryString.isEmpty()) - summaryString.append("/"); - summaryString.append(summary.getTotalVolume()); - } - return new String[] { summaryString.toString() }; - } - - @Override - protected String getCellHtml(Visit visit, SummaryByVisitParticipant summary) - { - if (summary == null || summary.getVialCount() == null) - return " "; - ActionURL link = SpecimenController.getSpecimensURL(_container, true); - link = updateURLFilterParameter(link, "SpecimenDetail.Visit/SequenceNumMin", visit.getSequenceNumMin()); - link = updateURLFilterParameter(link, "SpecimenDetail." + StudyService.get().getSubjectColumnName(getContainer()), summary.getParticipantId()); - String linkHtml = link.getLocalURIString(); - if (_filter != null) - { - String filterQueryString = getFilterQueryString(visit, summary); - if (filterQueryString != null && !filterQueryString.isEmpty()) - linkHtml += "&" + filterQueryString; - } - return buildCellHtml(visit, summary, linkHtml); - } -} diff --git a/specimen/src/org/labkey/specimen/report/request/BaseRequestReportFactory.java b/specimen/src/org/labkey/specimen/report/request/BaseRequestReportFactory.java deleted file mode 100644 index 96ae78152ef..00000000000 --- a/specimen/src/org/labkey/specimen/report/request/BaseRequestReportFactory.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2009-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.report.request; - -import org.labkey.api.security.User; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.Pair; -import org.labkey.api.util.OptionBuilder; -import org.labkey.api.util.SelectBuilder; -import org.labkey.specimen.report.specimentype.TypeReportFactory; - -import java.util.List; - -import static org.labkey.api.util.HtmlString.unsafe; - -/** - * User: brittp - * Date: Jun 25, 2009 - * Time: 10:57:36 AM - */ -public abstract class BaseRequestReportFactory extends TypeReportFactory -{ - private boolean _completedRequestsOnly = false; - - public boolean isCompletedRequestsOnly() - { - return _completedRequestsOnly; - } - - public void setCompletedRequestsOnly(boolean completedRequestsOnly) - { - _completedRequestsOnly = completedRequestsOnly; - } - - @Override - public List> getAdditionalFormInputHtml(User user) - { - List> superInputs = super.getAdditionalFormInputHtml(user); - - SelectBuilder sb = new SelectBuilder(); - - sb.addOption(new OptionBuilder() - .value(Boolean.toString(false)) - .label("Include in-process requests") - .build() - ); - - sb.addOption(new OptionBuilder() - .value(Boolean.toString(true)) - .label("Completed requests only") - .selected(_completedRequestsOnly) - .build() - ); - - superInputs.add(new Pair<>("Request status", unsafe(sb.toString()))); - return superInputs; - } -} diff --git a/specimen/src/org/labkey/specimen/report/request/RequestEnrollmentLocationReport.java b/specimen/src/org/labkey/specimen/report/request/RequestEnrollmentLocationReport.java deleted file mode 100644 index d1f112257bb..00000000000 --- a/specimen/src/org/labkey/specimen/report/request/RequestEnrollmentLocationReport.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2008-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.report.request; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.specimen.query.SpecimenQueryView; -import org.labkey.api.study.Visit; -import org.labkey.specimen.report.SpecimenTypeVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportParameters; -import org.labkey.specimen.report.SummaryByVisitType; - -import java.util.List; - -/** - * User: brittp - * Created: Jan 14, 2008 1:37:24 PM - */ -public class RequestEnrollmentLocationReport extends SpecimenTypeVisitReport -{ - private final int _locationId; - private final boolean _completedRequestsOnly; - - public RequestEnrollmentLocationReport(String titlePrefix, SimpleFilter filter, SpecimenVisitReportParameters parameters, - List visits, int locationId, boolean completedRequestsOnly) - { - super(titlePrefix, visits, filter, parameters); - _locationId = locationId; - _completedRequestsOnly = completedRequestsOnly; - } - - @Override - protected String getFilterQueryString(Visit visit, SummaryByVisitType summary) - { - return super.getFilterQueryString(visit, summary) + "&" + - (_completedRequestsOnly ? SpecimenQueryView.PARAMS.showCompleteRequestedByEnrollmentSite : - SpecimenQueryView.PARAMS.showRequestedByEnrollmentSite) + "=" + _locationId; - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/report/request/RequestEnrollmentSiteReportFactory.java b/specimen/src/org/labkey/specimen/report/request/RequestEnrollmentSiteReportFactory.java deleted file mode 100644 index 691c150d3b1..00000000000 --- a/specimen/src/org/labkey/specimen/report/request/RequestEnrollmentSiteReportFactory.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2008-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.report.request; - -import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.query.FieldKey; -import org.labkey.api.security.User; -import org.labkey.api.specimen.location.LocationImpl; -import org.labkey.api.specimen.location.LocationManager; -import org.labkey.specimen.query.SpecimenQueryView; -import org.labkey.api.study.Location; -import org.labkey.api.study.Visit; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.Pair; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.actions.SpecimenReportActions; -import org.labkey.specimen.report.SpecimenVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportAction; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -/** - * User: brittp - * Created: Jan 24, 2008 1:38:40 PM - */ -public class RequestEnrollmentSiteReportFactory extends BaseRequestReportFactory -{ - private Integer _enrollmentSiteId; - - public Integer getEnrollmentSiteId() - { - return _enrollmentSiteId; - } - - public void setEnrollmentSiteId(Integer enrollmentSiteId) - { - _enrollmentSiteId = enrollmentSiteId; - } - - @Override - public boolean allowsAvailabilityFilter() - { - return false; - } - - @Override - public String getLabel() - { - Location location = _enrollmentSiteId != null ? LocationManager.get().getLocation(getContainer(), _enrollmentSiteId) : null; - return "Requested by Enrollment Location" + (location != null ? ": " + location.getLabel() : ""); - } - - @Override - public String getReportType() - { - return "RequestedByEnrollmentLocation"; - } - - @Override - protected List createReports() - { - final Set locations; - - if (getEnrollmentSiteId() != null) - { - locations = Collections.singleton(LocationManager.get().getLocation(getContainer(), getEnrollmentSiteId())); - } - else - { - locations = SpecimenManager.get().getEnrollmentSitesWithRequests(getContainer(), getUser()); - // add null to the set so we can search for ptid without an enrollment site: - locations.add(null); - } - - List reports = new ArrayList<>(); - List visits = SpecimenManager.get().getVisitsWithSpecimens(getContainer(), getUser(), getCohort()); - - SQLFragment baseSql = SpecimenQueryView.getBaseRequestedEnrollmentSql(getContainer(), getUser(), isCompletedRequestsOnly()); - - for (LocationImpl location : locations) - { - SimpleFilter filter = new SimpleFilter(); - SQLFragment sql = new SQLFragment(baseSql); - if (location == null) - sql.append("IS NULL)"); - else - { - sql.append("= " + location.getRowId() + ")"); - } - - filter.addWhereClause(sql, FieldKey.fromParts("GlobalUniqueId")); - addBaseFilters(filter); - reports.add(new RequestEnrollmentLocationReport(location == null ? "[Unassigned enrollment location]" : location.getLabel(), - filter, this, visits, location != null ? location.getRowId() : -1, isCompletedRequestsOnly())); - } - return reports; - } - - @Override - public Class getAction() - { - return SpecimenReportActions.RequestEnrollmentSiteReportAction.class; - } - - @Override - public List> getAdditionalFormInputHtml(User user) - { - List> inputs = new ArrayList<>(super.getAdditionalFormInputHtml(user)); - Set locations = SpecimenManager.get().getEnrollmentSitesWithRequests(getContainer(), getUser()); - // add null to the set so we can search for ptid without an enrollment site: - locations.add(null); - inputs.add(getEnrollmentSitePicker("enrollmentSiteId", locations, _enrollmentSiteId)); - return inputs; - } -} diff --git a/specimen/src/org/labkey/specimen/report/request/RequestLocationReport.java b/specimen/src/org/labkey/specimen/report/request/RequestLocationReport.java deleted file mode 100644 index 520dc872961..00000000000 --- a/specimen/src/org/labkey/specimen/report/request/RequestLocationReport.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2008-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.report.request; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.specimen.query.SpecimenQueryView; -import org.labkey.api.study.Visit; -import org.labkey.specimen.report.SpecimenTypeVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportParameters; -import org.labkey.specimen.report.SummaryByVisitType; - -import java.util.List; - -/** - * User: brittp - * Created: Jan 14, 2008 1:37:24 PM - */ -public class RequestLocationReport extends SpecimenTypeVisitReport -{ - private final int _locationId; - private final boolean _completeRequestsOnly; - - public RequestLocationReport(String titlePrefix, SimpleFilter filter, SpecimenVisitReportParameters parameters, List visits, int locationId, boolean completeRequestsOnly) - { - super(titlePrefix, visits, filter, parameters); - _locationId = locationId; - _completeRequestsOnly = completeRequestsOnly; - } - - @Override - protected String getFilterQueryString(Visit visit, SummaryByVisitType summary) - { - return super.getFilterQueryString(visit, summary) + "&" + - (_completeRequestsOnly ? SpecimenQueryView.PARAMS.showCompleteRequestedBySite : SpecimenQueryView.PARAMS.showRequestedBySite) + "=" + _locationId; - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/report/request/RequestLocationReportFactory.java b/specimen/src/org/labkey/specimen/report/request/RequestLocationReportFactory.java deleted file mode 100644 index 7f5390fdb70..00000000000 --- a/specimen/src/org/labkey/specimen/report/request/RequestLocationReportFactory.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2008-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.report.request; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.security.User; -import org.labkey.api.specimen.location.LocationImpl; -import org.labkey.api.specimen.location.LocationManager; -import org.labkey.api.study.Location; -import org.labkey.api.study.Visit; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.Pair; -import org.labkey.api.util.OptionBuilder; -import org.labkey.api.util.SelectBuilder; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.actions.SpecimenReportActions; -import org.labkey.specimen.report.SpecimenVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportAction; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static org.labkey.api.util.HtmlString.unsafe; - -/** - * User: brittp - * Created: Jan 24, 2008 1:38:40 PM - */ -public class RequestLocationReportFactory extends BaseRequestReportFactory -{ - private Integer _locationId; - - public Integer getLocationId() - { - return _locationId; - } - - public void setLocationId(Integer locationId) - { - _locationId = locationId; - } - - @Override - public boolean allowsAvailabilityFilter() - { - return false; - } - - @Override - public String getLabel() - { - Location location = _locationId != null ? LocationManager.get().getLocation(getContainer(), _locationId) : null; - return "Requested by Requesting Location" + (location != null ? ": " + location.getLabel() : ""); - } - - @Override - public String getReportType() - { - return "RequestedByRequestingLocation"; - } - - @Override - protected List createReports() - { - LocationImpl[] locations; - if (getLocationId() != null) - locations = new LocationImpl[] { LocationManager.get().getLocation(getContainer(), getLocationId()) }; - else - locations = SpecimenManager.get().getSitesWithRequests(getContainer()); - if (locations == null) - return Collections.emptyList(); - List reports = new ArrayList<>(); - List visits = SpecimenManager.get().getVisitsWithSpecimens(getContainer(), getUser(), getCohort()); - for (LocationImpl location : locations) - { - SimpleFilter filter = new SimpleFilter(); - Object[] params; - if (isCompletedRequestsOnly()) - params = new Object[] { Boolean.TRUE, Boolean.TRUE, location.getRowId(), getContainer().getId()}; - else - params = new Object[] { Boolean.TRUE, location.getRowId(), getContainer().getId()}; - filter.addWhereClause("globaluniqueid IN\n" + - "(\n" + - " SELECT specimenglobaluniqueid FROM study.samplerequestspecimen WHERE samplerequestid IN\n" + - " (\n" + - " SELECT r.rowid FROM study.SampleRequest r JOIN study.SampleRequestStatus rs ON\n" + - " r.StatusId = rs.RowId\n" + - " AND rs.SpecimensLocked = ?\n" + - (isCompletedRequestsOnly() ? " AND rs.FinalState = ?\n" : "") + - " WHERE destinationsiteid = ? AND r.container = ?\n" + - " )\n" + - ")", params); - addBaseFilters(filter); - reports.add(new RequestLocationReport(location.getLabel(), filter, this, visits, location.getRowId(), isCompletedRequestsOnly())); - } - return reports; - } - - @Override - public Class getAction() - { - return SpecimenReportActions.RequestSiteReportAction.class; - } - - @Override - public List> getAdditionalFormInputHtml(User user) - { - - SelectBuilder builder = new SelectBuilder(); - - builder.name("locationId") - .addOption(new OptionBuilder() - .value("") - .label("All Requesting Locations") - .build()); - - for (LocationImpl location : SpecimenManager.get().getSitesWithRequests(getContainer())) - { - builder.addOption(new OptionBuilder() - .value(Integer.toString(location.getRowId())) - .label(location.getLabel()) - .selected(_locationId != null && location.getRowId() == _locationId) - .build()); - } - - List> inputs = new ArrayList<>(super.getAdditionalFormInputHtml(user)); - inputs.add(new Pair<>("Requesting location(s)", unsafe(builder.toString()))); - return inputs; - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/report/request/RequestParticipantReport.java b/specimen/src/org/labkey/specimen/report/request/RequestParticipantReport.java deleted file mode 100644 index e5d76b3ee4f..00000000000 --- a/specimen/src/org/labkey/specimen/report/request/RequestParticipantReport.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2008-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.report.request; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.study.Visit; -import org.labkey.api.view.ActionURL; -import org.labkey.specimen.actions.SpecimenController; -import org.labkey.specimen.query.SpecimenQueryView; -import org.labkey.specimen.report.RequestSummaryByVisitType; -import org.labkey.specimen.report.SpecimenReportManager; -import org.labkey.specimen.report.SpecimenTypeLevel; -import org.labkey.specimen.report.SpecimenVisitReport; - -import java.util.Collection; -import java.util.Map; -import java.util.TreeMap; - -public class RequestParticipantReport extends SpecimenVisitReport -{ - private final boolean _completeRequestsOnly; - - public RequestParticipantReport(String titlePrefix, Collection visits, SimpleFilter filter, RequestParticipantReportFactory parameters) - { - super(titlePrefix, visits, filter, parameters); - _completeRequestsOnly = parameters.isCompletedRequestsOnly(); - } - - @Override - public Collection createRows() - { - SpecimenTypeLevel level = getTypeLevelEnum(); - RequestSummaryByVisitType[] countSummary = - SpecimenReportManager.get().getRequestSummaryBySite(_container, getUser(), _filter, - isViewPtidList(), level, getBaseCustomView(), _completeRequestsOnly); - Map rows = new TreeMap<>(); - for (RequestSummaryByVisitType count : countSummary) - { - String key = count.getSiteLabel() + "/" + count.getPrimaryType() + "/" + - (count.getDerivative() != null ? count.getDerivative() : "All") + "/" + - (count.getAdditive() != null ? count.getAdditive() : "All"); - Row row = rows.get(key); - if (row == null) - { - String[] typeHierarchy = level.getTitleHierarchy(count); - String[] titleHierarchy = new String[typeHierarchy.length + 1]; - titleHierarchy[0] = count.getSiteLabel(); - System.arraycopy(typeHierarchy, 0, titleHierarchy, 1, typeHierarchy.length); - row = new Row(titleHierarchy); - rows.put(key, row); - } - setVisitAsNonEmpty(count.getVisit()); - row.add(count); - } - return rows.values(); - } - - @Override - protected String[] getCellExcelText(Visit visit, RequestSummaryByVisitType summary) - { - if (summary == null || summary.getVialCount() == null) - return new String[] {}; - StringBuilder summaryString = new StringBuilder(); - if (isViewVialCount()) - summaryString.append(summary.getVialCount()); - if (isViewVolume()) - { - if (!summaryString.isEmpty()) - summaryString.append("/"); - summaryString.append(summary.getTotalVolume()); - } - return new String[] { summaryString.toString() }; - } - - @Override - protected String getCellHtml(Visit visit, RequestSummaryByVisitType summary) - { - if (summary == null || summary.getVialCount() == null) - return " "; - ActionURL url = SpecimenController.getSpecimensURL(_container, true); - url = updateURLFilterParameter(url, "SpecimenDetail.Visit/SequenceNumMin", visit.getSequenceNumMin()); - url = updateURLFilterParameter(url, "SpecimenDetail.PrimaryType/Description", summary.getPrimaryType()); - SpecimenTypeLevel level = getTypeLevelEnum(); - if (level == SpecimenTypeLevel.Derivative || level == SpecimenTypeLevel.Additive) - url = updateURLFilterParameter(url, "SpecimenDetail.DerivativeType/Description", summary.getDerivative()); - if (level == SpecimenTypeLevel.Additive) - url = updateURLFilterParameter(url, "SpecimenDetail.AdditiveType/Description", summary.getAdditive()); - String linkHtml = url.getLocalURIString(); - if (_filter != null) - linkHtml += "&" + getFilterQueryString(visit, summary); - return buildCellHtml(visit, summary, linkHtml); - } - - @Override - protected String getFilterQueryString(Visit visit, RequestSummaryByVisitType summary) - { - return super.getFilterQueryString(visit, summary) + "&" + - (_completeRequestsOnly ? SpecimenQueryView.PARAMS.showCompleteRequestedBySite : SpecimenQueryView.PARAMS.showRequestedBySite) - + "=" + summary.getDestinationSiteId(); - } -} diff --git a/specimen/src/org/labkey/specimen/report/request/RequestParticipantReportFactory.java b/specimen/src/org/labkey/specimen/report/request/RequestParticipantReportFactory.java deleted file mode 100644 index e1a70e63be2..00000000000 --- a/specimen/src/org/labkey/specimen/report/request/RequestParticipantReportFactory.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) 2008-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.report.request; - -import org.labkey.api.collections.IntHashMap; -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.query.FieldKey; -import org.labkey.api.security.User; -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.study.model.CohortService; -import org.labkey.api.study.model.ParticipantGroupService; -import org.labkey.api.study.model.VisitService; -import org.labkey.api.util.DemoMode; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.Pair; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.actions.SpecimenReportActions; -import org.labkey.specimen.report.SpecimenVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportAction; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * User: brittp - * Created: Feb 5, 2008 2:03:52 PM - */ -public class RequestParticipantReportFactory extends BaseRequestReportFactory -{ - private String _participantId; - - public String getParticipantId() - { - return _participantId; - } - - public void setParticipantId(String participantId) - { - _participantId = participantId; - } - - @Override - public boolean allowsCohortFilter() - { - return false; - } - - @Override - public boolean allowsAvailabilityFilter() - { - return false; - } - - @Override - public boolean allowsParticipantAggregates() - { - return false; - } - - @Override - public boolean allowsParticipantGroupFilter() - { - return false; - } - - @Override - public List> getAdditionalFormInputHtml(User user) - { - List> inputs = new ArrayList<>(super.getAdditionalFormInputHtml(user)); - inputs.add(getParticipantPicker("participantId", _participantId)); - return inputs; - } - - @Override - protected List createReports() - { - Collection participantIds; - if (!isAllSubjectsOption(_participantId) && _participantId != null && !_participantId.trim().isEmpty()) - { - Study study = StudyService.get().getStudy(getContainer()); - if (!StudyService.get().participantExists(study, _participantId)) - return Collections.emptyList(); - participantIds = List.of(_participantId); - } - else - { - Study study = StudyService.get().getStudy(getContainer()); - if (getParticipantGroupFilter() >= 0) - participantIds = ParticipantGroupService.get().getParticipantIdsForGroup(study, getUser(), getParticipantGroupFilter()); - else - participantIds = StudyService.get().getParticipantIds(study, getUser()); - if (participantIds == null) - return Collections.emptyList(); - } - - List reports = new ArrayList<>(); - Map> visitListCache = new IntHashMap<>(); // cohort rowId -> visit - boolean showCohorts = StudyService.get().showCohorts(getContainer(), getUser()); - Collection allVisits = null; - Study study = StudyService.get().getStudy(getContainer()); - for (String participantId : participantIds) - { - SimpleFilter filter = new SimpleFilter(FieldKey.fromParts(StudyService.get().getSubjectColumnName(getContainer())), participantId); - addBaseFilters(filter); - Collection visits = null; - if (showCohorts) - { - Cohort cohort = CohortService.get().getCurrentCohortForParticipant(getContainer(), getUser(), participantId); - if (cohort != null) - { - visits = visitListCache.get(cohort.getRowId()); - if (visits == null) - { - visits = SpecimenManager.get().getVisitsWithSpecimens(getContainer(), getUser(), cohort); - visitListCache.put(cohort.getRowId(), visits); - } - } - } - - if (visits == null) - { - if (allVisits == null) - allVisits = VisitService.get().getVisits(study, Visit.Order.DISPLAY); - visits = allVisits; - } - reports.add(new RequestParticipantReport(DemoMode.id(participantId, getContainer(), getUser()), visits, filter, this)); - } - return reports; - } - - @Override - public String getLabel() - { - return "Requested by " + StudyService.get().getSubjectNounSingular(getContainer()); - } - - @Override - public String getReportType() - { - return "RequestedByParticipant"; - } - - @Override - public Class getAction() - { - return SpecimenReportActions.RequestParticipantReportAction.class; - } -} diff --git a/specimen/src/org/labkey/specimen/report/request/RequestReport.java b/specimen/src/org/labkey/specimen/report/request/RequestReport.java deleted file mode 100644 index 2ce435db337..00000000000 --- a/specimen/src/org/labkey/specimen/report/request/RequestReport.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2008-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.report.request; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.specimen.query.SpecimenQueryView; -import org.labkey.api.study.Visit; -import org.labkey.specimen.report.SpecimenTypeVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportParameters; -import org.labkey.specimen.report.SummaryByVisitType; - -import java.util.List; - -/** - * User: brittp - * Created: Jan 14, 2008 1:37:24 PM - */ -public class RequestReport extends SpecimenTypeVisitReport -{ - private final boolean _completedRequestsOnly; - - public RequestReport(String titlePrefix, SimpleFilter filter, SpecimenVisitReportParameters parameters, List visits, boolean completedRequestsOnly) - { - super(titlePrefix, visits, filter, parameters); - _completedRequestsOnly = completedRequestsOnly; - } - - @Override - protected String getFilterQueryString(Visit visit, SummaryByVisitType summary) - { - return super.getFilterQueryString(visit, summary) + "&" + - (_completedRequestsOnly ? SpecimenQueryView.PARAMS.showCompleteRequestedOnly : SpecimenQueryView.PARAMS.showRequestedOnly) + "=true"; - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/report/request/RequestReportFactory.java b/specimen/src/org/labkey/specimen/report/request/RequestReportFactory.java deleted file mode 100644 index 0c4802d8005..00000000000 --- a/specimen/src/org/labkey/specimen/report/request/RequestReportFactory.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2008-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.report.request; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.study.Location; -import org.labkey.api.study.Visit; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.actions.SpecimenReportActions; -import org.labkey.specimen.report.SpecimenVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportAction; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * User: brittp - * Created: Jan 24, 2008 1:38:40 PM - */ -public class RequestReportFactory extends BaseRequestReportFactory -{ - @Override - public String getLabel() - { - return "Requested Summary"; - } - - @Override - public String getReportType() - { - return "RequestedSummary"; - } - - @Override - public boolean allowsAvailabilityFilter() - { - return false; - } - - @Override - protected List createReports() - { - Location[] locations = SpecimenManager.get().getSitesWithRequests(getContainer()); - if (locations == null) - return Collections.emptyList(); - List reports = new ArrayList<>(); - List visits = SpecimenManager.get().getVisitsWithSpecimens(getContainer(), getUser(), getCohort()); - SimpleFilter filter = new SimpleFilter(); - if (isCompletedRequestsOnly()) - { - filter.addWhereClause("globaluniqueid IN\n" + - "(" + COMPLETED_REQUESTS_FILTER_SQL + ")", - new Object[] { Boolean.TRUE, Boolean.TRUE, getContainer().getId()}); - } - else - { - filter.addWhereClause("globaluniqueid IN\n" + - "(SELECT specimenglobaluniqueid FROM study.samplerequestspecimen WHERE container = ?) and LockedInRequest = ?", - new Object[] { getContainer().getId(), Boolean.TRUE }); - } - addBaseFilters(filter); - reports.add(new RequestReport("All Requested Specimens", filter, this, visits, isCompletedRequestsOnly())); - return reports; - } - - @Override - public Class getAction() - { - return SpecimenReportActions.RequestReportAction.class; - } -} diff --git a/specimen/src/org/labkey/specimen/report/specimentype/TypeCohortReport.java b/specimen/src/org/labkey/specimen/report/specimentype/TypeCohortReport.java deleted file mode 100644 index 350fc5c05b1..00000000000 --- a/specimen/src/org/labkey/specimen/report/specimentype/TypeCohortReport.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2008-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.report.specimentype; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.study.CohortFilter; -import org.labkey.api.study.Study; -import org.labkey.api.study.Visit; -import org.labkey.api.view.ActionURL; -import org.labkey.specimen.report.SpecimenTypeVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportParameters; - -import java.util.List; - -/** - * User: brittp - * Created: Jan 14, 2008 1:37:24 PM - */ -public class TypeCohortReport extends SpecimenTypeVisitReport -{ - private final CohortFilter _cohortFilter; - - public TypeCohortReport(String titlePrefix, List visits, SimpleFilter filter, SpecimenVisitReportParameters parameters, CohortFilter cohortFilter) - { - super(titlePrefix, visits, filter, parameters); - _cohortFilter = cohortFilter; - } - - // override addCohortURLFilter to use the filter we're passed, rather than the cohort filter on the base - // parameters object (the base parameter filter just contains the type, since this report can render multiple - // reports, one for each cohort): - @Override - protected void addCohortURLFilter(Study study, ActionURL url) - { - if (_cohortFilter != null) - _cohortFilter.addURLParameters(study, url, null); - } -} diff --git a/specimen/src/org/labkey/specimen/report/specimentype/TypeCohortReportFactory.java b/specimen/src/org/labkey/specimen/report/specimentype/TypeCohortReportFactory.java deleted file mode 100644 index c86fd0d7c5e..00000000000 --- a/specimen/src/org/labkey/specimen/report/specimentype/TypeCohortReportFactory.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2008-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.report.specimentype; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.security.User; -import org.labkey.api.study.Cohort; -import org.labkey.api.study.CohortFilter; -import org.labkey.api.study.Params; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.study.Visit; -import org.labkey.api.study.model.CohortService; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.Pair; -import org.labkey.api.util.InputBuilder; -import org.labkey.api.util.OptionBuilder; -import org.labkey.api.util.SelectBuilder; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.actions.SpecimenReportActions; -import org.labkey.specimen.report.SpecimenVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportAction; - -import java.util.ArrayList; -import java.util.List; - -import static org.labkey.api.util.HtmlString.unsafe; - -/** - * User: brittp - * Created: Jan 24, 2008 3:19:29 PM - */ -public class TypeCohortReportFactory extends TypeReportFactory -{ - @Override - public boolean allowsCohortFilter() - { - return false; - } - - @Override - public Class getAction() - { - return SpecimenReportActions.TypeCohortReportAction.class; - } - - @Override - public List> getAdditionalFormInputHtml(User user) - { - List> inputs = super.getAdditionalFormInputHtml(user); - Study study = StudyService.get().getStudy(getContainer()); - if (study.isAdvancedCohorts()) - { - CohortFilter.Type currentType = getCohortFilter() != null ? getCohortFilter().getType() : CohortFilter.Type.DATA_COLLECTION; - InputBuilder input = InputBuilder.hidden() - .value("0") - .name(Params.cohortId.name()); - - SelectBuilder select = new SelectBuilder() - .name(Params.cohortFilterType.name()); - - for (CohortFilter.Type type : CohortFilter.Type.values()) - { - select.addOption(new OptionBuilder() - .value(type.name()) - .label(type.getTitle()) - .selected(type == currentType) - .build() - ); - } - - String combinedElements = input.toString().concat(select.toString()); - inputs.add(new Pair<>("Cohort Filter Type", unsafe(combinedElements))); - } - return inputs; - } - - @Override - protected List createReports() - { - CohortFilter.Type type = getCohortFilter() != null ? getCohortFilter().getType() : CohortFilter.Type.DATA_COLLECTION; - List reportCohorts = new ArrayList<>(CohortService.get().getCohortFilters(type, getContainer(), getUser())); - reportCohorts.add(CohortService.get().getUnassignedCohortFilter()); - - List reports = new ArrayList<>(); - for (CohortFilter cohortFilter : reportCohorts) - { - Cohort cohort = cohortFilter.getCohort(getContainer(), getUser()); - String title = cohort != null ? cohort.getLabel() : "[No cohort assigned]"; - SimpleFilter filter = new SimpleFilter(); - addBaseFilters(filter); - addCohortFilter(filter, cohortFilter); - List visits = SpecimenManager.get().getVisitsWithSpecimens(getContainer(), getUser(), cohort); - reports.add(new TypeCohortReport(title, visits, filter, this, cohortFilter)); - } - return reports; - } - - @Override - public String getLabel() - { - CohortFilter filter = getCohortFilter(); - Cohort cohort = filter != null ? filter.getCohort(getContainer(), getUser()) : null; - return cohort != null ? cohort.getLabel() : "Type by Cohort"; - } - - @Override - public String getReportType() - { - return "TypeByCohort"; - } -} diff --git a/specimen/src/org/labkey/specimen/report/specimentype/TypeParticipantReportFactory.java b/specimen/src/org/labkey/specimen/report/specimentype/TypeParticipantReportFactory.java deleted file mode 100644 index d1cca1c17d0..00000000000 --- a/specimen/src/org/labkey/specimen/report/specimentype/TypeParticipantReportFactory.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (c) 2008-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.report.specimentype; - -import org.labkey.api.collections.IntHashMap; -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.query.FieldKey; -import org.labkey.api.security.User; -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.study.model.CohortService; -import org.labkey.api.study.model.ParticipantGroupService; -import org.labkey.api.study.model.VisitService; -import org.labkey.api.util.DemoMode; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.Pair; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.actions.SpecimenReportActions; -import org.labkey.specimen.report.SpecimenTypeVisitReport; -import org.labkey.specimen.report.SpecimenVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportAction; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public class TypeParticipantReportFactory extends TypeReportFactory -{ - private String _participantId; - - public String getParticipantId() - { - return _participantId; - } - - public void setParticipantId(String participantId) - { - _participantId = participantId; - } - - @Override - public String getLabel() - { - String subjectNoun = StudyService.get().getSubjectNounSingular(getContainer()); - return _participantId == null || isAllSubjectsOption(_participantId) ? "Type by " + subjectNoun : subjectNoun + " " + DemoMode.id(_participantId, getContainer(), getUser()); - } - - @Override - public String getReportType() - { - return "TypeByParticipant"; - } - - @Override - public boolean allowsCohortFilter() - { - return false; - } - - @Override - public boolean allowsParticipantAggregates() - { - return false; - } - - @Override - protected List createReports() - { - Collection participantIds; - if (!isAllSubjectsOption(_participantId) && _participantId != null && !_participantId.trim().isEmpty()) - { - Study study = StudyService.get().getStudy(getContainer()); - if (!StudyService.get().participantExists(study, _participantId)) - return Collections.emptyList(); - participantIds = List.of(_participantId); - } - else - { - Study study = StudyService.get().getStudy(getContainer()); - if (getParticipantGroupFilter() >= 0) - participantIds = ParticipantGroupService.get().getParticipantIdsForGroup(study, getUser(), getParticipantGroupFilter()); - else - participantIds = StudyService.get().getParticipantIds(study, getUser()); - if (participantIds == null) - return Collections.emptyList(); - } - List reports = new ArrayList<>(); - Map> visitListCache = new IntHashMap<>(); // cohort rowId -> visits - boolean showCohorts = StudyService.get().showCohorts(getContainer(), getUser()); - Collection allVisits = null; - Study study = StudyService.get().getStudy(getContainer()); - for (String participantId : participantIds) - { - SimpleFilter filter = new SimpleFilter(FieldKey.fromParts(StudyService.get().getSubjectColumnName(getContainer())), participantId); - addBaseFilters(filter); - Collection visits = null; - if (showCohorts) - { - Cohort cohort = CohortService.get().getCurrentCohortForParticipant(getContainer(), getUser(), participantId); - if (cohort != null) - { - visits = visitListCache.get(cohort.getRowId()); - if (visits == null) - { - visits = SpecimenManager.get().getVisitsWithSpecimens(getContainer(), getUser(), cohort); - visitListCache.put(cohort.getRowId(), visits); - } - } - } - - if (visits == null) - { - if (allVisits == null) - allVisits = VisitService.get().getVisits(study, Visit.Order.DISPLAY); - visits = allVisits; - } - reports.add(new SpecimenTypeVisitReport(DemoMode.id(participantId, getContainer(), getUser()), visits, filter, this)); - } - return reports; - } - - @Override - public Class getAction() - { - return SpecimenReportActions.TypeParticipantReportAction.class; - } - - @Override - public List> getAdditionalFormInputHtml(User user) - { - List> inputs = new ArrayList<>(super.getAdditionalFormInputHtml(user)); - inputs.add(getParticipantPicker("participantId", _participantId)); - return inputs; - } -} diff --git a/specimen/src/org/labkey/specimen/report/specimentype/TypeReportFactory.java b/specimen/src/org/labkey/specimen/report/specimentype/TypeReportFactory.java deleted file mode 100644 index d6f2a5f9736..00000000000 --- a/specimen/src/org/labkey/specimen/report/specimentype/TypeReportFactory.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2008-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.report.specimentype; - -import org.labkey.api.security.User; -import org.labkey.api.study.SpecimenService; -import org.labkey.api.util.HtmlString; -import org.labkey.api.util.Pair; -import org.labkey.api.util.OptionBuilder; -import org.labkey.api.util.SelectBuilder; -import org.labkey.specimen.report.SpecimenTypeLevel; -import org.labkey.specimen.report.SpecimenVisitReportParameters; - -import java.util.ArrayList; -import java.util.List; - -import static org.labkey.api.util.HtmlString.unsafe; - -/** - * User: brittp - * Created: Feb 1, 2008 - */ -public abstract class TypeReportFactory extends SpecimenVisitReportParameters -{ - @Override - public List> getAdditionalFormInputHtml(User user) - { - List> inputs = new ArrayList<>(super.getAdditionalFormInputHtml(user)); - - if (!SpecimenService.get().getRequestCustomizer().omitTypeGroupingsWhenReporting()) - { - SelectBuilder builder = new SelectBuilder(); - builder.name(PARAMS.typeLevel.name()); - - for (SpecimenTypeLevel level : SpecimenTypeLevel.values()) - { - builder.addOption(new OptionBuilder() - .value(level.name()) - .label("Show results by: " + level.getLabel()) - .selected(getTypeLevelEnum() == level) - .build() - ); - } - inputs.add(new Pair<>("Type breakdown", unsafe(builder.toString()))); - } - - return inputs; - } -} diff --git a/specimen/src/org/labkey/specimen/report/specimentype/TypeSummaryReportFactory.java b/specimen/src/org/labkey/specimen/report/specimentype/TypeSummaryReportFactory.java deleted file mode 100644 index 7b37bbea201..00000000000 --- a/specimen/src/org/labkey/specimen/report/specimentype/TypeSummaryReportFactory.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2008-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.report.specimentype; - -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.study.Visit; -import org.labkey.specimen.SpecimenManager; -import org.labkey.specimen.actions.SpecimenReportActions; -import org.labkey.specimen.report.SpecimenTypeVisitReport; -import org.labkey.specimen.report.SpecimenVisitReport; -import org.labkey.specimen.report.SpecimenVisitReportAction; - -import java.util.Collections; -import java.util.List; - -/** - * User: brittp - * Created: Jan 24, 2008 1:38:06 PM - */ -public class TypeSummaryReportFactory extends TypeReportFactory -{ - @Override - public String getLabel() - { - return "Type Summary Report"; - } - - @Override - public String getReportType() - { - return "TypeSummary"; - } - - @Override - protected List createReports() - { - List visits = SpecimenManager.get().getVisitsWithSpecimens(getContainer(), getUser(), getCohort()); - SimpleFilter filter = new SimpleFilter(); - addBaseFilters(filter); - SpecimenTypeVisitReport report = new SpecimenTypeVisitReport("Summary", visits, filter, this); - return Collections.singletonList(report); - } - - @Override - public Class getAction() - { - return SpecimenReportActions.TypeSummaryReportAction.class; - } -} diff --git a/specimen/src/org/labkey/specimen/requirements/DefaultActor.java b/specimen/src/org/labkey/specimen/requirements/DefaultActor.java deleted file mode 100644 index 96e0f55f744..00000000000 --- a/specimen/src/org/labkey/specimen/requirements/DefaultActor.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (c) 2008-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.requirements; - -import org.jetbrains.annotations.Nullable; -import org.labkey.api.data.DbScope; -import org.labkey.api.data.Table; -import org.labkey.api.data.TableInfo; -import org.labkey.api.security.Group; -import org.labkey.api.security.InvalidGroupMembershipException; -import org.labkey.api.security.PrincipalType; -import org.labkey.api.security.SecurityManager; -import org.labkey.api.security.User; -import org.labkey.api.security.UserManager; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.specimen.location.LocationManager; -import org.labkey.api.study.Location; -import org.labkey.api.util.ExceptionUtil; -import org.labkey.api.util.Pair; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -/** - * User: brittp - * Date: Jun 7, 2007 - * Time: 4:29:29 PM - */ -public abstract class DefaultActor> implements RequirementActor -{ - @Override - public void addMembers(@Nullable Location location, User... users) - { - Integer groupId = getGroupId(location, true); - Group group = SecurityManager.getGroup(groupId); - - for (User user : users) - { - try - { - SecurityManager.addMember(group, user); - } - catch (InvalidGroupMembershipException e) - { - // Best effort, but log any exceptions. Exception is unlikely at this point, since - // actors don't currently support groups as members. - ExceptionUtil.logExceptionToMothership(null, e); - } - } - } - - @Override - public User[] getMembers() - { - return getMembers(null); - } - - @Override - public User[] getMembers(@Nullable Location location) - { - Integer groupId = getGroupId(location, false); - if (groupId == null) - return new User[0]; - List> userIds = SecurityManager.getGroupMemberNamesAndIds(groupId, true); // include active and inactive - User[] users = new User[userIds.size()]; - Iterator> idIt = userIds.iterator(); - for (int i = 0; i < users.length; i++) - users[i] = UserManager.getUser(idIt.next().getKey()); - return users; - } - - @Override - public void removeMembers(User... members) - { - removeMembers(null, members); - } - - @Override - public void removeMembers(@Nullable Location location, User... members) - { - Integer groupId = getGroupId(location, false); - if (groupId == null) - throw new IllegalStateException("Group Id does not exist."); - Group group = SecurityManager.getGroup(groupId); - if (group == null) - throw new IllegalStateException("Group does not exist."); - for (User member : members) - SecurityManager.deleteMember(group, member); - } - - public Group getGroup(@Nullable Location location) - { - Integer groupId = getGroupId(location, false); - return SecurityManager.getGroup(groupId); - } - - public Integer getGroupId(boolean createIfMissing) - { - return getGroupId(null, createIfMissing); - } - - public Integer getGroupId(@Nullable Location location, boolean createIfMissing) - { - String groupName = getGroupName() + " " + getPrimaryKey(); - Integer groupId; - if (location != null) - { - groupId = SecurityManager.getGroupId(getContainer(), groupName, location.getEntityId(), false); - if (groupId == null && createIfMissing) - groupId = SecurityManager.createGroup(getContainer(), groupName, null, PrincipalType.MODULE, location.getEntityId()).getUserId(); - } - else - { - groupId = SecurityManager.getGroupId(getContainer(), groupName, false); - if (groupId == null && createIfMissing) - groupId = SecurityManager.createGroup(getContainer(), groupName, null, PrincipalType.MODULE).getUserId(); - } - return groupId; - } - - @Override - public void deleteAllGroups(User user) - { - List groupsToDelete = new ArrayList<>(); - List locations = LocationManager.get().getLocations(getContainer()); - for (Location location : locations) - { - Integer id = getGroupId(location, false); - if (id != null) - groupsToDelete.add(id); - } - Integer id = getGroupId(false); - if (id != null) - groupsToDelete.add(id); - - for (Integer groupId : groupsToDelete) - { - Group group = SecurityManager.getGroup(groupId); - if (group != null) - SecurityManager.deleteGroup(group, user); - } - } - - @Override - public A create(User user) - { - if (getContainer() == null) - throw new IllegalArgumentException("Container must be set for all requirements."); - return Table.insert(user, getTableInfo(), (A) this); - } - - @Override - public A update(User user) - { - return Table.update(user, getTableInfo(), (A) this, getPrimaryKey()); - } - - @Override - public void delete(User user) - { - DbScope scope = SpecimenSchema.get().getScope(); - try (DbScope.Transaction transaction = scope.ensureTransaction()) - { - deleteAllGroups(user); - Table.delete(getTableInfo(), getPrimaryKey()); - - transaction.commit(); - } - } - - protected abstract TableInfo getTableInfo(); -} diff --git a/specimen/src/org/labkey/specimen/requirements/DefaultRequirement.java b/specimen/src/org/labkey/specimen/requirements/DefaultRequirement.java deleted file mode 100644 index ef2d1c37149..00000000000 --- a/specimen/src/org/labkey/specimen/requirements/DefaultRequirement.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2008-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.requirements; - -import org.labkey.api.data.Table; -import org.labkey.api.data.TableInfo; -import org.labkey.api.security.User; - -/** - * User: brittp - * Date: Jun 7, 2007 - * Time: 4:29:24 PM - */ -public abstract class DefaultRequirement> implements Requirement -{ - @Override - public R update(User user) - { - return Table.update(user, getTableInfo(), (R) this, getPrimaryKeyValue()); - } - - @Override - public void delete() - { - Table.delete(getTableInfo(), getPrimaryKeyValue()); - } - - @Override - public R persist(User user, String ownerEntityId) - { - if (getContainer() == null) - throw new IllegalArgumentException("Container must be set for all requirements."); - if (ownerEntityId == null) - throw new IllegalArgumentException("Owner entity Id must be provided for all requirements."); - - R mutable = createMutable(); - mutable.setOwnerEntityId(ownerEntityId); - return Table.insert(user, getTableInfo(), mutable); - } - - protected abstract TableInfo getTableInfo(); - protected abstract Object getPrimaryKeyValue(); -} diff --git a/specimen/src/org/labkey/specimen/requirements/DefaultRequirementProvider.java b/specimen/src/org/labkey/specimen/requirements/DefaultRequirementProvider.java deleted file mode 100644 index 2a793175166..00000000000 --- a/specimen/src/org/labkey/specimen/requirements/DefaultRequirementProvider.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (c) 2008-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.requirements; - -import org.labkey.api.data.Container; -import org.labkey.api.data.DbScope; -import org.labkey.api.data.PropertyManager; -import org.labkey.api.data.PropertyManager.WritablePropertyMap; -import org.labkey.api.data.SimpleFilter; -import org.labkey.api.data.Sort; -import org.labkey.api.data.TableInfo; -import org.labkey.api.data.TableSelector; -import org.labkey.api.query.FieldKey; -import org.labkey.api.security.User; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.util.GUID; - -import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * User: brittp - * Date: Jun 4, 2007 - * Time: 2:36:43 PM - */ -public abstract class DefaultRequirementProvider, A extends RequirementActor> - implements RequirementProvider -{ - private final Class _requirementClass; - private final Class _actorClass; - - public DefaultRequirementProvider(Class requirementClass, Class actorClass) - { - _requirementClass = requirementClass; - _actorClass = actorClass; - } - - @Override - public R[] getDefaultRequirements(Container container, RequirementType type) - { - String ownerId = getDefaultRequirementPlaceholder(container, type, false); - if (ownerId == null) - return (R[]) Array.newInstance(_requirementClass, 0); - else - return getRequirements(container, ownerId); - } - - public R[] getDefaultRequirements(Container container) - { - Set defaultRequirements = new HashSet<>(); - for (RequirementType type : getRequirementTypes()) - defaultRequirements.addAll(Arrays.asList(getDefaultRequirements(container, type))); - return defaultRequirements.toArray((R[]) Array.newInstance(_requirementClass, defaultRequirements.size())); - } - - @Override - public R createDefaultRequirement(User user, R requirement, RequirementType type) - { - String owner = getDefaultRequirementPlaceholder(requirement.getContainer(), type, true); - requirement = requirement.createMutable(); - requirement.setOwnerEntityId(owner); - return requirement.persist(user, owner); - } - - private synchronized String getDefaultRequirementPlaceholder(final Container container, RequirementType type, boolean createIfMissing) - { - WritablePropertyMap props = PropertyManager.getWritableProperties(container, - "DefaultRequirement: " + getClass().getSimpleName(), true); - String ownerEntityId = props.get(type.name()); - if (ownerEntityId == null && createIfMissing) - { - ownerEntityId = GUID.makeGUID(); - props.put(type.name(), ownerEntityId); - props.save(); - } - return ownerEntityId; - } - - @Override - public R getRequirement(Container container, Object requirementPrimaryKey) - { - return new TableSelector(getRequirementTableInfo()).getObject(container, requirementPrimaryKey, _requirementClass); - } - - public R[] getRequirements(Container container, String ownerEntityId) - { - SimpleFilter filter = SimpleFilter.createContainerFilter(container); - filter.addCondition(getOwnerEntityIdColumnName(), ownerEntityId); - - return new TableSelector(getRequirementTableInfo(), filter, getDefaultRequirementSort()).getArray(_requirementClass); - } - - protected Sort getDefaultRequirementSort() - { - return new Sort("RowId"); - } - - public R[] getRequirements(Container container) - { - SimpleFilter filter = SimpleFilter.createContainerFilter(container); - return new TableSelector(getRequirementTableInfo(), filter, getDefaultRequirementSort()).getArray(_requirementClass); - } - - @Override - public A[] getActors(Container c) - { - SimpleFilter filter = SimpleFilter.createContainerFilter(c); - - return new TableSelector(getActorTableInfo(), filter, new Sort(getActorSortColumnName())).getArray(_actorClass); - } - - @Override - public A getActor(Container c, Object primaryKey) - { - return new TableSelector(getActorTableInfo()).getObject(c, primaryKey, _actorClass); - } - - @Override - public List getActorsByLabel(Container c, String label) - { - SimpleFilter filter = SimpleFilter.createContainerFilter(c); - filter.addCondition(FieldKey.fromParts("Label"), label); - return new TableSelector(getActorTableInfo(), filter, new Sort(getActorSortColumnName())).getArrayList(_actorClass); - } - - @Override - public Collection getActorsInUse(Container c) - { - Requirement[] requirements = getRequirements(c); - Map actors = new HashMap<>(); - for (Requirement requirement : requirements) - { - A actor = getActor(c, requirement.getActorPrimaryKey()); - actors.put(actor.getPrimaryKey(), actor); - } - Requirement[] defaultRequirements = getDefaultRequirements(c); - for (Requirement requirement : defaultRequirements) - { - A actor = getActor(c, requirement.getActorPrimaryKey()); - actors.put(actor.getPrimaryKey(), actor); - } - return actors.values(); - } - - @Override - public void purgeContainer(Container c, User user) - { - R[] requirements = getRequirements(c); - for (R requirement : requirements) - requirement.delete(); - - A[] actors = getActors(c); - for (A actor : actors) - actor.delete(user); - } - - - @Override - public void deleteRequirements(RequirementOwner owner) - { - R[] requirements = getRequirements(owner); - DbScope scope = SpecimenSchema.get().getScope(); - try (DbScope.Transaction transaction = scope.ensureTransaction()) - { - for (R requirement : requirements) - requirement.delete(); - - transaction.commit(); - } - } - - @Override - public R[] getRequirements(RequirementOwner owner) - { - return getRequirements(owner.getContainer(), owner.getEntityId()); - } - - @Override - public void generateDefaultRequirements(User user, RequirementOwner owner) - { - for (RequirementType type : getRequirementTypes()) - { - R[] defaultRequirements = getDefaultRequirements(owner.getContainer(), type); - for (R defaultRequirement : defaultRequirements) - { - List requirements = generateRequirementsFromDefault(owner, defaultRequirement, type); - for (R requirement : requirements) - createRequirement(user, owner, requirement); - } - } - } - - @Override - public R createRequirement(User user, RequirementOwner owner, R requirement) - { - return createRequirement(user, owner, requirement, false); - } - - - @Override - public R createRequirement(User user, RequirementOwner owner, R requirement, boolean forceDuplicate) - { - // unless we're forcing, we'll double-check to see if there's already an incomplete requirement matching this one: - if (!forceDuplicate) - { - for (R possibleMatch : getRequirements(owner)) - { - if (requirement.isEqual(possibleMatch)) - return null; - } - } - return requirement.persist(user, owner.getEntityId()); - } - - protected List generateRequirementsFromDefault(RequirementOwner owner, R defaultRequirement, RequirementType type) - { - return Collections.singletonList(defaultRequirement); - } - - protected abstract TableInfo getRequirementTableInfo(); - - protected abstract String getOwnerEntityIdColumnName(); - - protected abstract TableInfo getActorTableInfo(); - - protected abstract String getActorSortColumnName(); -} diff --git a/specimen/src/org/labkey/specimen/requirements/Requirement.java b/specimen/src/org/labkey/specimen/requirements/Requirement.java deleted file mode 100644 index 975a32c9553..00000000000 --- a/specimen/src/org/labkey/specimen/requirements/Requirement.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2008-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.requirements; - -import org.labkey.api.data.Container; -import org.labkey.api.security.User; - -/** - * User: brittp - * Date: Jun 4, 2007 - * Time: 2:31:52 PM - */ -public interface Requirement -{ - String getOwnerEntityId(); - - void setOwnerEntityId(String entityId); - - Container getContainer(); - - Object getActorPrimaryKey(); - - boolean isComplete(); - - R update(User user); - - R createMutable(); - - boolean isEqual(R requirement); - - void delete(); - - R persist(User user, String ownerEntityId); -} diff --git a/specimen/src/org/labkey/specimen/requirements/RequirementActor.java b/specimen/src/org/labkey/specimen/requirements/RequirementActor.java deleted file mode 100644 index 144a89c7d45..00000000000 --- a/specimen/src/org/labkey/specimen/requirements/RequirementActor.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2008-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.requirements; - -import org.labkey.api.data.Container; -import org.labkey.api.security.User; -import org.labkey.api.study.Location; - -/** - * User: brittp - * Date: Jun 4, 2007 - * Time: 2:56:54 PM - */ -public interface RequirementActor -{ - Object getPrimaryKey(); - - Container getContainer(); - - String getGroupName(); - - void addMembers(Location location, User... users); - - User[] getMembers(); - - User[] getMembers(Location location); - - void removeMembers(User... members); - - void removeMembers(Location location, User... members); - - void deleteAllGroups(User user); - - A create(User user); - - A update(User user); - - void delete(User user); -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/requirements/RequirementOwner.java b/specimen/src/org/labkey/specimen/requirements/RequirementOwner.java deleted file mode 100644 index 96543d5477b..00000000000 --- a/specimen/src/org/labkey/specimen/requirements/RequirementOwner.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2008-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.requirements; - -import org.labkey.api.data.Container; - -/** - * User: brittp - * Date: Jun 4, 2007 - * Time: 2:31:59 PM - */ -public interface RequirementOwner -{ - String getEntityId(); - - Container getContainer(); -} diff --git a/specimen/src/org/labkey/specimen/requirements/RequirementProvider.java b/specimen/src/org/labkey/specimen/requirements/RequirementProvider.java deleted file mode 100644 index 0c57fd815d5..00000000000 --- a/specimen/src/org/labkey/specimen/requirements/RequirementProvider.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2008-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.requirements; - -import org.labkey.api.data.Container; -import org.labkey.api.security.User; - -import java.util.Collection; -import java.util.List; - -/** - * User: brittp - * Date: Jun 4, 2007 - * Time: 2:37:53 PM - */ -public interface RequirementProvider, - A extends RequirementActor> -{ - R getRequirement(Container container, Object requirementPrimaryKey); - - A[] getActors(Container c); - - A getActor(Container c, Object primaryKey); - - List getActorsByLabel(Container c, String label); - - Collection getActorsInUse(Container c); - - R[] getDefaultRequirements(Container container, RequirementType type); - - void generateDefaultRequirements(User user, RequirementOwner owner); - - void purgeContainer(Container c, User user); - - R createDefaultRequirement(User user, R requirement, RequirementType type); - - RequirementType[] getRequirementTypes(); - - R createRequirement(User user, RequirementOwner owner, R requirement); - - R createRequirement(User user, RequirementOwner owner, R requirement, boolean forceDuplicate); - - void deleteRequirements(RequirementOwner owner); - - R[] getRequirements(RequirementOwner owner); -} diff --git a/specimen/src/org/labkey/specimen/requirements/RequirementType.java b/specimen/src/org/labkey/specimen/requirements/RequirementType.java deleted file mode 100644 index c3b72a93c24..00000000000 --- a/specimen/src/org/labkey/specimen/requirements/RequirementType.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2008-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.requirements; - -/** - * User: brittp - * Date: Jun 4, 2007 - * Time: 3:21:36 PM - */ -public interface RequirementType -{ - String name(); -} diff --git a/specimen/src/org/labkey/specimen/requirements/SpecimenRequest.java b/specimen/src/org/labkey/specimen/requirements/SpecimenRequest.java deleted file mode 100644 index ca63213524f..00000000000 --- a/specimen/src/org/labkey/specimen/requirements/SpecimenRequest.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (c) 2008-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.requirements; - -import org.jetbrains.annotations.NotNull; -import org.labkey.api.data.Container; -import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.SqlSelector; -import org.labkey.api.data.TableInfo; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.specimen.Vial; -import org.labkey.api.specimen.location.LocationManager; -import org.labkey.api.study.AbstractStudyCachable; -import org.labkey.api.study.Location; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Objects; - -public class SpecimenRequest extends AbstractStudyCachable implements RequirementOwner -{ - private Container _container; - private String _entityId; - private int _rowId; - private int _statusId; - private int _createdBy; - private long _created; - private int _modifiedBy; - private long _modified; - private String _comments; - private Integer _destinationSiteId; // This is a locationId, but still needs to match the column in the table - private boolean _hidden; - - @Override - public Integer getPrimaryKey() - { - return getRowId(); - } - - public int getRowId() - { - return _rowId; - } - - public void setRowId(int rowId) - { - verifyMutability(); - _rowId = rowId; - } - - public int getStatusId() - { - return _statusId; - } - - public void setStatusId(int statusId) - { - verifyMutability(); - _statusId = statusId; - } - - public String getComments() - { - return _comments; - } - - public void setComments(String comments) - { - verifyMutability(); - _comments = comments; - } - - public Integer getDestinationSiteId() - { - return _destinationSiteId; - } - - public void setDestinationSiteId(Integer destinationSiteId) - { - verifyMutability(); - _destinationSiteId = destinationSiteId; - } - - @NotNull - public List getVials() - { - final Container container = getContainer(); - SpecimenSchema specimenSchema = SpecimenSchema.get(); - TableInfo tableInfoSpecimen = specimenSchema.getTableInfoSpecimen(container); - TableInfo tableInfoVial = specimenSchema.getTableInfoVial(container); - SQLFragment sql = new SQLFragment("SELECT Specimens.*, Vial.*, ? As Container FROM "); - sql.add(container); - sql.append(specimenSchema.getTableInfoSampleRequest().getFromSQL("request")) - .append(", ").append(specimenSchema.getTableInfoSampleRequestSpecimen().getFromSQL("map")) - .append(", ").append(tableInfoSpecimen.getFromSQL("Specimens")) - .append(", ").append(tableInfoVial.getFromSQL("Vial")) - .append("\nWHERE request.RowId = map.SampleRequestId AND Vial.GlobalUniqueId = map.SpecimenGlobalUniqueId\n") - .append("AND Vial.SpecimenId = Specimens.RowId ") - .append("AND request.Container = map.Container AND map.Container = ? AND request.RowId = ?"); - sql.add(container); - sql.add(getRowId()); - - // TODO: LinkedList? - final List vials = new ArrayList<>(); - - new SqlSelector(specimenSchema.getSchema(), sql).forEachMap(map -> vials.add(new Vial(container, map))); - - return vials; - } - - public SpecimenRequestRequirement[] getRequirements() - { - return SpecimenRequestRequirementProvider.get().getRequirements(this); - } - - public boolean isHidden() - { - return _hidden; - } - - public void setHidden(boolean hidden) - { - verifyMutability(); - _hidden = hidden; - } - - @Override - public Container getContainer() - { - return _container; - } - - @Override - public void setContainer(Container container) - { - verifyMutability(); - _container = container; - } - - public Date getCreated() - { - return new Date(_created); - } - - public void setCreated(Date created) - { - verifyMutability(); - _created = created.getTime(); - } - - public int getCreatedBy() - { - return _createdBy; - } - - public void setCreatedBy(int createdBy) - { - verifyMutability(); - _createdBy = createdBy; - } - - public Date getModified() - { - return new Date(_modified); - } - - public void setModified(Date modified) - { - verifyMutability(); - _modified = modified.getTime(); - } - - public int getModifiedBy() - { - return _modifiedBy; - } - - public void setModifiedBy(int modifiedBy) - { - verifyMutability(); - _modifiedBy = modifiedBy; - } - - public String getRequestDescription() - { - StringBuilder builder = new StringBuilder(); - builder.append("Request ID ").append(_rowId); - if (_destinationSiteId != null) - { - Location destination = LocationManager.get().getLocation(_container, _destinationSiteId); - builder.append(", destination ").append(destination.getDisplayName()); - } - return builder.toString(); - } - - @Override - public String getEntityId() - { - return _entityId; - } - - public void setEntityId(String entityId) - { - _entityId = entityId; - } - - @Override - public boolean equals(Object o) - { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - SpecimenRequest that = (SpecimenRequest) o; - return _rowId == that._rowId && _statusId == that._statusId && _createdBy == that._createdBy && _created == that._created && _modifiedBy == that._modifiedBy && _modified == that._modified && _hidden == that._hidden && Objects.equals(_container, that._container) && Objects.equals(_entityId, that._entityId) && Objects.equals(_comments, that._comments) && Objects.equals(_destinationSiteId, that._destinationSiteId); - } - - @Override - public int hashCode() - { - return Objects.hash(_container, _entityId, _rowId, _statusId, _createdBy, _created, _modifiedBy, _modified, _comments, _destinationSiteId, _hidden); - } -} diff --git a/specimen/src/org/labkey/specimen/requirements/SpecimenRequestRequirement.java b/specimen/src/org/labkey/specimen/requirements/SpecimenRequestRequirement.java deleted file mode 100644 index 34a76260523..00000000000 --- a/specimen/src/org/labkey/specimen/requirements/SpecimenRequestRequirement.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (c) 2008-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.requirements; - -import org.labkey.api.data.Container; -import org.labkey.api.data.TableInfo; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.specimen.location.LocationImpl; -import org.labkey.api.specimen.location.LocationManager; -import org.labkey.api.study.StudyCachable; -import org.labkey.api.util.MemTracker; -import org.labkey.specimen.model.SpecimenRequestActor; - -public class SpecimenRequestRequirement extends DefaultRequirement - implements StudyCachable, Cloneable -{ - private int _rowId; - private Container _container; - private Integer _requestId; - private String _ownerEntityId; - private Integer _actorId; - private Integer _siteId; // This is a locationId, but must match column name in table - private boolean _complete; - private String _description; - private boolean _mutable = true; - - public SpecimenRequestRequirement() - { - MemTracker.getInstance().put(this); - } - - protected void verifyMutability() - { - if (!_mutable) - throw new IllegalStateException("Cached objects are immutable; createMutable must be called first."); - } - - public boolean isMutable() - { - return _mutable; - } - - protected void unlock() - { - _mutable = true; - } - - @Override - public void lock() - { - _mutable = false; - } - - - @Override - public SpecimenRequestRequirement createMutable() - { - try - { - SpecimenRequestRequirement obj = (SpecimenRequestRequirement) clone(); - obj.unlock(); - return obj; - } - catch (CloneNotSupportedException e) - { - throw new RuntimeException(e); - } - } - - public SpecimenRequestActor getActor() - { - if (_actorId == null) - return null; - return SpecimenRequestRequirementProvider.get().getActor(getContainer(), _actorId); - } - - public LocationImpl getLocation() - { - if (_siteId == null) - return null; - return LocationManager.get().getLocation(_container, _siteId); - } - - public Integer getActorId() - { - return _actorId; - } - - public void setActorId(Integer actorId) - { - verifyMutability(); - _actorId = actorId; - } - - @Override - public boolean isComplete() - { - return _complete; - } - - public void setComplete(boolean complete) - { - verifyMutability(); - _complete = complete; - } - - @Override - public Container getContainer() - { - return _container; - } - - public void setContainer(Container container) - { - verifyMutability(); - _container = container; - } - - public Integer getRequestId() - { - return _requestId; - } - - public void setRequestId(Integer requestId) - { - verifyMutability(); - _requestId = requestId; - } - - @Override - public Integer getPrimaryKey() - { - return getRowId(); - } - - public int getRowId() - { - return _rowId; - } - - public void setRowId(int rowId) - { - verifyMutability(); - _rowId = rowId; - } - - public Integer getSiteId() - { - return _siteId; - } - - public void setSiteId(Integer siteId) - { - verifyMutability(); - _siteId = siteId; - } - - public String getDescription() - { - return _description; - } - - public void setDescription(String description) - { - _description = description; - } - - public String getRequirementSummary() - { - StringBuilder builder = new StringBuilder(); - if (_actorId != null) - builder.append(getActor().getLabel()); - if (_siteId != null) - builder.append(" (").append(getLocation().getLabel()).append(")"); - if (_description != null) - { - if (!builder.isEmpty() && !_description.isEmpty()) - builder.append(", "); - builder.append(_description); - } - return builder.toString(); - } - - @Override - public String getOwnerEntityId() - { - return _ownerEntityId; - } - - @Override - public void setOwnerEntityId(String requestEntityId) - { - _ownerEntityId = requestEntityId; - } - - - @Override - public Object getActorPrimaryKey() - { - return _actorId; - } - - private int safeIntegerCompare(Integer first, Integer second) - { - if (first == null && second == null) - return 0; - if (first == null) - return -1; - if (second == null) - return 1; - return first.compareTo(second); - } - - @Override - public boolean isEqual(SpecimenRequestRequirement other) - { - if (this == other) - return true; - - int comp = safeIntegerCompare(getSiteId(), other.getSiteId()); - if (comp == 0) - comp = getDescription().compareTo(other.getDescription()); - if (comp == 0) - comp = safeIntegerCompare(getActorId(), other.getActorId()); - if (comp == 0) - comp = (isComplete() ? 1 : 0) - (other.isComplete() ? 1 : 0); - return comp == 0; - } - - - @Override - protected Object getPrimaryKeyValue() - { - return getRowId(); - } - - @Override - protected TableInfo getTableInfo() - { - return SpecimenSchema.get().getTableInfoSampleRequestRequirement(); - } -} diff --git a/specimen/src/org/labkey/specimen/requirements/SpecimenRequestRequirementProvider.java b/specimen/src/org/labkey/specimen/requirements/SpecimenRequestRequirementProvider.java deleted file mode 100644 index 2aa0387d199..00000000000 --- a/specimen/src/org/labkey/specimen/requirements/SpecimenRequestRequirementProvider.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2008-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.requirements; - -import org.junit.Test; -import org.labkey.api.data.Container; -import org.labkey.api.data.TableInfo; -import org.labkey.api.security.permissions.AbstractContainerScopingTest; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.util.GUID; -import org.labkey.specimen.model.SpecimenRequestActor; - -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * User: brittp - * Date: Jun 4, 2007 - * Time: 4:29:27 PM - */ -public class SpecimenRequestRequirementProvider extends DefaultRequirementProvider -{ - private static final SpecimenRequestRequirementProvider INSTANCE = new SpecimenRequestRequirementProvider(); - - public static SpecimenRequestRequirementProvider get() - { - return INSTANCE; - } - - private SpecimenRequestRequirementProvider() - { - super(SpecimenRequestRequirement.class, SpecimenRequestActor.class); - } - - @Override - public RequirementType[] getRequirementTypes() - { - return SpecimenRequestRequirementType.values(); - } - - @Override - protected String getOwnerEntityIdColumnName() - { - return "OwnerEntityId"; - } - - protected Object getPrimaryKeyValue(SpecimenRequestRequirement requirement) - { - return requirement.getRowId(); - } - - @Override - protected String getActorSortColumnName() - { - return "SortOrder"; - } - - @Override - protected TableInfo getActorTableInfo() - { - return SpecimenSchema.get().getTableInfoSampleRequestActor(); - } - - @Override - protected TableInfo getRequirementTableInfo() - { - return SpecimenSchema.get().getTableInfoSampleRequestRequirement(); - } - - protected SpecimenRequestRequirement createMutable(SpecimenRequestRequirement requirement) - { - return requirement.createMutable(); - } - - @Override - protected List generateRequirementsFromDefault(RequirementOwner owner, SpecimenRequestRequirement defaultRequirement, RequirementType type) - { - return ((SpecimenRequestRequirementType) type).generateRequirements((SpecimenRequest) owner, defaultRequirement); - } - - public Set getActorsInUseSet(Container container) - { - Collection actors = getActorsInUse(container); - Set ids = new HashSet<>(); - for (SpecimenRequestActor actor : actors) - ids.add(actor.getRowId()); - return ids; - } - - public static class ContainerScopingTestCase extends AbstractContainerScopingTest - { - @Test - public void testGetActorContainerScoping() - { - Container folderA = createContainer("A"); - Container folderB = createContainer("B"); - SpecimenRequestRequirementProvider provider = SpecimenRequestRequirementProvider.get(); - - SpecimenRequestActor actor = new SpecimenRequestActor(); - actor.setContainer(folderA); - actor.setLabel("Scoping test actor"); - actor = actor.create(getAdmin()); - int rowId = actor.getRowId(); - - assertNotNull("Actor should be visible from its own container", provider.getActor(folderA, rowId)); - assertNull("Actor must NOT be visible from another container", provider.getActor(folderB, rowId)); - } - - @Test - public void testGetRequirementContainerScoping() - { - Container folderA = createContainer("A"); - Container folderB = createContainer("B"); - SpecimenRequestRequirementProvider provider = SpecimenRequestRequirementProvider.get(); - - // A requirement references an actor through a NOT NULL foreign key, so create one in the same folder first - SpecimenRequestActor actor = new SpecimenRequestActor(); - actor.setContainer(folderA); - actor.setLabel("Requirement scoping test actor"); - actor = actor.create(getAdmin()); - - SpecimenRequestRequirement requirement = new SpecimenRequestRequirement(); - requirement.setContainer(folderA); - requirement.setRequestId(-1); // no real request row is needed for a primary-key lookup - requirement.setActorId(actor.getRowId()); - requirement.setDescription("Requirement scoping test"); - requirement = requirement.persist(getAdmin(), GUID.makeGUID()); - int rowId = requirement.getRowId(); - - assertNotNull("Requirement should be visible from its own container", provider.getRequirement(folderA, rowId)); - assertNull("Requirement must NOT be visible from another container", provider.getRequirement(folderB, rowId)); - } - } -} diff --git a/specimen/src/org/labkey/specimen/requirements/SpecimenRequestRequirementType.java b/specimen/src/org/labkey/specimen/requirements/SpecimenRequestRequirementType.java deleted file mode 100644 index 6186160d2c2..00000000000 --- a/specimen/src/org/labkey/specimen/requirements/SpecimenRequestRequirementType.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2008-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.requirements; - -import org.labkey.api.specimen.Vial; -import org.labkey.api.specimen.location.LocationImpl; -import org.labkey.api.specimen.location.LocationManager; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * User: brittp -* Date: Jun 7, 2007 -* Time: 11:26:49 AM -*/ -public enum SpecimenRequestRequirementType implements RequirementType -{ - ORIGINATING_SITE - { - @Override - public List generateRequirements(SpecimenRequest owner, SpecimenRequestRequirement defaultRequirement) - { - List requirements = new ArrayList<>(); - List vials = owner.getVials(); - if (!vials.isEmpty()) - { - // get a list of all providing and originating sites: - Set originatingLocationIds = new HashSet<>(); - for (Vial vial : vials) - { - LocationImpl originatingLocation = LocationManager.get().getOriginatingLocation(vial); - if (originatingLocation != null) - originatingLocationIds.add(originatingLocation.getRowId()); - } - for (Integer locationId : originatingLocationIds) - { - SpecimenRequestRequirement requirement = defaultRequirement.createMutable(); - requirement.setSiteId(locationId); - requirement.setRequestId(owner.getRowId()); - requirements.add(requirement); - } - } - return requirements; - } - }, - PROVIDING_SITE - { - @Override - public List generateRequirements(SpecimenRequest owner, SpecimenRequestRequirement defaultRequirement) - { - List requirements = new ArrayList<>(); - List vials = owner.getVials(); - if (!vials.isEmpty()) - { - // get a list of all providing and originating sites: - Set providerLocationIds = new HashSet<>(); - for (Vial vial : vials) - { - LocationImpl providingLocation = LocationManager.get().getCurrentLocation(vial); - if (providingLocation != null) - providerLocationIds.add(providingLocation.getRowId()); - } - for (Integer locationId : providerLocationIds) - { - SpecimenRequestRequirement requirement = defaultRequirement.createMutable(); - requirement.setRequestId(owner.getRowId()); - requirement.setSiteId(locationId); - requirements.add(requirement); - } - } - return requirements; - } - }, - RECEIVING_SITE - { - @Override - public List generateRequirements(SpecimenRequest owner, SpecimenRequestRequirement defaultRequirement) - { - if (owner.getDestinationSiteId() != null) - { - defaultRequirement.setSiteId(owner.getDestinationSiteId()); - defaultRequirement.setRequestId(owner.getRowId()); - return Collections.singletonList(defaultRequirement); - } - else - return Collections.emptyList(); - } - }, - NON_SITE_BASED - { - @Override - public List generateRequirements(SpecimenRequest owner, SpecimenRequestRequirement defaultRequirement) - { - defaultRequirement.setRequestId(owner.getRowId()); - return Collections.singletonList(defaultRequirement); - } - }; - - abstract public List generateRequirements(SpecimenRequest owner, SpecimenRequestRequirement defaultRequirement); -} diff --git a/specimen/src/org/labkey/specimen/security/permissions/EditSpecimenDataPermission.java b/specimen/src/org/labkey/specimen/security/permissions/EditSpecimenDataPermission.java deleted file mode 100644 index cf6ce7323f2..00000000000 --- a/specimen/src/org/labkey/specimen/security/permissions/EditSpecimenDataPermission.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2013-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.security.permissions; - -import org.labkey.api.security.permissions.AbstractPermission; -import org.labkey.specimen.SpecimenModule; - -public class EditSpecimenDataPermission extends AbstractPermission -{ - public EditSpecimenDataPermission() - { - super( - "Edit Specimen Data", - "Allows editing of specimen data", - SpecimenModule.class - ); - } -} diff --git a/specimen/src/org/labkey/specimen/security/permissions/LockSpecimensPermission.java b/specimen/src/org/labkey/specimen/security/permissions/LockSpecimensPermission.java deleted file mode 100644 index d54fd44721d..00000000000 --- a/specimen/src/org/labkey/specimen/security/permissions/LockSpecimensPermission.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2009-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.security.permissions; - -import org.labkey.api.security.permissions.AbstractPermission; -import org.labkey.specimen.SpecimenModule; - -/* -* User: Dave -* Date: May 18, 2009 -* Time: 11:40:13 AM -*/ -public class LockSpecimensPermission extends AbstractPermission -{ - public LockSpecimensPermission() - { - super("Lock Specimens", - "Allows locking of specimens", - SpecimenModule.class); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/security/permissions/ManageDisplaySettingsPermission.java b/specimen/src/org/labkey/specimen/security/permissions/ManageDisplaySettingsPermission.java deleted file mode 100644 index a3b77c191f0..00000000000 --- a/specimen/src/org/labkey/specimen/security/permissions/ManageDisplaySettingsPermission.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2009-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.security.permissions; - -import org.labkey.api.security.permissions.AbstractPermission; -import org.labkey.specimen.SpecimenModule; - -/* -* User: Dave -* Date: May 18, 2009 -* Time: 11:30:46 AM -*/ -public class ManageDisplaySettingsPermission extends AbstractPermission -{ - public ManageDisplaySettingsPermission() - { - super("Manage Specimen Display Settings", - "Allows management of display settings on the specimen request form", - SpecimenModule.class); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/security/permissions/ManageNewRequestFormPermission.java b/specimen/src/org/labkey/specimen/security/permissions/ManageNewRequestFormPermission.java deleted file mode 100644 index 360f3ad1ed3..00000000000 --- a/specimen/src/org/labkey/specimen/security/permissions/ManageNewRequestFormPermission.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2009-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.security.permissions; - -import org.labkey.api.security.permissions.AbstractPermission; -import org.labkey.specimen.SpecimenModule; - -/* -* User: Dave -* Date: May 18, 2009 -* Time: 11:27:04 AM -*/ -public class ManageNewRequestFormPermission extends AbstractPermission -{ - public ManageNewRequestFormPermission() - { - super("Manage New Request Form", - "Allows management of the form shown during a new specimen request", - SpecimenModule.class); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/security/permissions/ManageNotificationsPermission.java b/specimen/src/org/labkey/specimen/security/permissions/ManageNotificationsPermission.java deleted file mode 100644 index 952500e2822..00000000000 --- a/specimen/src/org/labkey/specimen/security/permissions/ManageNotificationsPermission.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2009-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.security.permissions; - -import org.labkey.api.security.permissions.AbstractPermission; -import org.labkey.specimen.SpecimenModule; - -/* -* User: Dave -* Date: May 18, 2009 -* Time: 11:29:07 AM -*/ -public class ManageNotificationsPermission extends AbstractPermission -{ - public ManageNotificationsPermission() - { - super("Manage Notifications", - "Allows management of specimen request notifications", - SpecimenModule.class); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/security/permissions/ManageRequestRequirementsPermission.java b/specimen/src/org/labkey/specimen/security/permissions/ManageRequestRequirementsPermission.java deleted file mode 100644 index 9c0805db5b9..00000000000 --- a/specimen/src/org/labkey/specimen/security/permissions/ManageRequestRequirementsPermission.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2009-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.security.permissions; - -import org.labkey.api.security.permissions.AbstractPermission; -import org.labkey.specimen.SpecimenModule; - -/* -* User: Dave -* Date: May 18, 2009 -* Time: 11:23:58 AM -*/ -public class ManageRequestRequirementsPermission extends AbstractPermission -{ - public ManageRequestRequirementsPermission() - { - super("Manage Specimen Request Default Requirements", - "Allows management of specimen request default requirements", - SpecimenModule.class); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/security/permissions/ManageRequestSettingsPermission.java b/specimen/src/org/labkey/specimen/security/permissions/ManageRequestSettingsPermission.java deleted file mode 100644 index 7354ae5384c..00000000000 --- a/specimen/src/org/labkey/specimen/security/permissions/ManageRequestSettingsPermission.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2009-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.security.permissions; - -import org.labkey.api.security.permissions.AbstractPermission; -import org.labkey.specimen.SpecimenModule; - -public class ManageRequestSettingsPermission extends AbstractPermission -{ - public ManageRequestSettingsPermission() - { - super( - "Manage Specimen Request and Tracking Settings", - "Allows management of specimen request and tracking settings", - SpecimenModule.class - ); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/security/permissions/ManageRequestStatusesPermission.java b/specimen/src/org/labkey/specimen/security/permissions/ManageRequestStatusesPermission.java deleted file mode 100644 index 0699ea5cefb..00000000000 --- a/specimen/src/org/labkey/specimen/security/permissions/ManageRequestStatusesPermission.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2009-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.security.permissions; - -import org.labkey.api.security.permissions.AbstractPermission; -import org.labkey.specimen.SpecimenModule; - -/* -* User: Dave -* Date: May 14, 2009 -* Time: 4:47:59 PM -*/ -public class ManageRequestStatusesPermission extends AbstractPermission -{ - public ManageRequestStatusesPermission() - { - super("Manage Request Statuses", "Allows management of specimen request statuses", - SpecimenModule.class); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/security/permissions/ManageRequestsPermission.java b/specimen/src/org/labkey/specimen/security/permissions/ManageRequestsPermission.java deleted file mode 100644 index bf85c5f736d..00000000000 --- a/specimen/src/org/labkey/specimen/security/permissions/ManageRequestsPermission.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2009-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.security.permissions; - -import org.labkey.api.security.permissions.AbstractPermission; -import org.labkey.api.study.StudyService; - -/* -* User: Dave -* Date: May 18, 2009 -* Time: 11:41:23 AM -*/ -public class ManageRequestsPermission extends AbstractPermission -{ - public ManageRequestsPermission() - { - super("Manage Specimen Requests", - "Allows management and approval of specimen requests", - StudyService.get().getStudyModuleClass()); // TODO: Switch to SpecimenModule.class - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/security/permissions/ManageSpecimenActorsPermission.java b/specimen/src/org/labkey/specimen/security/permissions/ManageSpecimenActorsPermission.java deleted file mode 100644 index 6a997f217ca..00000000000 --- a/specimen/src/org/labkey/specimen/security/permissions/ManageSpecimenActorsPermission.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2009-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.security.permissions; - -import org.labkey.api.security.permissions.AbstractPermission; -import org.labkey.specimen.SpecimenModule; - -/* -* User: Dave -* Date: May 13, 2009 -* Time: 3:22:10 PM -*/ -public class ManageSpecimenActorsPermission extends AbstractPermission -{ - public ManageSpecimenActorsPermission() - { - super("Manage Specimen Actors", "Allows management of specimen actors", - SpecimenModule.class); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/security/permissions/RequestSpecimensPermission.java b/specimen/src/org/labkey/specimen/security/permissions/RequestSpecimensPermission.java deleted file mode 100644 index 3e34fcf3360..00000000000 --- a/specimen/src/org/labkey/specimen/security/permissions/RequestSpecimensPermission.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2009-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.security.permissions; - -import org.labkey.api.security.permissions.AbstractPermission; -import org.labkey.specimen.SpecimenModule; - -public class RequestSpecimensPermission extends AbstractPermission -{ - public RequestSpecimensPermission() - { - super( - "Request Specimens", - "Allows request of specimens", - SpecimenModule.class - ); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/security/permissions/SetSpecimenCommentsPermission.java b/specimen/src/org/labkey/specimen/security/permissions/SetSpecimenCommentsPermission.java deleted file mode 100644 index 2acb1515d6a..00000000000 --- a/specimen/src/org/labkey/specimen/security/permissions/SetSpecimenCommentsPermission.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2009-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.security.permissions; - -import org.labkey.api.security.permissions.AbstractPermission; -import org.labkey.specimen.SpecimenModule; - -/* -* User: Dave -* Date: May 18, 2009 -* Time: 11:39:16 AM -*/ -public class SetSpecimenCommentsPermission extends AbstractPermission -{ - public SetSpecimenCommentsPermission() - { - super("Set Specimen Comments", - "Allows the setting of comments on specimens", - SpecimenModule.class); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/security/roles/AbstractSpecimenRole.java b/specimen/src/org/labkey/specimen/security/roles/AbstractSpecimenRole.java deleted file mode 100644 index dd8602c688b..00000000000 --- a/specimen/src/org/labkey/specimen/security/roles/AbstractSpecimenRole.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2011-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.security.roles; - -import org.labkey.api.data.Container; -import org.labkey.api.security.SecurableResource; -import org.labkey.api.security.permissions.Permission; -import org.labkey.api.security.roles.AbstractRole; -import org.labkey.api.study.StudyService; -import org.labkey.specimen.SpecimenModule; - -/** - * User: jeckels - * Date: Feb 25, 2011 - */ -public class AbstractSpecimenRole extends AbstractRole -{ - @SafeVarargs - protected AbstractSpecimenRole(String name, String description, Class... perms) - { - super(name, description, SpecimenModule.class, perms); - excludeGuests(); - } - - @Override - public boolean isApplicable(SecurableResource resource) - { - return super.isApplicable(resource) && ((Container)resource).hasActiveModuleByName(SpecimenModule.NAME) && branchContainsStudy((Container)resource); - } - - private boolean branchContainsStudy(Container container) - { - StudyService ss = StudyService.get(); - if (null != ss && null != ss.getStudy(container)) - return true; - - for (Container child : container.getChildren()) - { - // Issue 44437 - don't bother recursing into workbook children for perf reasons since they're not used - // for studies - if (!child.isWorkbook() && branchContainsStudy(child)) - return true; - } - - return false; - } -} diff --git a/specimen/src/org/labkey/specimen/security/roles/SpecimenCoordinatorRole.java b/specimen/src/org/labkey/specimen/security/roles/SpecimenCoordinatorRole.java deleted file mode 100644 index 77f1b49555c..00000000000 --- a/specimen/src/org/labkey/specimen/security/roles/SpecimenCoordinatorRole.java +++ /dev/null @@ -1,68 +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.security.roles; - -import org.jetbrains.annotations.NotNull; -import org.labkey.api.security.permissions.EditSharedViewPermission; -import org.labkey.api.study.security.permissions.ManageStudyPermission; -import org.labkey.specimen.security.permissions.EditSpecimenDataPermission; -import org.labkey.specimen.security.permissions.LockSpecimensPermission; -import org.labkey.specimen.security.permissions.ManageDisplaySettingsPermission; -import org.labkey.specimen.security.permissions.ManageNewRequestFormPermission; -import org.labkey.specimen.security.permissions.ManageNotificationsPermission; -import org.labkey.specimen.security.permissions.ManageRequestRequirementsPermission; -import org.labkey.specimen.security.permissions.ManageRequestSettingsPermission; -import org.labkey.specimen.security.permissions.ManageRequestStatusesPermission; -import org.labkey.specimen.security.permissions.ManageRequestsPermission; -import org.labkey.specimen.security.permissions.ManageSpecimenActorsPermission; -import org.labkey.specimen.security.permissions.RequestSpecimensPermission; -import org.labkey.specimen.security.permissions.SetSpecimenCommentsPermission; - -import java.util.Collection; -import java.util.Set; - -public class SpecimenCoordinatorRole extends AbstractSpecimenRole -{ - public SpecimenCoordinatorRole() - { - super("Specimen Coordinator", - "Specimen Coordinators may perform a number of management tasks related to specimens", - - EditSharedViewPermission.class, //spec-coord needs to be able to set global default view for specimens queries - EditSpecimenDataPermission.class, - LockSpecimensPermission.class, - ManageDisplaySettingsPermission.class, - ManageNewRequestFormPermission.class, - ManageNotificationsPermission.class, - ManageRequestRequirementsPermission.class, - ManageRequestSettingsPermission.class, - ManageRequestStatusesPermission.class, - ManageRequestsPermission.class, - ManageSpecimenActorsPermission.class, - ManageStudyPermission.class, - RequestSpecimensPermission.class, - SetSpecimenCommentsPermission.class - ); - excludeGuests(); - excludeUsers(); - } - - @Override - public @NotNull Collection getSerializationAliases() - { - return Set.of("org.labkey.study.security.roles.SpecimenCoordinatorRole"); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/security/roles/SpecimenRequesterRole.java b/specimen/src/org/labkey/specimen/security/roles/SpecimenRequesterRole.java deleted file mode 100644 index 6f6f5d4f660..00000000000 --- a/specimen/src/org/labkey/specimen/security/roles/SpecimenRequesterRole.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2009-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.security.roles; - -import org.jetbrains.annotations.NotNull; -import org.labkey.specimen.security.permissions.RequestSpecimensPermission; - -import java.util.Collection; -import java.util.Set; - -public class SpecimenRequesterRole extends AbstractSpecimenRole -{ - public SpecimenRequesterRole() - { - super("Specimen Requester", - "Specimen Requesters may request specimen vials.", - RequestSpecimensPermission.class - ); - excludeGuests(); - } - - @Override - public @NotNull Collection getSerializationAliases() - { - return Set.of("org.labkey.study.security.roles.SpecimenRequesterRole"); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/settings/DisplaySettings.java b/specimen/src/org/labkey/specimen/settings/DisplaySettings.java deleted file mode 100644 index 32841b40216..00000000000 --- a/specimen/src/org/labkey/specimen/settings/DisplaySettings.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2009-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.settings; - -import java.util.Map; -/* - * User: brittp - * Date: May 8, 2009 - * Time: 2:51:21 PM - */ - -public class DisplaySettings -{ - private static final String KEY_FLAG_ONE_AVAIL_VIAL = "OneAvailableVial"; - private static final String KEY_FLAG_ZERO_AVAIL_VIALS = "ZeroAvailableVials"; - - private static final String KEY_DEFAULT_COMMENT_MODE = "DefaultToCommentMode"; - private static final String KEY_ENABLE_MANUAL_QC_FLAGGING = "EnableManualQCFlagging"; - - private boolean _defaultToCommentsMode = false; - private boolean _enableManualQCFlagging = true; - - public enum DisplayOption - { - NONE, - ALL_USERS, - ADMINS_ONLY - } - - private DisplayOption _lastVial; - private DisplayOption _zeroVials; - - public DisplaySettings() - { - // no-arg constructor for BeanViewForm reflection - } - - public DisplaySettings(Map map) - { - // Defect 15165: check for NULL properties; (this case seems to be _lastVial was present but not _zeroVials) - String propertyValue = map.get(KEY_FLAG_ONE_AVAIL_VIAL); - _lastVial = (propertyValue != null) ? DisplayOption.valueOf(propertyValue) : DisplayOption.ALL_USERS; - propertyValue = map.get(KEY_FLAG_ZERO_AVAIL_VIALS); - _zeroVials = (propertyValue != null) ? DisplayOption.valueOf(propertyValue) : DisplayOption.ALL_USERS; - _defaultToCommentsMode = getBoolean(map.get(KEY_DEFAULT_COMMENT_MODE), false); - _enableManualQCFlagging = getBoolean(map.get(KEY_ENABLE_MANUAL_QC_FLAGGING), true); - } - - private boolean getBoolean(Object value, boolean defaultValue) - { - if (value == null) - return defaultValue; - if (value instanceof String) - return Boolean.valueOf((String) value).booleanValue(); - throw new IllegalArgumentException("Cannot convert object to boolean: " + value); - } - - public void populateMap(Map map) - { - map.put(KEY_FLAG_ONE_AVAIL_VIAL, _lastVial.name()); - map.put(KEY_FLAG_ZERO_AVAIL_VIALS, _zeroVials.name()); - map.put(KEY_DEFAULT_COMMENT_MODE, Boolean.toString(_defaultToCommentsMode)); - map.put(KEY_ENABLE_MANUAL_QC_FLAGGING, Boolean.toString(_enableManualQCFlagging)); - } - - public static DisplaySettings getDefaultSettings() - { - DisplaySettings defaults = new DisplaySettings(); - defaults.setLastVial(DisplayOption.ALL_USERS.name()); - defaults.setZeroVials(DisplayOption.ALL_USERS.name()); - defaults.setDefaultToCommentsMode(false); - defaults.setEnableManualQCFlagging(true); - return defaults; - } - - public String getLastVial() - { - return _lastVial.name(); - } - - public void setLastVial(String lastVial) - { - _lastVial = DisplayOption.valueOf(lastVial); - } - - public String getZeroVials() - { - return _zeroVials.name(); - } - - public void setZeroVials(String zeroVials) - { - _zeroVials = DisplayOption.valueOf(zeroVials); - } - - public DisplayOption getLastVialEnum() - { - return _lastVial; - } - - public DisplayOption getZeroVialsEnum() - { - return _zeroVials; - } - - public boolean isDefaultToCommentsMode() - { - return _defaultToCommentsMode; - } - - public void setDefaultToCommentsMode(boolean defaultToCommentsMode) - { - _defaultToCommentsMode = defaultToCommentsMode; - } - - public boolean isEnableManualQCFlagging() - { - return _enableManualQCFlagging; - } - - public void setEnableManualQCFlagging(boolean enableManualQCFlagging) - { - _enableManualQCFlagging = enableManualQCFlagging; - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/settings/RepositorySettings.java b/specimen/src/org/labkey/specimen/settings/RepositorySettings.java deleted file mode 100644 index f7da8f95925..00000000000 --- a/specimen/src/org/labkey/specimen/settings/RepositorySettings.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (c) 2009-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.settings; - -import org.labkey.api.data.Container; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -/* - * User: brittp - * Date: May 8, 2009 - * Time: 2:51:41 PM - */ - -public class RepositorySettings -{ - private static final String KEY_SIMPLE = "Simple"; - private static final String KEY_ENABLE_REQUESTS = "EnableRequests"; - private static final String KEY_SPECIMENDATA_EDITABLE = "SpecimenDataEditable"; - - // Columns to group by n the specimen web part: Group1: Group By 1, Then By 2, Then By 3. Same for Group2 - private static String makeKeySpecWebPartGroup(Integer grouping, Integer groupBy) - { - return "SpecWebPart_Group" + grouping.toString() + "." + groupBy.toString(); - } - - private final String[][] _specWebPartColumnGroup = new String[2][3]; // 2 groupings; 3 groupBys within each - private final Map _mapOldNamesToNewNames = new HashMap<>(); // TODO: needed for any studies saved between 1/20/2013 and 2/1/2013 - private final Container _container; - - private boolean _simple; - private boolean _enableRequests; - private boolean _specimenDataEditable; - - private RepositorySettings(Container container) - { - _container = container; - setSpecimenWebPartGroupingDefaults(); - } - - public RepositorySettings(Container container, Map map) - { - this(container); - String simple = map.get(KEY_SIMPLE); - _simple = Boolean.parseBoolean(simple); - String enableRequests = map.get(KEY_ENABLE_REQUESTS); - _enableRequests = null == enableRequests ? !_simple : Boolean.parseBoolean(enableRequests); - String specimenDataEditable = map.get(KEY_SPECIMENDATA_EDITABLE); - _specimenDataEditable = Boolean.parseBoolean(specimenDataEditable); - - String firstGrouping = map.get(makeKeySpecWebPartGroup(0,0)); - if (null != firstGrouping) - { - for (int i = 0; i < 2; i += 1) // Only 2 grouping supported - { - for (int k = 0; k < 3; k += 1) // Only 2 groupBys supported - { - String group = map.get(makeKeySpecWebPartGroup(i,k)); - if (_mapOldNamesToNewNames.containsKey(group)) - group = _mapOldNamesToNewNames.get(group); - _specWebPartColumnGroup[i][k] = group; - if (null == _specWebPartColumnGroup[i][k]) - _specWebPartColumnGroup[i][k] = ""; - } - } - } - } - - public void populateMap(Map map) - { - map.put(KEY_SIMPLE, Boolean.toString(_simple)); - map.put(KEY_ENABLE_REQUESTS, Boolean.toString(_enableRequests)); - map.put(KEY_SPECIMENDATA_EDITABLE, Boolean.toString(_specimenDataEditable)); - - for (int i = 0; i < 2; i += 1) // Only 2 grouping supported - { - for (int k = 0; k < 3; k += 1) // Only 2 groupBys supported - { - map.put(makeKeySpecWebPartGroup(i,k), - (null != _specWebPartColumnGroup[i][k]) ? _specWebPartColumnGroup[i][k] : ""); - } - } - } - - public boolean isSimple() - { - return _simple; - } - - public void setSimple(boolean simple) - { - _simple = simple; - } - - public boolean isEnableRequests() - { - Study study = StudyService.get().getStudy(_container); - if (study != null && (study.hasSourceStudy() || study.isSnapshotStudy())) - return false; - return _enableRequests; - } - - public Container getContainer() - { - return _container; - } - - public void setEnableRequests(boolean enableRequests) - { - assert !_simple || !enableRequests : "Specimen requests may only be enabled for advanced specimen repository type"; - _enableRequests = enableRequests; - } - - public ArrayList getSpecimenWebPartGroupings() - { - // List of groupings - // Each grouping is a list of Strings that name the columns - ArrayList groupings = new ArrayList<>(2); - for (int i = 0; i < 2; i += 1) // Only 2 grouping supported - { - String[] grouping = new String[3]; - for (int k = 0; k < 3; k += 1) // Only 3 groupBys supported - { - grouping[k] = (null != _specWebPartColumnGroup[i][k]) ? _specWebPartColumnGroup[i][k] : ""; - } - groupings.add(grouping); - } - return groupings; - } - - public void setSpecimenWebPartGroupings(ArrayList groupings) - { - setSpecimenWebPartGroupingDefaults(); - for (int i = 0; i < groupings.size() && i < 2; i += 1) // Only 2 groupings supported - { - String[] grouping = groupings.get(i); - for (int k = 0; k < grouping.length && k < 3; k += 1) // Only 3 groupBys supported - { - String group = (null != grouping[k]) ? grouping[k] : ""; - if (_mapOldNamesToNewNames.containsKey(group)) - group = _mapOldNamesToNewNames.get(group); - _specWebPartColumnGroup[i][k] = group; - } - } - } - - private void setSpecimenWebPartGroupingDefaults() - { - _specWebPartColumnGroup[0][0] = "Primary Type"; - _specWebPartColumnGroup[0][1] = "Derivative Type"; - _specWebPartColumnGroup[0][2] = "Additive Type"; - _specWebPartColumnGroup[1][0] = "Derivative Type"; - _specWebPartColumnGroup[1][1] = "Additive Type"; - _specWebPartColumnGroup[1][2] = ""; - - _mapOldNamesToNewNames.put("PrimaryType", "Primary Type"); - _mapOldNamesToNewNames.put("DerivativeType", "Derivative Type"); - _mapOldNamesToNewNames.put("AdditiveType", "Additive Type"); - _mapOldNamesToNewNames.put("DerivativeType2", "Derivative Type2"); - _mapOldNamesToNewNames.put("SubAdditiveDerivative", "Sub Additive Derivative"); - _mapOldNamesToNewNames.put("ProcessingLocation", "Processing Location"); - _mapOldNamesToNewNames.put("ProtocolNumber", "Protocol Number"); - _mapOldNamesToNewNames.put("TubeType", "Tube Type"); - _mapOldNamesToNewNames.put("SiteName", "Site Name"); - _mapOldNamesToNewNames.put("Fr_Container", "Fr Container"); - _mapOldNamesToNewNames.put("Fr_Position", "Fr Position"); - _mapOldNamesToNewNames.put("Fr_Level1", "Fr Level1"); - _mapOldNamesToNewNames.put("Fr_Level2", "Fr Level2"); - } - - public static RepositorySettings getDefaultSettings(Container container) - { - RepositorySettings settings = new RepositorySettings(container); - settings.setSimple(true); - settings.setEnableRequests(false); - settings.setSpecimenDataEditable(false); - return settings; - } - - public boolean isSpecimenDataEditable() - { - return _specimenDataEditable; - } - - public void setSpecimenDataEditable(boolean specimenDataEditable) - { - _specimenDataEditable = specimenDataEditable; - } -} diff --git a/specimen/src/org/labkey/specimen/settings/RequestNotificationSettings.java b/specimen/src/org/labkey/specimen/settings/RequestNotificationSettings.java deleted file mode 100644 index 97718896db8..00000000000 --- a/specimen/src/org/labkey/specimen/settings/RequestNotificationSettings.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (c) 2009-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.settings; - -import jakarta.mail.Address; -import org.labkey.api.data.Container; -import org.labkey.api.security.User; -import org.labkey.api.security.ValidEmail; -import org.labkey.api.settings.LookAndFeelProperties; -import org.labkey.api.util.SafeToRenderEnum; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.StringTokenizer; - -public class RequestNotificationSettings -{ - public static final String REPLY_TO_CURRENT_USER_VALUE = "[current user]"; - private static final String KEY_REPLYTO = "ReplyTo"; - private static final String KEY_CC = "CC"; - private static final String KEY_SUBJECTSUFFIX = "SubjectSuffix"; - private static final String KEY_NEWREQUESTNOTIFY = "NewRequestNotify"; - private static final String KEY_DEFAULTEMAILNOTIFY = "DefaultEmailNotify"; - private static final String KEY_SPECIMENSATTACHMENT = "SpecimensAttachment"; - private String _replyTo; - private String _cc; - private String _subjectSuffix; - private String _newRequestNotify; - private boolean _ccCheckbox; - private boolean _newRequestNotifyCheckbox; - private DefaultEmailNotifyEnum _defaultEmailNotify = DefaultEmailNotifyEnum.ActorsInvolved; - private SpecimensAttachmentEnum _specimensAttachment = SpecimensAttachmentEnum.InEmailBody; - - public enum DefaultEmailNotifyEnum implements SafeToRenderEnum - {All, None, ActorsInvolved} - public enum SpecimensAttachmentEnum implements SafeToRenderEnum - {InEmailBody, ExcelAttachment, TextAttachment, Never} - - public RequestNotificationSettings() - { - // no-arg constructor for struts reflection - } - - public RequestNotificationSettings(Map map) - { - _replyTo = map.get(KEY_REPLYTO); - _cc = map.get(KEY_CC); - _subjectSuffix = map.get(KEY_SUBJECTSUFFIX); - _newRequestNotify = map.get(KEY_NEWREQUESTNOTIFY); - setDefaultEmailNotify(map.get(KEY_DEFAULTEMAILNOTIFY)); - setSpecimensAttachment(map.get(KEY_SPECIMENSATTACHMENT)); - } - - public String getReplyToEmailAddress(User currentAdmin) - { - if (RequestNotificationSettings.REPLY_TO_CURRENT_USER_VALUE.equals(getReplyTo())) - return currentAdmin.getEmail(); - else - return getReplyTo(); - } - - public String getReplyTo() - { - return _replyTo; - } - - public void setReplyTo(String replyTo) - { - _replyTo = replyTo; - } - - public String getCc() - { - return _cc; - } - - public void setCc(String cc) - { - _cc = cc; - } - - public String getSubjectSuffix() - { - return _subjectSuffix; - } - - public void setSubjectSuffix(String subjectSuffix) - { - _subjectSuffix = subjectSuffix; - } - - public String getNewRequestNotify() - { - return _newRequestNotify; - } - - public void setNewRequestNotify(String newRequestNotify) - { - _newRequestNotify = newRequestNotify; - } - - public boolean isCcCheckbox() - { - return _ccCheckbox; - } - - public void setCcCheckbox(boolean ccCheckbox) - { - _ccCheckbox = ccCheckbox; - } - - public boolean isNewRequestNotifyCheckbox() - { - return _newRequestNotifyCheckbox; - } - - public void setNewRequestNotifyCheckbox(boolean newRequestNotifyCheckbox) - { - _newRequestNotifyCheckbox = newRequestNotifyCheckbox; - } - - public void populateMap(Map map) - { - map.put(KEY_REPLYTO, _replyTo); - map.put(KEY_CC, _cc); - map.put(KEY_SUBJECTSUFFIX, _subjectSuffix); - map.put(KEY_NEWREQUESTNOTIFY, _newRequestNotify); - map.put(KEY_DEFAULTEMAILNOTIFY, getDefaultEmailNotify()); - map.put(KEY_SPECIMENSATTACHMENT, getSpecimensAttachment()); - } - - public static RequestNotificationSettings getDefaultSettings(Container c) - { - RequestNotificationSettings defaults = new RequestNotificationSettings(); - defaults.setReplyTo(LookAndFeelProperties.getInstance(c).getSystemEmailAddress()); - defaults.setCc(""); - defaults.setNewRequestNotify(""); - defaults.setSubjectSuffix("Specimen Request Notification"); - return defaults; - } - - public Address[] getCCAddresses() throws ValidEmail.InvalidEmailException - { - if (_cc == null || _cc.isEmpty()) - return null; - StringTokenizer splitter = new StringTokenizer(_cc, ",;:\t\n\r"); - List
addresses = new ArrayList<>(); - while (splitter.hasMoreTokens()) - { - String token = splitter.nextToken(); - ValidEmail tester = new ValidEmail(token); - addresses.add(tester.getAddress()); - } - return addresses.toArray(new Address[0]); - } - - public Address[] getNewRequestNotifyAddresses() throws ValidEmail.InvalidEmailException - { - if (_newRequestNotify == null || _newRequestNotify.isEmpty()) - return null; - StringTokenizer splitter = new StringTokenizer(_newRequestNotify, ",;:\t\n\r"); - List
addresses = new ArrayList<>(); - while (splitter.hasMoreTokens()) - { - String token = splitter.nextToken(); - ValidEmail tester = new ValidEmail(token); - addresses.add(tester.getAddress()); - } - return addresses.toArray(new Address[0]); - } - - public boolean isReplyToCurrentUser() - { - return RequestNotificationSettings.REPLY_TO_CURRENT_USER_VALUE.equals(getReplyTo()); - } - - public String getDefaultEmailNotify() - { - return _defaultEmailNotify.name(); - } - - public DefaultEmailNotifyEnum getDefaultEmailNotifyEnum() - { - return _defaultEmailNotify; - } - - public void setDefaultEmailNotify(String defaultEmailNotify) - { - if (null == defaultEmailNotify) - _defaultEmailNotify = DefaultEmailNotifyEnum.ActorsInvolved; - else - _defaultEmailNotify = DefaultEmailNotifyEnum.valueOf(defaultEmailNotify); - } - - public String getSpecimensAttachment() - { - return _specimensAttachment.name(); - } - - public SpecimensAttachmentEnum getSpecimensAttachmentEnum() - { - return _specimensAttachment; - } - - public void setSpecimensAttachment(String specimensAttachment) - { - if (null == specimensAttachment) - _specimensAttachment = SpecimensAttachmentEnum.InEmailBody; - else - _specimensAttachment = SpecimensAttachmentEnum.valueOf(specimensAttachment); - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/settings/SettingsManager.java b/specimen/src/org/labkey/specimen/settings/SettingsManager.java deleted file mode 100644 index 5cd0dd1522f..00000000000 --- a/specimen/src/org/labkey/specimen/settings/SettingsManager.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2024-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.settings; - -import org.labkey.api.action.SpringActionController; -import org.labkey.api.data.Container; -import org.labkey.api.data.PropertyManager; -import org.labkey.api.data.PropertyManager.WritablePropertyMap; -import org.labkey.api.security.User; -import org.labkey.api.security.UserManager; -import org.labkey.specimen.SpecimenRequestManager; -import org.labkey.specimen.SpecimenRequestStatus; - -import java.util.Collection; -import java.util.Map; - -public class SettingsManager -{ - private static final SettingsManager INSTANCE = new SettingsManager(); - - public static SettingsManager get() - { - return INSTANCE; - } - - private SettingsManager() - { - } - - public RequestNotificationSettings getRequestNotificationSettings(Container container) - { - Map settingsMap = PropertyManager.getProperties(UserManager.getGuestUser(), - container, "SpecimenRequestNotifications"); - if (settingsMap.get("ReplyTo") == null) - { - try (var ignore = SpringActionController.ignoreSqlUpdates()) - { - RequestNotificationSettings defaults = RequestNotificationSettings.getDefaultSettings(container); - saveRequestNotificationSettings(container, defaults); - return defaults; - } - } - else - return new RequestNotificationSettings(settingsMap); - } - - public void saveRequestNotificationSettings(Container container, RequestNotificationSettings settings) - { - WritablePropertyMap settingsMap = PropertyManager.getWritableProperties(UserManager.getGuestUser(), - container, "SpecimenRequestNotifications", true); - settings.populateMap(settingsMap); - settingsMap.save(); - } - - public void saveDisplaySettings(Container container, DisplaySettings settings) - { - WritablePropertyMap settingsMap = PropertyManager.getWritableProperties(UserManager.getGuestUser(), - container, "SpecimenRequestDisplay", true); - settings.populateMap(settingsMap); - settingsMap.save(); - } - - public void saveStatusSettings(Container container, StatusSettings settings) - { - WritablePropertyMap settingsMap = PropertyManager.getWritableProperties(UserManager.getGuestUser(), - container, "SpecimenRequestStatus", true); - settings.populateMap(settingsMap); - settingsMap.save(); - } - - public void saveRepositorySettings(Container container, RepositorySettings settings) - { - WritablePropertyMap settingsMap = PropertyManager.getWritableProperties(UserManager.getGuestUser(), - container, "SpecimenRepositorySettings", true); - settings.populateMap(settingsMap); - settingsMap.save(); - SpecimenRequestManager.get().clearGroupedValuesForColumn(container); // May have changed groupings - } - - public boolean isSpecimenRequestEnabled(Container container, User user) - { - return isSpecimenRequestEnabled(container, true, user); - } - - public boolean isSpecimenRequestEnabled(Container container, boolean checkExistingStatuses, User user) - { - if (!checkExistingStatuses) - { - return SettingsManager.get().getRepositorySettings(container).isEnableRequests(); - } - else - { - if (!SettingsManager.get().getRepositorySettings(container).isEnableRequests()) - return false; - Collection statuses = SpecimenRequestManager.get().getRequestStatuses(container, user); - return (statuses != null && statuses.size() > 1); - } - } - - public DisplaySettings getDisplaySettings(Container container) - { - Map settingsMap = PropertyManager.getProperties(UserManager.getGuestUser(), container, "SpecimenRequestDisplay"); - return settingsMap.isEmpty() ? DisplaySettings.getDefaultSettings() : new DisplaySettings(settingsMap); - } - - public StatusSettings getStatusSettings(Container container) - { - Map settingsMap = PropertyManager.getProperties(UserManager.getGuestUser(), container, "SpecimenRequestStatus"); - return settingsMap.get(StatusSettings.KEY_USE_SHOPPING_CART) == null ? StatusSettings.getDefaultSettings() : new StatusSettings(settingsMap); - } - - public boolean isSpecimenShoppingCartEnabled(Container container) - { - return getStatusSettings(container).isUseShoppingCart(); - } - - public RepositorySettings getRepositorySettings(Container container) - { - Map settingsMap = PropertyManager.getProperties(UserManager.getGuestUser(), container, "SpecimenRepositorySettings"); - return settingsMap.isEmpty() ? RepositorySettings.getDefaultSettings(container) : new RepositorySettings(container, settingsMap); - } -} diff --git a/specimen/src/org/labkey/specimen/settings/StatusSettings.java b/specimen/src/org/labkey/specimen/settings/StatusSettings.java deleted file mode 100644 index 291da06d896..00000000000 --- a/specimen/src/org/labkey/specimen/settings/StatusSettings.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2009-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.settings; - -import java.util.Map; - -/* - * User: brittp - * Date: May 8, 2009 - * Time: 2:50:55 PM - */ - -public class StatusSettings -{ - public static final String KEY_USE_SHOPPING_CART = "UseShoppingCart"; - private boolean _useShoppingCart; - - public StatusSettings() - { - // no-arg constructor for struts reflection - } - - public StatusSettings(Map map) - { - String boolString = map.get(KEY_USE_SHOPPING_CART); - _useShoppingCart = Boolean.parseBoolean(boolString); - } - - public void populateMap(Map map) - { - map.put(KEY_USE_SHOPPING_CART, String.valueOf(_useShoppingCart)); - } - - public static StatusSettings getDefaultSettings() - { - StatusSettings defaults = new StatusSettings(); - defaults.setUseShoppingCart(true); - return defaults; - } - - public boolean isUseShoppingCart() - { - return _useShoppingCart; - } - - public void setUseShoppingCart(boolean useShoppingCart) - { - _useShoppingCart = useShoppingCart; - } -} \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/view/ManageSpecimenView.java b/specimen/src/org/labkey/specimen/view/ManageSpecimenView.java deleted file mode 100644 index d36a747c19c..00000000000 --- a/specimen/src/org/labkey/specimen/view/ManageSpecimenView.java +++ /dev/null @@ -1,26 +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.view; - -import org.labkey.api.view.JspView; - -public class ManageSpecimenView extends JspView -{ - public ManageSpecimenView() - { - super("/org/labkey/specimen/view/manageSpecimens.jsp"); - } -} diff --git a/specimen/src/org/labkey/specimen/view/NotificationBean.java b/specimen/src/org/labkey/specimen/view/NotificationBean.java deleted file mode 100644 index 06e00700f79..00000000000 --- a/specimen/src/org/labkey/specimen/view/NotificationBean.java +++ /dev/null @@ -1,137 +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.view; - -import org.jetbrains.annotations.NotNull; -import org.labkey.api.attachments.Attachment; -import org.labkey.api.data.Container; -import org.labkey.api.security.User; -import org.labkey.api.specimen.location.LocationManager; -import org.labkey.api.study.Location; -import org.labkey.api.study.SpecimenUrls; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.ViewContext; -import org.labkey.specimen.SpecimenRequestManager; -import org.labkey.specimen.SpecimenRequestStatus; -import org.labkey.specimen.model.SpecimenRequestEvent; -import org.labkey.specimen.notifications.DefaultRequestNotification; - -import java.util.List; - -public class NotificationBean -{ - private final DefaultRequestNotification _notification; - private final User _user; - private final String _specimenList; - private final String _studyName; - private final ActionURL _requestURL; - - private boolean _includeSpecimensInBody; - - public NotificationBean(ViewContext context, DefaultRequestNotification notification, String specimenList, String studyName) - { - _notification = notification; - _user = context.getUser(); - _specimenList = specimenList; - _studyName = studyName; - _requestURL = PageFlowUtil.urlProvider(SpecimenUrls.class).getManageRequestURL(context.getContainer(), getRequestId()); - - _includeSpecimensInBody = null != specimenList; - } - - public @NotNull List getAttachments() - { - return _notification.getAttachments(); - } - - public String getComments() - { - return _notification.getComments(); - } - - public int getRequestId() - { - return _notification.getSpecimenRequest().getRowId(); - } - - public String getModifyingUser() - { - return _user.getEmail(); - } - - public String getRequestingSiteName() - { - Location destLocation = LocationManager.get().getLocation(_notification.getSpecimenRequest().getContainer(), - _notification.getSpecimenRequest().getDestinationSiteId()); - if (destLocation != null) - return destLocation.getDisplayName(); - else - return null; - } - - public String getStatus() - { - SpecimenRequestStatus status = SpecimenRequestManager.get().getRequestStatus(_notification.getSpecimenRequest().getContainer(), - _notification.getSpecimenRequest().getStatusId()); - return status != null ? status.getLabel() : "Unknown"; - } - - public Container getContainer() - { - return _notification.getSpecimenRequest().getContainer(); - } - - public String getEventDescription() - { - return _notification.getEventSummary(); - } - - public String getRequestDescription() - { - return _notification.getSpecimenRequest().getComments(); - } - - public String getSpecimenList() - { - return _specimenList; - } - - public String getStudyName() - { - return _studyName; - } - - public ActionURL getRequestURL() - { - return _requestURL; - } - - public boolean getIncludeSpecimensInBody() - { - return _includeSpecimensInBody; - } - - public void setIncludeSpecimensInBody(boolean includeSpecimensInBody) - { - _includeSpecimensInBody = includeSpecimensInBody; - } - - public SpecimenRequestEvent getEvent() - { - return _notification.getEvent(); - } -} diff --git a/specimen/src/org/labkey/specimen/view/SpecimenReportWebPartFactory.java b/specimen/src/org/labkey/specimen/view/SpecimenReportWebPartFactory.java deleted file mode 100644 index 0754a8a903a..00000000000 --- a/specimen/src/org/labkey/specimen/view/SpecimenReportWebPartFactory.java +++ /dev/null @@ -1,89 +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.view; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.labkey.specimen.actions.ReportConfigurationBean; -import org.labkey.specimen.report.SpecimenVisitReportParameters; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.BaseWebPartFactory; -import org.labkey.api.view.HttpView; -import org.labkey.api.view.JspView; -import org.labkey.api.view.Portal; -import org.labkey.api.view.VBox; -import org.labkey.api.view.ViewContext; -import org.labkey.api.view.WebPartView; - -import java.util.Set; - -public class SpecimenReportWebPartFactory extends BaseWebPartFactory -{ - public static final String REPORT_TYPE_PARAMETER_NAME = "reportType"; - - public SpecimenReportWebPartFactory() - { - super("Specimen Report", true, true, LOCATION_BODY); - } - - @Override - public WebPartView getWebPartView(@NotNull ViewContext context, @NotNull Portal.WebPart part) - { - SpecimenVisitReportParameters factory = getFactory(context, part); - - JspView reportView = new JspView<>("/org/labkey/specimen/view/specimenVisitReport.jsp", factory); - WebPartView configView = new JspView<>("/org/labkey/specimen/view/autoReportList.jsp", new ReportConfigurationBean(factory, false, part.getIndex())); - ActionURL url = new ActionURL(factory.getAction(), context.getContainer()); - - VBox outer = new VBox(configView, reportView); - outer.setTitleHref(url); - outer.setFrame(WebPartView.FrameType.PORTAL); - outer.setTitle("Specimen Report: " + factory.getLabel()); - - return outer; - } - - private SpecimenVisitReportParameters getFactory(ViewContext context, Portal.WebPart part) - { - @Nullable String name = part.getPropertyMap().get(REPORT_TYPE_PARAMETER_NAME); - ReportConfigurationBean bean = new ReportConfigurationBean(context); - Set categories = bean.getCategories(); - - // First element of first category is the default (should be "TypeSummary") - SpecimenVisitReportParameters factory = bean.getFactories(categories.iterator().next()).getFirst(); - assert "TypeSummary".equals(factory.getReportType()); - - if (null == name) - return factory; - - for (String category : categories) - { - for (SpecimenVisitReportParameters candidate : bean.getFactories(category)) - { - if (candidate.getReportType().equals(name)) - return candidate; - } - } - - return factory; - } - - @Override - public HttpView getEditView(Portal.WebPart webPart, ViewContext context) - { - return new JspView<>("/org/labkey/specimen/view/customizeSpecimenReportWebPart.jsp", webPart); - } -} diff --git a/specimen/src/org/labkey/specimen/view/SpecimenRequestNotificationEmailTemplate.java b/specimen/src/org/labkey/specimen/view/SpecimenRequestNotificationEmailTemplate.java deleted file mode 100644 index 70d1edc283d..00000000000 --- a/specimen/src/org/labkey/specimen/view/SpecimenRequestNotificationEmailTemplate.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (c) 2013-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.view; - -import org.labkey.api.attachments.Attachment; -import org.labkey.api.data.Container; -import org.labkey.api.security.User; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.util.LinkBuilder; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.util.emailTemplate.EmailTemplate; -import org.labkey.specimen.actions.SpecimenController; -import org.labkey.specimen.settings.RequestNotificationSettings; -import org.labkey.specimen.settings.SettingsManager; - -import java.util.List; -import java.util.function.Function; - -public class SpecimenRequestNotificationEmailTemplate extends EmailTemplate -{ - public static final String NAME = "Specimen request notification"; - - protected static final String DEFAULT_SUBJECT = "^studyName^: ^subjectSuffix^"; - protected static final String DEFAULT_SENDER = "^userDisplayName^"; - - private String _subjectSuffix; - private NotificationBean _notification; - private User _originatingUser; - - private static String loadBody() - { - return - """ -
-
- Specimen request #^specimenRequestNumber^ was ^simpleStatus^ in ^studyName^. -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - ^attachments|^ -
Request Details
Specimen Request^specimenRequestNumber^
Destination^destinationLocation^
Status^status^
Modified by^modifiedBy^
Action^action^
Attachments%s
- ^comments|

Current Comments
%s

^ -

- ^requestDescription^ -

- ^specimenList^ - """; - } - - public SpecimenRequestNotificationEmailTemplate() - { - super(NAME, "Sent to users when a specimen request has been edited or inserted.", DEFAULT_SUBJECT, loadBody(), ContentType.HTML, Scope.SiteOrFolder); - setSenderName(DEFAULT_SENDER); - } - - /** - * Evaluates to null if there's no associated specimen request (as there would be in the admin preview mode), - * otherwise delegates to subclass to determine the actual value. - */ - private final class NotificationReplacement extends ReplacementParam - { - private final Function _valueGetter; - - public NotificationReplacement(String name, Class valueType, String description, Function valueGetter) - { - super(name, valueType, description, ContentType.Plain); - _valueGetter = valueGetter; - } - - public NotificationReplacement(String name, Class valueType, String description, ContentType contentType, Function valueGetter) - { - super(name, valueType, description, contentType); - _valueGetter = valueGetter; - } - - @Override - public Type getValue(Container c) - { - // No request available, so can't come up with an actual value - if (_notification == null) - { - return null; - } - - return _valueGetter.apply(c); - } - } - - public void init(NotificationBean notification) - { - _notification = notification; - - // For backwards compatibility, use the template specified in the notification management UI - RequestNotificationSettings settings = SettingsManager.get().getRequestNotificationSettings(notification.getContainer()); - - _subjectSuffix = settings.getSubjectSuffix().replaceAll("%requestId%", "" + notification.getRequestId()); - } - - public void setOriginatingUser(User user){_originatingUser = user;} - - @Override - protected void addCustomReplacements(Replacements replacements) - { - replacements.add("studyName", String.class, "The name of this folder's study", ContentType.Plain, c -> { - Study study = StudyService.get().getStudy(c); - return study == null ? "" : study.getLabel(); - }); - replacements.add("subjectSuffix", String.class, "Email subject suffix, configured in specimen request notification settings", ContentType.Plain, c -> _subjectSuffix); - replacements.add(new NotificationReplacement<>("specimenRequestNumber", Integer.class, "Unique, auto-incrementing number for this request", c -> _notification.getRequestId())); - replacements.add(new NotificationReplacement<>("destinationLocation", String.class, "The location to which the specimen is to be sent", c -> _notification.getRequestingSiteName())); - replacements.add(new NotificationReplacement<>("status", String.class, "The status of the request (submitted, approved, etc)", c -> _notification.getStatus())); - replacements.add(new NotificationReplacement<>("simpleStatus", String.class, "Either 'submitted' for brand new requests, or 'updated' for all other changes", c -> "submitted".equalsIgnoreCase(_notification.getStatus()) ? "submitted" : "updated")); - replacements.add(new NotificationReplacement<>("modifiedBy", String.class, "The user who created or modified the request", c -> _notification.getModifyingUser())); - replacements.add(new NotificationReplacement<>("action", String.class, "A description of what action was performed, such as 'New Request Created'", c -> _notification.getEventDescription())); - replacements.add(new NotificationReplacement<>("attachments", String.class, "A list of direct links to all the attachments associated with the request", ContentType.HTML, c -> { - List attachments = _notification.getAttachments(); - if (attachments.isEmpty()) - { - return null; - } - StringBuilder sb = new StringBuilder(); - for (Attachment att : attachments) - { - sb.append(LinkBuilder.simpleLink(att.getName(), SpecimenController.getDownloadURL(_notification.getEvent(), att.getName()))); - sb.append("
"); - } - return sb.toString(); - })); - replacements.add(new NotificationReplacement<>("specimenList", String.class, "A list of the specimens in the request, as configured in the Specimen Notification settings", ContentType.HTML, c -> { - if (_notification.getSpecimenList() != null) - { - StringBuilder sb = new StringBuilder("

"); - sb.append("Specimen List (Request Link)

\n"); - if (_notification.getIncludeSpecimensInBody()) - { - sb.append(_notification.getSpecimenList()); - } - sb.append("

"); - return sb.toString(); - } - return null; - })); - replacements.add(new NotificationReplacement<>("requestDescription", String.class, "Typically includes assay plan, shipping information, etc", c -> _notification.getRequestDescription())); - replacements.add(new NotificationReplacement<>("comments", String.class, "Text submitter wrote in Comments text box, or '[Not provided]'", c -> { - String comments = _notification.getComments(); - return comments == null ? "[Not provided]" : comments; - })); - replacements.add("userFirstName", String.class, "First name of the user performing the operation, only works when reply to current user", ContentType.Plain, c -> _originatingUser == null ? null : _originatingUser.getFirstName()); - replacements.add("userLastName", String.class, "Last name of the user performing the operation, only works when reply to current user", ContentType.Plain, c -> _originatingUser == null ? null : _originatingUser.getLastName()); - replacements.add("userDisplayName", String.class, "Display name of the user performing the operation, only works when reply to current user", ContentType.Plain, c -> _originatingUser == null ? null : _originatingUser.getFriendlyName()); - } -} diff --git a/specimen/src/org/labkey/specimen/view/SpecimenSearchWebPart.java b/specimen/src/org/labkey/specimen/view/SpecimenSearchWebPart.java deleted file mode 100644 index 4a95964f519..00000000000 --- a/specimen/src/org/labkey/specimen/view/SpecimenSearchWebPart.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2010-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.view; - -import org.labkey.specimen.actions.SpecimenSearchBean; -import org.labkey.api.view.JspView; - -/** - * User: brittp - * Date: Oct 21, 2010 1:39:16 PM - */ -public class SpecimenSearchWebPart extends JspView -{ - public SpecimenSearchWebPart(boolean showVials) - { - super("/org/labkey/specimen/view/search.jsp", new SpecimenSearchBean()); - getModelBean().init(getViewContext(), showVials, true); - getModelBean().setWebPartId(getWebPartRowId()); - setTitle(showVials ? "Vial Search" : "Vial Group Search"); - } -} diff --git a/specimen/src/org/labkey/specimen/view/SpecimenSearchWebPartFactory.java b/specimen/src/org/labkey/specimen/view/SpecimenSearchWebPartFactory.java deleted file mode 100644 index 324c85063ae..00000000000 --- a/specimen/src/org/labkey/specimen/view/SpecimenSearchWebPartFactory.java +++ /dev/null @@ -1,46 +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.view; - -import org.jetbrains.annotations.NotNull; -import org.labkey.api.security.permissions.ReadPermission; -import org.labkey.api.study.StudyService; -import org.labkey.api.util.HtmlString; -import org.labkey.api.view.DefaultWebPartFactory; -import org.labkey.api.view.HtmlView; -import org.labkey.api.view.Portal; -import org.labkey.api.view.ViewContext; -import org.labkey.api.view.WebPartView; - -public class SpecimenSearchWebPartFactory extends DefaultWebPartFactory -{ - public SpecimenSearchWebPartFactory(String position) - { - super("Specimen Search", SpecimenSearchWebPart.class, position); - addLegacyNames("Specimen Search (Experimental)"); - } - - @Override - public WebPartView getWebPartView(@NotNull ViewContext portalCtx, @NotNull Portal.WebPart webPart) - { - if (!portalCtx.hasPermission(ReadPermission.class)) - return new HtmlView("Specimens", HtmlString.of(portalCtx.getUser().isGuest() ? "Please log in to see this data." : "You do not have permission to see this data")); - - if (null == StudyService.get().getStudy(portalCtx.getContainer())) - return new HtmlView("Specimens", HtmlString.of("This folder does not contain a study.")); - return new SpecimenSearchWebPart(true); - } -} diff --git a/specimen/src/org/labkey/specimen/view/SpecimenToolsWebPartFactory.java b/specimen/src/org/labkey/specimen/view/SpecimenToolsWebPartFactory.java deleted file mode 100644 index a0b3a28ad3d..00000000000 --- a/specimen/src/org/labkey/specimen/view/SpecimenToolsWebPartFactory.java +++ /dev/null @@ -1,73 +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.view; - -import org.labkey.api.study.StudyUrls; -import org.labkey.api.study.security.permissions.ManageStudyPermission; -import org.labkey.api.study.view.StudyToolsWebPart; -import org.labkey.api.study.view.ToolsWebPartFactory; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.view.ActionURL; -import org.labkey.api.view.ViewContext; -import org.labkey.api.view.WebPartFactory; -import org.labkey.specimen.actions.ShowSearchAction; -import org.labkey.specimen.actions.SpecimenController.AutoReportListAction; -import org.labkey.specimen.actions.SpecimenController.ShowCreateSpecimenRequestAction; -import org.labkey.specimen.security.permissions.RequestSpecimensPermission; -import org.labkey.specimen.settings.SettingsManager; - -import java.util.ArrayList; -import java.util.List; - -import static org.labkey.api.specimen.SpecimensPage.SPECIMEN_TOOLS_WEBPART_NAME; - -public class SpecimenToolsWebPartFactory extends ToolsWebPartFactory -{ -// public static final String SPECIMEN_TOOLS_WEBPART_NAME = "Specimen Tools"; TODO: Remove from SpecimenPage and uncomment - - public SpecimenToolsWebPartFactory() - { - super(SPECIMEN_TOOLS_WEBPART_NAME, WebPartFactory.LOCATION_BODY, WebPartFactory.LOCATION_RIGHT); - } - - @Override - protected List getItems(ViewContext portalCtx) - { - String iconBase = portalCtx.getContextPath() + "/study/tools/"; - List items = new ArrayList<>(); - - ActionURL vialSearchURL = ShowSearchAction.getShowSearchURL(portalCtx.getContainer(), true); - items.add(new StudyToolsWebPart.Item("Vial Search", iconBase + "specimen_search.png", vialSearchURL)); - - if (SettingsManager.get().isSpecimenRequestEnabled(portalCtx.getContainer(), portalCtx.getUser())) - { - if (portalCtx.getContainer().hasPermission(portalCtx.getUser(), RequestSpecimensPermission.class)) - items.add(new StudyToolsWebPart.Item("New Request", iconBase + "specimen_request.png", new ActionURL(ShowCreateSpecimenRequestAction.class, portalCtx.getContainer()))); - } - items.add(new StudyToolsWebPart.Item("Specimen Reports", iconBase + "specimen_report.png", new ActionURL(AutoReportListAction.class, portalCtx.getContainer()))); - - if (portalCtx.getContainer().hasPermission(portalCtx.getUser(), ManageStudyPermission.class)) - items.add(new StudyToolsWebPart.Item("Settings", iconBase + "settings.png", PageFlowUtil.urlProvider(StudyUrls.class).getManageStudyURL(portalCtx.getContainer()))); - - return items; - } - - @Override - protected String getTitle() - { - return SPECIMEN_TOOLS_WEBPART_NAME; - } -} diff --git a/specimen/src/org/labkey/specimen/view/SpecimenWebPart.java b/specimen/src/org/labkey/specimen/view/SpecimenWebPart.java deleted file mode 100644 index 100e9d757e3..00000000000 --- a/specimen/src/org/labkey/specimen/view/SpecimenWebPart.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2014-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.view; - -import org.labkey.api.study.Study; -import org.labkey.api.view.JspView; -import org.labkey.specimen.settings.SettingsManager; - -public class SpecimenWebPart extends JspView -{ - @SuppressWarnings("unused") // Invoked via reflection - public SpecimenWebPart() - { - this(false, null); - } - - public SpecimenWebPart(boolean wide, Study study) - { - super("/org/labkey/specimen/view/webPart.jsp", new SpecimenWebPartBean(wide, - (null != study) && SettingsManager.get().getRepositorySettings(study.getContainer()).isSpecimenDataEditable())); - setTitle("Specimens"); - } - - public static class SpecimenWebPartBean - { - private final boolean _wide; - private final boolean _isEditableSpecimens; - - public SpecimenWebPartBean(boolean wide, boolean isEditableSpecimens) - { - _wide = wide; - _isEditableSpecimens = isEditableSpecimens; - } - - public boolean isWide() - { - return _wide; - } - - public boolean isEditableSpecimens() - { - return _isEditableSpecimens; - } - } -} diff --git a/specimen/src/org/labkey/specimen/view/SpecimenWebPartFactory.java b/specimen/src/org/labkey/specimen/view/SpecimenWebPartFactory.java deleted file mode 100644 index 8863d0e1378..00000000000 --- a/specimen/src/org/labkey/specimen/view/SpecimenWebPartFactory.java +++ /dev/null @@ -1,50 +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.view; - -import org.jetbrains.annotations.NotNull; -import org.labkey.api.security.permissions.ReadPermission; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.util.HtmlString; -import org.labkey.api.view.DefaultWebPartFactory; -import org.labkey.api.view.HtmlView; -import org.labkey.api.view.HttpView; -import org.labkey.api.view.Portal; -import org.labkey.api.view.ViewContext; -import org.labkey.api.view.WebPartFactory; -import org.labkey.api.view.WebPartView; - -public class SpecimenWebPartFactory extends DefaultWebPartFactory -{ - public SpecimenWebPartFactory() - { - super("Specimens", SpecimenWebPart.class, WebPartFactory.LOCATION_BODY, WebPartFactory.LOCATION_RIGHT); - addLegacyNames("Specimen Browse (Experimental)"); - } - - @Override - public WebPartView getWebPartView(@NotNull ViewContext portalCtx, @NotNull Portal.WebPart webPart) - { - if (!portalCtx.hasPermission(ReadPermission.class)) - return new HtmlView("Specimens", HtmlString.of(portalCtx.getUser().isGuest() ? "Please log in to see this data." : "You do not have permission to see this data")); - - Study study = StudyService.get().getStudy(portalCtx.getContainer()); - if (null == study) - return new HtmlView("Specimens", HtmlString.of("This folder does not contain a study.")); - return new SpecimenWebPart(webPart.getLocation().equals(HttpView.BODY), study); - } -} diff --git a/specimen/src/org/labkey/specimen/view/autoReportList.jsp b/specimen/src/org/labkey/specimen/view/autoReportList.jsp deleted file mode 100644 index 21b0c536888..00000000000 --- a/specimen/src/org/labkey/specimen/view/autoReportList.jsp +++ /dev/null @@ -1,347 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.data.AbstractParticipantCategory"%> -<%@ page import="org.labkey.api.data.AbstractParticipantGroup" %> -<%@ page import="org.labkey.api.data.Container" %> -<%@ page import="org.labkey.api.query.CustomView" %> -<%@ page import="org.labkey.api.security.User" %> -<%@ page import="org.labkey.api.study.Cohort" %> -<%@ page import="org.labkey.api.study.CohortFilter" %> -<%@ page import="org.labkey.api.study.Params" %> -<%@ page import="org.labkey.api.study.Study" %> -<%@ page import="org.labkey.api.study.StudyService" %> -<%@ page import="org.labkey.api.study.model.CohortService" %> -<%@ page import="org.labkey.api.study.model.ParticipantGroupService" %> -<%@ page import="org.labkey.api.util.HtmlString" %> -<%@ page import="org.labkey.api.util.Pair" %> -<%@ page import="org.labkey.api.view.ActionURL" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.template.FrameFactoryClassic" %> -<%@ page import="org.labkey.specimen.actions.ReportConfigurationBean" %> -<%@ page import="org.labkey.specimen.report.SpecimenVisitReportParameters" %> -<%@ page import="java.util.Collection" %> -<%@ page import="java.util.List" %> -<%@ page import="java.util.Map" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - JspView me = HttpView.currentView(); - ReportConfigurationBean bean = me.getModelBean(); - Container container = getContainer(); - User user = getUser(); - boolean showCohorts = StudyService.get().showCohorts(container, user); - Study study = StudyService.get().getStudy(container); - Collection cohorts = null; - if (showCohorts) - cohorts = CohortService.get().getCohorts(container, user); - HtmlString optionLabelStyle = HtmlString.unsafe("text-align: left; padding: 5px 5px 0 5px;"); - Map views = bean.getCustomViews(getViewContext()); - - List categories = ParticipantGroupService.get().getParticipantCategories(container, user); - boolean showParticipantGroups = categories != null && !categories.isEmpty(); -%> -<% - if (study == null) - { -%> -This folder does not contain a study. -<% - } - else - { -%> - -<% - int categoryIndex = 0; - for (String category : bean.getCategories()) - { - categoryIndex++; -%> -<% if (bean.isListView()) - FrameFactoryClassic.startTitleFrame(out, category, null, "100%", null); %> - -<% - int formRowIndex = 0; - HtmlString rowClass; - for (SpecimenVisitReportParameters factory : bean.getFactories(category)) - { - rowClass = getShadeRowClass(formRowIndex++); - String showHideSuffix = "_" + categoryIndex + "_" + formRowIndex + "_" + bean.getUniqueId(); - String formName = "form" + showHideSuffix; -%> - -<% - } -%> -
- - <% - if (bean.isListView()) - { - %> -
- - - - - -
<%= h(factory.getLabel())%> - <%=link("show options").onClick("return showOrHide('" + showHideSuffix + "')").id("showOptionsLink" + showHideSuffix)%> - <%= button("View").submit(true) %> -
-
- <% - } - %> -
-
- ; padding: 10px 0;"> - - <% - if (showCohorts && factory.allowsCohortFilter()) - { - CohortFilter.Type selectedCohortType = CohortFilter.Type.PTID_CURRENT; - int selectedCohortId = -1; - if (factory.getCohortFilter() != null) - { - selectedCohortType = factory.getCohortFilter().getType(); - selectedCohortId = factory.getCohortFilter().getCohortId(); - } - %> - - - - - <% - if (study.isAdvancedCohorts()) - { - %> - - - - - <% - } - else - { - %> - - <% - } - } - - if (showParticipantGroups && factory.allowsParticipantGroupFilter()) - { - %> - - - - - <% - } - if (factory.allowsAvailabilityFilter()) - { - %> - - - - - <% - } - if (factory.allowsCustomViewFilter()) - { - HtmlString viewPickerHtml = factory.getCustomViewPicker(views); - %> - - - - - <% - } - - // AdditionalFormInputs values are html generated in classes that extend SpecimenVisitReportParameters - List> additionalFormInputs = factory.getAdditionalFormInputHtml(user); - for (Pair inputPair : additionalFormInputs) - { - %> - - - - - <% - } - boolean atLeastOneChecked = factory.isViewVialCount() || - factory.isViewParticipantCount() || - factory.isViewVolume() || - factory.isViewPtidList(); - %> - - - - - <% if (!bean.isListView()) - { - %> - - - - <% - } - %> -
Cohort filter - -
Cohort filter type - -
<%= h(study.getSubjectNounSingular()) %> Group - -
Availability status - -
Base - <%= viewPickerHtml %> -
<%= h(inputPair.getKey()) %><%= inputPair.getValue() %>
  - -
- -
- -
- <% - if (factory.allowsParticipantAggregates()) - { - %> - -
- - <% - } - %> -
- <%= button("Refresh").submit(true).onClick("document['" + formName + "']['excelExport'].value=false;") %> - <%= button("Print").submit(true).onClick("document['" + formName + "']['_print'].value=1; document['" + formName + "']['excelExport'].value=false;") %> - <%= bean.hasReports() ? button("Export to Excel").submit(true).onClick("document['" + formName + "']['excelExport'].value=true;") : - button("Export to Excel").submit(true).onClick("document['" + formName + "']['excelExport'].value=true;").enabled(false) %> -
-
-
-
- - -
-
-<% - if (bean.isListView()) - FrameFactoryClassic.endTitleFrame(out); - } - } -%> diff --git a/specimen/src/org/labkey/specimen/view/chooseImporter.jsp b/specimen/src/org/labkey/specimen/view/chooseImporter.jsp deleted file mode 100644 index 5798fe92fb2..00000000000 --- a/specimen/src/org/labkey/specimen/view/chooseImporter.jsp +++ /dev/null @@ -1,156 +0,0 @@ -<% -/* - * Copyright (c) 2020-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. - */ -%> -<%@ page import="com.google.common.collect.Iterables" %> -<%@ page import="org.labkey.api.data.Container" %> -<%@ page import="org.labkey.api.security.User" %> -<%@ page import="org.labkey.api.study.SpecimenService" %> -<%@ page import="org.labkey.api.study.SpecimenTransform" %> -<%@ page import="org.labkey.api.study.StudyUrls" %> -<%@ page import="org.labkey.api.util.ButtonBuilder" %> -<%@ page import="org.labkey.api.util.HtmlString" %> -<%@ page import="org.labkey.api.util.URLHelper" %> -<%@ page import="org.labkey.api.view.ActionURL" %> -<%@ page import="org.labkey.specimen.actions.ShowUploadSpecimensAction" %> -<%@ page import="java.util.Collection" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<% - Container c = getContainer(); - User user = getUser(); - Collection specimenTransforms = SpecimenService.get().getSpecimenTransforms(c); - specimenTransforms.removeIf(transform -> null == transform.getManageAction(c, user)); - - URLHelper cancelLink = getActionURL().getReturnUrl(); - if (cancelLink == null) - cancelLink = urlProvider(StudyUrls.class).getManageStudyURL(getContainer()); - int numberOfTransforms = specimenTransforms.size(); - int rowNumber = 0; - - String selected = SpecimenService.get().getActiveSpecimenImporter(c); - HtmlString manuallyImportSpecimensLink = h(urlFor(ShowUploadSpecimensAction.class)); -%> - - - - - -<% - // At the moment, there's exactly one SpecimenTransform, QueryBasedSpecimenTransform, which is provided by the - // Specimen module. As a result, this "choose importer" page is never linked (if a single transform exists, the - // "Configure Specimen Import" link navigates straight its configuration page. This probably won't ever change, - // but we'll leave this page in place just in case. -%> -
- <% if (numberOfTransforms > 1) { %> -

- Activate automatic import of specimen data from an external source on this page. In order to prevent automated reloads from overwriting specimen data upon manual or scheduled imports, only one specimen import mechanism may be active at a time for a given container. -

- <% - } - %> -

- Learn more about <%=helpLink("externalSpecimens", "Automated External Specimen Imports")%>. -

- -
- - -

Configure Specimen Import

- - <% if (numberOfTransforms > 1) { %> - - - - - - - - - <% - for (SpecimenTransform transform : specimenTransforms) - { - ActionURL manageAction = transform.getManageAction(c, user); - HtmlString transformName = h(transform.getName()); - - %> - - - - - - <% - } - %> -
NameActive
<%=transformName%> -
- - > -
-
<%=link("configure", manageAction)%>
- -
- - <%= new ButtonBuilder("Save") - .submit(true) - .build() - %> - - <%= new ButtonBuilder("Cancel") - .href(cancelLink) - .build() - %> -
- - <% } else if (numberOfTransforms == 1) { - SpecimenTransform transform = Iterables.get(specimenTransforms, 0); - ActionURL manageAction = transform.getManageAction(c, user); - %> - -
- - - - - - -
<%=h(transform.getName())%><%=link("configure", manageAction)%>
- -

- - <%= new ButtonBuilder("Done") - .href(cancelLink) - .build() - %> - - <% } else { %> - - > Import specimens manually - <% - } - %> -
-
diff --git a/specimen/src/org/labkey/specimen/view/configRequestabilityRules.jsp b/specimen/src/org/labkey/specimen/view/configRequestabilityRules.jsp deleted file mode 100644 index 0c847291566..00000000000 --- a/specimen/src/org/labkey/specimen/view/configRequestabilityRules.jsp +++ /dev/null @@ -1,662 +0,0 @@ -<% -/* - * Copyright (c) 2010-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. - */ -%> -<%@ page import="org.labkey.api.data.Container"%> -<%@ page import="org.labkey.api.study.StudyUrls"%> -<%@ page import="org.labkey.api.view.ActionURL"%> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> -<%@ page import="org.labkey.specimen.importer.RequestabilityManager" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%! - @Override - public void addClientDependencies(ClientDependencies dependencies) - { - dependencies.add("Ext4"); - } -%> -<% - // TODO: This should use Selector.js - Container c = getContainer(); -%> - - - - - - - - - - - - - - - -
Requestability Rule Configuration
- Whether a given vial is requestable is determined by running a series of configurable rules. Each - rule may change the requestability state of any vial(s). Rules are run in order, so a vial's final state will be - determined by the last rule to affect that vial.

- Note: if present, the <%= h(RequestabilityManager.RuleType.LOCKED_IN_REQUEST.getName()) %> - ensures that a single vial can never be part of two simultaneous requests. - -
 
- -
- - - - - - - -<% - for (RequestabilityManager.RuleType type : RequestabilityManager.RuleType.values()) - { -%> - - - - -<% - } -%> -
Available Rule Types
<%= h(type.getName())%>  <%= h(type.getDescription())%>
diff --git a/specimen/src/org/labkey/specimen/view/configurationRequired.jsp b/specimen/src/org/labkey/specimen/view/configurationRequired.jsp deleted file mode 100644 index 19aaf5f7431..00000000000 --- a/specimen/src/org/labkey/specimen/view/configurationRequired.jsp +++ /dev/null @@ -1,20 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -Specimen management is not configured for this study.

-A project administrator must first configure request statuses and actors before this action can be performed. diff --git a/specimen/src/org/labkey/specimen/view/configureQueryImport.jsp b/specimen/src/org/labkey/specimen/view/configureQueryImport.jsp deleted file mode 100644 index f3f60e80c41..00000000000 --- a/specimen/src/org/labkey/specimen/view/configureQueryImport.jsp +++ /dev/null @@ -1,143 +0,0 @@ -<% -/* - * Copyright (c) 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. - */ -%> -<%@ page import="org.labkey.api.data.Container" %> -<%@ page import="org.labkey.api.data.PropertyManager" %> -<%@ page import="org.labkey.api.pipeline.PipelineStatusUrls" %> -<%@ page import="org.labkey.api.study.SpecimenService" %> -<%@ page import="org.labkey.api.study.StudyUrls" %> -<%@ page import="org.labkey.api.util.ButtonBuilder" %> -<%@ page import="org.labkey.api.util.InputBuilder" %> -<%@ page import="org.labkey.api.util.InputBuilder.Input" %> -<%@ page import="org.labkey.api.util.PageFlowUtil" %> -<%@ page import="org.labkey.api.util.SelectBuilder" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ReloadQueryBasedImportAction" %> -<%@ page import="org.labkey.specimen.importer.QueryBasedSpecimenTransform" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> - -<% - Container c = getContainer(); - String selected = SpecimenService.get().getActiveSpecimenImporter(c); - boolean active = selected == null || selected.equals(QueryBasedSpecimenTransform.NAME); - String QBSpecimenImportKey = QueryBasedSpecimenTransform.PROPERTY_MAP_KEY; - - PropertyManager.PropertyMap props = PropertyManager.getProperties(c, QBSpecimenImportKey); - - String schemaName = props.get("schemaName"); - String queryName = props.get("queryName"); - String viewName = props.get("viewName"); - boolean enabled = ("on").equals(props.get("enabled")); -%> - - - - - -
-

- Specimen data can be automatically loaded from an existing query using the specimen import configuration - defined on this page. The schema you specify must already be defined as an external schema, and the query - and view selected must map and filter rows as expected by the LabKey specimen repository. -

- -

- Learn more about configuring <%=helpLink("querySpecimenImport", "Query-based specimen reload")%>. -

- -
- - -

Configure connection

- -
- <%=InputBuilder.checkbox() - .name("enabled") - .label("Enable reload") - .layout(Input.Layout.HORIZONTAL) - .checked(enabled) - .formGroup(true) - %> - - <%= new SelectBuilder() - .name("schemaName") - .id("schemaName") - .label("Schema") - .layout(Input.Layout.HORIZONTAL) - .formGroup(true) - %> - - <%= new SelectBuilder() - .name("queryName") - .id("queryName") - .label("Query") - .layout(Input.Layout.HORIZONTAL) - .formGroup(true) - %> - - <%= new SelectBuilder() - .name("viewName") - .id("viewName") - .label("View") - .layout(Input.Layout.HORIZONTAL) - .formGroup(true) - %> -
-
- - <%= new ButtonBuilder("Cancel") - .href(PageFlowUtil.urlProvider(StudyUrls.class).getManageStudyURL(getContainer())) - .build() - %> - - <%= new ButtonBuilder("Save") - .submit(true) - .build() - %> - - <%= new ButtonBuilder("Reload Now") - .id("reloadNow") - .enabled(enabled && active) - .onClick("reloadNow();") - .build() - %> -
- - -
diff --git a/specimen/src/org/labkey/specimen/view/customizeSpecimenReportWebPart.jsp b/specimen/src/org/labkey/specimen/view/customizeSpecimenReportWebPart.jsp deleted file mode 100644 index b76ecc0ff3b..00000000000 --- a/specimen/src/org/labkey/specimen/view/customizeSpecimenReportWebPart.jsp +++ /dev/null @@ -1,71 +0,0 @@ -<% -/* - * Copyright (c) 2014-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. - */ -%> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.Portal.WebPart" %> -<%@ page import="org.labkey.specimen.actions.ReportConfigurationBean" %> -<%@ page import="org.labkey.specimen.report.SpecimenVisitReportParameters" %> -<%@ page import="org.labkey.specimen.view.SpecimenReportWebPartFactory" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - JspView me = HttpView.currentView(); - WebPart webpart = me.getModelBean(); - String currentReportName = webpart.getPropertyMap().get(SpecimenReportWebPartFactory.REPORT_TYPE_PARAMETER_NAME); - - ReportConfigurationBean bean = new ReportConfigurationBean(getViewContext()); -%> - - - - - - - - - - - -
- Choose the specimen report to display in this webpart
-
- Type - -
- - - - -
- <%= button("Submit").submit(true) %> - <%= button("Cancel").href(getContainer().getStartURL(getUser())) %> -
-
-
diff --git a/specimen/src/org/labkey/specimen/view/groupMembers.jsp b/specimen/src/org/labkey/specimen/view/groupMembers.jsp deleted file mode 100644 index 4b3d044b9c8..00000000000 --- a/specimen/src/org/labkey/specimen/view/groupMembers.jsp +++ /dev/null @@ -1,86 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.security.AuthenticationManager" %> -<%@ page import="org.labkey.api.security.User" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.specimen.actions.ShowGroupMembersAction" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<% - JspView me = HttpView.currentView(); - ShowGroupMembersAction.GroupMembersBean bean = me.getModelBean(); -%> - - - - <%=generateReturnUrlFormField(bean.getReturnUrl())%> -<% - if (bean.getMembers() == null || bean.getMembers().length <= 0) - { - %>

This group currently has no members.

<% - } - else - { -%> -
- Group members -
- - - - - - <% - for (User member : bean.getMembers()) - { - %> - - - - - <% - } - %> -
RemoveEmail
<%= unsafe(member.isActive() ? "" : "")%><%=h(member.getAutocompleteName(getContainer(), getUser()))%><%=unsafe(member.isActive() ? "" : " (inactive)") %>
-
- <% - } - %> -
- -
- Add New Members (enter one email address per line):
-
- <%=AuthenticationManager.getStandardSendVerificationEmailsMessage()%>

- - - <% - if (bean.getLocation() != null) - { - %> - - <% - } - %> - <%= button("Update Members").submit(true) %> -
-
-
\ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/view/importSpecimens.jsp b/specimen/src/org/labkey/specimen/view/importSpecimens.jsp deleted file mode 100644 index 2773cbc5b90..00000000000 --- a/specimen/src/org/labkey/specimen/view/importSpecimens.jsp +++ /dev/null @@ -1,123 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page import="org.labkey.api.view.HttpView"%> -<%@ page import="org.labkey.api.view.JspView"%> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ImportSpecimensBean"%> -<%@ page import="org.labkey.specimen.actions.SpecimenController.SubmitSpecimenBatchImportAction" %> -<%@ page import="org.labkey.specimen.pipeline.SpecimenArchive" %> -<%@ page import="java.util.List" %> -<%@ page import="java.util.zip.ZipException" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - JspView me = HttpView.currentView(); - ImportSpecimensBean bean = me.getModelBean(); - boolean hasError = !bean.getErrors().isEmpty(); - int archiveCount = bean.getArchives().size(); - String replaceOrMerge = bean.isDefaultMerge() ? "merge" : "replace"; - String mergeText = bean.isEditableSpecimens() ? "Insert new specimens. An error will result if any existing specimens are in the archive." : "Insert new specimens and update existing specimens."; - String replaceText = bean.isEditableSpecimens() ? "Replace all of the existing specimens. All edits will be lost." : "Replace all of the existing specimens."; -%> -<%= archiveCount %> specimen archive<%= unsafe(archiveCount > 1 ? "s" : "") %> selected.

-<% - for (SpecimenArchive archive : bean.getArchives()) - { - String archiveName = archive.getDefinitionFile().getName(); - try - { - List entries = archive.getEntryDescriptions(getContainer()); -%> - Specimen archive <%= h(archiveName) %> contains the following files:

- - - <% - int row = 0; - for (SpecimenArchive.EntryDescription entry : entries) - { - %> - - - - - - <% - } - %> -
FileSizeModified
<%= h(entry.getName()) %><%= entry.getSize() == 0 ? 0L : Math.max(1, entry.getSize() / 1000) %> kb<%=formatDateTime(entry.getDate())%>

-<% - } - catch (ZipException z) - { - hasError = true; -%> -

Specimen archive <%=h(archiveName)%> is corrupt and cannot be read.


-<% - } - } -%> - -
- <% - if (hasError) - { - for (String error : bean.getErrors()) - { - %> -
<%= h(error) %> - <% - } - } - else - { -%> - - - <% - for (String file : bean.getFiles()) - { - %> - - <% - } - %> - <% - if (!bean.isNoSpecimens()) - { - %> - - -
- - - <% - } - else - { - %> - - <% - } - %> -

- - <%= button("Start Import").submit(true) %> - -<% - } - %> -

diff --git a/specimen/src/org/labkey/specimen/view/labSpecimenLists.jsp b/specimen/src/org/labkey/specimen/view/labSpecimenLists.jsp deleted file mode 100644 index a592622c68a..00000000000 --- a/specimen/src/org/labkey/specimen/view/labSpecimenLists.jsp +++ /dev/null @@ -1,198 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.specimen.Vial"%> -<%@ page import="org.labkey.api.specimen.location.LocationImpl"%> -<%@ page import="org.labkey.api.view.ActionURL"%> -<%@ page import="org.labkey.api.view.HttpView"%> -<%@ page import="org.labkey.api.view.JspView"%> -<%@ page import="org.labkey.specimen.actions.SpecimenController" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.DownloadSpecimenListAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.EmailLabSpecimenListsAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.LabSpecimenListsBean" %> -<%@ page import="org.labkey.specimen.notifications.ActorNotificationRecipientSet" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - JspView me = HttpView.currentView(); - LabSpecimenListsBean bean = me.getModelBean(); - boolean originating = bean.getType() == LabSpecimenListsBean.Type.ORIGINATING; -%> - - - - - - <% - if (!bean.isRequirementsComplete()) - { - %> - - - - <% - } - %> - - - - - - - <% - int rowCount = 0; - for (LocationImpl location : bean.getLabs()) - { - ActionURL downloadURL = urlFor(DownloadSpecimenListAction.class) - .addParameter("id", bean.getSpecimenRequest().getRowId()) - .addParameter("destSiteId", bean.getSpecimenRequest().getDestinationSiteId()) - .addParameter("listType", bean.getType().toString()) - .addParameter("sourceSiteId", location.getRowId()); - %> - - - - - - - <% - } - %> -
- - WARNING: The requirements for this request are incomplete. - -
<%= unsafe(originating ? "Originating" : "Providing") %> LocationDownload optionsSpecimen IDsEmail Recipients
<%= h(location.getDisplayName()) %> - - - - - - - -
<%=link("Export to Excel", downloadURL.replaceParameter("export", "xls"))%>
<%=link("Export to text file", downloadURL.replaceParameter("export", "tsv")) %>
-
-
- - <% - for (Vial vial : bean.getSpecimens(location)) - { - %> - - - - <% - } - %> -
<%= h(vial.getGlobalUniqueId()) %>
-
- - <% - boolean hasInactiveEmailAddress = false; - for (ActorNotificationRecipientSet possibleNotification : bean.getPossibleNotifications()) - { - LocationImpl notifyLocation = possibleNotification.getLocation(); - if (notifyLocation != null && - notifyLocation.getRowId() != bean.getSpecimenRequest().getDestinationSiteId() && - notifyLocation.getRowId() != location.getRowId()) - continue; - - boolean hasEmailAddresses = possibleNotification.getAllEmailAddresses().length > 0; - if (hasEmailAddresses) - hasInactiveEmailAddress |= possibleNotification.hasInactiveEmailAddress(); - - %> - - - - - - <% - } - if (hasInactiveEmailAddress) - { - %> - - - - - <% - } - %> -
- > - - <%=possibleNotification.getHtmlDescriptionAndLink(hasEmailAddresses, getActionURL())%> - - <%= unsafe(notifyLocation != null && notifyLocation.getRowId() == bean.getSpecimenRequest().getDestinationSiteId() ? "Requesting Location " : "") %> - <%= unsafe(notifyLocation != null && notifyLocation.getRowId() == location.getRowId() ? bean.getType().getDisplay() + " Location" : "") %> -
- - - Include inactive users
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  - Any comments will be included in all notifications that are sent. Additionally, they
- will appear in the request history. -
Comments
  - Selecting an attachment format will automatically attach appropriate specimen lists to
- any notifications. It is not necessary to attach the lists manually.
Attachment Format(s) - Excel
- Text
-
  - Supporting documents are optional. If added, they will be available to all notification
- recipients, as well as in the request history.
Supporting
Documents
-
-
-
-
- -
 <%= button("Send Email").submit(true) %> <%= button("Cancel").href(SpecimenController.getManageRequestURL(getContainer(), bean.getSpecimenRequest().getRowId(), null))%>
-
\ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/view/manageActorOrder.jsp b/specimen/src/org/labkey/specimen/view/manageActorOrder.jsp deleted file mode 100644 index 7e006c8d70c..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageActorOrder.jsp +++ /dev/null @@ -1,104 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageActorOrderAction"%> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageActorsAction"%> -<%@ page import="org.labkey.specimen.model.SpecimenRequestActor" %> -<%@ page import="org.labkey.specimen.requirements.SpecimenRequestRequirementProvider" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - SpecimenRequestActor[] actors = SpecimenRequestRequirementProvider.get().getActors(getContainer()); -%> - - - - - - - - - -
- <% - %> - - - <%= button("Move Up").submit(true).onClick("return orderModule(0)") %>

- <%= button("Move Down").submit(true).onClick("return orderModule(1)") %> -
- - <%= button("Save").submit(true) %> <%= button("Cancel").href(urlFor(ManageActorsAction.class)) %> -
diff --git a/specimen/src/org/labkey/specimen/view/manageActors.jsp b/specimen/src/org/labkey/specimen/view/manageActors.jsp deleted file mode 100644 index d9ff828533f..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageActors.jsp +++ /dev/null @@ -1,143 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.specimen.location.LocationImpl" %> -<%@ page import="org.labkey.api.specimen.location.LocationManager" %> -<%@ page import="org.labkey.api.study.Study" %> -<%@ page import="org.labkey.api.study.StudyUrls" %> -<%@ page import="org.labkey.api.view.ActionURL"%> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.specimen.actions.ShowGroupMembersAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.DeleteActorAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageActorOrderAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageActorsAction" %> -<%@ page import="org.labkey.specimen.model.SpecimenRequestActor" %> -<%@ page import="org.labkey.specimen.requirements.SpecimenRequestRequirementProvider" %> -<%@ page import="java.util.Set" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - JspView me = HttpView.currentView(); - Study study = me.getModelBean(); - SpecimenRequestActor[] actors = SpecimenRequestRequirementProvider.get().getActors(getContainer()); - String showMemberSitesIdStr = request.getParameter("showMemberSites"); - int showMemberSitesId = -1; - Set inUseActorIds = SpecimenRequestRequirementProvider.get().getActorsInUseSet(getContainer()); - if (showMemberSitesIdStr != null && !showMemberSitesIdStr.isEmpty()) - { - try - { - showMemberSitesId = Integer.parseInt(showMemberSitesIdStr); - } - catch (NumberFormatException e) - { - // fall through - } - } -%> - - - - - - - - - - <% - if (actors != null && actors.length > 0) - { - for (SpecimenRequestActor actor : actors) - { - ActionURL updateMembersLink = null; - if (actor.isPerSite()) - { - if (showMemberSitesId != actor.getRowId()) - updateMembersLink = urlFor(ManageActorsAction.class).addParameter("showMemberSites", actor.getRowId()); - } - else - updateMembersLink = ShowGroupMembersAction.getShowGroupMembersURL(getContainer(), actor.getRowId(), null, null); - %> - - - - - - - <% - } - } - %> - - - - - - - - - - -
 Actor NameActor Type 
  - - - <%=h(actor.isPerSite() ? "Multiple Per Study (Location Affiliated)" : "One Per Study")%> - <% - if (showMemberSitesId == actor.getRowId()) - { - if (!study.getLocations().isEmpty()) - { - %> - Choose Site:
- <% - for (LocationImpl location : LocationManager.get().getLocations(study.getContainer())) - { - %><%= h(location.getDisplayName()) %>
<% - } - } - else - { - %> - No Sites Configured - <% - } - } - else - { - %><%= link("Update Members", updateMembersLink) %><% - if (!inUseActorIds.contains(actor.getRowId())) - { - %><%=link("Delete").onClick("return LABKEY.Utils.confirmAndPost('Deleting this actor will delete all information about its membership. All member emails will need to be entered again if you recreate this actor.', '" + h(urlFor(DeleteActorAction.class).addParameter("id", actor.getRowId())) + "')") %><% - } - } - %> -
New Actor: - -
  - <%= button("Save").submit(true) %>  - <%= button("Done").submit(true).onClick("document.manageActors.nextPage.value=''; return true;") %> - <%= button("Cancel").href(urlProvider(StudyUrls.class).getManageStudyURL(study.getContainer())) %>  - <%= button("Change Order").submit(true).onClick("document.manageActors.nextPage.value=" + q(urlFor(ManageActorOrderAction.class)) + "; return true;") %> - -  
-
\ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/view/manageComments.jsp b/specimen/src/org/labkey/specimen/view/manageComments.jsp deleted file mode 100644 index e449f2ee6b6..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageComments.jsp +++ /dev/null @@ -1,215 +0,0 @@ -<% -/* - * Copyright (c) 2009-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. - */ -%> -<%@ page import="org.labkey.api.exp.OntologyManager" %> -<%@ page import="org.labkey.api.exp.PropertyDescriptor" %> -<%@ page import="org.labkey.api.exp.PropertyType" %> -<%@ page import="org.labkey.api.study.Dataset" %> -<%@ page import="org.labkey.api.study.Study" %> -<%@ page import="org.labkey.api.study.StudyInternalService" %> -<%@ page import="org.labkey.api.study.StudyService" %> -<%@ page import="org.labkey.api.study.StudyUrls" %> -<%@ page import="org.labkey.api.study.TimepointType" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageCommentsForm" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageSpecimenCommentsAction" %> -<%@ page import="java.util.Collections" %> -<%@ page import="java.util.List" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<% - JspView me = HttpView.currentView(); - ManageCommentsForm bean = me.getModelBean(); - - Study study = StudyService.get().getStudy(getContainer()); - - List datasets = StudyInternalService.get().getDatasets(study); - - List ptidDescriptors = Collections.emptyList(); - List ptidVisitDescriptors = Collections.emptyList(); - Integer participantCommentDatasetId = bean.getParticipantCommentDatasetId(); - Integer participantVisitCommentDatasetId = bean.getParticipantVisitCommentDatasetId(); - - if (participantCommentDatasetId != null && participantCommentDatasetId >= 0) - { - Dataset dataset = StudyService.get().getDataset(study.getContainer(), participantCommentDatasetId); - if (dataset != null) - ptidDescriptors = OntologyManager.getPropertiesForType(dataset.getTypeURI(), study.getContainer()); - } - - if (study.getTimepointType() != TimepointType.CONTINUOUS) - { - if (participantVisitCommentDatasetId != null && participantVisitCommentDatasetId >= 0) - { - Dataset dataset = StudyService.get().getDataset(study.getContainer(), participantVisitCommentDatasetId); - if (dataset != null) - ptidVisitDescriptors = OntologyManager.getPropertiesForType(dataset.getTypeURI(), study.getContainer()); - } - } - - String subjectNounSingle = StudyService.get().getSubjectNounSingular(getContainer()); -%> - - - - - - - - - - - - -
Note: Only users with read access to the selected dataset(s) will be able to view comment - information. -
-
- The comments associated with each <%= h(subjectNounSingle) %> or <%= h(subjectNounSingle) %>/Visit are - saved as fields in datasets. - Each of the datasets can contain multiple fields, but only one field can - be designated to hold the comment text. Comment fields must be of type text or multi-line text. - Comments will appear automatically in columns for the specimen and vial views. -
- - - - -
<%=h(subjectNounSingle)%> Comment Assignment
- - - - - - - - - -
Comment - Dataset<%= helpPopup(subjectNounSingle + "/Comment Dataset", "Comments can be associated with each " + - subjectNounSingle.toLowerCase() + ". The dataset selected must be a demographics dataset.")%> - - <% addHandler("participantCommentDatasetId", "change", "document.manageComments.participantCommentProperty.value=''; document.manageComments.method='get'; document.manageComments.submit()"); %> - -
Comment Field Name - -
- -
<%=h(subjectNounSingle)%>/Visit Comment Assignment
- <% - - if (study.getTimepointType() == TimepointType.CONTINUOUS) - { - %> - Not available in continuous date-based studies. - <% - } - else - { - %> - - - - - - - - - - - - -
Comment - Dataset<%= helpPopup(subjectNounSingle + "/Comment Dataset", "Comments can be associated with each " + - subjectNounSingle.toLowerCase() + "/visit combination. The dataset selected cannot be a demographics dataset.")%> - - <% addHandler("participantVisitCommentDatasetId", "change", "document.manageComments.participantVisitCommentProperty.value=''; document.manageComments.method='get'; document.manageComments.submit()"); %> - -
Comment Field Name - -
-
- <%= button("Save").submit(true) %> - <%= button("Cancel").href(urlProvider(StudyUrls.class).getManageStudyURL(getContainer())) %> -
- <% - } - %> -
\ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/view/manageDefaultReqs.jsp b/specimen/src/org/labkey/specimen/view/manageDefaultReqs.jsp deleted file mode 100644 index 7dba116aa42..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageDefaultReqs.jsp +++ /dev/null @@ -1,233 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.study.StudyUrls" %> -<%@ page import="org.labkey.api.util.HtmlString" %> -<%@ page import="org.labkey.api.view.ActionURL" %> -<%@ page import="org.labkey.api.view.HttpView"%> -<%@ page import="org.labkey.api.view.JspView"%> -<%@ page import="org.labkey.specimen.actions.ManageReqsBean" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageDefaultReqsAction" %> -<%@ page import="org.labkey.specimen.model.SpecimenRequestActor" %> -<%@ page import="org.labkey.specimen.requirements.SpecimenRequestRequirement" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - JspView me = HttpView.currentView(); - ManageReqsBean bean = me.getModelBean(); - SpecimenRequestRequirement[] providerRequirements = bean.getProviderRequirements(); - SpecimenRequestRequirement[] originatingRequirements = bean.getOriginatorRequirements(); - SpecimenRequestRequirement[] receiverRequirements = bean.getReceiverRequirements(); - SpecimenRequestRequirement[] generalRequirements = bean.getGeneralRequirements(); - SpecimenRequestActor[] actors = bean.getActors(); - ActionURL deleteDefaultRequirement = urlFor(SpecimenController.DeleteDefaultRequirementAction.class).addParameter("id",0); -%> - - - - - - - - - - <% - for (SpecimenRequestRequirement requirement : originatingRequirements) - { - %> - - - - - - <% - } - %> - - - - - - -
 ActorDescription
<%= link("Delete", deleteDefaultRequirement.replaceParameter("id", requirement.getRowId())).usePost() %><%= h(requirement.getActor().getLabel()) %><%= requirement.getDescription() != null ? h(requirement.getDescription()) : HtmlString.NBSP %>
  - - <%= button("Add Requirement").submit(true).onClick("return verifyNewRequirement('originator');") %>
-
- - - - - - - - - <% - for (SpecimenRequestRequirement requirement : providerRequirements) - { - %> - - - - - - <% - } - %> - - - - - - -
 ActorDescription
<%= link("Delete", deleteDefaultRequirement.replaceParameter("id", requirement.getRowId())).usePost() %><%= h(requirement.getActor().getLabel()) %><%= requirement.getDescription() != null ? h(requirement.getDescription()) : HtmlString.NBSP %>
  - - <%= button("Add Requirement").submit(true).onClick("return verifyNewRequirement('provider');") %>
-
- - - - - - - - - <% - for (SpecimenRequestRequirement requirement : receiverRequirements) - { - %> - - - - - - <% - } - %> - - - - - - -
 ActorDescription
<%= link("Delete", deleteDefaultRequirement.replaceParameter("id", requirement.getRowId())).usePost() %><%= h(requirement.getActor().getLabel()) %><%= requirement.getDescription() != null ? h(requirement.getDescription()) : HtmlString.NBSP %>
  - - <%= button("Add Requirement").submit(true).onClick("return verifyNewRequirement('receiver');") %>
-
- - - - - - - - - <% - for (SpecimenRequestRequirement requirement : generalRequirements) - { - %> - - - - - - <% - } - %> - - - - - - -
 ActorDescription
<%= link("Delete", deleteDefaultRequirement.replaceParameter("id", requirement.getRowId())).usePost() %><%= h(requirement.getActor().getLabel()) %><%= requirement.getDescription() != null ? h(requirement.getDescription()) : HtmlString.NBSP %>
  - - <%= button("Add Requirement").submit(true).onClick("return verifyNewRequirement('general');") %>
-
- -
-<%= link("manage study", urlProvider(StudyUrls.class).getManageStudyClass()) %> \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/view/manageDisplay.jsp b/specimen/src/org/labkey/specimen/view/manageDisplay.jsp deleted file mode 100644 index 997925a2eaf..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageDisplay.jsp +++ /dev/null @@ -1,108 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.study.StudyUrls"%> -<%@ page import="org.labkey.api.util.HtmlString"%> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageDisplaySettingsAction" %> -<%@ page import="org.labkey.specimen.settings.DisplaySettings" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - JspView me = HttpView.currentView(); - DisplaySettings bean = me.getModelBean(); -%> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Comments and Quality Control -
The specimen system can function in two modes: request mode, where vials are requested and requests are managed, - and comments/QC mode, where vial information is modified. Users with appropriate permissions can always manually switch modes.
Default mode: - -
Manual QC flagging/unflagging<%= helpPopup("Manual QC Flagging", HtmlString.unsafe("Vials are automatically flagged for QC " + - "at time of import if a vial's history contains conflicting information." + - "

Manual QC flagging/unflagging allows these states to be changed without updating the underlying specimen data. " + - "

Once a vial's QC state is set manually, it will no longer be updated automatically during the import process."))%>:

- -
- Low vial warnings -
If enabled, the specimen request system can display warning icons when one or zero vials of any primary specimen are available for request. The icon will appear next to all vials of that the primary specimen.
Display one available vial warning: - -
Display zero available vials warning: - -
-
- <%= button("Save").submit(true) %>  - <%= button("Cancel").href(urlFor(urlProvider(StudyUrls.class).getManageStudyClass())) %> -
-
diff --git a/specimen/src/org/labkey/specimen/view/manageLocationTypes.jsp b/specimen/src/org/labkey/specimen/view/manageLocationTypes.jsp deleted file mode 100644 index fcecd6fcadc..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageLocationTypes.jsp +++ /dev/null @@ -1,113 +0,0 @@ -<% -/* - * Copyright (c) 2012-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. - */ -%> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageLocationTypesForm" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%! - @Override - public void addClientDependencies(ClientDependencies dependencies) - { - dependencies.add("Ext4"); - } -%> -<% - JspView me = HttpView.currentView(); - ManageLocationTypesForm bean = me.getModelBean(); -%> -
-

Select which location types are allowed to be requesting locations:

-
-
- - diff --git a/specimen/src/org/labkey/specimen/view/manageNotifications.jsp b/specimen/src/org/labkey/specimen/view/manageNotifications.jsp deleted file mode 100644 index 3ab1ec35796..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageNotifications.jsp +++ /dev/null @@ -1,283 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.apache.commons.lang3.StringUtils"%> -<%@ page import="org.labkey.api.admin.AdminUrls"%> -<%@ page import="org.labkey.api.data.Container" %> -<%@ page import="org.labkey.api.security.SecurityUrls" %> -<%@ page import="org.labkey.api.security.permissions.AdminPermission" %> -<%@ page import="org.labkey.api.study.StudyService" %> -<%@ page import="org.labkey.api.study.StudyUrls" %> -<%@ page import="org.labkey.api.view.ActionURL" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageNotificationsAction" %> -<%@ page import="org.labkey.specimen.settings.RequestNotificationSettings" %> -<%@ page import="org.labkey.specimen.settings.RequestNotificationSettings.DefaultEmailNotifyEnum" %> -<%@ page import="org.labkey.specimen.settings.RequestNotificationSettings.SpecimensAttachmentEnum" %> -<%@ page import="org.labkey.specimen.view.SpecimenRequestNotificationEmailTemplate" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> - - - -<% - JspView me = HttpView.currentView(); - RequestNotificationSettings bean = me.getModelBean(); - Container container = getContainer(); - - ActionURL completionURLPrefix = urlProvider(SecurityUrls.class).getCompleteUserURL(container); - boolean newRequestNotifyChecked = ("POST".equalsIgnoreCase(getViewContext().getRequest().getMethod()) ? - bean.isNewRequestNotifyCheckbox() : StringUtils.isNotEmpty(bean.getNewRequestNotify())); - boolean ccChecked = ("POST".equalsIgnoreCase(getViewContext().getRequest().getMethod()) ? - bean.isCcCheckbox() : StringUtils.isNotEmpty(bean.getCc())); - DefaultEmailNotifyEnum defaultEmailNotifyEnum = (bean.getDefaultEmailNotifyEnum()); // Checking getMethod bot needed because one of the radio buttons will always POST - SpecimensAttachmentEnum specimensAttachmentEnum = (bean.getSpecimensAttachmentEnum()); -%> - - - - - - - - - - - - - - - - - - <% - boolean replyToCurrentUser = RequestNotificationSettings.REPLY_TO_CURRENT_USER_VALUE.equals(bean.getReplyTo()); - %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - "> - - - - - - - - - - - - "> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The specimen request system sends emails as requested by the specimen administrator. - Some properties of these email notifications can be configured here.
Notification emails will be sent from the specified reply-to address. - This is the address that will receive replies and error messages, so it should be a monitored address.
Reply-to Address:Replies to specimen request notifications should go to:
- -
- -
- "> -
Specimen request emails have a configurable template, which controls - the subject line and body of the email. - <% if (getContainer().hasPermission(getUser(), AdminPermission.class)) { %><%= link("Edit Email Template", urlProvider(AdminUrls.class).getCustomizeEmailURL(getContainer(), SpecimenRequestNotificationEmailTemplate.class, getActionURL()))%><% } - else { %>You must have administrator permissions to edit the template.<% } %> -
All specimen request emails have the same subject line format. %requestId% may be used - to insert the specimen request's study-specific ID number. The default format for the subject line is: - <%= h(StudyService.get().getStudy(container).getLabel()) %>: [Subject Suffix] -
Subject Suffix: - -
Notification can be sent whenever a new specimen request is submitted.
- -
Notify of new requests
(one per line):
- -
Email addresses listed under "always CC" will receive a single copy of each email notification. - Please keep security issues in mind when adding users to this list.
- -
Always CC
(one per line):
- -
Each request requirement notification email allows you to specify which actors will receive the email. - The selection below indicates which actors will receive the email if the coordinator does not explicitly override.
Default Email Recipients: - -
- -
- -
In each notification email, a table of requested specimens can be included in the email body or as an attachment or not at all. To customize - that table, create a custom view called SpecimenEmail on the SpecimenDetail table.
Include Requested Specimens Table: - -
- -
- -
- -
  - <%= button("Save").submit(true) %>  - <%= button("Cancel").href(urlProvider(StudyUrls.class).getManageStudyURL(container)) %> -
-
- - - diff --git a/specimen/src/org/labkey/specimen/view/manageRepositorySettings.jsp b/specimen/src/org/labkey/specimen/view/manageRepositorySettings.jsp deleted file mode 100644 index 5e52f5e7ebd..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageRepositorySettings.jsp +++ /dev/null @@ -1,113 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.query.QueryUrls" %> -<%@ page import="org.labkey.api.study.Study" %> -<%@ page import="org.labkey.api.study.StudyService" %> -<%@ page import="org.labkey.api.view.ActionURL" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageRepositorySettingsAction" %> -<%@ page import="org.labkey.specimen.settings.RepositorySettings" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - RepositorySettings settings = (RepositorySettings) getModelBean(); - Study study = StudyService.get().getStudy(settings.getContainer()); - ActionURL specimenDetailURL = new ActionURL(); - specimenDetailURL.setContainer(getContainer()); - specimenDetailURL.addParameter("schemaName", "study"); - specimenDetailURL.addParameter("query.queryName", "SpecimenDetail"); -%> - - -<% - if (study != null && !study.hasSourceStudy() && !study.isSnapshotStudy()) - { -%> - - - - - -
Repository Type
-
- -
-
- -
-
- -
"> - - - - - - - -
Specimen Data
-
- -
-
- -
-
Specimen Requests
-
- -
-
- -
-
-
-
-
- <%= button("Submit").submit(true) %> <%= button("Back").href(urlFor(ManageRepositorySettingsAction.class)).onClick("window.history.back();return false;") %> -
-
-<% - } - else - { -%> -

Specimen repository and request settings are not available for published studies.

-<%= button("Back").href(urlFor(ManageRepositorySettingsAction.class)).onClick("window.history.back();return false;") %> -<% - } -%> \ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/view/manageRequest.jsp b/specimen/src/org/labkey/specimen/view/manageRequest.jsp deleted file mode 100644 index 96df8b7e2a7..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageRequest.jsp +++ /dev/null @@ -1,520 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.collections.LabKeyCollectors"%> -<%@ page import="org.labkey.api.data.Container" %> -<%@ page import="org.labkey.api.data.ContainerManager"%> -<%@ page import="org.labkey.api.security.User"%> -<%@ page import="org.labkey.api.security.UserManager"%> -<%@ page import="org.labkey.api.specimen.Vial"%> -<%@ page import="org.labkey.api.specimen.location.LocationImpl" %> -<%@ page import="org.labkey.api.specimen.location.LocationManager"%> -<%@ page import="org.labkey.api.study.Location" %> -<%@ page import="org.labkey.api.study.SpecimenService" %> -<%@ page import="org.labkey.api.study.StudyUtils" %> -<%@ page import="org.labkey.api.util.HtmlString" %> -<%@ page import="org.labkey.api.util.SafeToRender" %> -<%@ page import="org.labkey.api.view.ActionURL" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> -<%@ page import="org.labkey.specimen.SpecimenManager" %> -<%@ page import="org.labkey.specimen.SpecimenRequestManager" %> -<%@ page import="org.labkey.specimen.SpecimenRequestStatus" %> -<%@ page import="org.labkey.specimen.actions.ManageRequestBean" %> -<%@ page import="org.labkey.specimen.actions.ShowSearchAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.DeleteMissingRequestSpecimensAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.DeleteRequestAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ExtendedSpecimenRequestAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ImportVialIdsAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.LabSpecimenListsAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.LabSpecimenListsBean" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageRequestAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageRequirementAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.RequestHistoryAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.SubmitRequestAction" %> -<%@ page import="org.labkey.specimen.model.SpecimenRequestActor" %> -<%@ page import="org.labkey.specimen.requirements.SpecimenRequestRequirement" %> -<%@ page import="org.labkey.specimen.requirements.SpecimenRequestRequirementProvider" %> -<%@ page import="org.labkey.specimen.settings.SettingsManager" %> -<%@ page import="java.util.Arrays" %> -<%@ page import="java.util.List" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%! - @Override - public void addClientDependencies(ClientDependencies dependencies) - { - dependencies.add("clientapi/ext3"); - dependencies.add("FileUploadField.js"); - } -%> -<% - JspView me = HttpView.currentView(); - Container c = getContainer(); - User user = getUser(); - ManageRequestBean bean = me.getModelBean(); - String comments = bean.getSpecimenRequest().getComments(); - if (comments == null) - comments = "[No description provided]"; - boolean hasExtendedRequestView = SpecimenManager.get().getExtendedSpecimenRequestView(getViewContext()) != null; - SpecimenRequestActor[] actors = SpecimenRequestRequirementProvider.get().getActors(c); - SpecimenRequestRequirement[] requirements = SpecimenRequestManager.get().getRequestRequirements(bean.getSpecimenRequest()); - Location destinationLocation = bean.getDestinationSite(); - User creatingUser = UserManager.getUser(bean.getSpecimenRequest().getCreatedBy()); - List locations = LocationManager.get().getLocations(c); - boolean notYetSubmitted = false; - if (SettingsManager.get().isSpecimenShoppingCartEnabled(c)) - { - SpecimenRequestStatus cartStatus = SpecimenRequestManager.get().getRequestShoppingCartStatus(c, user); - notYetSubmitted = bean.getSpecimenRequest().getStatusId() == cartStatus.getRowId(); - } - - SafeToRender specimenSearchButton = SpecimenRequestManager.get().hasEditRequestPermissions(user, bean.getSpecimenRequest()) ? - button("Specimen Search").href(ShowSearchAction.getShowSearchURL(getContainer(), true)) : HtmlString.EMPTY_STRING; - SafeToRender importVialIdsButton = SpecimenRequestManager.get().hasEditRequestPermissions(user, bean.getSpecimenRequest()) ? - button("Upload Specimen Ids").href(urlFor(ImportVialIdsAction.class).addParameter("id", bean.getSpecimenRequest().getRowId())) : HtmlString.EMPTY_STRING; - - String availableStudyName = ContainerManager.getAvailableChildContainerName(c, "New Study"); -%> - - -<% - if (bean.isSuccessfulSubmission()) - { -%> -

Your request has been successfully submitted.

-<% - } - %> - -<% - if (!SpecimenService.get().getRequestCustomizer().hideRequestWarnings()) { %> - <% - boolean multipleSites = bean.getProvidingLocations().length > 1; - if (bean.hasMissingSpecimens() || multipleSites) - { - %> - - - - <% - if (bean.hasMissingSpecimens()) - { - %> - - - - <% - } - - if (multipleSites && !bean.isFinalState()) - { - %> - - - - <% - } - } - if (bean.isRequestManager() && (bean.isFinalState() || bean.isRequirementsComplete())) - { - %> - - - - - - -<% } - } - if (notYetSubmitted) - { -%> - - - - - - -<% - } - else - { -%> - Only the request creator (<%= h(creatingUser.getDisplayName(user)) %>) or an administrator may submit or cancel this request. -<% - } - } - else // is submitted - { %> - <%= SpecimenService.get().getRequestCustomizer().getSubmittedMessage(getContainer(), bean.getSpecimenRequest().getRowId()) %><% - } -%> - - - - - - - - - - - - - - - - - - -<% - if (null == bean.getSpecimenQueryView()) - { -%> - - - -<% - } -%> -
Request Warnings
- WARNING: Missing Specimens

- The following specimen(s) are part of this request, but have been deleted from the database:
- <% - for (String specId : bean.getMissingSpecimens()) - { - %><%= h(specId) %>
<% - } - - if (bean.isRequestManager()) - { - %> -
You may remove these specimens from this request if they are not expected to be re-added to the database.
- <%= button("Delete missing specimens") - .onClick("return LABKEY.Utils.confirmAndPost('Delete missing specimens? This action cannot be undone.', '" + h(urlFor(DeleteMissingRequestSpecimensAction.class).addParameter("id", bean.getSpecimenRequest().getRowId())) + "')") %><% - } - %> -
- WARNING: Vials are at multiple locations.
- Requests containing vials from multiple providing locations may require increased processing time.
- Multiple locations are expected if some vials have already shipped while others have not. - Current locations for this request are:
- <% - for (Location location : bean.getProvidingLocations()) - { - %><%= h(location.getLabel()) %>
<% - } - %> -
Request Notes
- <% - if (bean.isFinalState()) - { - %> - This request is in a final state; no changes are allowed.
- To make changes, you must - change the request's status to a non-final state. - <% - } - else if (bean.isRequirementsComplete()) - { - %> - This request's requirements are complete. Next steps include:
-
    -
  • Email specimen lists to their originating locations: <%= link("Originating Location Specimen Lists", - urlFor(LabSpecimenListsAction.class) - .addParameter("id", bean.getSpecimenRequest().getRowId()) - .addParameter("listType", LabSpecimenListsBean.Type.ORIGINATING.toString())) %> -
  • -
  • Email specimen lists to their providing locations: <%= link("Providing Location Specimen Lists", - urlFor(LabSpecimenListsAction.class) - .addParameter("id", bean.getSpecimenRequest().getRowId()) - .addParameter("listType", LabSpecimenListsBean.Type.PROVIDING.toString())) %> -
  • -
  • Update request status to indicate completion: <%= link("Update Request", - SpecimenController.getManageRequestStatusURL(getContainer(), bean.getSpecimenRequest().getRowId())) %> -
  • -
- <% - } - %> -
Unsubmitted Request
This request has not been submitted yet. -<% - if (SpecimenRequestManager.get().hasEditRequestPermissions(user, bean.getSpecimenRequest())) - { -%> -
-<% - List vials = bean.getSpecimenRequest().getVials(); - if (!vials.isEmpty() || SpecimenService.get().getRequestCustomizer().allowEmptyRequests()) - { -%> - Request processing will begin after the request has been submitted.

- <%= button("Submit Request") - .href(urlFor(SubmitRequestAction.class).addParameter("id", bean.getSpecimenRequest().getRowId())) - .onClick("return LABKEY.Utils.confirmAndPost('" + StudyUtils.SUBMISSION_WARNING + "', '" + h(urlFor(SubmitRequestAction.class).addParameter("id", bean.getSpecimenRequest().getRowId())) + "')") %> -<% - } - else - { -%> - You must add specimens before submitting your request.

- <%=specimenSearchButton%> - <%=importVialIdsButton%> -<% - } -%> - <%= button("Cancel Request") - .onClick("return LABKEY.Utils.confirmAndPost('" + StudyUtils.CANCELLATION_WARNING + "', '" + h(urlFor(DeleteRequestAction.class).addParameter("id", bean.getSpecimenRequest().getRowId())) + "')") %> -<% - if (bean.getReturnUrl() != null) - { -%> - <%= button("Return to Specimen View").href(bean.getReturnUrl()) %> -<% - } -%> -
-
Request Information
- - - - - - - - - - - - - - - - - - - - - -
Requester<%= h(creatingUser != null ? creatingUser.getDisplayName(user) : "Unknown") %>
Requesting Location<%= h(destinationLocation != null ? destinationLocation.getDisplayName() : "Not specified") %>
Request Date<%=formatDateTime(bean.getSpecimenRequest().getCreated())%>
Description<%=HtmlString.unsafe(h(comments).toString().replaceAll("\\n", "
\n"))%>
Status<%= h(bean.getStatus().getLabel()) %>
-
- <%= link("View History", urlFor(RequestHistoryAction.class).addParameter("id", bean.getSpecimenRequest().getRowId())) %>  - <%= bean.isRequestManager() ? link("Update Request", SpecimenController.getManageRequestStatusURL(getContainer(), bean.getSpecimenRequest().getRowId())) : HtmlString.EMPTY_STRING %> - <% - if (hasExtendedRequestView) - { - %> - <%= bean.isRequestManager() ? link("Update Extended Request", urlFor(ExtendedSpecimenRequestAction.class).addParameter("id", bean.getSpecimenRequest().getRowId())) : HtmlString.EMPTY_STRING %> - <% - } - %> - <%= bean.isRequestManager() ? link("Originating Location Specimen Lists", - new ActionURL(LabSpecimenListsAction.class, c) - .addParameter("id", bean.getSpecimenRequest().getRowId()) - .addParameter("listType", LabSpecimenListsBean.Type.ORIGINATING.toString())) : HtmlString.EMPTY_STRING %> - <%= bean.isRequestManager() ? link("Providing Location Specimen Lists", - new ActionURL(LabSpecimenListsAction.class, c) - .addParameter("id", bean.getSpecimenRequest().getRowId()) - .addParameter("listType", LabSpecimenListsBean.Type.PROVIDING.toString())) : HtmlString.EMPTY_STRING %> -
Current Requirements
- <% - if (!bean.isRequestManager() && requirements.length == 0) - { - %> - No requirements are associated with this request. - <% - } - else - { - %> - - - - - - - - - <% - for (SpecimenRequestRequirement requirement : requirements) - { - Location location = requirement.getLocation(); - String siteLabel; - if (location == null) - siteLabel = "N/A"; - else if (location.getLabel() == null) - siteLabel = ""; - else - siteLabel = location.getDisplayName(); - %> - - - - - - - - <% - } - if (bean.isRequestManager() && !bean.isFinalState()) - { - %> - - - - - - - <% - } - %> -
ActorLocationDescription<%= unsafe(requirements.length > 0 ? "Status" : "") %> 
<%= h(requirement.getActor().getLabel()) %><%= h(siteLabel) %><%= requirement.getDescription() != null ? h(requirement.getDescription()) : HtmlString.NBSP %> - " style="font-weight:bold;"> - <%= unsafe(requirement.isComplete() ? "Complete" : "Incomplete") %> - - - <%= link("Details", urlFor(ManageRequirementAction.class) - .addParameter("id", requirement.getRequestId()) - .addParameter("requirementId", requirement.getRowId()))%> -
- <% addHandler("newActor", "change", "updateSiteSelector();"); %> - - - - <%= button("Add Requirement").submit(true).onClick("if (validateNewRequirement()) document.addRequirementForm.submit(); return false;") %>
- <% - } - %> -
- No specimens are associated with this request.
- <%=specimenSearchButton%> - <%=importVialIdsButton%> -
diff --git a/specimen/src/org/labkey/specimen/view/manageRequestInputs.jsp b/specimen/src/org/labkey/specimen/view/manageRequestInputs.jsp deleted file mode 100644 index 163b1fc45b2..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageRequestInputs.jsp +++ /dev/null @@ -1,221 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.study.StudyUrls"%> -<%@ page import="org.labkey.api.view.HttpView"%> -<%@ page import="org.labkey.api.view.JspView"%> -<%@ page import="org.labkey.specimen.SpecimenRequestManager.SpecimenRequestInput"%> -<%@ page import="org.labkey.specimen.actions.ManageRequestInputsBean"%> -<%@ page import="org.labkey.specimen.actions.SpecimenController.HandleUpdateRequestInputsAction" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - JspView me = HttpView.currentView(); - ManageRequestInputsBean bean = me.getModelBean(); - - String tdTitle = ""; - String tdHelpText = ""; - String tdMultiline = ""; - String tdRequired = ""; - String tdRememberSiteValue = ""; -%> - - - - - - - - - - - - <% - SpecimenRequestInput[] inputs = bean.getInputs(); - for (int inputIndex = 0; inputIndex < inputs.length; inputIndex++) - { - SpecimenRequestInput input = inputs[inputIndex]; - %> - - - - - - - - - <% - } - %> -
 TitleHelp TextMultilineRequiredRemember by Site<%= helpPopup("Remember by Location", - "If checked, the input will be pre-populated with the previous value entered for the destination location.")%>
>>>
- <%= button("Add New Input").submit(true).onClick("return addRow();") %> - <%= button("Save").submit(true) %> - <%= button("Cancel").href(urlProvider(StudyUrls.class).getManageStudyURL(bean.getContainer())) %> -
\ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/view/manageRequestStatus.jsp b/specimen/src/org/labkey/specimen/view/manageRequestStatus.jsp deleted file mode 100644 index 90040173f31..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageRequestStatus.jsp +++ /dev/null @@ -1,121 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.study.SpecimenService" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView"%> -<%@ page import="org.labkey.specimen.SpecimenRequestManager"%> -<%@ page import="org.labkey.specimen.SpecimenRequestStatus"%> -<%@ page import="org.labkey.specimen.actions.ManageRequestBean" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController" %> -<%@ page import="org.labkey.specimen.notifications.ActorNotificationRecipientSet" %> -<%@ page import="java.util.Collection" %> -<%@ page import="java.util.List" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<% - JspView me = HttpView.currentView(); - ManageRequestBean bean = me.getModelBean(); - Collection statuses = SpecimenRequestManager.get().getRequestStatuses(getContainer(), getUser()); -%> - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Request Description - -
Current Status - <% if (SpecimenService.get().getRequestCustomizer().canChangeStatus(getUser())) { %> - <% - } - else - { - for (SpecimenRequestStatus status : statuses) - { - if(bean.getSpecimenRequest().getStatusId() == status.getRowId()) - { %> - <%= h(status.getLabel()) %><% - } - } - } %> -
Comments
Supporting
Documents
-
-
-
-
- -
Notify - <% - boolean hasInactiveEmailAddress = false; - List possibleNotifications = bean.getPossibleNotifications(); - for (ActorNotificationRecipientSet possibleNotification : possibleNotifications) - { - boolean hasEmailAddresses = possibleNotification.getAllEmailAddresses().length > 0; - if (hasEmailAddresses) - hasInactiveEmailAddress |= possibleNotification.hasInactiveEmailAddress(); - %> - > - <%=possibleNotification.getHtmlDescriptionAndLink(hasEmailAddresses, getActionURL())%>
- <% - } - if (hasInactiveEmailAddress) - { - %> - - Include inactive users
- <% - } - %> -
  - <%= button("Save Changes and Send Notifications").submit(true) %>  - <%= button("Cancel").href(SpecimenController.getManageRequestURL(getContainer(), bean.getSpecimenRequest().getRowId(), null)) %> -
-
diff --git a/specimen/src/org/labkey/specimen/view/manageRequirement.jsp b/specimen/src/org/labkey/specimen/view/manageRequirement.jsp deleted file mode 100644 index 41f68a5a7f5..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageRequirement.jsp +++ /dev/null @@ -1,198 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.study.Location"%> -<%@ page import="org.labkey.api.view.ActionURL"%> -<%@ page import="org.labkey.api.view.HttpView"%> -<%@ page import="org.labkey.api.view.JspView"%> -<%@ page import="org.labkey.api.view.template.ClientDependencies"%> -<%@ page import="org.labkey.specimen.actions.SpecimenController" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.DeleteRequirementAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageRequirementAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageRequirementBean" %> -<%@ page import="org.labkey.specimen.notifications.ActorNotificationRecipientSet" %> -<%@ page import="org.labkey.specimen.requirements.SpecimenRequestRequirement" %> -<%@ page import="java.util.List" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%! - @Override - public void addClientDependencies(ClientDependencies dependencies) - { - // TODO: --Ext3-- This should be declared as part of the included views - dependencies.add("clientapi/ext3"); - } -%> -<% - JspView me = HttpView.currentView(); - ManageRequirementBean bean = me.getModelBean(); - SpecimenRequestRequirement requirement = bean.getRequirement(); - Location location = requirement.getLocation(); - String locationLabel = location != null ? location.getDisplayName() : "N/A"; - - ActionURL deleteURL = urlFor(DeleteRequirementAction.class); -%> - - - - - -<% - if (bean.isRequestManager() && bean.isFinalState()) - { -%> - - - - - - -<% - } -%> - - - - - - - <% - if (bean.isRequestManager()) - { -%> - - - -<% - if (!bean.isFinalState()) - { - %> - - - - <% - } - } - %> - - - - - - -
<%= link("View Request", SpecimenController.getManageRequestURL(getContainer(), requirement.getRequestId(), null))%>
Requirement Notes
- This request is in a final state; no changes are allowed.
- To make changes, you must - change the request's status to a non-final state. -
Requirement Details
- - - - - - - - - - - - - - <% - if (!bean.isRequestManager()) - { - %> - - - - - <% - } - %> -
Actor<%= h(requirement.getActor().getLabel()) %>
Location<%= h(locationLabel) %>
Description<%= h(requirement.getDescription()) %>
Status - " style="font-weight:bold;"> - <%= unsafe(requirement.isComplete() ? "Complete" : "Incomplete") %> - -
-
Change Status
- - - - - - - - - - - - - - - - - - - - - - - - -
  - > - Complete -
Comments - -
Attachments -
-
- -
Notify - <% - boolean hasInactiveEmailAddress = false; - List possibleNotifications = bean.getPossibleNotifications(); - for (ActorNotificationRecipientSet possibleNotification : possibleNotifications) - { - boolean hasEmailAddresses = possibleNotification.getAllEmailAddresses().length > 0; - if (hasEmailAddresses) - hasInactiveEmailAddress |= possibleNotification.hasInactiveEmailAddress(); - %> - - <%=checked(hasEmailAddresses && bean.isDefaultNotification(possibleNotification)) %>> - <%=possibleNotification.getHtmlDescriptionAndLink(hasEmailAddresses, getActionURL())%>
- <% - } - if (hasInactiveEmailAddress) - { - %> - - Include inactive users
- <% - } - %> -
  - <%= button("Save Changes and Send Notifications").submit(true) %>  - <%= button("Delete Requirement").submit(true).onClick("this.form.action='" + h(deleteURL) + "'") %>  - <%= button("Cancel").href(SpecimenController.getManageRequestURL(getContainer(), requirement.getRequestId(), null)) %> -
-
-
History
<% me.include(bean.getHistoryView(), out); %>
diff --git a/specimen/src/org/labkey/specimen/view/manageSpecimenWebPart.jsp b/specimen/src/org/labkey/specimen/view/manageSpecimenWebPart.jsp deleted file mode 100644 index e9937bd1305..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageSpecimenWebPart.jsp +++ /dev/null @@ -1,239 +0,0 @@ -<% -/* - * Copyright (c) 2013-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. - */ -%> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.SpecimenWebPartForm" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%! - @Override - public void addClientDependencies(ClientDependencies dependencies) - { - dependencies.add("Ext4"); - } -%> -<% - JspView me = HttpView.currentView(); - SpecimenWebPartForm bean = me.getModelBean(); - String[] grouping1 = bean.getGrouping1(); - String[] grouping2 = bean.getGrouping2(); - String[] columns = bean.getColumns(); -%> -
-

You can specify one or two specimen grouping sections on the specimen web part that allow specimens to be easily viewed by those groupings. -

-
-
- - diff --git a/specimen/src/org/labkey/specimen/view/manageSpecimens.jsp b/specimen/src/org/labkey/specimen/view/manageSpecimens.jsp deleted file mode 100644 index 278fab27581..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageSpecimens.jsp +++ /dev/null @@ -1,209 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="com.google.common.collect.Iterables" %> -<%@ page import="org.labkey.api.data.Container" %> -<%@ page import="org.labkey.api.exp.property.Domain" %> -<%@ page import="org.labkey.api.security.User" %> -<%@ page import="org.labkey.api.security.permissions.AdminPermission" %> -<%@ page import="org.labkey.api.specimen.model.SpecimenTablesProvider" %> -<%@ page import="org.labkey.api.study.SpecimenService" %> -<%@ page import="org.labkey.api.study.SpecimenTransform" %> -<%@ page import="org.labkey.api.study.Study" %> -<%@ page import="org.labkey.api.study.StudyService" %> -<%@ page import="org.labkey.api.view.ActionURL" %> -<%@ page import="org.labkey.specimen.SpecimenRequestManager" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ChooseImporterAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ConfigureRequestabilityRulesAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageActorsAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageDefaultReqsAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageDisplaySettingsAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageLocationTypesAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageNotificationsAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageRepositorySettingsAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageRequestInputsAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageSpecimenCommentsAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageSpecimenWebPartAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageStatusesAction" %> -<%@ page import="org.labkey.specimen.requirements.SpecimenRequestRequirementProvider" %> -<%@ page import="org.labkey.specimen.security.permissions.ManageRequestSettingsPermission" %> -<%@ page import="org.labkey.specimen.settings.SettingsManager" %> -<%@ page import="java.util.Collection" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<% - Container c = getContainer(); - Study study = StudyService.get().getStudy(c); - User user = getUser(); - String subjectNounSingle = study.getSubjectNounSingular().toLowerCase(); - - if (c.hasPermission(user, AdminPermission.class)) - { - if (!study.hasSourceStudy() && !study.isSnapshotStudy()) - { - SpecimenTablesProvider stp = new SpecimenTablesProvider(c, user, null); - Domain domainEvent = stp.getDomain("specimenevent",false, false); - Domain domainVial = stp.getDomain("vial",false, false); - Domain domainSpecimen = stp.getDomain("specimen",false, false); - - ActionURL specimenEventUrl = null; - ActionURL vialUrl = null; - ActionURL specimenUrl = null; - - Collection specimenTransforms = SpecimenService.get().getSpecimenTransforms(getContainer()); - specimenTransforms.removeIf(transform -> null == transform.getManageAction(getContainer(), getUser())); - int numberOfTransforms = specimenTransforms.size(); - - if (domainEvent != null) - { - specimenEventUrl = domainEvent.getDomainKind().urlEditDefinition(domainEvent, getViewContext()) - .addReturnUrl(getViewContext().getActionURL()); - } - - if (domainVial != null) - { - vialUrl = domainVial.getDomainKind().urlEditDefinition(domainVial, getViewContext()) - .addReturnUrl(getViewContext().getActionURL()); - } - - if (domainSpecimen != null) - { - specimenUrl = domainSpecimen.getDomainKind().urlEditDefinition(domainSpecimen, getViewContext()) - .addReturnUrl(getViewContext().getActionURL()); - } - -%> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <% if (numberOfTransforms == 1) { %> - <% - SpecimenTransform transform = Iterables.get(specimenTransforms, 0); - ActionURL manageAction = transform.getManageAction(getContainer(), getUser()); - %> - - - - - - <% } else { %> - - - - - - <% - } - %> -
Repository TypeThis study uses the <%=unsafe(SettingsManager.get().getRepositorySettings(study.getContainer()).isSimple() ? "standard" : "advanced")%> specimen repository<%=link("Change Repository Type", ManageRepositorySettingsAction.class)%>
Specimen FieldsCustomize specimen fields for this repository<%=link("Edit Specimen fields", specimenUrl)%>
Vial FieldsCustomize vial fields for this repository<%=link("Edit Vial fields", vialUrl)%>
Specimen Event FieldsCustomize specimen event fields for this repository<%=link("Edit Specimen Event fields", specimenEventUrl)%>
Display and BehaviorManage warnings, comments, and workflow<%= link("Manage Display and Behavior", ManageDisplaySettingsAction.class) %>
CommentsManage <%=h(subjectNounSingle)%> and <%=h(subjectNounSingle)%>/visit comments<%= link("Manage Comments", ManageSpecimenCommentsAction.class) %>
Specimen Web PartConfigure the specimen groupings in the specimen web part<%= link("Configure Specimen Groupings", ManageSpecimenWebPartAction.class) %>
Specimen ImportConfigure a specimen import<%=link("Configure specimen import", manageAction)%>
Specimen ImportChoose and configure a specimen import<%=link("Configure specimen import", ChooseImporterAction.class)%>
-
- <% - } - else - { - String childStudyType = study.getStudySnapshotType().getTitle().toLowerCase(); - %> -

Note: specimen repository and request settings are not available for <%=unsafe(childStudyType)%> studies.

- <% - } - } // admin permission - - if (c.hasPermission(user, ManageRequestSettingsPermission.class)) - { - if (SettingsManager.get().getRepositorySettings(getContainer()).isEnableRequests()) - { - %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Location TypesConfigure which location types are allowed to be requesting locations<%= link("Manage Location Types", ManageLocationTypesAction.class) %>
StatusesThis study defines <%=SpecimenRequestManager.get().getRequestStatuses(study.getContainer(), getUser()).size() %> specimen request - statuses<%= link("Manage Request Statuses", ManageStatusesAction.class) %>
ActorsThis study defines <%=SpecimenRequestRequirementProvider.get().getActors(getContainer()).length %> specimen request - actors<%= link("Manage Actors and Groups", ManageActorsAction.class) %>
Request RequirementsManage default requirements for new requests<%= link("Manage Default Requirements", ManageDefaultReqsAction.class) %>
Request FormManage inputs required for a new specimen request <%= link("Manage New Request Form", ManageRequestInputsAction.class) %>
NotificationsManage specimen request notifications<%= link("Manage Notifications", ManageNotificationsAction.class) %>
Requestability RulesManage the rules used to determine specimen availability for request<%= link("Manage Requestability Rules", ConfigureRequestabilityRulesAction.class) %>
-
-<% - } - } -%> diff --git a/specimen/src/org/labkey/specimen/view/manageStatusOrder.jsp b/specimen/src/org/labkey/specimen/view/manageStatusOrder.jsp deleted file mode 100644 index 8be539388a0..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageStatusOrder.jsp +++ /dev/null @@ -1,114 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.study.Study"%> -<%@ page import="org.labkey.api.view.HttpView"%> -<%@ page import="org.labkey.api.view.JspView"%> -<%@ page import="org.labkey.specimen.SpecimenRequestManager"%> -<%@ page import="org.labkey.specimen.SpecimenRequestStatus"%> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageStatusOrderAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageStatusesAction" %> -<%@ page import="java.util.Collection" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - JspView me = HttpView.currentView(); - Study study = me.getModelBean(); - Collection statuses = SpecimenRequestManager.get().getRequestStatuses(study.getContainer(), getUser()); -%> - - - - - - - - - -
- <% - %> - - - <%= button("Move Up").submit(true).onClick("return orderModule(0)") %>
- <%= button("Move Down").submit(true).onClick("return orderModule(1)") %> -
-
- - <%= button("Save").submit(true) %> <%= button("Cancel").href(urlFor(ManageStatusesAction.class)) %> -
diff --git a/specimen/src/org/labkey/specimen/view/manageStatuses.jsp b/specimen/src/org/labkey/specimen/view/manageStatuses.jsp deleted file mode 100644 index 5ec5a6dddef..00000000000 --- a/specimen/src/org/labkey/specimen/view/manageStatuses.jsp +++ /dev/null @@ -1,145 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.study.Study"%> -<%@ page import="org.labkey.api.study.StudyUrls"%> -<%@ page import="org.labkey.api.view.ActionURL"%> -<%@ page import="org.labkey.api.view.HttpView"%> -<%@ page import="org.labkey.api.view.JspView"%> -<%@ page import="org.labkey.specimen.SpecimenRequestManager" %> -<%@ page import="org.labkey.specimen.SpecimenRequestStatus" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.DeleteStatusAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageStatusOrderAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ManageStatusesAction" %> -<%@ page import="org.labkey.specimen.settings.SettingsManager" %> -<%@ page import="org.labkey.specimen.settings.StatusSettings" %> -<%@ page import="java.util.Collection" %> -<%@ page import="java.util.Set" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - JspView me = HttpView.currentView(); - Study study = me.getModelBean(); - Collection statuses = SpecimenRequestManager.get().getRequestStatuses(study.getContainer(), getUser()); - Set inUseStatuses = SpecimenRequestManager.get().getRequestStatusIdsInUse(study.getContainer()); - StatusSettings settings = SettingsManager.get().getStatusSettings(study.getContainer()); - boolean showSystemStatuses = settings.isUseShoppingCart(); -%> - - - -

- Request statuses help a coordinator organize and track requests through the system and - communicate progress to requesters. All requests do not need to pass through all states. -

- - - - - - - - - - <% - if (statuses != null && !statuses.isEmpty()) - { - for(SpecimenRequestStatus status : statuses) - { - %> - <%= unsafe(!showSystemStatuses && status.isSystemStatus() ? "style=\"display:none\"" : "") %>> - - - - - <% - if (status.isSystemStatus() || inUseStatuses.contains(status.getRowId())) - { - %> - - <% - } - else - { - %> - - <% - } - %> - - <% - } - } - %> - - - - - - - - - - -
<%= unsafe(statuses != null && !statuses.isEmpty() ? "Step Number" : " ") %>Status NameFinal State<%= helpPopup("Final States", "States are final if they indicate no further processing will occur for a request. For example, 'Completed', or 'Rejected' could be final states.")%>Lock Specimens<%= helpPopup("Locked Specimen States", "Specifies whether specimens should be available for additional requests while in each status.")%> 
<%= status.isSystemStatus() ? 1 : status.getSortOrder() + 1 %> - <% - if (!status.isSystemStatus()) - { - %> - - <% - } - %> - " - <%=disabled(status.isSystemStatus()) %>> - <%=disabled(status.isSystemStatus()) %>> - - <%=disabled(status.isSystemStatus()) %>> - In-use<%= helpPopup("In-use Status", status.isSystemStatus() ? "This is a system status that cannot be deleted." : - "This status cannot be deleted because one or more requests are currently in this status.") %><%=link("delete", urlFor(DeleteStatusAction.class).addParameter("id", status.getRowId())).usePost()%>
New Status: 
-
- <%= button("Save").submit(true) %>  - <%= button("Change Order").submit(true).onClick("document.manageStatuses.nextPage.value='" + urlFor(ManageStatusOrderAction.class) + "'; return true;") %> - -
- -
- -

- Allowing users to build up specimen requests over multiple - searches is generally more convenient, but requires the coordinator to watch for abandoned unsubmitted requests. -

-
- -
- - <%= button("Done").submit(true).onClick("document.manageStatuses.nextPage.value=''; return true;")%> - <%= button("Cancel").href(urlProvider(StudyUrls.class).getManageStudyURL(study.getContainer())) %>  -
\ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/view/relevantRequests.jsp b/specimen/src/org/labkey/specimen/view/relevantRequests.jsp deleted file mode 100644 index 2073c0f9fdc..00000000000 --- a/specimen/src/org/labkey/specimen/view/relevantRequests.jsp +++ /dev/null @@ -1,19 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -There are no requests involving this specimen. diff --git a/specimen/src/org/labkey/specimen/view/requestSpecimens.jsp b/specimen/src/org/labkey/specimen/view/requestSpecimens.jsp deleted file mode 100644 index a4d7ce4c3a7..00000000000 --- a/specimen/src/org/labkey/specimen/view/requestSpecimens.jsp +++ /dev/null @@ -1,241 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.data.Container"%> -<%@ page import="org.labkey.api.specimen.Vial"%> -<%@ page import="org.labkey.api.specimen.location.LocationImpl"%> -<%@ page import="org.labkey.api.specimen.location.LocationManager"%> -<%@ page import="org.labkey.api.study.SpecimenService"%> -<%@ page import="org.labkey.api.util.OptionBuilder"%> -<%@ page import="org.labkey.api.view.ActionURL"%> -<%@ page import="org.labkey.api.view.HttpView"%> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.ViewContext" %> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> -<%@ page import="org.labkey.specimen.SpecimenManager" %> -<%@ page import="org.labkey.specimen.SpecimenRequestManager.SpecimenRequestInput" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.CreateSpecimenRequestForm" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.HandleCreateSpecimenRequestAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.NewRequestBean" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ViewRequestsAction" %> -<%@ page import="org.labkey.specimen.settings.SettingsManager" %> -<%@ page import="org.springframework.validation.BindException" %> -<%@ page import="org.springframework.validation.ObjectError" %> -<%@ page import="java.util.List" %> -<%@ page import="java.util.Map" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%! - @Override - public void addClientDependencies(ClientDependencies dependencies) - { - dependencies.add("Ext4"); - } -%> -<% - JspView me = HttpView.currentView(); - NewRequestBean bean = me.getModelBean(); - ViewContext context = getViewContext(); - Container c = getContainer(); - List locations = LocationManager.get().getValidRequestingLocations(c); - boolean shoppingCart = SettingsManager.get().isSpecimenShoppingCartEnabled(c); - boolean hasExtendedRequestView = SpecimenManager.get().getExtendedSpecimenRequestView(context) != null; - List vials = bean.getVials(); - SpecimenRequestInput[] inputs = bean.getInputs(); -%> - - <% - BindException errors = bean.getErrors(); - if (errors != null) - { - for (ObjectError e : errors.getAllErrors()) - { - %><%=h(context.getMessage(e))%>
<% - } - } - %> -
- - - - <%=generateReturnUrlFormField(bean.getReturnUrl())%> - <% - if (vials != null) - { - for (Vial vial : vials) - { - %> - - <% - } - } - %> - - <% - if (shoppingCart) - { - %> - - - - <% - } - if (SpecimenService.get().getRequestCustomizer().getDefaultDestinationSiteId() != null) - { %> - - <% } - else - { %> - - - - - - - <% } %> - <% - for (int i = 0; i < inputs.length; i++) - { - SpecimenRequestInput input = inputs[i]; - %> - - - - - - - - - - <% - } - %> - - - - - <% - if (vials != null && !vials.isEmpty()) - { - %> - - - - - - - <% - } - %> - -
Please fill out this form to create a new specimen request. You will have the chance to add or remove specimens before the request is submitted.
Requesting Location (Required):
- <%= - select() - .id("destinationLocation") - .name("destinationLocation") - .addOption("", "0") - .addOptions(locations.stream().map(loc -> new OptionBuilder(loc.getDisplayName(), loc.getRowId()))) - .selected(bean.getSelectedSite()) - .className(null) - .onChange("setDefaults()") - %> -
- -
<%= h(input.getTitle()) %> <%= unsafe(input.isRequired() ? "(Required)" : "(Optional)") %>: -
<%= h(input.getHelpText()) %>
- <% - if (input.isMultiLine()) - { - %> - - <% - } - else - { - %> - value="<%= h(bean.getValue(i)) %>"> - <% - } - %> -
- - - <% - boolean hasReturnURL = bean.getReturnUrl() != null; - if (hasExtendedRequestView) - { - %> - <%= button("Save & Continue").submit(true).onClick("document.CreateSpecimenRequest." + CreateSpecimenRequestForm.PARAMS.extendedRequestUrl.name() + ".value='true'; return true;") %> - <% - } - else - { - if (hasReturnURL) - { - %> - <%= button((shoppingCart ? "Create" : "Submit") + " and Return to Specimens").submit(true) %> - <% - } - %> - <%= button((shoppingCart ? "Create" : "Submit") + " and View Details").submit(true) - .onClick("document.CreateSpecimenRequest." + CreateSpecimenRequestForm.PARAMS.ignoreReturnUrl.name() + ".value='true'; return true;") %> - <% - } - %> - <%= unsafe(hasReturnURL ? button("Cancel").href(bean.getReturnUrl()).toString() : button("Cancel").href(new ActionURL(ViewRequestsAction.class, getContainer())).toString()) %> -
Selected Specimens:
<% me.include(bean.getSpecimenQueryView(), out); %>
-
\ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/view/search.jsp b/specimen/src/org/labkey/specimen/view/search.jsp deleted file mode 100644 index e4e9d5fbe93..00000000000 --- a/specimen/src/org/labkey/specimen/view/search.jsp +++ /dev/null @@ -1,92 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> -<%@ page import="org.labkey.api.view.template.ClientDependency" %> -<%@ page import="org.labkey.specimen.actions.SpecimenSearchBean" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%! - @Override - public void addClientDependencies(ClientDependencies dependencies) - { - dependencies.add("clientapi"); - dependencies.add("Ext4ClientApi"); - dependencies.add("extWidgets/SearchPanel.js"); - dependencies.add("ux/CheckCombo/CheckCombo.js"); - dependencies.add("ux/CheckCombo/CheckCombo.css"); - dependencies.add("study/SpecimenSearchPanel.js"); - dependencies.add(ClientDependency.fromModuleName("Study")); - } -%> -<% - JspView me = HttpView.currentView(); - int webPartId = me.getModelBean().getWebPartId(); - String renderTarget = "labkey-specimen-search-"+ webPartId; -%> -
- diff --git a/specimen/src/org/labkey/specimen/view/selectSpecimenProvider.jsp b/specimen/src/org/labkey/specimen/view/selectSpecimenProvider.jsp deleted file mode 100644 index f88310a10be..00000000000 --- a/specimen/src/org/labkey/specimen/view/selectSpecimenProvider.jsp +++ /dev/null @@ -1,48 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.specimen.location.LocationImpl"%> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.specimen.actions.SelectSpecimenProviderBean" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - JspView me = HttpView.currentView(); - SelectSpecimenProviderBean bean = me.getModelBean(); -%> - -

Vials from the selected specimens can be shipped to you from multiple locations. Please select your preferred location:

- -<%= h(bean.getSourceForm().getHiddenFormInputs(getViewContext())) %> -

- -

-

- <%= generateBackButton() %> - <%= button("Select").submit(true) %> -

-
\ No newline at end of file diff --git a/specimen/src/org/labkey/specimen/view/specimen.jsp b/specimen/src/org/labkey/specimen/view/specimen.jsp deleted file mode 100644 index 9692bda83d2..00000000000 --- a/specimen/src/org/labkey/specimen/view/specimen.jsp +++ /dev/null @@ -1,106 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.specimen.Vial"%> -<%@ page import="org.labkey.api.specimen.location.LocationManager"%> -<%@ page import="org.labkey.api.specimen.model.SpecimenComment" %> -<%@ page import="org.labkey.api.study.Location" %> -<%@ page import="org.labkey.api.study.StudyService" %> -<%@ page import="org.labkey.api.view.ActionURL" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.specimen.SpecimenManager" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController" %> -<%@ page import="org.labkey.specimen.actions.SpecimenEventBean" %> -<%@ page import="org.labkey.specimen.security.permissions.SetSpecimenCommentsPermission" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - JspView me = HttpView.currentView(); - SpecimenEventBean bean = me.getModelBean(); - Vial vial = bean.getVial(); - Location originatingLocation = LocationManager.get().getOriginatingLocation(vial); - SpecimenComment comment = SpecimenManager.get().getSpecimenCommentForVial(vial); - ActionURL commentsLink = new ActionURL(SpecimenController.UpdateCommentsAction.class, vial.getContainer()); - commentsLink.addParameter("rowId", vial.getRowId()); - commentsLink.addParameter("referrer", getActionURL().getLocalURIString()); -%> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Globally Unique ID<%= h(vial.getGlobalUniqueId()) %>
<%= h(StudyService.get().getSubjectNounSingular(vial.getContainer())) %><%= h(id(vial.getPtid())) %>
Visit<%= h(vial.getVisitDescription()) %> <%= vial.getVisitValue() %>
Volume<%= h(vial.getVolume()) %> <%= h(vial.getVolumeUnits()) %>
Collection Date<%=vial.getDrawTimestamp() != null ? formatDateTime(vial.getDrawTimestamp()) : h("Unknown")%>
Collection Location<%= h(originatingLocation != null ? originatingLocation.getDisplayName() : "Unknown") %>
Comments and QC - <%= h(comment != null ? comment.getComment() : null) %> - <% if (vial.getContainer().hasPermission(getUser(), SetSpecimenCommentsPermission.class)) { %> - <%= link("update", commentsLink) %> - <% } %> -
-
-<% -if (comment != null) -{ - if (comment.isQualityControlFlag()) - { -%> - Vial is flagged for quality control. -<% - } - if (comment.getQualityControlComments() != null && !comment.getQualityControlComments().isEmpty()) - { -%> - <%= h(comment.getQualityControlComments()) %>
-<% - } - if (comment.isQualityControlFlagForced()) - { -%> -
NOTE: Quality control state for this vial was set manually; it will remain - <%= unsafe(comment.isQualityControlFlag() ? "flagged" : "unflagged") %> until manually changed.
-<% - } -} -if (bean.getReturnUrl() != null && !bean.getReturnUrl().isEmpty()) -{ -%> -
<%=link("return to vial view", bean.getReturnUrl() )%> -<% - } -%> diff --git a/specimen/src/org/labkey/specimen/view/specimenHeader.jsp b/specimen/src/org/labkey/specimen/view/specimenHeader.jsp deleted file mode 100644 index e9a90044ddf..00000000000 --- a/specimen/src/org/labkey/specimen/view/specimenHeader.jsp +++ /dev/null @@ -1,156 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.apache.commons.lang3.StringUtils" %> -<%@ page import="org.labkey.api.security.permissions.AdminPermission" %> -<%@ page import="org.labkey.api.study.StudyService" %> -<%@ page import="org.labkey.api.study.StudyUrls" %> -<%@ page import="org.labkey.api.util.HtmlStringBuilder" %> -<%@ page import="org.labkey.api.view.ActionURL" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> -<%@ page import="org.labkey.specimen.actions.ShowSearchAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.PtidVisit" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.SpecimensAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenHeaderBean" %> -<%@ page import="java.util.Iterator" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%! - @Override - public void addClientDependencies(ClientDependencies dependencies) - { - dependencies.add("clientapi/ext3"); - dependencies.add("study/sampleRequest.js"); - } -%> -<% - JspView me = HttpView.currentView(); - SpecimenHeaderBean bean = me.getModelBean(); - ActionURL createRequestURL = new ActionURL(ShowSearchAction.class, getContainer()); - createRequestURL.addParameter("fromGroupedView", !bean.isShowingVials()); - createRequestURL.addReturnUrl(getActionURL()); - String subjectNounSingle = StudyService.get().getSubjectNounSingular(getContainer()); - String subjectNounPlural = StudyService.get().getSubjectNounPlural(getContainer()); -%> - -<% - // boolean enableRequests = SampleManager.getInstance().getRepositorySettings(getContainer()).isEnableRequests(); - String vialLinkText = bean.isShowingVials() ? "Group vials" : "Show individual vials"; - - if (getContainer().hasPermission(getUser(), AdminPermission.class)) - { -%> -<%=link("Manage Study", urlProvider(StudyUrls.class).getManageStudyClass())%>  -<% - } -%> -<%=link(vialLinkText, bean.getOtherViewURL())%>  -<%=link("Search", ShowSearchAction.getShowSearchURL(getContainer(), bean.isShowingVials()))%>  -<%=link("Reports", urlFor(SpecimenController.AutoReportListAction.class)) %> -<% - if (!bean.getPtidVisits().isEmpty()) - { - // get the first visit label: - HtmlStringBuilder builder = HtmlStringBuilder.of() - .unsafeAppend("") - .append("This view is displaying specimens only from "); - boolean usePlural = bean.getPtidVisits().size() != 1; - if (bean.isSingleVisitFilter()) - { - builder.append((usePlural ? subjectNounPlural : subjectNounSingle).toLowerCase()) - .append(" "); - for (Iterator it = bean.getPtidVisits().iterator(); it.hasNext();) - { - String ptid = it.next().ptid(); - builder.append(ptid); - if (it.hasNext()) - builder.append(", "); - } - String visit = bean.getPtidVisits().iterator().next().visit(); - if (visit != null) - builder.append(" at visit ").append(visit); - - builder.append(".") - .unsafeAppend("
"); - } - else - { - builder.append(" the following ") - .append(subjectNounSingle.toLowerCase()) - .append("/visit ").append(usePlural ? "pairs" : "pair") - .append(":") - .unsafeAppend("
"); - for (Iterator it = bean.getPtidVisits().iterator(); it.hasNext();) - { - PtidVisit ptidVisit = it.next(); - builder.append(ptidVisit.ptid()) - .append("/") - .append(StringUtils.trimToEmpty(ptidVisit.visit())); - if (it.hasNext()) - builder.append(", "); - } - builder.append("."); - } - ActionURL noFilterUrl = getViewContext().cloneActionURL().setAction(SpecimensAction.class); -%> -

- - -
<%=builder%>
-

-<%= link("Remove " + subjectNounSingle + "/Visit Filter", noFilterUrl)%><% - } -%> -
- - - - - - - - - - -
- - - - - - -
Select request:<%=link("Create new request").onClick("createRequest(); return false;").id("sample-request-create-link")%>
-
- -
- -
-
diff --git a/specimen/src/org/labkey/specimen/view/specimenVisitReport.jsp b/specimen/src/org/labkey/specimen/view/specimenVisitReport.jsp deleted file mode 100644 index 5525ddba341..00000000000 --- a/specimen/src/org/labkey/specimen/view/specimenVisitReport.jsp +++ /dev/null @@ -1,156 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.study.SpecimenService" %> -<%@ page import="org.labkey.api.study.Study" %> -<%@ page import="org.labkey.api.study.StudyService" %> -<%@ page import="org.labkey.api.study.Visit" %> -<%@ page import="org.labkey.api.util.Pair" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.specimen.report.SpecimenReportTitle" %> -<%@ page import="org.labkey.specimen.report.SpecimenVisitReport" %> -<%@ page import="org.labkey.specimen.report.SpecimenVisitReportParameters" %> -<%@ page import="java.util.Collection" %> -<%@ page import="java.util.List" %> -<%@ page import="org.apache.commons.lang3.Strings" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - JspView me = HttpView.currentView(); - SpecimenVisitReportParameters bean = me.getModelBean(); - Study study = StudyService.get().getStudy(getContainer()); - int tableContainerId = getRequestScopedUID(); - if (study == null) - { -%> -This folder does not contain a study. -<% - return; - } - - List reports = bean.getReports(); - if (reports == null || reports.isEmpty()) - { -%> -The request has produced no records. -<% - } - else - { - for (SpecimenVisitReport report : reports) - { - Collection visits = report.getVisits(); - int colCount = visits.size() + report.getLabelDepth(); -%> -
-- <% - for (int i = 0; i < colCount; i++) - { - %><% - } - %> - - - - - <% - if (report.getLabelDepth() > 0) - { - %><% - } - for (Visit visit : visits) - { - String label = visit.getDisplayString(); - %><% - } - %> - - <% - if (report.getRows() == null || report.getRows().isEmpty()) - { - %> - - <% - } - else - { - // pre-compute layout for header rows (Pair) - int width = report.getLabelDepth(); - int rows = report.getRows().size(); - Pair[][] rowtitles = new Pair[rows][]; - int rowIndex = -1; - - for (SpecimenVisitReport.Row row : (Collection) report.getRows()) - { - rowIndex++; - SpecimenReportTitle[] currentTitleHierarchy = row.getTitleHierarchy(); - rowtitles[rowIndex] = new Pair[width]; - for (int i=0 ; i(currentTitleHierarchy[i], 1); - } - - for (int row = rows-1 ; row > 0 ; row--) - { - for (int col=0 ; col < width ; col++) - { - if (!Strings.CS.equals(rowtitles[row][col].first.getValue(), rowtitles[row-1][col].first.getValue())) - break; - rowtitles[row-1][col].second += rowtitles[row][col].second; - rowtitles[row][col].second = 0; - } - } - - rowIndex = -1; - for (SpecimenVisitReport.Row row : (Collection) report.getRows()) - { - rowIndex++; - %><% - int colMax = SpecimenService.get().getRequestCustomizer().omitTypeGroupingsWhenReporting() ? 1 : width; - - for (int col = 0; col<% - } - if (SpecimenService.get().getRequestCustomizer().omitTypeGroupingsWhenReporting()){ %><% } - - for (Visit visit : visits) - { - %><% - } - %><% - } - } -%> -
<%= h(report.getTitle())%>
 <%= h(label) %>
No data to show.
- <%= h(title) %> - <% - %><%= unsafe(row.getCellHtml(visit)) %><% - %>


-
-<% - } - } -%> diff --git a/specimen/src/org/labkey/specimen/view/updateComments.jsp b/specimen/src/org/labkey/specimen/view/updateComments.jsp deleted file mode 100644 index 7eb8b5d6f46..00000000000 --- a/specimen/src/org/labkey/specimen/view/updateComments.jsp +++ /dev/null @@ -1,262 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.apache.commons.lang3.StringUtils" %> -<%@ page import="org.labkey.api.data.Container" %> -<%@ page import="org.labkey.api.data.TableInfo" %> -<%@ page import="org.labkey.api.security.User" %> -<%@ page import="org.labkey.api.security.permissions.UpdatePermission" %> -<%@ page import="org.labkey.api.specimen.Vial" %> -<%@ page import="org.labkey.api.study.Dataset" %> -<%@ page import="org.labkey.api.study.Study" %> -<%@ page import="org.labkey.api.study.StudyInternalService" %> -<%@ page import="org.labkey.api.study.StudyService" %> -<%@ page import="org.labkey.api.view.ActionURL" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.NavTree" %> -<%@ page import="org.labkey.api.view.PopupMenu" %> -<%@ page import="org.labkey.api.view.template.ClientDependencies" %> -<%@ page import="org.labkey.api.view.template.FrameFactoryClassic" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.UpdateCommentsAction" %> -<%@ page import="org.labkey.specimen.actions.UpdateSpecimenCommentsBean" %> -<%@ page import="org.labkey.specimen.settings.SettingsManager" %> -<%@ page import="java.util.Map" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%! - @Override - public void addClientDependencies(ClientDependencies dependencies) - { - // TODO: --Ext3-- This should be declared as part of the included views - dependencies.add("clientapi/ext3"); - } -%> -<% - JspView me = HttpView.currentView(); - UpdateSpecimenCommentsBean bean = me.getModelBean(); - Container container = getContainer(); - - NavTree copyButton = createCopyCommentButton(bean.getParticipantVisitMap(), StudyService.get().getStudy(container), getUser()); -%> - - - - - -<% - if (SettingsManager.get().getDisplaySettings(container).isEnableManualQCFlagging()) - { - FrameFactoryClassic.startTitleFrame(out, "Quality Control Flags", null, null, null); - if (bean.isMixedFlagState()) - { -%> - Note: A subset of the selected vials have quality control flags. See vial list below for details.

-<% - } -%> - What quality control state should be applied to the selected vials?
- Do not change quality control state
-<% - // allow users to flag if our current state is mixed or unflagged: - if (bean.isMixedFlagState() || !bean.isCurrentFlagState()) - { -%> - Add quality control flag
-<% - } - // allow users to unflag if our current state is mixed or flagged: - if (bean.isMixedFlagState() || bean.isCurrentFlagState()) - { -%> - Remove quality control flag

-<% - } - FrameFactoryClassic.endTitleFrame(out); - } - FrameFactoryClassic.startTitleFrame(out, "Comments", null, null, null); -%> - - <% - if (bean.isMixedComments()) - { - %> - Note: Some or all of the selected vials have existing comments. See vial list below for details.

- What action should be performed on vials with existing comments?
- Replace existing comments with new comments
- Append new comments to existing comments
- Do not change comments for vials with existing comments

- New comment:
- <% - } - else - { - %> - - <% - } - %> - - - <% - for (Vial vial : bean.getVials()) - { - %> - - <% - } - %> - - - - - - - -
-
- -
- <%= button("Save Changes").submit(true) %> - <% - if (!StringUtils.isBlank(bean.getCurrentComment()) && copyButton != null) - { - PopupMenu menu = new PopupMenu(copyButton); - menu.render(out); - } - %> - <%= button("Cancel").href(new ActionURL(bean.getReferrer())) %> -
- -<% - FrameFactoryClassic.endTitleFrame(out); - FrameFactoryClassic.startTitleFrame(out, "Selected Vials", null, null, null); -%> -<% me.include(bean.getSpecimenQueryView(), out); %> -<% - FrameFactoryClassic.endTitleFrame(out); -%> - -<%! - private NavTree createCopyCommentButton(Map> pvMap, Study study, User user) - { - Integer participantCommentDatasetId = StudyInternalService.get().getParticipantCommentDatasetId(study); - Integer participantVisitCommentDatasetId = StudyInternalService.get().getParticipantVisitCommentDatasetId(study); - boolean hasParticipantMenu = participantCommentDatasetId != null && participantCommentDatasetId != -1; - boolean hasParticipantVisitMenu = participantVisitCommentDatasetId != null && participantVisitCommentDatasetId != -1; - - if (hasParticipantMenu) - { - Dataset ds = StudyService.get().getDataset(study.getContainer(), participantCommentDatasetId); - TableInfo t = null==ds ? null : ds.getTableInfo(user); - hasParticipantMenu = t != null && t.hasPermission(user, UpdatePermission.class); - } - - if (hasParticipantVisitMenu) - { - Dataset ds = StudyService.get().getDataset(study.getContainer(), participantVisitCommentDatasetId); - TableInfo t = null==ds ? null : ds.getTableInfo(user); - hasParticipantVisitMenu = t != null && t.hasPermission(user, UpdatePermission.class); - } - - if (hasParticipantMenu || hasParticipantVisitMenu) - { - NavTree button = new NavTree("Copy or Move Comment(s)"); - - NavTree moveButton = new NavTree("Move"); - moveButton.setId("Comment:Move"); - NavTree copyButton = new NavTree("Copy"); - copyButton.setId("Comment:Copy"); - - button.addChild(moveButton); - button.addChild(copyButton); - - addParticipantMenuItems(moveButton, pvMap, hasParticipantMenu, hasParticipantVisitMenu, true); - addParticipantMenuItems(copyButton, pvMap, hasParticipantMenu, hasParticipantVisitMenu, false); - - return button; - } - return null; - } - - private void addParticipantMenuItems(NavTree button, Map> pvMap, - boolean hasParticipantMenu, boolean hasParticipantVisitMenu, boolean isMove) - { - String subjectNoun = StudyService.get().getSubjectNounSingular(getContainer()); - // participant comments - if (hasParticipantMenu) - { - StringBuilder sb = new StringBuilder(); - NavTree participantItem = new NavTree("To " + subjectNoun, "#"); - participantItem.setId(isMove ? "Move:ToParticipant" : "Copy:ToParticipant"); - button.addChild(participantItem); - - for (String ptid : pvMap.keySet()) - { - NavTree subItem = new NavTree(ptid, "#"); - sb.setLength(0); - if (isMove) - sb.append("if (confirm('This will permanently remove all vial comments for the displayed vials. Continue?')){"); - sb.append("document.updateComments.copyToParticipant.value='true';"); - sb.append("document.updateComments.copyParticipantId.value='").append(ptid).append("';"); - sb.append("document.updateComments.deleteVialComment.value='").append(isMove).append("';"); - sb.append("document.updateComments.submit()"); - if (isMove) - sb.append("}"); - - subItem.setScript(sb.toString()); - if (isMove) - subItem.setId("MovePtid:" + ptid); - else - subItem.setId("CopyPtid:" + ptid); - participantItem.addChild(subItem); - } - } - - // participant/visit comments - if (hasParticipantVisitMenu) - { - StringBuilder sb = new StringBuilder(); - NavTree participantVisitItem = new NavTree("To " + subjectNoun + "/Visit", "#"); - participantVisitItem.setId(isMove ? "Move:ToParticipantVisit" : "Copy:ToParticipantVisit"); - button.addChild(participantVisitItem); - for (Map.Entry> entry : pvMap.entrySet()) - { - NavTree ptidItem = new NavTree(entry.getKey()); - ptidItem.setId("PtidVisit:" + entry.getKey()); - - for (Map.Entry visitEntry : entry.getValue().entrySet()) - { - NavTree visitItem = new NavTree(visitEntry.getKey()); - sb.setLength(0); - if (isMove) - sb.append("if (confirm('This will permanently remove all vial comments for the displayed vials. Continue?')){"); - sb.append("document.updateComments.copyToParticipant.value='true';"); - sb.append("document.updateComments.copySampleId.value='").append(visitEntry.getValue()).append("';"); - sb.append("document.updateComments.deleteVialComment.value='").append(isMove).append("';"); - sb.append("document.updateComments.submit()"); - if (isMove) - sb.append("}"); - - visitItem.setScript(sb.toString()); - ptidItem.addChild(visitItem); - } - participantVisitItem.addChild(ptidItem); - } - } - } -%> diff --git a/specimen/src/org/labkey/specimen/view/uploadSimpleSpecimens.jsp b/specimen/src/org/labkey/specimen/view/uploadSimpleSpecimens.jsp deleted file mode 100644 index 3e7a0fe60cc..00000000000 --- a/specimen/src/org/labkey/specimen/view/uploadSimpleSpecimens.jsp +++ /dev/null @@ -1,154 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.data.Container" %> -<%@ page import="org.labkey.api.study.Study" %> -<%@ page import="org.labkey.api.study.StudyService" %> -<%@ page import="org.labkey.api.study.TimepointType" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.specimen.actions.ShowUploadSpecimensAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.GetSpecimenExcelAction" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<%@ taglib prefix="labkey" uri="http://www.labkey.org/taglib" %> -<% - JspView me = HttpView.currentView(); - ShowUploadSpecimensAction.UploadSpecimensForm bean = me.getModelBean(); - Container c = getContainer(); - Study study = StudyService.get().getStudy(c); -%> - -Use this form to insert or update specimens in the repository.
-<%=link("Download a template workbook", GetSpecimenExcelAction.class)%>
- -

<%=link("Show Expected Data Fields").onClick("document.getElementById('expectedDataFields').style.display = 'block'; document.getElementById('showExpectedDataFieldsDiv').style.display = 'none'; return false;").id("showExpectedDataFieldsLink") %>
- - -
-Paste data in the area below - -
- -<% - if (!bean.isNoSpecimens()) - { -%> -

- - -
- - -<% - } - else - { -%> - -<% - } -%> -

- <%= button("Submit").submit(true) %> <%= generateBackButton("Cancel") %> - - diff --git a/specimen/src/org/labkey/specimen/view/viewRequestsHeader.jsp b/specimen/src/org/labkey/specimen/view/viewRequestsHeader.jsp deleted file mode 100644 index 530e1db4f95..00000000000 --- a/specimen/src/org/labkey/specimen/view/viewRequestsHeader.jsp +++ /dev/null @@ -1,58 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.view.ActionURL" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.api.view.JspView" %> -<%@ page import="org.labkey.api.view.ViewContext" %> -<%@ page import="org.labkey.specimen.SpecimenRequestStatus" %> -<%@ page import="org.labkey.specimen.actions.ViewRequestsHeaderBean" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - JspView me = HttpView.currentView(); - ViewContext context = getViewContext(); - ViewRequestsHeaderBean bean = me.getModelBean(); - ActionURL userLink = context.cloneActionURL(); -%> -<%= link("All User Requests", userLink.deleteParameter(ViewRequestsHeaderBean.PARAM_CREATEDBY)) %> -<%= link("My Requests", userLink.replaceParameter(ViewRequestsHeaderBean.PARAM_CREATEDBY, getUser().getDisplayName(getUser()))) %> -<% addHandler("viewRequestsSelect", "change", "document.location=options[selectedIndex].value;"); %> -Filter by status: -<% - String userFilter = getActionURL().getParameter(ViewRequestsHeaderBean.PARAM_CREATEDBY); - if (userFilter != null) - { -%> -Showing requests from user <%= h(userFilter) %> -<% - } -%> diff --git a/specimen/src/org/labkey/specimen/view/webPart.jsp b/specimen/src/org/labkey/specimen/view/webPart.jsp deleted file mode 100644 index 8f2fdaf5f7e..00000000000 --- a/specimen/src/org/labkey/specimen/view/webPart.jsp +++ /dev/null @@ -1,368 +0,0 @@ -<% -/* - * Copyright (c) 2008-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. - */ -%> -<%@ page import="org.labkey.api.data.Container" %> -<%@ page import="org.labkey.api.security.User" %> -<%@ page import="org.labkey.api.security.permissions.AdminPermission" %> -<%@ page import="org.labkey.api.study.StudyUrls" %> -<%@ page import="org.labkey.api.util.HtmlString" %> -<%@ page import="org.labkey.api.view.ActionURL" %> -<%@ page import="org.labkey.api.view.HttpView" %> -<%@ page import="org.labkey.specimen.actions.ShowSearchAction" %> -<%@ page import="org.labkey.specimen.actions.ShowUploadSpecimensAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.AutoReportListAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ShowCreateSpecimenRequestAction" %> -<%@ page import="org.labkey.specimen.actions.SpecimenController.ViewRequestsAction" %> -<%@ page import="org.labkey.specimen.security.permissions.RequestSpecimensPermission" %> -<%@ page import="org.labkey.specimen.settings.SettingsManager" %> -<%@ page import="org.labkey.specimen.view.SpecimenWebPart" %> -<%@ page extends="org.labkey.api.jsp.JspBase" %> -<% - SpecimenWebPart webpart = HttpView.currentView(); - SpecimenWebPart.SpecimenWebPartBean bean = webpart.getModelBean(); - - Container c = getContainer(); - User user = getUser(); - String time = Long.toString(System.currentTimeMillis()); - boolean isAdmin = c.hasPermission(user, AdminPermission.class); - - String contentSpanName = "specimen-browse-webpart-content" + time; - String groupHeading1 = "groupHeading1-" + time; - String groupHeading2 = "groupHeading2-" + time; - String group1 = "group1-" + time; - String group2 = "group2-" + time; - String groupControl1 = "groupControl1-" + time; - String groupControl2 = "groupControl2-" + time; - - getPageConfig().addHandlerForQuerySelector("A.webPart_toggleLink","click","return LABKEY.Utils.toggleLink(this, false);"); -%> - -

diff --git a/specimen/src/org/labkey/specimen/writer/AbstractSpecimenWriter.java b/specimen/src/org/labkey/specimen/writer/AbstractSpecimenWriter.java deleted file mode 100644 index 23445298777..00000000000 --- a/specimen/src/org/labkey/specimen/writer/AbstractSpecimenWriter.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2013-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.writer; - -import org.labkey.api.study.writer.AbstractStudyContext; -import org.labkey.api.study.writer.SimpleStudyWriter; -import org.labkey.study.xml.StudyDocument; - -/** - * User: kevink - * Date: 6/13/13 - */ -abstract class AbstractSpecimenWriter implements SimpleStudyWriter -{ - protected static final String DEFAULT_DIRECTORY = "specimens"; - - protected StudyDocument.Study.Specimens ensureSpecimensElement(AbstractStudyContext ctx) throws Exception - { - StudyDocument.Study studyXml = ctx.getXml(); - StudyDocument.Study.Specimens specimensXml; - if (studyXml.isSetSpecimens()) - { - specimensXml = studyXml.getSpecimens(); - assert DEFAULT_DIRECTORY.equals(specimensXml.getDir()); - } - else - { - specimensXml = studyXml.addNewSpecimens(); - specimensXml.setDir(DEFAULT_DIRECTORY); - } - - return specimensXml; - } -} diff --git a/specimen/src/org/labkey/specimen/writer/LocationSpecimenWriter.java b/specimen/src/org/labkey/specimen/writer/LocationSpecimenWriter.java deleted file mode 100644 index 053357b0ae6..00000000000 --- a/specimen/src/org/labkey/specimen/writer/LocationSpecimenWriter.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2013-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.writer; - -import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.TableInfo; -import org.labkey.api.data.dialect.SqlDialect; -import org.labkey.specimen.importer.ImportableColumn; -import org.labkey.api.study.writer.SimpleStudyExportContext; - -import java.util.Collection; - -/** - * User: cnathe - * Date: 2/11/13 - */ -public class LocationSpecimenWriter extends StandardSpecimenWriter -{ - @Override - protected SQLFragment generateSql(SimpleStudyExportContext ctx, TableInfo tinfo, Collection columns) - { - SqlDialect d = tinfo.getSqlDialect(); - - // the generated SQL for the study.Location table needs to take into consideration the "maskClinic" setting on the export context - SQLFragment sql = new SQLFragment().append("SELECT "); - String comma = ""; - - for (ImportableColumn column : columns) - { - sql.append(comma); - - // when masking, use generic label for clinics and remove the LabwareLabCode, Description, and Address fields for clinics - if (ctx.isMaskClinic() && column.getDbColumnName().equalsIgnoreCase("label")) - sql.append("CASE WHEN Clinic = ").append(d.getBooleanTRUE()).append(" THEN ").appendValue("Clinic").append(" ELSE Label END AS Label"); - else if (ctx.isMaskClinic() && column.isMaskOnExport()) - sql.append(getMaskClinicSql(d, column.getDbColumnName())); - else - sql.append(column.getDbColumnName()); - - comma = ", "; - } - - sql.append(" FROM "); - sql.append(tinfo, "ti"); - sql.append(" WHERE Container = ? ORDER BY ExternalId"); - - sql.add(ctx.getContainer()); - return sql; - } - - private SQLFragment getMaskClinicSql(SqlDialect d, String colName) - { - SQLFragment sql = new SQLFragment(); - sql.append("CASE WHEN Clinic = "); - sql.append(d.getBooleanTRUE()); - sql.append(" THEN NULL ELSE "); - sql.append(colName); - sql.append(" END AS "); - sql.append(colName); - return sql; - } -} diff --git a/specimen/src/org/labkey/specimen/writer/SpecimenArchiveDataTypes.java b/specimen/src/org/labkey/specimen/writer/SpecimenArchiveDataTypes.java deleted file mode 100644 index 60d6d5d3127..00000000000 --- a/specimen/src/org/labkey/specimen/writer/SpecimenArchiveDataTypes.java +++ /dev/null @@ -1,29 +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.writer; - -import org.labkey.api.specimen.SpecimenMigrationService; - -public class SpecimenArchiveDataTypes -{ - public static final String SCHEMA_FILENAME = "specimens_metadata.xml"; - public static final String SPECIMEN_SETTINGS = "Specimen Settings"; - public static final String SPECIMENS = SpecimenMigrationService.SPECIMENS_ARCHIVE_TYPE; - - private SpecimenArchiveDataTypes() - { - } -} diff --git a/specimen/src/org/labkey/specimen/writer/SpecimenArchiveWriter.java b/specimen/src/org/labkey/specimen/writer/SpecimenArchiveWriter.java deleted file mode 100644 index b7d836ca6db..00000000000 --- a/specimen/src/org/labkey/specimen/writer/SpecimenArchiveWriter.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 2009-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.writer; - -import org.junit.Assert; -import org.junit.Test; -import org.labkey.api.collections.CaseInsensitiveHashMap; -import org.labkey.api.data.BaseColumnInfo; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.Container; -import org.labkey.api.data.JdbcType; -import org.labkey.api.data.PHI; -import org.labkey.api.data.TableInfo; -import org.labkey.api.data.TableInfoWriter; -import org.labkey.api.exp.property.Domain; -import org.labkey.api.exp.property.DomainProperty; -import org.labkey.specimen.SpecimenColumns; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.api.specimen.model.SpecimenTablesProvider; -import org.labkey.api.study.Study; -import org.labkey.api.study.writer.SimpleStudyExportContext; -import org.labkey.api.writer.VirtualFile; -import org.labkey.data.xml.ColumnType; -import org.labkey.data.xml.TableType; -import org.labkey.data.xml.TablesDocument; -import org.labkey.data.xml.TablesType; -import org.labkey.specimen.writer.StandardSpecimenWriter.QueryInfo; -import org.labkey.study.xml.StudyDocument; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * User: adam - * Date: Apr 23, 2009 - * Time: 11:28:37 AM - */ -public class SpecimenArchiveWriter extends AbstractSpecimenWriter -{ - @Override - public String getDataType() - { - return SpecimenArchiveDataTypes.SPECIMENS; - } - - @Override - public void write(Study study, SimpleStudyExportContext ctx, VirtualFile root) throws Exception - { - StudyDocument.Study.Specimens specimensXml = ensureSpecimensElement(ctx); - VirtualFile specimensDir = root.getDir(DEFAULT_DIRECTORY); - String archiveName = specimensDir.makeLegalName(study.getShortName() + ".specimens"); - - try (VirtualFile zip = specimensDir.createZipArchive(archiveName)) - { - if (!zip.equals(specimensDir)) // MemoryVirtualFile doesn't add a zip archive, it just returns vf - specimensXml.setFile(archiveName); - - SpecimenSchema schema = SpecimenSchema.get(); - - new LocationSpecimenWriter().write(new QueryInfo(schema.getTableInfoLocation(ctx.getContainer()), "labs", SpecimenColumns.SITE_COLUMNS), ctx, zip); - new StandardSpecimenWriter().write(new QueryInfo(schema.getTableInfoSpecimenPrimaryType(ctx.getContainer()), "primary_types", SpecimenColumns.PRIMARYTYPE_COLUMNS), ctx, zip); - new StandardSpecimenWriter().write(new QueryInfo(schema.getTableInfoSpecimenAdditive(ctx.getContainer()), "additives", SpecimenColumns.ADDITIVE_COLUMNS), ctx, zip); - new StandardSpecimenWriter().write(new QueryInfo(schema.getTableInfoSpecimenDerivative(ctx.getContainer()), "derivatives", SpecimenColumns.DERIVATIVE_COLUMNS), ctx, zip); - - new SpecimenWriter().write(study, ctx, zip); - } - - // Create specimens metadata file and write out the table infos for the specimens, event, and vial tables - TablesDocument tablesDoc = TablesDocument.Factory.newInstance(); - SpecimenTablesProvider tablesProvider = new SpecimenTablesProvider(ctx.getContainer(), ctx.getUser(), null); - TablesType tablesXml = tablesDoc.addNewTables(); - Map specimenTables = new HashMap<>(); - - TableInfo eventTable = tablesProvider.getTableInfoIfExists(SpecimenTablesProvider.SPECIMEN_EVENT_TABLE_NAME); - if (eventTable != null) - specimenTables.put(SpecimenTablesProvider.SPECIMEN_EVENT_TABLE_NAME, eventTable); - - TableInfo specimenTable = tablesProvider.getTableInfoIfExists(SpecimenTablesProvider.SPECIMEN_TABLE_NAME); - if (specimenTable != null) - specimenTables.put(SpecimenTablesProvider.SPECIMEN_TABLE_NAME, specimenTable); - - TableInfo vialTable = tablesProvider.getTableInfoIfExists(SpecimenTablesProvider.VIAL_TABLE_NAME); - if (vialTable != null) - specimenTables.put(SpecimenTablesProvider.VIAL_TABLE_NAME, vialTable); - - for (Map.Entry entry : specimenTables.entrySet()) - { - TableType tableXml = tablesXml.addNewTable(); - - Domain domain = tablesProvider.getDomain(entry.getKey(), false, false); - TableInfo table = entry.getValue(); - List columns = new ArrayList<>(); - - for (ColumnInfo col : table.getColumns()) - { - if (!shouldRemovePhi(ctx.getPhiLevel(), col) || !col.isKeyField()) - columns.add(col); - } - - SpecimenTableInfoWriter xmlWriter = new SpecimenTableInfoWriter(ctx.getContainer(), table, entry.getKey(), domain, columns); - xmlWriter.writeTable(tableXml); - } - specimensDir.saveXmlBean(SpecimenArchiveDataTypes.SCHEMA_FILENAME, tablesDoc); - } - - private static boolean shouldRemovePhi(PHI exportPhiLevel, ColumnInfo column) - { - return !column.getPHI().isExportLevelAllowed(exportPhiLevel); - } - - @Override - public boolean includeWithTemplate() - { - return false; - } - - private static class SpecimenTableInfoWriter extends TableInfoWriter - { - private final Map _properties = new CaseInsensitiveHashMap<>(); - private final String _name; - - protected SpecimenTableInfoWriter(Container c, TableInfo ti, String tableName, Domain domain, Collection columns) - { - super(c, ti, columns); - _name = tableName; - - for (DomainProperty prop : domain.getProperties()) - _properties.put(prop.getName(), prop); - } - - @Override - public void writeTable(TableType tableXml) - { - super.writeTable(tableXml); - tableXml.setTableName(_name); // Use specimen table name, not provisioned storage name - } - - @Override // No reason to ever export list PropertyURIs - protected String getPropertyURI(ColumnInfo column) - { - return null; - } - - @Override - public void writeColumn(ColumnInfo column, ColumnType columnXml) - { - super.writeColumn(column, columnXml); - - // Since the specimen tables only return a SchemaTableInfo, the column names will be decapitalized, - // to preserve casing we defer to the DomainProperty names. This is mostly cosmetic - if (_properties.containsKey(column.getName())) - { - DomainProperty dp = _properties.get(column.getName()); - if (dp.getName() != null) - columnXml.setColumnName(dp.getName()); - if (0 != dp.getScale()) - columnXml.setScale(dp.getScale()); - } - } - } - - public static class TestCase extends Assert - { - @Test - public void testShouldRemovePhi() - { - var ciNotPhi = new BaseColumnInfo("test", JdbcType.OTHER); - ciNotPhi.setPHI(PHI.NotPHI); - var ciLimitedPhi = new BaseColumnInfo("test", JdbcType.OTHER); - ciLimitedPhi.setPHI(PHI.Limited); - var ciPhi = new BaseColumnInfo("test", JdbcType.OTHER); - ciPhi.setPHI(PHI.PHI); - var ciRestrictedPhi = new BaseColumnInfo("test", JdbcType.OTHER); - ciRestrictedPhi.setPHI(PHI.Restricted); - - // should remove if it is at or above PHI export level - assertTrue(shouldRemovePhi(PHI.Restricted, ciRestrictedPhi)); - assertTrue(shouldRemovePhi(PHI.PHI, ciRestrictedPhi)); - assertTrue(shouldRemovePhi(PHI.Limited, ciRestrictedPhi)); - assertTrue(shouldRemovePhi(PHI.PHI, ciPhi)); - assertTrue(shouldRemovePhi(PHI.Limited, ciPhi)); - assertTrue(shouldRemovePhi(PHI.Limited, ciLimitedPhi)); - - // shouldn't remove if it is not at or above PHI export level - assertFalse(shouldRemovePhi(PHI.Restricted, ciPhi)); - assertFalse(shouldRemovePhi(PHI.Restricted, ciLimitedPhi)); - assertFalse(shouldRemovePhi(PHI.PHI, ciLimitedPhi)); - assertFalse(shouldRemovePhi(PHI.Restricted, ciNotPhi)); - assertFalse(shouldRemovePhi(PHI.PHI, ciNotPhi)); - assertFalse(shouldRemovePhi(PHI.Limited, ciNotPhi)); - } - } -} diff --git a/specimen/src/org/labkey/specimen/writer/SpecimenSettingsWriter.java b/specimen/src/org/labkey/specimen/writer/SpecimenSettingsWriter.java deleted file mode 100644 index 33f99f2e162..00000000000 --- a/specimen/src/org/labkey/specimen/writer/SpecimenSettingsWriter.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright (c) 2013-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.writer; - -import org.jetbrains.annotations.Nullable; -import org.labkey.api.security.GroupManager; -import org.labkey.api.study.Location; -import org.labkey.api.study.Study; -import org.labkey.api.study.writer.SimpleStudyExportContext; -import org.labkey.api.writer.VirtualFile; -import org.labkey.security.xml.GroupType; -import org.labkey.security.xml.GroupsType; -import org.labkey.specimen.SpecimenRequestManager; -import org.labkey.specimen.SpecimenRequestManager.SpecimenRequestInput; -import org.labkey.specimen.SpecimenRequestStatus; -import org.labkey.specimen.actions.ManageReqsBean; -import org.labkey.specimen.importer.RequestabilityManager; -import org.labkey.specimen.model.SpecimenRequestActor; -import org.labkey.specimen.requirements.SpecimenRequestRequirement; -import org.labkey.specimen.requirements.SpecimenRequestRequirementProvider; -import org.labkey.specimen.settings.DisplaySettings; -import org.labkey.specimen.settings.RepositorySettings; -import org.labkey.specimen.settings.RequestNotificationSettings; -import org.labkey.specimen.settings.SettingsManager; -import org.labkey.specimen.settings.StatusSettings; -import org.labkey.study.xml.DefaultRequirementType; -import org.labkey.study.xml.DefaultRequirementsType; -import org.labkey.study.xml.SpecimenRepositoryType; -import org.labkey.study.xml.SpecimenSettingsType; -import org.labkey.study.xml.SpecimensDocument; -import org.labkey.study.xml.StudyDocument; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -public class SpecimenSettingsWriter extends AbstractSpecimenWriter -{ - private static final String DEFAULT_SETTINGS_FILE = "specimen_settings.xml"; - - @Nullable - @Override - public String getDataType() - { - return SpecimenArchiveDataTypes.SPECIMEN_SETTINGS; - } - - @Override - public void write(Study study, SimpleStudyExportContext ctx, VirtualFile root) throws Exception - { - StudyDocument.Study.Specimens specimensXml = ensureSpecimensElement(ctx); - specimensXml.setSettings(DEFAULT_SETTINGS_FILE); - - VirtualFile vf = root.getDir(DEFAULT_DIRECTORY); - writeSettings(study, ctx, vf); - } - - private void writeSettings(Study study, SimpleStudyExportContext ctx, VirtualFile dir) throws Exception - { - SpecimensDocument xmlSettingsDoc = SpecimensDocument.Factory.newInstance(); - SpecimenSettingsType xmlSettings = xmlSettingsDoc.addNewSpecimens(); - RepositorySettings repositorySettings = SettingsManager.get().getRepositorySettings(study.getContainer()); - - writeRepositoryType(xmlSettings, repositorySettings); - writeLocationTypes(xmlSettings, study); - writeSpecimenGroupings(xmlSettings, repositorySettings); - writeDisplaySettings(xmlSettings, ctx); - - // these settings only apply if repository type is Advanced and specimen requests are enabled - if (!repositorySettings.isSimple() && repositorySettings.isEnableRequests()) - { - writeRequestStatuses(xmlSettings, study, ctx); - writeActorsAndGroups(xmlSettings, study); - writeDefaultRequirements(xmlSettings, study, ctx); - writeRequestForm(xmlSettings, ctx); - writeNotifications(xmlSettings, ctx); - writeRequestabilityRules(xmlSettings, ctx); - } - - // write out the xml - dir.saveXmlBean(DEFAULT_SETTINGS_FILE, xmlSettingsDoc); - } - - private void writeRepositoryType(SpecimenSettingsType specimenSettingsType, RepositorySettings repositorySettings) - { - specimenSettingsType.setRepositoryType(repositorySettings.isSimple() ? SpecimenRepositoryType.STANDARD : SpecimenRepositoryType.ADVANCED); - specimenSettingsType.setEnableRequests(repositorySettings.isEnableRequests()); - specimenSettingsType.setEditableRepository(repositorySettings.isSpecimenDataEditable()); - } - - private void writeLocationTypes(SpecimenSettingsType specimenSettingsType, Study study) - { - SpecimenSettingsType.LocationTypes xmlLocationTypes = specimenSettingsType.addNewLocationTypes(); - xmlLocationTypes.addNewRepository().setAllowRequests(study.isAllowReqLocRepository()); - xmlLocationTypes.addNewClinic().setAllowRequests(study.isAllowReqLocClinic()); - xmlLocationTypes.addNewSiteAffiliatedLab().setAllowRequests(study.isAllowReqLocSal()); - xmlLocationTypes.addNewEndpointLab().setAllowRequests(study.isAllowReqLocEndpoint()); - } - - private void writeSpecimenGroupings(SpecimenSettingsType specimenSettingsType, RepositorySettings repositorySettings) - { - ArrayList groupings = repositorySettings.getSpecimenWebPartGroupings(); - if (!groupings.isEmpty()) - { - SpecimenSettingsType.WebPartGroupings xmlWebPartGroupings = specimenSettingsType.addNewWebPartGroupings(); - for (String[] grouping : groupings) - { - SpecimenSettingsType.WebPartGroupings.Grouping xmlGrouping = xmlWebPartGroupings.addNewGrouping(); - xmlGrouping.setGroupByArray(grouping); - } - } - } - - private void writeRequestStatuses(SpecimenSettingsType specimenSettingsType, Study study, SimpleStudyExportContext ctx) - { - SpecimenSettingsType.RequestStatuses xmlRequestStatuses = null; - Collection statuses = SpecimenRequestManager.get().getRequestStatuses(study.getContainer(), ctx.getUser()); - if (!statuses.isEmpty()) - { - for (SpecimenRequestStatus status : statuses) - { - if (!status.isSystemStatus()) // don't export system statuses - { - if (xmlRequestStatuses == null) xmlRequestStatuses = specimenSettingsType.addNewRequestStatuses(); - SpecimenSettingsType.RequestStatuses.Status xmlStatus = xmlRequestStatuses.addNewStatus(); - xmlStatus.setLabel(status.getLabel()); - xmlStatus.setFinalState(status.isFinalState()); - xmlStatus.setLockSpecimens(status.isSpecimensLocked()); - } - } - } - StatusSettings statusSettings = SettingsManager.get().getStatusSettings(study.getContainer()); - if (!statusSettings.isUseShoppingCart()) // default is to use shopping cart - { - if (xmlRequestStatuses == null) xmlRequestStatuses = specimenSettingsType.addNewRequestStatuses(); - xmlRequestStatuses.setMultipleSearch(statusSettings.isUseShoppingCart()); - } - } - - private void writeActorsAndGroups(SpecimenSettingsType specimenSettingsType, Study study) - { - SpecimenRequestActor[] actors = SpecimenRequestRequirementProvider.get().getActors(study.getContainer()); - if (actors != null && actors.length > 0) - { - SpecimenSettingsType.RequestActors xmlRequestActors = specimenSettingsType.addNewRequestActors(); - for (SpecimenRequestActor actor : actors) - { - SpecimenSettingsType.RequestActors.Actor xmlActor = xmlRequestActors.addNewActor(); - xmlActor.setLabel(actor.getLabel()); - xmlActor.setType(actor.isPerSite() ? SpecimenSettingsType.RequestActors.Actor.Type.LOCATION : SpecimenSettingsType.RequestActors.Actor.Type.STUDY); - - GroupsType xmlGroups = xmlActor.addNewGroups(); - if (!actor.isPerSite()) - { - if (actor.getMembers().length > 0) - { - GroupType xmlGroup = xmlGroups.addNewGroup(); - writeActorGroup(actor, null, xmlGroup); - } - } - else - { - for (Location location : study.getLocations()) - { - if (actor.getMembers(location).length > 0) - { - GroupType xmlGroup = xmlGroups.addNewGroup(); - writeActorGroup(actor, location, xmlGroup); - } - } - } - } - } - } - - private void writeDefaultRequirements(SpecimenSettingsType specimenSettingsType, Study study, SimpleStudyExportContext ctx) - { - ManageReqsBean defRequirements = new ManageReqsBean(ctx.getUser(), study.getContainer()); - SpecimenSettingsType.DefaultRequirements xmlDefRequirements = null; - if (defRequirements.getOriginatorRequirements().length > 0) - { - xmlDefRequirements = specimenSettingsType.addNewDefaultRequirements(); - DefaultRequirementsType xmlOrigLabReq = xmlDefRequirements.addNewOriginatingLab(); - for (SpecimenRequestRequirement req : defRequirements.getOriginatorRequirements()) - writeDefaultRequirement(xmlOrigLabReq, req); - } - if (defRequirements.getProviderRequirements().length > 0) - { - if (xmlDefRequirements == null) xmlDefRequirements = specimenSettingsType.addNewDefaultRequirements(); - DefaultRequirementsType xmlProviderReq = xmlDefRequirements.addNewProvidingLab(); - for (SpecimenRequestRequirement req : defRequirements.getProviderRequirements()) - writeDefaultRequirement(xmlProviderReq, req); - } - if (defRequirements.getReceiverRequirements().length > 0) - { - if (xmlDefRequirements == null) xmlDefRequirements = specimenSettingsType.addNewDefaultRequirements(); - DefaultRequirementsType xmlReceiverReq = xmlDefRequirements.addNewReceivingLab(); - for (SpecimenRequestRequirement req : defRequirements.getReceiverRequirements()) - writeDefaultRequirement(xmlReceiverReq, req); - } - if (defRequirements.getGeneralRequirements().length > 0) - { - if (xmlDefRequirements == null) xmlDefRequirements = specimenSettingsType.addNewDefaultRequirements(); - DefaultRequirementsType xmlGeneralReq = xmlDefRequirements.addNewGeneral(); - for (SpecimenRequestRequirement req : defRequirements.getGeneralRequirements()) - writeDefaultRequirement(xmlGeneralReq, req); - } - } - - private void writeDefaultRequirement(DefaultRequirementsType xmlReqType, SpecimenRequestRequirement req) - { - DefaultRequirementType xmlReq = xmlReqType.addNewRequirement(); - xmlReq.setActor(req.getActor().getLabel()); - xmlReq.setDescription(req.getDescription()); - } - - private void writeActorGroup(SpecimenRequestActor actor, @Nullable Location location, GroupType xmlGroupType) - { - // Note: these actor groups only currently have Users (no groups within groups) - GroupManager.exportGroupMembers(actor.getGroup(location), Collections.emptyList(), Arrays.asList(actor.getMembers(location)), xmlGroupType); - - // for a actor type of per location, use the location label as the group name - // otherwise use the actor label in the per study case - xmlGroupType.setName(location != null ? location.getLabel() : actor.getLabel()); - } - - private void writeDisplaySettings(SpecimenSettingsType specimenSettingsType, SimpleStudyExportContext ctx) - { - ctx.getLogger().info("Exporting specimen display settings"); - DisplaySettings settings = SettingsManager.get().getDisplaySettings(ctx.getContainer()); - - SpecimenSettingsType.DisplaySettings xmlSettings = specimenSettingsType.addNewDisplaySettings(); - - SpecimenSettingsType.DisplaySettings.CommentsAndQC commentsAndQC = xmlSettings.addNewCommentsAndQC(); - commentsAndQC.setDefaultToCommentsMode(settings.isDefaultToCommentsMode()); - commentsAndQC.setEnableManualQCFlagging(settings.isEnableManualQCFlagging()); - - SpecimenSettingsType.DisplaySettings.LowSpecimenWarnings warnings = xmlSettings.addNewLowSpecimenWarnings(); - if (settings.getLastVial() != null) - warnings.setLastVial(settings.getLastVial()); - if (settings.getZeroVials() != null) - warnings.setZeroVials(settings.getZeroVials()); - } - - private void writeRequestForm(SpecimenSettingsType specimenSettingsType, SimpleStudyExportContext ctx) throws SQLException - { - ctx.getLogger().info("Exporting specimen request forms"); - SpecimenRequestInput[] inputs = SpecimenRequestManager.get().getNewSpecimenRequestInputs(ctx.getContainer()); - - if (inputs != null && inputs.length > 0) - { - SpecimenSettingsType.RequestForms forms = specimenSettingsType.addNewRequestForms(); - for (SpecimenRequestInput input : inputs) - { - SpecimenSettingsType.RequestForms.Form requestForm = forms.addNewForm(); - - if (input.getTitle() != null) - requestForm.setTitle(input.getTitle()); - if (input.getHelpText() != null) - requestForm.setHelpText(input.getHelpText()); - requestForm.setMultiLine(input.isMultiLine()); - requestForm.setRequired(input.isRequired()); - requestForm.setRememberSiteValue(input.isRememberSiteValue()); - requestForm.setDisplayOrder(input.getDisplayOrder()); - } - } - } - - private void writeNotifications(SpecimenSettingsType specimenSettingsType, SimpleStudyExportContext ctx) - { - ctx.getLogger().info("Exporting specimen notification settings"); - RequestNotificationSettings notifications = org.labkey.specimen.settings.SettingsManager.get().getRequestNotificationSettings(ctx.getContainer()); - SpecimenSettingsType.Notifications xmlNotifications = specimenSettingsType.addNewNotifications(); - - if (notifications.getReplyTo() != null) - xmlNotifications.setReplyTo(notifications.getReplyTo()); - if (notifications.getSubjectSuffix() != null) - xmlNotifications.setSubjectSuffix(notifications.getSubjectSuffix()); - xmlNotifications.setNewRequestNotifyCheckbox(notifications.isNewRequestNotifyCheckbox()); - if (notifications.getNewRequestNotify() != null) - xmlNotifications.setNewRequestNotify(notifications.getNewRequestNotify()); - xmlNotifications.setCcCheckbox(notifications.isCcCheckbox()); - if (notifications.getCc() != null) - xmlNotifications.setCc(notifications.getCc()); - if (notifications.getDefaultEmailNotify() != null) - xmlNotifications.setDefaultEmailNotify(notifications.getDefaultEmailNotify()); - if (notifications.getSpecimensAttachment() != null) - xmlNotifications.setSpecimensAttachment(notifications.getSpecimensAttachment()); - } - - private void writeRequestabilityRules(SpecimenSettingsType specimenSettingsType, SimpleStudyExportContext ctx) - { - ctx.getLogger().info("Exporting specimen requestability rules"); - List rules = RequestabilityManager.getInstance().getRules(ctx.getContainer()); - if (!rules.isEmpty()) - { - SpecimenSettingsType.RequestabilityRules rulesXml = specimenSettingsType.addNewRequestabilityRules(); - for (RequestabilityManager.RequestableRule rule : rules) - { - SpecimenSettingsType.RequestabilityRules.Rule xmlRule = rulesXml.addNewRule(); - - if (rule.getType() != null) - xmlRule.setType(rule.getType().name()); - if (rule.getRuleData() != null) - xmlRule.setRuleData(rule.getRuleData()); - } - } - } -} diff --git a/specimen/src/org/labkey/specimen/writer/SpecimenWriter.java b/specimen/src/org/labkey/specimen/writer/SpecimenWriter.java deleted file mode 100644 index c99408096e4..00000000000 --- a/specimen/src/org/labkey/specimen/writer/SpecimenWriter.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright (c) 2009-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.writer; - -import org.junit.Assert; -import org.junit.Test; -import org.labkey.api.data.BaseColumnInfo; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.Container; -import org.labkey.api.data.DataColumn; -import org.labkey.api.data.DisplayColumn; -import org.labkey.api.data.JdbcType; -import org.labkey.api.data.PHI; -import org.labkey.api.data.ResultsFactory; -import org.labkey.api.data.ResultsImpl; -import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.SqlSelector; -import org.labkey.api.data.TSVGridWriter; -import org.labkey.api.data.TableInfo; -import org.labkey.api.data.dialect.SqlDialect; -import org.labkey.api.exp.PropertyDescriptor; -import org.labkey.api.query.UserSchema; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.specimen.SpecimenTableManager; -import org.labkey.specimen.importer.SpecimenColumn; -import org.labkey.specimen.importer.TargetTable; -import org.labkey.api.study.Study; -import org.labkey.api.study.StudyService; -import org.labkey.api.study.writer.SimpleStudyExportContext; -import org.labkey.api.writer.VirtualFile; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * User: adam - * Date: May 7, 2009 - * Time: 3:49:32 PM - */ -public class SpecimenWriter extends AbstractSpecimenWriter -{ - @Override - public String getDataType() - { - return null; - } - - @Override - public void write(Study study, SimpleStudyExportContext ctx, VirtualFile vf) throws Exception - { - SpecimenSchema schema = SpecimenSchema.get(); - UserSchema querySchema = StudyService.get().getStudyQuerySchema(study, ctx.getUser()); // to use for checking overlayed XMl metadata - Container c = ctx.getContainer(); - SpecimenTableManager specimenTableManager = new SpecimenTableManager(c, ctx.getUser()); - Collection columns = specimenTableManager.getSpecimenColumns(); - - PrintWriter pw = vf.getPrintWriter("specimens.tsv"); - - pw.println("# specimens"); - - SQLFragment sql = new SQLFragment().append("\nSELECT "); - List displayColumns = new ArrayList<>(columns.size()); - List selectColumns = new ArrayList<>(columns.size()); - String comma = ""; - - TableInfo tableInfoSpecimenDetail = querySchema.getTable("SpecimenWrap"); - TableInfo tableInfoSpecimenEvent = schema.getTableInfoSpecimenEvent(c); - TableInfo queryTableSpecimenDetail = querySchema.getTable("SpecimenDetail"); - TableInfo queryTableSpecimenEvent = querySchema.getTable("SpecimenEvent"); - if (null == tableInfoSpecimenDetail || null == queryTableSpecimenDetail || null == queryTableSpecimenEvent) - throw new IllegalStateException("TableInfos not found."); - - SqlDialect dialect = schema.getSqlDialect(); - for (SpecimenColumn column : columns) - { - TargetTable tt = column.getTargetTable(); - TableInfo tinfo = tt.isEvents() ? tableInfoSpecimenEvent : tableInfoSpecimenDetail; - TableInfo queryTable = tt.isEvents() ? queryTableSpecimenEvent : queryTableSpecimenDetail; - ColumnInfo ci = tinfo.getColumn(column.getDbColumnName()); - if (null == ci) - { - ctx.getLogger().warn("Specimen Column '{}' not found in table '{}'", column.getDbColumnName(), tinfo.getPublicName()); - continue; - } - DataColumn dc = new DataColumn(ci); - selectColumns.add(dc.getDisplayColumn()); - dc.setCaption(column.getPrimaryTsvColumnName()); - displayColumns.add(dc); - SQLFragment col; - - // column info that includes the XML metadata override properties - ColumnInfo queryColumn = getSpecimenQueryColumn(queryTable, column, dialect); - - // export alternate ID in place of Ptid if set in StudyExportContext - if (ctx.isAlternateIds() && column.getDbColumnName().equals("Ptid")) - { - col = new SQLFragment("ParticipantLookup.AlternateId AS Ptid"); - } - else if (null == column.getFkColumn()) - { - // Note that columns can be events, vials, or specimens (grouped vials); the SpecimenDetail view that's - // used for export joins vials and specimens into a single view which we're calling 's'. isEvents catches - // those columns that are part of the events table, while !isEvents() catches the rest. (equivalent to - // isVials() || isSpecimens().) - col = ci.getValueSql(tt.isEvents() ? "se" : "s"); - - // add expression to shift the date columns - if (ctx.isShiftDates() && column.isDateType() && !queryColumn.isExcludeFromShifting()) - { - col = new SQLFragment("{fn timestampadd(SQL_TSI_DAY, -ParticipantLookup.DateOffset, ").append(col).append(")}"); - } - - // Don't export values for columns set at or above the PHI export level - if (shouldRemovePhi(ctx.getPhiLevel(), column, queryColumn)) - { - col = new SQLFragment("NULL"); - } - col.append(" AS ").appendIdentifier(dc.getDisplayColumnInfo().getAlias()); - } - else - { - // DisplayColumn will use getAlias() to retrieve the value from the map - col = new SQLFragment(column.getFkTableAlias() + "." + column.getFkColumn() + " AS ").appendIdentifier(dc.getDisplayColumn().getAlias()); - - // Don't export values for columns set at or above the PHI export level - if (shouldRemovePhi(ctx.getPhiLevel(), column, queryColumn)) - { - col = new SQLFragment("NULL AS ").appendIdentifier(dc.getDisplayColumn().getAlias()); - } - } - - sql.append(comma); - sql.append(col); - comma = ", "; - } - - sql.append("\nFROM ").append(tableInfoSpecimenEvent.getFromSQL("se")).append(" JOIN ") - .append(tableInfoSpecimenDetail.getFromSQL("s")).append(" ON se.VialId = s.RowId"); - - for (SpecimenColumn column : columns) - { - if (null != column.getFkColumn()) - { - assert column.getTargetTable().isEvents(); - - TargetTable tt = column.getTargetTable(); - TableInfo tinfo = tt.isEvents() ? tableInfoSpecimenEvent : tableInfoSpecimenDetail; - ColumnInfo ci = tinfo.getColumn(column.getDbColumnName()); - sql.append("\n "); - if (column.getJoinType() != null) - sql.append(column.getJoinType()).append(" "); - sql.append("JOIN ").append(specimenTableManager.getTableInfoFromFkTableName(column.getFkTable())).append(" AS ").append(column.getFkTableAlias()).append(" ON "); - sql.append("(se."); - sql.appendIdentifier(ci.getSelectIdentifier()).append(" = ").append(column.getFkTableAlias()).append(".RowId)"); - } - } - - // add join to study.Participant table if we are using alternate IDs or shifting dates - if (ctx.isAlternateIds() || ctx.isShiftDates()) - { - sql.append("\n LEFT JOIN study.Participant AS ParticipantLookup ON (s.Ptid = ParticipantLookup.ParticipantId AND s.Container = ParticipantLookup.Container)"); - } - // add join to study.ParticipantVisit table if we are filtering by visit IDs - if (ctx.getVisitIds() != null && !ctx.getVisitIds().isEmpty()) - { - sql.append("\n LEFT JOIN study.ParticipantVisit AS ParticipantVisitLookup ON (s.Ptid = ParticipantVisitLookup.ParticipantId AND s.ParticipantSequenceNum = ParticipantVisitLookup.ParticipantSequenceNum AND s.Container = ParticipantVisitLookup.Container)"); - } - - sql.append("\n"); - - // add filter for selected participant IDs and Visits IDs - String conjunction = "WHERE "; - if (ctx.getVisitIds() != null && !ctx.getVisitIds().isEmpty()) - { - sql.append(conjunction).append("\n ParticipantVisitLookup.VisitRowId IN ("); - sql.append(convertListToString(new ArrayList<>(ctx.getVisitIds()), false)); - sql.append(")"); - conjunction = " AND "; - } - - if (null != ctx.getParticipants() && !ctx.getParticipants().isEmpty()) - { - sql.append(conjunction); - if (ctx.isAlternateIds()) - sql.append("\n ParticipantLookup.AlternateId IN ("); - else - sql.append("\n se.Ptid IN ("); - - sql.append(convertListToString(ctx.getParticipants(), true)); - sql.append(")"); - } - - sql.append("\nORDER BY se.ExternalId"); - - // Note: must be uncached result set -- this query can be very large - ResultsFactory factory = ()->new ResultsImpl(new SqlSelector(schema.getSchema(), sql).getResultSet(false), selectColumns); - - // TSVGridWriter generates and closes the Results - try (TSVGridWriter gridWriter = new TSVGridWriter(factory, displayColumns)) - { - gridWriter.write(pw); - } - } - - private ColumnInfo getSpecimenQueryColumn(TableInfo queryTable, SpecimenColumn column, SqlDialect dialect) - { - // if the query table contains the column using the DBColumnName, use that, otherwise try removing the 'id' from the end of the column name - if (queryTable != null && column != null) - { - if (null != queryTable.getColumn(column.getDbColumnName())) - return queryTable.getColumn(column.getDbColumnName()); - - var legalName = PropertyDescriptor.getLegalSelectNameFromStorageName(dialect, column.getDbColumnName()); - if (null != queryTable.getColumn(legalName.getId())) - return queryTable.getColumn(legalName.getId()); - - if (column.getDbColumnName().toLowerCase().endsWith("id")) - { - String tempColName = column.getDbColumnName().substring(0, column.getDbColumnName().length() - 2); - return queryTable.getColumn(tempColName); - } - } - return null; - } - - private static boolean shouldRemovePhi(PHI exportPhiLevel, SpecimenColumn column, ColumnInfo queryColumn) - { - if (!column.isKeyColumn()) - { - return (queryColumn != null) && !(queryColumn.getPHI().isExportLevelAllowed(exportPhiLevel)); - } - - return false; - } - - private static String convertListToString(List list, boolean withQuotes) - { - StringBuilder sb = new StringBuilder(); - String sep = ""; - for (Object obj : list) - { - sb.append(sep); - if (withQuotes) sb.append("'"); - sb.append(obj.toString()); - if (withQuotes) sb.append("'"); - sep = ","; - } - return sb.toString(); - } - - public static class TestCase extends Assert - { - @Test - public void testConvertListToString() - { - List ints = new ArrayList<>(); - ints.add(1); - ints.add(2); - ints.add(3); - assertEquals("1,2,3", convertListToString(ints, false)); - assertEquals("'1','2','3'", convertListToString(ints, true)); - - List ptids = new ArrayList<>(); - ptids.add("Ptid1"); - ptids.add("Ptid2"); - ptids.add("Ptid3"); - assertEquals("Ptid1,Ptid2,Ptid3", convertListToString(ptids, false)); - assertEquals("'Ptid1','Ptid2','Ptid3'", convertListToString(ptids, true)); - } - - @Test - public void testShouldRemovePhi() - { - var ciNotPhi = new BaseColumnInfo("test", JdbcType.OTHER); - ciNotPhi.setPHI(PHI.NotPHI); - var ciLimitedPhi = new BaseColumnInfo("test", JdbcType.OTHER); - ciLimitedPhi.setPHI(PHI.Limited); - var ciPhi = new BaseColumnInfo("test", JdbcType.OTHER); - ciPhi.setPHI(PHI.PHI); - var ciRestrictedPhi = new BaseColumnInfo("test", JdbcType.OTHER); - ciRestrictedPhi.setPHI(PHI.Restricted); - - SpecimenColumn notKeyCol = new SpecimenColumn("test", "test", "INT", TargetTable.SPECIMEN_EVENTS); - SpecimenColumn keyCol = new SpecimenColumn("test", "test", "INT", true, TargetTable.SPECIMEN_EVENTS); - - // should remove if not a key column and it is at or above PHI export level - assertTrue(shouldRemovePhi(PHI.PHI, notKeyCol, ciRestrictedPhi)); - assertTrue(shouldRemovePhi(PHI.Limited, notKeyCol, ciRestrictedPhi)); - assertTrue(shouldRemovePhi(PHI.NotPHI, notKeyCol, ciRestrictedPhi)); - assertTrue(shouldRemovePhi(PHI.Limited, notKeyCol, ciPhi)); - assertTrue(shouldRemovePhi(PHI.NotPHI, notKeyCol, ciPhi)); - assertTrue(shouldRemovePhi(PHI.NotPHI, notKeyCol, ciLimitedPhi)); - - // shouldn't remove if not a key column and it is not at or above PHI export level - assertFalse(shouldRemovePhi(PHI.Restricted, notKeyCol, ciPhi)); - assertFalse(shouldRemovePhi(PHI.PHI, notKeyCol, ciPhi)); - assertFalse(shouldRemovePhi(PHI.Restricted, notKeyCol, ciLimitedPhi)); - assertFalse(shouldRemovePhi(PHI.PHI, notKeyCol, ciLimitedPhi)); - assertFalse(shouldRemovePhi(PHI.Limited, notKeyCol, ciLimitedPhi)); - assertFalse(shouldRemovePhi(PHI.Restricted, notKeyCol, ciNotPhi)); - assertFalse(shouldRemovePhi(PHI.PHI, notKeyCol, ciNotPhi)); - assertFalse(shouldRemovePhi(PHI.Limited, notKeyCol, ciNotPhi)); - assertFalse(shouldRemovePhi(PHI.NotPHI, notKeyCol, ciNotPhi)); - - // shouldn't remove if it is a key column - assertFalse(shouldRemovePhi(PHI.Restricted, keyCol, ciRestrictedPhi)); - assertFalse(shouldRemovePhi(PHI.PHI, keyCol, ciRestrictedPhi)); - assertFalse(shouldRemovePhi(PHI.Limited, keyCol, ciRestrictedPhi)); - assertFalse(shouldRemovePhi(PHI.PHI, keyCol, ciPhi)); - assertFalse(shouldRemovePhi(PHI.Limited, keyCol, ciPhi)); - assertFalse(shouldRemovePhi(PHI.Limited, keyCol, ciLimitedPhi)); - } - } -} diff --git a/specimen/src/org/labkey/specimen/writer/StandardSpecimenWriter.java b/specimen/src/org/labkey/specimen/writer/StandardSpecimenWriter.java deleted file mode 100644 index c01b5a9fc08..00000000000 --- a/specimen/src/org/labkey/specimen/writer/StandardSpecimenWriter.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2009-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.writer; - -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.DataColumn; -import org.labkey.api.data.DisplayColumn; -import org.labkey.api.data.ResultsFactory; -import org.labkey.api.data.ResultsImpl; -import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.SqlSelector; -import org.labkey.api.data.TSVGridWriter; -import org.labkey.api.data.TableInfo; -import org.labkey.api.specimen.SpecimenSchema; -import org.labkey.specimen.importer.ImportableColumn; -import org.labkey.api.study.writer.SimpleStudyExportContext; -import org.labkey.api.writer.VirtualFile; -import org.labkey.api.writer.Writer; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * User: adam - * Date: May 7, 2009 - * Time: 9:51:36 AM - */ -class StandardSpecimenWriter implements Writer -{ - @Override - public String getDataType() - { - return null; - } - - @Override - public void write(QueryInfo queryInfo, SimpleStudyExportContext ctx, VirtualFile vf) throws Exception - { - TableInfo tinfo = queryInfo.getTableInfo(); - Collection columns = queryInfo.getColumns(); - - PrintWriter pw = vf.getPrintWriter(queryInfo.getFilename() + ".tsv"); - - pw.print("# "); - pw.println(queryInfo.getFilename()); - - List displayColumns = new ArrayList<>(columns.size()); - for (ImportableColumn column : columns) - { - ColumnInfo ci = tinfo.getColumn(column.getDbColumnName()); - DisplayColumn dc = new DataColumn(ci); - dc.setCaption(column.getPrimaryTsvColumnName()); - - if (column.getJavaClass() == Boolean.class) - dc.setTsvFormatString("1;0;"); - - displayColumns.add(dc); - } - - SQLFragment sql = generateSql(ctx, tinfo, columns); - - // TSVGridWriter generates and closes the Results - ResultsFactory factory = ()->new ResultsImpl(new SqlSelector(SpecimenSchema.get().getSchema(), sql).getResultSet(false)); - - try (TSVGridWriter gridWriter = new TSVGridWriter(factory, displayColumns)) - { - gridWriter.write(pw); - } - } - - protected SQLFragment generateSql(SimpleStudyExportContext ctx, TableInfo tinfo, Collection columns) - { - SQLFragment sql = new SQLFragment().append("SELECT "); - String comma = ""; - - for (ImportableColumn column : columns) - { - sql.append(comma); - sql.append(column.getDbColumnName()); - comma = ", "; - } - - sql.append(" FROM "); - sql.append(tinfo, "ti"); - sql.append(" WHERE Container = ? ORDER BY ExternalId"); - - sql.add(ctx.getContainer()); - return sql; - } - - public static class QueryInfo - { - private final TableInfo _tinfo; - private final String _filename; - private final Collection _columns; - - public QueryInfo(TableInfo tinfo, String filename, Collection columns) - { - _tinfo = tinfo; - _filename = filename; - _columns = columns; - } - - public TableInfo getTableInfo() - { - return _tinfo; - } - - public String getFilename() - { - return _filename; - } - - public Collection getColumns() - { - return _columns; - } - } -} diff --git a/specimen/test/sampledata/import/100_specimens.xlsx b/specimen/test/sampledata/import/100_specimens.xlsx deleted file mode 100644 index 75b54324a2e..00000000000 Binary files a/specimen/test/sampledata/import/100_specimens.xlsx and /dev/null differ diff --git a/specimen/test/src/org/labkey/test/pages/specimen/ConfigureSpecimenImportPage.java b/specimen/test/src/org/labkey/test/pages/specimen/ConfigureSpecimenImportPage.java deleted file mode 100644 index a686842eb75..00000000000 --- a/specimen/test/src/org/labkey/test/pages/specimen/ConfigureSpecimenImportPage.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 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.test.pages.specimen; - -import org.labkey.test.Locator; -import org.labkey.test.WebDriverWrapper; -import org.labkey.test.WebTestHelper; -import org.labkey.test.pages.LabKeyPage; -import org.labkey.test.pages.study.ManageStudyPage; -import org.labkey.test.util.DataRegionTable; -import org.labkey.test.util.PipelineStatusTable; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; - -import java.util.Optional; - -public class ConfigureSpecimenImportPage extends LabKeyPage -{ - public ConfigureSpecimenImportPage(WebDriver driver) - { - super(driver); - } - - public static ConfigureSpecimenImportPage beginAt(WebDriverWrapper webDriverWrapper) - { - return beginAt(webDriverWrapper, webDriverWrapper.getCurrentContainerPath()); - } - - public static ConfigureSpecimenImportPage beginAt(WebDriverWrapper webDriverWrapper, String containerPath) - { - webDriverWrapper.beginAt(WebTestHelper.buildURL("specimen", containerPath, "configureQueryImport")); - return new ConfigureSpecimenImportPage(webDriverWrapper.getDriver()); - } - - @Override - protected void waitForPage() - { - WebDriverWrapper.waitFor(()-> elementCache().schemaSelect.isEnabled(), - "The page did not become ready in time", WAIT_FOR_JAVASCRIPT); - } - - public ConfigureSpecimenImportPage configureQueryBasedImport(boolean enableReload, String schema, String query, String view) - { - setCheckbox(elementCache().reloadBoxElement, enableReload); - setFormElement(elementCache().schemaSelect, schema); - setFormElement(elementCache().querySelect, query); - setFormElement(elementCache().viewSelect, view); - return this; - } - - public ManageStudyPage clickSave() - { - clickAndWait(elementCache().saveButton); - return new ManageStudyPage(getDriver()); - } - - public ManageStudyPage clickCancel() - { - clickAndWait(elementCache().cancelButton); - return new ManageStudyPage(getDriver()); - } - - public boolean isReloadNowButtonPresent() - { - return elementCache().reloadNowButton().isPresent(); - } - - public DataRegionTable clickReloadNow() - { - doAndWaitForPageToLoad(()-> elementCache().reloadNowButton().get().click()); - return PipelineStatusTable.finder(getDriver()).waitFor(); - } - - @Override - protected ElementCache newElementCache() - { - return new ElementCache(); - } - - protected class ElementCache extends LabKeyPage.ElementCache - { - WebElement cancelButton = Locator.linkWithSpan("Cancel").findWhenNeeded(getDriver()); - WebElement saveButton = Locator.linkWithSpan("Save").findWhenNeeded(getDriver()); - Optional reloadNowButton() - { - return Locator.linkWithSpan("Reload Now").findOptionalElement(getDriver()); - } - - WebElement formContainer = Locator.tagWithClass("div", "QBSpecimenImportFormFields") - .findWhenNeeded(getDriver()).withTimeout(WAIT_FOR_JAVASCRIPT); - - WebElement reloadBoxElement = Locator.checkbox().withAttribute("name", "enabled") - .findWhenNeeded(formContainer); - WebElement schemaSelect = Locator.id("schemaName").findWhenNeeded(formContainer); - WebElement querySelect = Locator.id("queryName").findWhenNeeded(formContainer); - WebElement viewSelect = Locator.id("viewName").findWhenNeeded(formContainer); - } -} diff --git a/specimen/test/src/org/labkey/test/tests/specimen/QueryBasedSpecimenImportTest.java b/specimen/test/src/org/labkey/test/tests/specimen/QueryBasedSpecimenImportTest.java deleted file mode 100644 index 92f0105e522..00000000000 --- a/specimen/test/src/org/labkey/test/tests/specimen/QueryBasedSpecimenImportTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 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.test.tests.specimen; - -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.labkey.test.BaseWebDriverTest; -import org.labkey.test.TestFileUtils; -import org.labkey.test.categories.Git; -import org.labkey.test.categories.Specimen; -import org.labkey.test.pages.specimen.ConfigureSpecimenImportPage; - -import java.io.File; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -import static org.hamcrest.CoreMatchers.hasItems; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -@Category({Git.class, Specimen.class}) -public class QueryBasedSpecimenImportTest extends BaseWebDriverTest -{ - String TEST_SCHEMA = "lists"; - String TEST_QUERY = "test_specimen_data"; - String TEST_VIEW = "Default"; - File SPECIMENS_FILE = TestFileUtils.getSampleData("import/100_specimens.xlsx"); - - @BeforeClass - public static void setupProject() - { - QueryBasedSpecimenImportTest init = getCurrentTest(); - init.doSetup(); - } - - private void doSetup() - { - _containerHelper.createProject(getProjectName(), "Study"); - _studyHelper.startCreateStudy() - .createStudy(); - goToFolderManagement() - .goToFolderTypeTab() - .enableModule("Specimen") - .save(); - - // create a list of specimens to import from - _listHelper.createListFromFile(getProjectName(), TEST_QUERY, SPECIMENS_FILE); - } - - @Before - public void preTest() - { - goToProjectHome(); - } - - @Test - public void testQueryBasedSpecimenImport() - { - // the test configures one query-based importer - goToManageStudy() - .clickConfigureSpecimenImport(); - new ConfigureSpecimenImportPage(getDriver()) - .configureQueryBasedImport(true, TEST_SCHEMA, TEST_QUERY, TEST_VIEW) - .clickSave(); - - // navigate directly to configure query-based import, user 'reload now' button to load the specimens from the query - ConfigureSpecimenImportPage.beginAt(this, getProjectName()) - .clickReloadNow(); - waitForPipelineJobsToComplete(1, false); - - // navigate to Specimens tab, verify - clickTab("Specimen Data", true); - - // iterate over the list, ensure that for each there is a matching specimen in specimenDetails - List> sourceSpecimenData =executeSelectRowCommand(TEST_SCHEMA, TEST_QUERY).getRows(); - List> specimenDetailsList = executeSelectRowCommand("study", "SpecimenDetail").getRows(); - List> specimenPrimaryTypes = executeSelectRowCommand("study", "SpecimenPrimaryType").getRows(); - - List primaryTypes = specimenPrimaryTypes.stream().map(a-> a.get("PrimaryType").toString()).collect(Collectors.toList()); - assertThat("expect 4 kinds of primary vial types", - primaryTypes, hasItems("PBMC-Na Hep", "Serum-Clot", "urine super", "whole blood")); - - for (Map specimenRow : sourceSpecimenData) - { - String globalUniqueSpecimenId = specimenRow.get("global_unique_specimen_id").toString(); - - Optional> matchingSpecimenDetail = specimenDetailsList.stream() - .filter(a-> a.get("GlobalUniqueId").equals(globalUniqueSpecimenId)).findFirst(); - assertTrue("Expect to find matching SpecimenDetail for [" +globalUniqueSpecimenId+ "]", - matchingSpecimenDetail.isPresent()); - } - } - - @Override - protected BrowserType bestBrowser() - { - return BrowserType.CHROME; - } - - @Override - protected String getProjectName() - { - return "SpecimenImportTest Project"; - } - - @Override - public List getAssociatedModules() - { - return Arrays.asList("Specimen"); - } -} diff --git a/specimen/webapp/WEB-INF/specimen/specimenContext.xml b/specimen/webapp/WEB-INF/specimen/specimenContext.xml deleted file mode 100644 index bc6713ba82c..00000000000 --- a/specimen/webapp/WEB-INF/specimen/specimenContext.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - org.labkey.specimen.pipeline.StandaloneSpecimenTask - - - - - - - - - - - - org.labkey.specimen.pipeline.SpecimenReloadTask - - - org.labkey.specimen.pipeline.StandaloneSpecimenTask - - - - - - - - - - - - - - - - - - - - - - - - org.labkey.specimen.pipeline.FileAnalysisSpecimenTask - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/study/test/src/org/labkey/test/tests/search/SearchTest.java b/study/test/src/org/labkey/test/tests/search/SearchTest.java index 437c084aa0f..ca134606a6e 100644 --- a/study/test/src/org/labkey/test/tests/search/SearchTest.java +++ b/study/test/src/org/labkey/test/tests/search/SearchTest.java @@ -510,10 +510,12 @@ private void addSearchableStudy() { importStudy(); startSpecimenImport(2); + waitForSpecimenImport(); - waitForPipelineJobsToComplete(2, "study import", false); - - _searchHelper.enqueueSearchItem("999320016", Locator.linkContainingText("999320016")); + if (_studyHelper.isSpecimenModulePresent()) + { + _searchHelper.enqueueSearchItem("999320016", Locator.linkContainingText("999320016")); + } _searchHelper.enqueueSearchItem("Urinalysis", Locator.linkContainingText("URF-1"), Locator.linkContainingText("URF-2"), Locator.linkContainingText("URS-1")); diff --git a/study/test/src/org/labkey/test/tests/study/AssayTest.java b/study/test/src/org/labkey/test/tests/study/AssayTest.java index 7d4e2865e3d..3ffbc0ff9e0 100644 --- a/study/test/src/org/labkey/test/tests/study/AssayTest.java +++ b/study/test/src/org/labkey/test/tests/study/AssayTest.java @@ -221,8 +221,11 @@ public void testAssaySecurity() throws Exception log("Starting Assay security scenario tests"); setupEnvironment(); setupPipeline(getProjectName()); - SpecimenImporter importer = new SpecimenImporter(TestFileUtils.getTestTempDir(), StudyHelper.SPECIMEN_ARCHIVE_A, FileUtil.appendName(TestFileUtils.getTestTempDir(), "specimensSubDir"), TEST_ASSAY_FLDR_STUDY2, 1); - importer.importAndWaitForComplete(); + if (_studyHelper.isSpecimenModulePresent()) + { + SpecimenImporter importer = new SpecimenImporter(TestFileUtils.getTestTempDir(), StudyHelper.SPECIMEN_ARCHIVE_A, FileUtil.appendName(TestFileUtils.getTestTempDir(), "specimensSubDir"), TEST_ASSAY_FLDR_STUDY2, 1); + importer.importAndWaitForComplete(); + } defineAssay(); uploadRuns(TEST_ASSAY_FLDR_LAB1, TEST_ASSAY_USR_TECH1); editResults(); @@ -569,61 +572,65 @@ private void uploadRuns(String folder, String asUser) TEST_ASSAY_SET_PROPERTIES[3]); clickAndWait(Locator.linkWithText(TEST_RUN1)); assertElementNotPresent(Locator.tagWithText("td", "7.0")); - // Make sure that our specimen IDs resolved correctly - assertTextPresent( - "AAA07XSF-02", - "999320885", - "301", - "AAA07XK5-05", - "999320812", - "601", - TEST_ASSAY_DATA_PROP_NAME + "4", - TEST_ASSAY_DATA_PROP_NAME + "5", - TEST_ASSAY_DATA_PROP_NAME + "6", - "2000-06-06", - "0.0", - "f", - ALIASED_DATA); - - _customizeViewsHelper.openCustomizeViewPanel(); - _customizeViewsHelper.addColumn("SpecimenID/GlobalUniqueId"); - _customizeViewsHelper.addColumn("SpecimenID/Specimen/PrimaryType"); - _customizeViewsHelper.addColumn("SpecimenID/AssayMatch"); - _customizeViewsHelper.removeColumn("Run/testAssayRunProp1"); - _customizeViewsHelper.removeColumn("Run/Batch/testAssaySetProp2"); - _customizeViewsHelper.removeColumn("testAssayDataProp4"); - _customizeViewsHelper.applyCustomView(); - - assertTextPresent("Blood (Whole)", 4); - - Locator.XPathLocator trueLocator = Locator.xpath("//table[contains(@class, 'labkey-data-region')]//td[text() = 'true']"); - int totalTrues = getElementCount(trueLocator); - assertEquals(4, totalTrues); - - DataRegionTable region = new DataRegionTable("Data", this); - region.setFilter("SpecimenID", "Starts With", "AssayTestControl"); - - // verify that there are no trues showing for the assay match column that were filtered out - totalTrues = getElementCount(trueLocator); - assertEquals(0, totalTrues); - - log("Check out the data for all of the runs"); - clickAndWait(Locator.linkWithText("view results")); - region.clearAllFilters("SpecimenID"); - assertElementPresent(Locator.tagWithText("td", "7.0")); - assertElementPresent(Locator.tagWithText("td", "18")); - - assertTextPresent("Blood (Whole)", 7); - - Locator.XPathLocator falseLocator = Locator.xpath("//table[contains(@class, 'labkey-data-region')]//td[text() = 'false']"); - int totalFalses = getElementCount(falseLocator); - assertEquals(3, totalFalses); - - region.setFilter("SpecimenID", "Does Not Start With", "BAQ"); - - // verify the falses have been filtered out - totalFalses = getElementCount(falseLocator); - assertEquals(0, totalFalses); + // Make sure that our specimen IDs resolved correctly -- this whole block relies on specimen-to-assay + // resolution (SpecimenID lookups, AssayMatch), so it's skipped entirely when the module is absent. + if (_studyHelper.isSpecimenModulePresent()) + { + assertTextPresent( + "AAA07XSF-02", + "999320885", + "301", + "AAA07XK5-05", + "999320812", + "601", + TEST_ASSAY_DATA_PROP_NAME + "4", + TEST_ASSAY_DATA_PROP_NAME + "5", + TEST_ASSAY_DATA_PROP_NAME + "6", + "2000-06-06", + "0.0", + "f", + ALIASED_DATA); + + _customizeViewsHelper.openCustomizeViewPanel(); + _customizeViewsHelper.addColumn("SpecimenID/GlobalUniqueId"); + _customizeViewsHelper.addColumn("SpecimenID/Specimen/PrimaryType"); + _customizeViewsHelper.addColumn("SpecimenID/AssayMatch"); + _customizeViewsHelper.removeColumn("Run/testAssayRunProp1"); + _customizeViewsHelper.removeColumn("Run/Batch/testAssaySetProp2"); + _customizeViewsHelper.removeColumn("testAssayDataProp4"); + _customizeViewsHelper.applyCustomView(); + + assertTextPresent("Blood (Whole)", 4); + + Locator.XPathLocator trueLocator = Locator.xpath("//table[contains(@class, 'labkey-data-region')]//td[text() = 'true']"); + int totalTrues = getElementCount(trueLocator); + assertEquals(4, totalTrues); + + DataRegionTable region = new DataRegionTable("Data", this); + region.setFilter("SpecimenID", "Starts With", "AssayTestControl"); + + // verify that there are no trues showing for the assay match column that were filtered out + totalTrues = getElementCount(trueLocator); + assertEquals(0, totalTrues); + + log("Check out the data for all of the runs"); + clickAndWait(Locator.linkWithText("view results")); + region.clearAllFilters("SpecimenID"); + assertElementPresent(Locator.tagWithText("td", "7.0")); + assertElementPresent(Locator.tagWithText("td", "18")); + + assertTextPresent("Blood (Whole)", 7); + + Locator.XPathLocator falseLocator = Locator.xpath("//table[contains(@class, 'labkey-data-region')]//td[text() = 'false']"); + int totalFalses = getElementCount(falseLocator); + assertEquals(3, totalFalses); + + region.setFilter("SpecimenID", "Does Not Start With", "BAQ"); + + // verify the falses have been filtered out + totalFalses = getElementCount(falseLocator); + assertEquals(0, totalFalses); + } stopImpersonating(); } @@ -683,7 +690,14 @@ private void publishData() clickButton("Next"); assertTextPresent("Link to " + TEST_ASSAY_FLDR_STUDY1 + " Study: Verify Results"); - setFormElement(Locator.name("visitId"), "301.5"); + if (!_studyHelper.isSpecimenModulePresent()) + { + setFormElement(Locator.name("visitId"), "301.5"); + for (int i = 1; i <= 3; i++) + { + uncheckCheckbox(Locator.checkboxByNameAndValue(".select", String.valueOf(i + 1))); + } + } clickButton("Link to Study"); log("Verifying that the data was published"); @@ -698,31 +712,37 @@ private void publishData() clickAndWait(Locator.linkWithText("Study Navigator")); log("Test participant counts and row counts in study overview"); - String[] row2 = new String[]{TEST_ASSAY, "8", "1", "1", "1", "1", "1", "1", "2"}; + String[] row2 = _studyHelper.isSpecimenModulePresent() ? new String[]{TEST_ASSAY, "8", "1", "1", "1", "1", "1", "1", "2"} + : new String[]{TEST_ASSAY, "6", "1", "1", "1", "1", "1", "1"}; assertTableRowsEqual("studyOverview", 1, new String[][]{row2}); // Manually click the checkbox -- normal checkCheckbox() method doesn't seem to work for checkbox that reloads using onchange event clickAndWait(Locator.checkboxByNameAndValue("visitStatistic", "RowCount")); - row2 = new String[]{TEST_ASSAY, "8 / 9", "1 / 1", "1 / 1", "1 / 1", "1 / 1", "1 / 1", "1 / 1", "2 / 3"}; + row2 = _studyHelper.isSpecimenModulePresent() ? new String[]{TEST_ASSAY, "8 / 9", "1 / 1", "1 / 1", "1 / 1", "1 / 1", "1 / 1", "1 / 1", "2 / 3"} + : new String[]{TEST_ASSAY, "6 / 6", "1 / 1", "1 / 1", "1 / 1", "1 / 1", "1 / 1", "1 / 1"}; assertTableRowsEqual("studyOverview", 1, new String[][]{row2}); doAndWaitForPageToLoad(() -> uncheckCheckbox(Locator.checkboxByNameAndValue("visitStatistic", "ParticipantCount"))); - row2 = new String[]{TEST_ASSAY, "9", "1", "1", "1", "1", "1", "1", "3"}; + row2 = _studyHelper.isSpecimenModulePresent() ? new String[]{TEST_ASSAY, "9", "1", "1", "1", "1", "1", "1", "3"} + : new String[]{TEST_ASSAY, "6", "1", "1", "1", "1", "1", "1"}; assertTableRowsEqual("studyOverview", 1, new String[][]{row2}); - clickAndWait(Locator.linkWithText("9")); - - assertElementPresent(Locator.linkWithText("999320885"), 1); - assertElementPresent(Locator.linkWithText("999320885"), 1); - assertTextPresent( - "301.0", - "9.0", - "8.0", - TEST_RUN1_COMMENTS, - TEST_RUN2_COMMENTS, - TEST_RUN1, - TEST_RUN2, - "2000-06-06", - TEST_ASSAY_RUN_PROP1, - "18"); + if (_studyHelper.isSpecimenModulePresent()) + { + clickAndWait(Locator.linkWithText("9")); + + assertElementPresent(Locator.linkWithText("999320885"), 1); + assertElementPresent(Locator.linkWithText("999320885"), 1); + assertTextPresent( + "301.0", + "9.0", + "8.0", + TEST_RUN1_COMMENTS, + TEST_RUN2_COMMENTS, + TEST_RUN1, + TEST_RUN2, + "2000-06-06", + TEST_ASSAY_RUN_PROP1, + "18"); + } // test recall navigateToFolder(getProjectName(), TEST_ASSAY_FLDR_LAB1); @@ -813,15 +833,15 @@ private void publishDataToDateBasedStudy() linkStudy.clickHeaderButtonAndWait("Re-Validate"); //validate timepoints: - assertElementPresent(Locator.xpath("//td[text()='Day 32 - 39' and following-sibling::td/a[text()='AAA07XMC-02'] and following-sibling::td[text()='301.0']]")); - assertElementPresent(Locator.xpath("//td[text()='Preexisting Timepoint' and following-sibling::td/a[text()='AAA07XMC-04'] and following-sibling::td[not(text())]]")); - assertElementPresent(Locator.xpath("//td[text()='Day 90 - 95' and following-sibling::td/a[text()='AAA07XSF-02'] and following-sibling::td[not(text())]]")); + assertElementPresent(Locator.xpath("//td[text()='Day 32 - 39' and following-sibling::td[normalize-space()='AAA07XMC-02'] and following-sibling::td["+(_studyHelper.isSpecimenModulePresent() ? "text()='301.0'" : "not(text())") +"]]")); + assertElementPresent(Locator.xpath("//td[text()='Preexisting Timepoint' and following-sibling::td[normalize-space()='AAA07XMC-04'] and following-sibling::td[not(text())]]")); + assertElementPresent(Locator.xpath("//td[text()='Day 90 - 95' and following-sibling::td[normalize-space()='AAA07XSF-02'] and following-sibling::td[not(text())]]")); - assertElementPresent(Locator.xpath("//td[text()='Day 120 - 127' and following-sibling::td/a[text()='AssayTestControl1'] and following-sibling::td[text()='5.0']]")); - assertElementPresent(Locator.xpath("//td[text()='Day 152 - 159' and following-sibling::td/a[text()='AssayTestControl2'] and following-sibling::td[text()='6.0']]")); - assertElementPresent(Locator.xpath("//td[text()='Day 0 - 7' and following-sibling::td/a[text()='BAQ00051-09'] and following-sibling::td[text()='7.0']]")); - assertElementPresent(Locator.xpath("//td[text()='Day 32 - 39' and following-sibling::td/a[text()='BAQ00051-08'] and following-sibling::td[text()='8.0']]")); - assertElementPresent(Locator.xpath("//td[text()='Preexisting Timepoint' and following-sibling::td/a[text()='BAQ00051-11'] and following-sibling::td[text()='9.0']]")); + assertElementPresent(Locator.xpath("//td[text()='Day 120 - 127' and following-sibling::td[normalize-space()='AssayTestControl1'] and following-sibling::td[text()='5.0']]")); + assertElementPresent(Locator.xpath("//td[text()='Day 152 - 159' and following-sibling::td[normalize-space()='AssayTestControl2'] and following-sibling::td[text()='6.0']]")); + assertElementPresent(Locator.xpath("//td[text()='Day 0 - 7' and following-sibling::td[normalize-space()='BAQ00051-09'] and following-sibling::td[text()='7.0']]")); + assertElementPresent(Locator.xpath("//td[text()='Day 32 - 39' and following-sibling::td[normalize-space()='BAQ00051-08'] and following-sibling::td[text()='8.0']]")); + assertElementPresent(Locator.xpath("//td[text()='Preexisting Timepoint' and following-sibling::td[normalize-space()='BAQ00051-11'] and following-sibling::td[text()='9.0']]")); linkStudy.clickHeaderButtonAndWait("Link to Study"); @@ -914,15 +934,15 @@ private void publishDataToVisitBasedStudy() linkStudy.clickHeaderButtonAndWait("Re-Validate"); //validate timepoints: - assertElementPresent(Locator.xpath("//td[text()='Test Visit3' and following-sibling::td/a[text()='AAA07XMC-02']]")); - assertElementPresent(Locator.xpath("//td[text()='33.0' and following-sibling::td/a[text()='AAA07XMC-04']]")); - assertElementPresent(Locator.xpath("//td[text()='4.0' and following-sibling::td/a[text()='AAA07XSF-02']]")); + assertElementPresent(Locator.xpath("//td[text()='Test Visit3' and following-sibling::td[normalize-space()='AAA07XMC-02']]")); + assertElementPresent(Locator.xpath("//td[text()='33.0' and following-sibling::td[normalize-space()='AAA07XMC-04']]")); + assertElementPresent(Locator.xpath("//td[text()='4.0' and following-sibling::td[normalize-space()='AAA07XSF-02']]")); - assertElementPresent(Locator.xpath("//td[text()='Test Visit2' and following-sibling::td/a[text()='AssayTestControl1']]")); - assertElementPresent(Locator.xpath("//td[text()='Test Visit1' and following-sibling::td/a[text()='AssayTestControl2']]")); - assertElementPresent(Locator.xpath("//td[text()='Test Visit1' and following-sibling::td/a[text()='BAQ00051-09']]")); - assertElementPresent(Locator.xpath("//td[text()='Test Visit1' and following-sibling::td/a[text()='BAQ00051-08']]")); - assertElementPresent(Locator.xpath("//td[text()='Test Visit1' and following-sibling::td/a[text()='BAQ00051-11']]")); + assertElementPresent(Locator.xpath("//td[text()='Test Visit2' and following-sibling::td[normalize-space()='AssayTestControl1']]")); + assertElementPresent(Locator.xpath("//td[text()='Test Visit1' and following-sibling::td[normalize-space()='AssayTestControl2']]")); + assertElementPresent(Locator.xpath("//td[text()='Test Visit1' and following-sibling::td[normalize-space()='BAQ00051-09']]")); + assertElementPresent(Locator.xpath("//td[text()='Test Visit1' and following-sibling::td[normalize-space()='BAQ00051-08']]")); + assertElementPresent(Locator.xpath("//td[text()='Test Visit1' and following-sibling::td[normalize-space()='BAQ00051-11']]")); linkStudy.clickHeaderButtonAndWait("Link to Study"); @@ -934,11 +954,13 @@ private void publishDataToVisitBasedStudy() clickAndWait(Locator.linkWithText("Study Navigator")); log("Test participant counts and row counts in study overview"); - String[] row2 = new String[]{TEST_ASSAY, "9", " ", " ", " ", "1", " ", " ", "1", " ", " ", "4", " ", " ", " ", " ", "1", "1", " ", " ", " ", "1", " ", " ", " ", " ", " "}; + List rowList = Stream.of(TEST_ASSAY, "9", " ", " ", " ", "1", " ", " ", "1", " ", " ", "4", " ", " ", " ", " ", "1", "1", " ", " ", " ", "1", " ", " ", " ", " ", " ") + .filter(v -> _studyHelper.isSpecimenModulePresent() || !v.isBlank()).toList(); + String[] row2 = rowList.toArray(new String[]{}); assertTableRowsEqual("studyOverview", 1, new String[][]{row2}); // Manually click the checkbox -- normal checkCheckbox() method doesn't seem to work for checkbox that reloads using onchange event clickAndWait(Locator.checkboxByNameAndValue("visitStatistic", "RowCount")); - row2 = new String[]{TEST_ASSAY, "9 / 9", " ", " ", " ", "1 / 1", " ", " ", "1 / 1", " ", " ", "4 / 4", " ", " ", " ", " ", "1 / 1", "1 / 1", " ", " ", " ", "1 / 1", " ", " ", " ", " ", " "}; + row2 = rowList.stream().map(v -> !v.isBlank() && v.length() == 1 ? v + " / " + v : v).toList().toArray(new String[]{}); assertTableRowsEqual("studyOverview", 1, new String[][]{row2}); log("Test that correct timepoints were created"); @@ -1099,10 +1121,13 @@ private void verifyStudyList() private void verifySpecimensPresent(int aaa07Count, int controlCount, int baq00051Count) { - // need to double the count, once for the label and once for the param in the link url - assertTextPresent("AAA07", aaa07Count * 2); - assertTextPresent("AssayTestControl", controlCount * 2); - assertTextPresent("BAQ00051", baq00051Count * 2); + if (_studyHelper.isSpecimenModulePresent()) + { + // need to double the count, once for the label and once for the param in the link url + assertTextPresent("AAA07", aaa07Count * 2); + assertTextPresent("AssayTestControl", controlCount * 2); + assertTextPresent("BAQ00051", baq00051Count * 2); + } } @Test // Issue 53625 diff --git a/study/test/src/org/labkey/test/tests/study/CohortTest.java b/study/test/src/org/labkey/test/tests/study/CohortTest.java index 58d04d4ef40..8b0f2df5300 100644 --- a/study/test/src/org/labkey/test/tests/study/CohortTest.java +++ b/study/test/src/org/labkey/test/tests/study/CohortTest.java @@ -18,6 +18,7 @@ import org.jetbrains.annotations.Nullable; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.labkey.remoteapi.Connection; import org.labkey.test.BaseWebDriverTest; import org.labkey.test.Locator; import org.labkey.test.Locators; @@ -26,6 +27,7 @@ import org.labkey.test.categories.Daily; import org.labkey.test.components.ParticipantListWebPart; import org.labkey.test.pages.study.ManageVisitPage; +import org.labkey.test.params.study.DatasetDefinition; import org.labkey.test.util.DataRegionTable; import org.labkey.test.util.LogMethod; import org.labkey.test.util.PortalHelper; @@ -35,6 +37,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -65,7 +68,7 @@ public class CohortTest extends BaseWebDriverTest private static final String[] PTIDS_POSITIVE_NOCOHORT = {INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1}; @Test - public void testSteps() + public void testSteps() throws Exception { doSetup(); cohortTest(); @@ -75,14 +78,26 @@ public void testSteps() } @LogMethod - private void doSetup() + private void doSetup() throws Exception { log("Check advanced cohort features."); _containerHelper.createProject(PROJECT_NAME, "Study"); - _containerHelper.enableModule("Specimen"); + if (_studyHelper.isSpecimenModulePresent()) + _containerHelper.enableModule("Specimen"); importStudyFromZip(COHORT_STUDY_ZIP); clickProject(PROJECT_NAME); - new PortalHelper(this).addWebPart("Specimens"); + if (_studyHelper.isSpecimenModulePresent()) + { + new PortalHelper(this).addWebPart("Specimens"); + } + else + { + // Ensure Unassigned participant exists (otherwise created by specimen import) + Connection connection = createDefaultConnection(); + new DatasetDefinition("Dummy Dataset") + .create(connection, PROJECT_NAME) + .insertRows(connection, List.of(Map.of("ParticipantID", UNASSIGNED_1, "SequenceNum", "1"))); + } // Check all cohorts after initial import. } @@ -90,121 +105,126 @@ private void doSetup() private void cohortTest() { Locator.XPathLocator specimenReportTableLoc = Locators.bodyPanel().append(Locator.tagWithClass("table", "labkey-data-region-legacy")); - - waitAndClick(WAIT_FOR_JAVASCRIPT, Locator.linkWithText("Blood"), WAIT_FOR_PAGE); + DataRegionTable specimenTable = null; + List specimenReportTables; - DataRegionTable specimenTable = new DataRegionTable("SpecimenDetail", getDriver()); - assertEquals("Incorrect number of vials.", "Count (non-blank): 25", specimenTable.getSummaryStatFooterText("Global Unique Id")); // 5 participants x 5 visits - List cohortValues = specimenTable.getColumnDataAsText("Collection Cohort"); - assertEquals(10, Collections.frequency(cohortValues, "Positive")); - assertEquals(10, Collections.frequency(cohortValues, "Negative")); - - setCohortFilter(COHORT_NEGATIVE, AdvancedCohortType.INITIAL); - verifyVialCount(specimenTable, 20); // One participant has no cohorts. - setCohortFilter(COHORT_POSITIVE, AdvancedCohortType.INITIAL); - verifyVialCount(specimenTable, 0); // All participants initially negative - setCohortFilter(COHORT_NEGATIVE, AdvancedCohortType.CURRENT); - verifyVialCount(specimenTable, 0); // All participants are positive by the last visit - setCohortFilter(COHORT_POSITIVE, AdvancedCohortType.CURRENT); - verifyVialCount(specimenTable, 20); // All participants are positive by the last visit - setCohortFilter(COHORT_NEGATIVE, AdvancedCohortType.DATA_COLLECTION); - verifyVialCount(specimenTable, 10); - setCohortFilter(COHORT_POSITIVE, AdvancedCohortType.DATA_COLLECTION); - verifyVialCount(specimenTable, 10); - - clickAndWait(Locator.linkWithText("Reports")); - clickButtonByIndex("View", 2); // Specimen Report: Type by Cohort - assertTextPresent("Specimen Report: Type by Cohort"); - checkCheckbox(Locator.checkboxByName("viewPtidList")); - clickButton("Refresh"); - List specimenReportTables = specimenReportTableLoc.findElements(getDriver()); - assignId(specimenReportTables.get(0), TABLE_NEGATIVE); - assignId(specimenReportTables.get(1), TABLE_POSITIVE); - assignId(specimenReportTables.get(2), TABLE_UNASSIGNED); - assertTableCellContains(TABLE_NEGATIVE, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellContains(TABLE_NEGATIVE, 2, 3, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellContains(TABLE_NEGATIVE, 2, 4, INFECTED_3, INFECTED_4); - assertTableCellContains(TABLE_NEGATIVE, 2, 5, INFECTED_4); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 2, UNASSIGNED_1); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 3, INFECTED_1, UNASSIGNED_1); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 4, INFECTED_1, INFECTED_2, UNASSIGNED_1); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 6, INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1, INFECTED_4); - assertTableCellContains(TABLE_POSITIVE, 2, 3, INFECTED_1); - assertTableCellContains(TABLE_POSITIVE, 2, 4, INFECTED_1, INFECTED_2); - assertTableCellContains(TABLE_POSITIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3); - assertTableCellContains(TABLE_POSITIVE, 2, 6, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_POSITIVE, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4, UNASSIGNED_1); - assertTableCellNotContains(TABLE_POSITIVE, 2, 3, INFECTED_2, INFECTED_3, INFECTED_4, UNASSIGNED_1); - assertTableCellNotContains(TABLE_POSITIVE, 2, 4, INFECTED_3, INFECTED_4, UNASSIGNED_1); - assertTableCellNotContains(TABLE_POSITIVE, 2, 5, INFECTED_4, UNASSIGNED_1); - assertTableCellNotContains(TABLE_POSITIVE, 2, 6, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 2, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 3, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 4, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 5, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 6, UNASSIGNED_1); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 6, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - - selectOptionByText(Locator.name("cohortFilterType"), AdvancedCohortType.INITIAL.toString()); - clickButton("Refresh"); - specimenReportTables = specimenReportTableLoc.findElements(getDriver()); - assignId(specimenReportTables.get(0), TABLE_NEGATIVE); - assignId(specimenReportTables.get(1), TABLE_POSITIVE); - assignId(specimenReportTables.get(2), TABLE_UNASSIGNED); - assertTableCellContains(TABLE_NEGATIVE, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellContains(TABLE_NEGATIVE, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellContains(TABLE_NEGATIVE, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellContains(TABLE_NEGATIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellContains(TABLE_NEGATIVE, 2, 6, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 2, UNASSIGNED_1); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 3, UNASSIGNED_1); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 4, UNASSIGNED_1); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 5, UNASSIGNED_1); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 6, UNASSIGNED_1); - assertTableCellContains(TABLE_POSITIVE, 2, 0, "No data to show."); - assertTableCellContains(TABLE_UNASSIGNED, 2, 2, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 3, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 4, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 5, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 6, UNASSIGNED_1); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 6, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - - selectOptionByText(Locator.name("cohortFilterType"), AdvancedCohortType.CURRENT.toString()); - clickButton("Refresh"); - specimenReportTables = specimenReportTableLoc.findElements(getDriver()); - assignId(specimenReportTables.get(0), TABLE_NEGATIVE); - assignId(specimenReportTables.get(1), TABLE_POSITIVE); - assignId(specimenReportTables.get(2), TABLE_UNASSIGNED); - assertTableCellContains(TABLE_NEGATIVE, 2, 0, "No data to show."); - assertTableCellContains(TABLE_POSITIVE, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellContains(TABLE_POSITIVE, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellContains(TABLE_POSITIVE, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellContains(TABLE_POSITIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellContains(TABLE_POSITIVE, 2, 6, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_POSITIVE, 2, 2, UNASSIGNED_1); - assertTableCellNotContains(TABLE_POSITIVE, 2, 3, UNASSIGNED_1); - assertTableCellNotContains(TABLE_POSITIVE, 2, 4, UNASSIGNED_1); - assertTableCellNotContains(TABLE_POSITIVE, 2, 5, UNASSIGNED_1); - assertTableCellNotContains(TABLE_POSITIVE, 2, 6, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 2, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 3, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 4, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 5, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 6, UNASSIGNED_1); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 6, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + if (_studyHelper.isSpecimenModulePresent()) + { + waitAndClick(WAIT_FOR_JAVASCRIPT, Locator.linkWithText("Blood"), WAIT_FOR_PAGE); + + specimenTable = new DataRegionTable("SpecimenDetail", getDriver()); + assertEquals("Incorrect number of vials.", "Count (non-blank): 25", specimenTable.getSummaryStatFooterText("Global Unique Id")); // 5 participants x 5 visits + List cohortValues = specimenTable.getColumnDataAsText("Collection Cohort"); + assertEquals(10, Collections.frequency(cohortValues, "Positive")); + assertEquals(10, Collections.frequency(cohortValues, "Negative")); + + setCohortFilter(COHORT_NEGATIVE, AdvancedCohortType.INITIAL); + verifyVialCount(specimenTable, 20); // One participant has no cohorts. + setCohortFilter(COHORT_POSITIVE, AdvancedCohortType.INITIAL); + verifyVialCount(specimenTable, 0); // All participants initially negative + setCohortFilter(COHORT_NEGATIVE, AdvancedCohortType.CURRENT); + verifyVialCount(specimenTable, 0); // All participants are positive by the last visit + setCohortFilter(COHORT_POSITIVE, AdvancedCohortType.CURRENT); + verifyVialCount(specimenTable, 20); // All participants are positive by the last visit + setCohortFilter(COHORT_NEGATIVE, AdvancedCohortType.DATA_COLLECTION); + verifyVialCount(specimenTable, 10); + setCohortFilter(COHORT_POSITIVE, AdvancedCohortType.DATA_COLLECTION); + verifyVialCount(specimenTable, 10); + + clickAndWait(Locator.linkWithText("Reports")); + clickButtonByIndex("View", 2); // Specimen Report: Type by Cohort + assertTextPresent("Specimen Report: Type by Cohort"); + checkCheckbox(Locator.checkboxByName("viewPtidList")); + clickButton("Refresh"); + specimenReportTables = specimenReportTableLoc.findElements(getDriver()); + assignId(specimenReportTables.get(0), TABLE_NEGATIVE); + assignId(specimenReportTables.get(1), TABLE_POSITIVE); + assignId(specimenReportTables.get(2), TABLE_UNASSIGNED); + assertTableCellContains(TABLE_NEGATIVE, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellContains(TABLE_NEGATIVE, 2, 3, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellContains(TABLE_NEGATIVE, 2, 4, INFECTED_3, INFECTED_4); + assertTableCellContains(TABLE_NEGATIVE, 2, 5, INFECTED_4); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 2, UNASSIGNED_1); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 3, INFECTED_1, UNASSIGNED_1); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 4, INFECTED_1, INFECTED_2, UNASSIGNED_1); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 6, INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1, INFECTED_4); + assertTableCellContains(TABLE_POSITIVE, 2, 3, INFECTED_1); + assertTableCellContains(TABLE_POSITIVE, 2, 4, INFECTED_1, INFECTED_2); + assertTableCellContains(TABLE_POSITIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3); + assertTableCellContains(TABLE_POSITIVE, 2, 6, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_POSITIVE, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4, UNASSIGNED_1); + assertTableCellNotContains(TABLE_POSITIVE, 2, 3, INFECTED_2, INFECTED_3, INFECTED_4, UNASSIGNED_1); + assertTableCellNotContains(TABLE_POSITIVE, 2, 4, INFECTED_3, INFECTED_4, UNASSIGNED_1); + assertTableCellNotContains(TABLE_POSITIVE, 2, 5, INFECTED_4, UNASSIGNED_1); + assertTableCellNotContains(TABLE_POSITIVE, 2, 6, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 2, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 3, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 4, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 5, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 6, UNASSIGNED_1); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 6, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + + selectOptionByText(Locator.name("cohortFilterType"), AdvancedCohortType.INITIAL.toString()); + clickButton("Refresh"); + specimenReportTables = specimenReportTableLoc.findElements(getDriver()); + assignId(specimenReportTables.get(0), TABLE_NEGATIVE); + assignId(specimenReportTables.get(1), TABLE_POSITIVE); + assignId(specimenReportTables.get(2), TABLE_UNASSIGNED); + assertTableCellContains(TABLE_NEGATIVE, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellContains(TABLE_NEGATIVE, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellContains(TABLE_NEGATIVE, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellContains(TABLE_NEGATIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellContains(TABLE_NEGATIVE, 2, 6, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 2, UNASSIGNED_1); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 3, UNASSIGNED_1); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 4, UNASSIGNED_1); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 5, UNASSIGNED_1); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 6, UNASSIGNED_1); + assertTableCellContains(TABLE_POSITIVE, 2, 0, "No data to show."); + assertTableCellContains(TABLE_UNASSIGNED, 2, 2, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 3, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 4, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 5, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 6, UNASSIGNED_1); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 6, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + + selectOptionByText(Locator.name("cohortFilterType"), AdvancedCohortType.CURRENT.toString()); + clickButton("Refresh"); + specimenReportTables = specimenReportTableLoc.findElements(getDriver()); + assignId(specimenReportTables.get(0), TABLE_NEGATIVE); + assignId(specimenReportTables.get(1), TABLE_POSITIVE); + assignId(specimenReportTables.get(2), TABLE_UNASSIGNED); + assertTableCellContains(TABLE_NEGATIVE, 2, 0, "No data to show."); + assertTableCellContains(TABLE_POSITIVE, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellContains(TABLE_POSITIVE, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellContains(TABLE_POSITIVE, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellContains(TABLE_POSITIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellContains(TABLE_POSITIVE, 2, 6, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_POSITIVE, 2, 2, UNASSIGNED_1); + assertTableCellNotContains(TABLE_POSITIVE, 2, 3, UNASSIGNED_1); + assertTableCellNotContains(TABLE_POSITIVE, 2, 4, UNASSIGNED_1); + assertTableCellNotContains(TABLE_POSITIVE, 2, 5, UNASSIGNED_1); + assertTableCellNotContains(TABLE_POSITIVE, 2, 6, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 2, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 3, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 4, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 5, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 6, UNASSIGNED_1); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 6, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + } // Check that cohort filters persist through participant view @@ -220,23 +240,26 @@ private void cohortTest() clickButtonByIndex("Move Up", 1, 0); clickButtonByIndex("Move Up", 1, 0); clickButton("Save"); - clickProject(PROJECT_NAME); - click(Locator.tagContainingText("span", "Specimen Reports")); // expand - clickAndWait(Locator.linkWithText("View Available Reports")); - clickButtonByIndex("View", 2); - checkCheckbox(Locator.checkboxByName("viewPtidList")); - clickButton("Refresh"); - specimenReportTables = specimenReportTableLoc.findElements(getDriver()); - assignId(specimenReportTables.get(0), TABLE_NEGATIVE); - assignId(specimenReportTables.get(1), TABLE_POSITIVE); - assertTableCellContains(TABLE_NEGATIVE, 2, 3, INFECTED_1, INFECTED_2, INFECTED_4); - assertTableCellContains(TABLE_NEGATIVE, 2, 5, INFECTED_4); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 3, INFECTED_3, UNASSIGNED_1); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1); - assertTableCellContains(TABLE_POSITIVE, 2, 3, INFECTED_3); - assertTableCellContains(TABLE_POSITIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3); - assertTableCellNotContains(TABLE_POSITIVE, 2, 3, INFECTED_1, INFECTED_2, INFECTED_4, UNASSIGNED_1); - assertTableCellNotContains(TABLE_POSITIVE, 2, 5, INFECTED_4, UNASSIGNED_1); + if (_studyHelper.isSpecimenModulePresent()) + { + clickProject(PROJECT_NAME); + click(Locator.tagContainingText("span", "Specimen Reports")); // expand + clickAndWait(Locator.linkWithText("View Available Reports")); + clickButtonByIndex("View", 2); + checkCheckbox(Locator.checkboxByName("viewPtidList")); + clickButton("Refresh"); + specimenReportTables = specimenReportTableLoc.findElements(getDriver()); + assignId(specimenReportTables.get(0), TABLE_NEGATIVE); + assignId(specimenReportTables.get(1), TABLE_POSITIVE); + assertTableCellContains(TABLE_NEGATIVE, 2, 3, INFECTED_1, INFECTED_2, INFECTED_4); + assertTableCellContains(TABLE_NEGATIVE, 2, 5, INFECTED_4); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 3, INFECTED_3, UNASSIGNED_1); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1); + assertTableCellContains(TABLE_POSITIVE, 2, 3, INFECTED_3); + assertTableCellContains(TABLE_POSITIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3); + assertTableCellNotContains(TABLE_POSITIVE, 2, 3, INFECTED_1, INFECTED_2, INFECTED_4, UNASSIGNED_1); + assertTableCellNotContains(TABLE_POSITIVE, 2, 5, INFECTED_4, UNASSIGNED_1); + } // Check that deleting a visit changes the cohort. clickProject(PROJECT_NAME); @@ -251,28 +274,31 @@ private void cohortTest() assertTableCellTextEquals(COHORT_ASSIGNMENT_TABLE_ID, 4, 1, "Negative"); // Infected4 // Check all cohorts after manipulation. - clickProject(PROJECT_NAME); - waitAndClick(WAIT_FOR_JAVASCRIPT, Locator.linkWithText("Blood"), WAIT_FOR_PAGE); - - specimenTable = new DataRegionTable("SpecimenDetail", getDriver()); - verifyVialCount(specimenTable, 20); // 5 participants x 4 visits (was five visits, but one was just deleted) - - setCohortFilter(COHORT_NEGATIVE, AdvancedCohortType.INITIAL); - verifyVialCount(specimenTable, 16); // One participant has no cohorts. - setCohortFilter(COHORT_POSITIVE, AdvancedCohortType.INITIAL); - verifyVialCount(specimenTable, 0); // All participants initially negative - setCohortFilter(COHORT_NEGATIVE, AdvancedCohortType.CURRENT); - verifyVialCount(specimenTable, 4); // Final visit (where Infected4 joins Positive cohort) has been deleted. - setCohortFilter(COHORT_POSITIVE, AdvancedCohortType.CURRENT); - verifyVialCount(specimenTable, 12); - setCohortFilter(COHORT_NEGATIVE, AdvancedCohortType.DATA_COLLECTION); - verifyVialCount(specimenTable, 10); - setCohortFilter(COHORT_POSITIVE, AdvancedCohortType.DATA_COLLECTION); - verifyVialCount(specimenTable, 6); // Visit4 samples no longer have a cohort, and are thus not shown. + if (_studyHelper.isSpecimenModulePresent()) + { + clickProject(PROJECT_NAME); + waitAndClick(WAIT_FOR_JAVASCRIPT, Locator.linkWithText("Blood"), WAIT_FOR_PAGE); + + specimenTable = new DataRegionTable("SpecimenDetail", getDriver()); + verifyVialCount(specimenTable, 20); // 5 participants x 4 visits (was five visits, but one was just deleted) + + setCohortFilter(COHORT_NEGATIVE, AdvancedCohortType.INITIAL); + verifyVialCount(specimenTable, 16); // One participant has no cohorts. + setCohortFilter(COHORT_POSITIVE, AdvancedCohortType.INITIAL); + verifyVialCount(specimenTable, 0); // All participants initially negative + setCohortFilter(COHORT_NEGATIVE, AdvancedCohortType.CURRENT); + verifyVialCount(specimenTable, 4); // Final visit (where Infected4 joins Positive cohort) has been deleted. + setCohortFilter(COHORT_POSITIVE, AdvancedCohortType.CURRENT); + verifyVialCount(specimenTable, 12); + setCohortFilter(COHORT_NEGATIVE, AdvancedCohortType.DATA_COLLECTION); + verifyVialCount(specimenTable, 10); + setCohortFilter(COHORT_POSITIVE, AdvancedCohortType.DATA_COLLECTION); + verifyVialCount(specimenTable, 6); // Visit4 samples no longer have a cohort, and are thus not shown. + } // Check that participant view respects filter. clickProject(PROJECT_NAME); - clickAndWait(Locator.linkWithText("2 datasets")); + clickAndWait(Locator.linkContainingText("datasets")); clickAndWait(Locator.linkWithText("Test Results")); _customizeViewsHelper.openCustomizeViewPanel(); _customizeViewsHelper.addSort("ParticipantId", SortDirection.ASC); @@ -300,50 +326,53 @@ private void cohortTest() assertAlertContains("Update cohort assignments now?"); }); - clickProject(PROJECT_NAME); - waitAndClick(Locator.linkWithText("Blood")); - DataRegionTable vials = new DataRegionTable("SpecimenDetail", getDriver()); - vials.setFilter("CollectionCohort", "Equals", COHORT_POSITIVE); - assertEquals("Unexpected number of collection cohort rows", 12, vials.getDataRowCount()); - vials.setFilter("CollectionCohort", "Equals", COHORT_NEGATIVE); - assertEquals("Unexpected number of collection cohort rows", 4, vials.getDataRowCount()); - vials.clearFilter("CollectionCohort"); - - clickAndWait(Locator.linkWithText("Reports")); - clickButtonByIndex("View", 2); // Specimen Report: Type by Cohort - assertTextPresent("Specimen Report: Type by Cohort"); - checkCheckbox(Locator.checkboxByName("viewPtidList")); - clickButton("Refresh"); - - // Basic cohorts should be determined only by the most recent cohort assignment. - specimenReportTables = specimenReportTableLoc.findElements(getDriver()); - assignId(specimenReportTables.get(0), TABLE_NEGATIVE); - assignId(specimenReportTables.get(1), TABLE_POSITIVE); - assignId(specimenReportTables.get(2), TABLE_UNASSIGNED); - assertTableCellContains(TABLE_NEGATIVE, 2, 2, INFECTED_4); - assertTableCellContains(TABLE_NEGATIVE, 2, 3, INFECTED_4); - assertTableCellContains(TABLE_NEGATIVE, 2, 4, INFECTED_4); - assertTableCellContains(TABLE_NEGATIVE, 2, 5, INFECTED_4); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1); - assertTableCellNotContains(TABLE_NEGATIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1); - assertTableCellContains(TABLE_POSITIVE, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3); - assertTableCellContains(TABLE_POSITIVE, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3); - assertTableCellContains(TABLE_POSITIVE, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3); - assertTableCellContains(TABLE_POSITIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3); - assertTableCellNotContains(TABLE_POSITIVE, 2, 2, UNASSIGNED_1, INFECTED_4); - assertTableCellNotContains(TABLE_POSITIVE, 2, 3, UNASSIGNED_1, INFECTED_4); - assertTableCellNotContains(TABLE_POSITIVE, 2, 4, UNASSIGNED_1, INFECTED_4); - assertTableCellNotContains(TABLE_POSITIVE, 2, 5, UNASSIGNED_1, INFECTED_4); - assertTableCellContains(TABLE_UNASSIGNED, 2, 2, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 3, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 4, UNASSIGNED_1); - assertTableCellContains(TABLE_UNASSIGNED, 2, 5, UNASSIGNED_1); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); - assertTableCellNotContains(TABLE_UNASSIGNED, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + if (_studyHelper.isSpecimenModulePresent()) + { + clickProject(PROJECT_NAME); + waitAndClick(Locator.linkWithText("Blood")); + DataRegionTable vials = new DataRegionTable("SpecimenDetail", getDriver()); + vials.setFilter("CollectionCohort", "Equals", COHORT_POSITIVE); + assertEquals("Unexpected number of collection cohort rows", 12, vials.getDataRowCount()); + vials.setFilter("CollectionCohort", "Equals", COHORT_NEGATIVE); + assertEquals("Unexpected number of collection cohort rows", 4, vials.getDataRowCount()); + vials.clearFilter("CollectionCohort"); + + clickAndWait(Locator.linkWithText("Reports")); + clickButtonByIndex("View", 2); // Specimen Report: Type by Cohort + assertTextPresent("Specimen Report: Type by Cohort"); + checkCheckbox(Locator.checkboxByName("viewPtidList")); + clickButton("Refresh"); + + // Basic cohorts should be determined only by the most recent cohort assignment. + specimenReportTables = specimenReportTableLoc.findElements(getDriver()); + assignId(specimenReportTables.get(0), TABLE_NEGATIVE); + assignId(specimenReportTables.get(1), TABLE_POSITIVE); + assignId(specimenReportTables.get(2), TABLE_UNASSIGNED); + assertTableCellContains(TABLE_NEGATIVE, 2, 2, INFECTED_4); + assertTableCellContains(TABLE_NEGATIVE, 2, 3, INFECTED_4); + assertTableCellContains(TABLE_NEGATIVE, 2, 4, INFECTED_4); + assertTableCellContains(TABLE_NEGATIVE, 2, 5, INFECTED_4); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1); + assertTableCellNotContains(TABLE_NEGATIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, UNASSIGNED_1); + assertTableCellContains(TABLE_POSITIVE, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3); + assertTableCellContains(TABLE_POSITIVE, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3); + assertTableCellContains(TABLE_POSITIVE, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3); + assertTableCellContains(TABLE_POSITIVE, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3); + assertTableCellNotContains(TABLE_POSITIVE, 2, 2, UNASSIGNED_1, INFECTED_4); + assertTableCellNotContains(TABLE_POSITIVE, 2, 3, UNASSIGNED_1, INFECTED_4); + assertTableCellNotContains(TABLE_POSITIVE, 2, 4, UNASSIGNED_1, INFECTED_4); + assertTableCellNotContains(TABLE_POSITIVE, 2, 5, UNASSIGNED_1, INFECTED_4); + assertTableCellContains(TABLE_UNASSIGNED, 2, 2, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 3, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 4, UNASSIGNED_1); + assertTableCellContains(TABLE_UNASSIGNED, 2, 5, UNASSIGNED_1); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 2, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 3, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 4, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + assertTableCellNotContains(TABLE_UNASSIGNED, 2, 5, INFECTED_1, INFECTED_2, INFECTED_3, INFECTED_4); + } } private void assignId(WebElement el, String id) @@ -461,7 +490,8 @@ private void enrolledCohortTest() }); verifyDatasetEnrolledCohortFilterAdvanced("Test Results", 16, 0, 12, 6); - verifySpecimenEnrolledCohortFilterAdvanced("By Individual Vial", 20, 4, 16, 10); + if (_studyHelper.isSpecimenModulePresent()) + verifySpecimenEnrolledCohortFilterAdvanced("By Individual Vial", 20, 4, 16, 10); } @LogMethod @@ -469,7 +499,7 @@ private void updateCohortAssignmentTest() { // Regression test for Issue: 30616 clickProject(PROJECT_NAME); - clickAndWait(Locator.linkWithText("2 datasets")); + clickAndWait(Locator.linkContainingText("datasets")); clickAndWait(Locator.linkWithText("Cohort Assignments")); DataRegionTable dataset = new DataRegionTable("Dataset", getDriver()); clickAndWait(dataset.updateLink(0)); @@ -487,7 +517,7 @@ private void savedCohortFilterTest() { log("Create cohort filtered views"); clickProject(PROJECT_NAME); - clickAndWait(Locator.linkWithText("2 datasets")); + clickAndWait(Locator.linkContainingText("datasets")); clickAndWait(Locator.linkWithText("Test Results")); setCohortFilter(COHORT_NEGATIVE, AdvancedCohortType.CURRENT); // 4 rows @@ -558,7 +588,7 @@ private void verifyDatasetEnrolledCohortFilterAdvanced(String datasetName, int a private DataRegionTable verifyUnfilteredDataset(String datasetName, int allRowCount) { clickTab("Overview"); - clickAndWait(Locator.linkWithText("2 datasets")); + clickAndWait(Locator.linkContainingText("datasets")); clickAndWait(Locator.linkWithText(datasetName)); assertTextNotPresent("Current cohort is enrolled or unassigned"); @@ -571,17 +601,20 @@ private DataRegionTable verifyUnfilteredDataset(String datasetName, int allRowCo private void verifySpecimenEnrolledCohortFilter(String specimenLink, boolean enrolledMenu, int allRowCount, int enrolledRowCount) { - verifyUnfilteredSpecimens(specimenLink, allRowCount); - - if (enrolledMenu) - { - DataRegionTable specimenTable = new DataRegionTable("SpecimenDetail", getDriver()); - DataRegionTable.findDataRegion(this).clickHeaderMenu("Groups", "Enrolled"); - verifyVialCount(specimenTable, enrolledRowCount); - } - else + if (_studyHelper.isSpecimenModulePresent()) { - assertFalse("Enrolled menu should not be present", _extHelper.isExtMenuPresent("Groups", "Enrolled")); + verifyUnfilteredSpecimens(specimenLink, allRowCount); + + if (enrolledMenu) + { + DataRegionTable specimenTable = new DataRegionTable("SpecimenDetail", getDriver()); + DataRegionTable.findDataRegion(this).clickHeaderMenu("Groups", "Enrolled"); + verifyVialCount(specimenTable, enrolledRowCount); + } + else + { + assertFalse("Enrolled menu should not be present", _extHelper.isExtMenuPresent("Groups", "Enrolled")); + } } } diff --git a/study/test/src/org/labkey/test/tests/study/SpecimenMergeTest.java b/study/test/src/org/labkey/test/tests/study/SpecimenMergeTest.java deleted file mode 100644 index 989076548f2..00000000000 --- a/study/test/src/org/labkey/test/tests/study/SpecimenMergeTest.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2016-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.test.tests.study; - -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.labkey.test.BaseWebDriverTest; -import org.labkey.test.Locator; -import org.labkey.test.TestTimeoutException; -import org.labkey.test.categories.Daily; -import org.labkey.test.categories.Specimen; -import org.labkey.test.util.StudyHelper; - -import java.io.File; -import java.util.Arrays; -import java.util.List; - -/** - * CreateVialsTest also uses the specimen merge feature. - */ -@Category({Daily.class, Specimen.class}) -@BaseWebDriverTest.ClassTimeout(minutes = 7) -public class SpecimenMergeTest extends BaseWebDriverTest -{ - protected static final String PROJECT_NAME = "SpecimenMergeTest"; - protected static final String FOLDER_NAME = "My Study"; - - protected static final File LAB19_SPECIMENS = StudyHelper.getSpecimenArchiveFile("lab19.specimens"); - protected static final File LAB20_SPECIMENS = StudyHelper.getSpecimenArchiveFile("lab20.specimens"); - protected static final File LAB21_SPECIMENS = StudyHelper.getSpecimenArchiveFile("lab21.specimens"); - - protected static final File SPECIMEN_TEMP_DIR = StudyHelper.getStudyTempDir(); - protected int pipelineJobCount = 3; - - protected final String _studyDataRoot = StudyHelper.getStudySubfolderPath(); - - @Override - public List getAssociatedModules() - { - return Arrays.asList("study"); - } - - @Override - protected BrowserType bestBrowser() - { - return BrowserType.CHROME; - } - - @Override - protected String getProjectName() - { - return PROJECT_NAME; - } - - @Override - protected void doCleanup(boolean afterTest) throws TestTimeoutException - { - File tempDir = SPECIMEN_TEMP_DIR; - if (tempDir.exists()) - { - for (File file : tempDir.listFiles()) - file.delete(); - tempDir.delete(); - } - _containerHelper.deleteProject(getProjectName(), afterTest); - } - - @Test - public void testSteps() - { - setUpSteps(); - - importFirstFileSet(); - } - - protected void importFirstFileSet() - { - File[] archives = new File[]{ - LAB19_SPECIMENS, - LAB20_SPECIMENS, - LAB21_SPECIMENS - }; - SpecimenImporter importer = new SpecimenImporter(new File(_studyDataRoot), archives, SPECIMEN_TEMP_DIR, FOLDER_NAME, pipelineJobCount); - importer.setExpectError(true); - importer.importAndWaitForComplete(); - - // Check there was an error in the specimen merge. - clickAndWait(Locator.linkWithText("ERROR")); - assertTextPresent("lab20", "Conflicting specimens found for GlobalUniqueId 'AAA07XK5-02'"); - checkExpectedErrors(2); - } - - protected void setUpSteps() - { - _containerHelper.createProject(PROJECT_NAME, null); - _containerHelper.createSubfolder(PROJECT_NAME, PROJECT_NAME, FOLDER_NAME, "Study", null); - _containerHelper.enableModule("Specimen"); - clickButton("Create Study"); - clickButton("Create Study"); - - setPipelineRoot(_studyDataRoot); - clickFolder("My Study"); - clickAndWait(Locator.linkWithText("Manage Files")); - } -} diff --git a/study/test/src/org/labkey/test/tests/study/SpecimenReplaceTest.java b/study/test/src/org/labkey/test/tests/study/SpecimenReplaceTest.java deleted file mode 100644 index 0f431007196..00000000000 --- a/study/test/src/org/labkey/test/tests/study/SpecimenReplaceTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2016-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.test.tests.study; - -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.labkey.test.BaseWebDriverTest; -import org.labkey.test.Locator; -import org.labkey.test.categories.Daily; -import org.labkey.test.categories.Specimen; -import org.labkey.test.util.StudyHelper; - -import java.io.File; - -@Category({Daily.class, Specimen.class}) -@BaseWebDriverTest.ClassTimeout(minutes = 6) -public class SpecimenReplaceTest extends SpecimenMergeTest -{ - protected static final File LAB_EDITED_SPECIMENS = StudyHelper.getSpecimenArchiveFile("lab19edit.specimens"); - protected static final File LAB15_SPECIMENS = StudyHelper.getSpecimenArchiveFile("lab15.specimens"); - protected static final File LAB20_SPECIMENS = StudyHelper.getSpecimenArchiveFile("lab20.specimens"); - protected static final File LAB21_SPECIMENS = StudyHelper.getSpecimenArchiveFile("lab21.specimens"); - - @Override - @Test - public void testSteps() - { - setUpSteps(); - importFirstFileSet(); - - verifyReplaceWithIdenticalFiles(); - verifyReplaceWithSlightlyModifiedData(); - verifyReplaceWithNewData(); - } - - private void verifyReplaceWithNewData() - { - SpecimenImporter importer = new SpecimenImporter(new File(_studyDataRoot), new File[] {LAB15_SPECIMENS}, SPECIMEN_TEMP_DIR, FOLDER_NAME, ++pipelineJobCount); - importer.setExpectError(true); - importer.importAndWaitForComplete(); - //go to individual vial list - goToIndividualvialsDRT(); - assertElementPresent(Locator.paginationText(12)); - - //entry for participant 999320812 have been replaced with 123123123 - assertTextPresent("999320812"); - assertTextNotPresent("123123123"); - } - - private void verifyReplaceWithSlightlyModifiedData() - { - SpecimenImporter importer = new SpecimenImporter(new File(_studyDataRoot), new File[] {LAB_EDITED_SPECIMENS}, SPECIMEN_TEMP_DIR, FOLDER_NAME, ++pipelineJobCount); - importer.setExpectError(true); - importer.importAndWaitForComplete(); - //go to individual vial list - goToIndividualvialsDRT(); - assertElementPresent(Locator.paginationText(1, 100, 666)); - - //entry for participant 999320812 have been replaced with 123123123 - assertTextNotPresent("999320812"); - assertTextPresent("123123123"); - } - - private void verifyReplaceWithIdenticalFiles() - { - pipelineJobCount += 3; - importFirstFileSet(); - goToIndividualvialsDRT(); - - assertElementPresent(Locator.paginationText(1, 100, 667)); - } - - private void goToIndividualvialsDRT() - { - clickTab("Specimen Data"); - waitAndClickAndWait(Locator.linkWithText("By Individual Vial")); - } -} diff --git a/study/test/src/org/labkey/test/tests/study/StudyExportTest.java b/study/test/src/org/labkey/test/tests/study/StudyExportTest.java index 44e3453856a..4773d175aaf 100644 --- a/study/test/src/org/labkey/test/tests/study/StudyExportTest.java +++ b/study/test/src/org/labkey/test/tests/study/StudyExportTest.java @@ -130,7 +130,12 @@ public void runApiTests() throws Exception @Override protected int getVisitCount() { - return super.getVisitCount() + 1; + return super.getVisitCount() + 1 + (_studyHelper.isSpecimenModulePresent() ? 0 : -11); + } + + protected int getUnusedVisitCount() + { + return _studyHelper.isSpecimenModulePresent() ? 24 : 26; } protected void doCohortCreateSteps() diff --git a/study/test/src/org/labkey/test/tests/study/StudyManageParticipantsTest.java b/study/test/src/org/labkey/test/tests/study/StudyManageParticipantsTest.java index f95ffdb5541..456d47d2c99 100644 --- a/study/test/src/org/labkey/test/tests/study/StudyManageParticipantsTest.java +++ b/study/test/src/org/labkey/test/tests/study/StudyManageParticipantsTest.java @@ -229,15 +229,19 @@ public void testMergeParticipants() setFormElement(OLD_ID_FIELD, PTID_WITH_ALIAS); setFormElement(NEW_ID_FIELD, PTID_NEW_1); waitAndClick(Ext4Helper.Locators.ext4ButtonEnabled("Preview")); - waitForElement(Locator.tag("span").containing("Specimen data is not editable"), MERGE_SUCCESS_TIMEOUT); + if (_studyHelper.isSpecimenModulePresent()) + waitForElement(Locator.tag("span").containing("Specimen data is not editable"), MERGE_SUCCESS_TIMEOUT); // Error on missing value for source field. - assertElementPresent(Locator.tag("span").containing("Missing value for required property")); + waitForElement(Locator.tag("span").containing("Missing value for required property")); + waitForElementToDisappear(Locator.tag("td").withText("Loading...")); log("Check not reporting conflict when no conflict exists, and warning on existing alias"); setFormElement(ALIAS_SOURCE_FIELD, ALIAS_SOURCE_2); waitAndClick(Ext4Helper.Locators.ext4ButtonEnabled("Preview")); waitForElement(Locator.tag("span").containing("Preview Complete"), MERGE_SUCCESS_TIMEOUT); - assertElementPresent(Locator.tag("td").containing("Warning: Specimen data is not editable")); + waitForElementToDisappear(Locator.tag("td").withText("Loading...")); + if (_studyHelper.isSpecimenModulePresent()) + assertElementPresent(Locator.tag("td").containing("Warning: Specimen data is not editable")); assertElementNotPresent(Locator.linkContainingText("Conflict!")); assertElementPresent(Locator.tag("td").containing("Aliases are not updated by this process")); assertElementPresent(Locator.tag("td").containing("Warning: " + PTID_WITH_ALIAS + " has existing aliases")); @@ -258,6 +262,7 @@ public void testMergeParticipants() setFormElement(NEW_ID_FIELD, PTID_NEW_2); waitAndClick(Ext4Helper.Locators.ext4ButtonEnabled("Preview")); waitForElement(Locator.tag("span").containing("Preview Complete"), MERGE_SUCCESS_TIMEOUT); + waitForElementToDisappear(Locator.tag("td").withText("Loading...")); assertElementNotPresent(Locator.linkContainingText(PTID_NO_ALIAS + " has existing aliases")); assertElementPresent(Locator.linkContainingText("Conflict!")); clickButton("Merge", 0); diff --git a/study/test/src/org/labkey/test/tests/study/StudyManualTest.java b/study/test/src/org/labkey/test/tests/study/StudyManualTest.java index 347ddd9dcf1..9d2e0940dc5 100644 --- a/study/test/src/org/labkey/test/tests/study/StudyManualTest.java +++ b/study/test/src/org/labkey/test/tests/study/StudyManualTest.java @@ -39,6 +39,7 @@ protected void doCreateSteps() { createStudyManually(); startSpecimenImport(2); + waitForSpecimenImport(); afterManualCreate(); } diff --git a/study/test/src/org/labkey/test/tests/study/StudyPHIExportTest.java b/study/test/src/org/labkey/test/tests/study/StudyPHIExportTest.java index d7da557528f..a05c71434e6 100644 --- a/study/test/src/org/labkey/test/tests/study/StudyPHIExportTest.java +++ b/study/test/src/org/labkey/test/tests/study/StudyPHIExportTest.java @@ -251,21 +251,24 @@ protected void verifyMaskedClinics(int clinicCount) // } // assertEquals("Unexpected number of clinics", clinicCount, foundClinics); - clickTab("Specimen Data"); - sleep(2000); // the link moves while the specimen search form finishes layout - waitAndClickAndWait(Locator.linkWithText("Blood (Whole)")); - DataRegionTable vialsTable = new DataRegionTable("SpecimenDetail", this); - List procLocs = vialsTable.getColumnDataAsText("Processing Location"); - procLocs.removeLast(); // Skip aggregate row - for (String procLoc : procLocs) - { - assertTrue("Processing Locations was not masked", procLoc.equals("Clinic") || nonClinics.contains(procLoc)); - } - List siteNames = vialsTable.getColumnDataAsText("Site Name"); - siteNames.removeLast(); // Skip aggregate row - for (String siteName : siteNames) + if (_studyHelper.isSpecimenModuleActive()) { - assertTrue("Site Name was not masked", siteName.equals("Clinic") || siteName.equals("In Transit")); + clickTab("Specimen Data"); + sleep(2000); // the link moves while the specimen search form finishes layout + waitAndClickAndWait(Locator.linkWithText("Blood (Whole)")); + DataRegionTable vialsTable = new DataRegionTable("SpecimenDetail", this); + List procLocs = vialsTable.getColumnDataAsText("Processing Location"); + procLocs.removeLast(); // Skip aggregate row + for (String procLoc : procLocs) + { + assertTrue("Processing Locations was not masked", procLoc.equals("Clinic") || nonClinics.contains(procLoc)); + } + List siteNames = vialsTable.getColumnDataAsText("Site Name"); + siteNames.removeLast(); // Skip aggregate row + for (String siteName : siteNames) + { + assertTrue("Site Name was not masked", siteName.equals("Clinic") || siteName.equals("In Transit")); + } } } diff --git a/study/test/src/org/labkey/test/tests/study/StudyProtocolDesignerTest.java b/study/test/src/org/labkey/test/tests/study/StudyProtocolDesignerTest.java index 0680c708cd2..48dfa6d2ed8 100644 --- a/study/test/src/org/labkey/test/tests/study/StudyProtocolDesignerTest.java +++ b/study/test/src/org/labkey/test/tests/study/StudyProtocolDesignerTest.java @@ -17,6 +17,7 @@ import org.jetbrains.annotations.Nullable; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -106,6 +107,8 @@ public static void doSetup() { StudyProtocolDesignerTest initTest = getCurrentTest(); + Assume.assumeTrue("Test requires specimen module", initTest._studyHelper.isSpecimenModulePresent()); + initTest._containerHelper.createProject(initTest.getProjectName(), null); initTest.importFolderFromZip(FOLDER_ARCHIVE); diff --git a/study/test/src/org/labkey/test/tests/study/StudySimpleExportTest.java b/study/test/src/org/labkey/test/tests/study/StudySimpleExportTest.java index 41915d6c813..77c40edafb5 100644 --- a/study/test/src/org/labkey/test/tests/study/StudySimpleExportTest.java +++ b/study/test/src/org/labkey/test/tests/study/StudySimpleExportTest.java @@ -17,6 +17,7 @@ import org.apache.commons.collections4.MapUtils; import org.jetbrains.annotations.Nullable; +import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; @@ -1098,6 +1099,8 @@ private void createSubfolderAndImportFolderFromPipeline(String subfolderName) @Test public void verifySpecimenSettings() { + Assume.assumeTrue("Specimen module not present", _studyHelper.isSpecimenModulePresent()); + final String FOLDER_NAME = "Specimen Settings"; log("Export specimen request settings"); diff --git a/study/test/src/org/labkey/test/tests/study/StudyTest.java b/study/test/src/org/labkey/test/tests/study/StudyTest.java index e232f66c248..e14a331cdc0 100644 --- a/study/test/src/org/labkey/test/tests/study/StudyTest.java +++ b/study/test/src/org/labkey/test/tests/study/StudyTest.java @@ -35,7 +35,6 @@ import org.labkey.test.TestTimeoutException; import org.labkey.test.WebTestHelper; import org.labkey.test.categories.Daily; -import org.labkey.test.categories.Specimen; import org.labkey.test.components.html.BootstrapMenu; import org.labkey.test.pages.DatasetPropertiesPage; import org.labkey.test.pages.ImportDataPage; @@ -75,7 +74,7 @@ import static org.labkey.test.util.PasswordUtil.getUsername; import static org.labkey.test.util.PermissionsHelper.AUTHOR_ROLE; -@Category({Specimen.class, Daily.class}) +@Category({Daily.class}) @BaseWebDriverTest.ClassTimeout(minutes = 15) public class StudyTest extends StudyBaseTest { @@ -385,12 +384,12 @@ private void verifyDeleteUnusedVisits() protected int getVisitCount() { - return _studyHelper.isSpecimenModulePresent() ? 66 : 55; + return 66; } protected int getUnusedVisitCount() { - return _studyHelper.isSpecimenModulePresent() ? 24 : 26; + return _studyHelper.isSpecimenModulePresent() ? 24 : 35; } protected static final String SUBJECT_NOUN = "Mouse"; diff --git a/study/test/src/org/labkey/test/tests/study/StudyVisitManagementTest.java b/study/test/src/org/labkey/test/tests/study/StudyVisitManagementTest.java index fd081729427..094e1cb1130 100644 --- a/study/test/src/org/labkey/test/tests/study/StudyVisitManagementTest.java +++ b/study/test/src/org/labkey/test/tests/study/StudyVisitManagementTest.java @@ -19,6 +19,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -126,7 +127,8 @@ private void verifyDeleteVisitDataCounts(Map> vis for (Map.Entry> countEntry : visitDataCounts.entrySet()) { assertEquals("Unexpected visit dataset row count", countEntry.getValue().getLeft().intValue(), deleteMultipleVisitsPage.getVisitDatasetRowCount(countEntry.getKey())); - assertEquals("Unexpected visit specimen row count", countEntry.getValue().getRight().intValue(), deleteMultipleVisitsPage.getVisitSpecimenRowCount(countEntry.getKey())); + if (_studyHelper.isSpecimenModulePresent()) + assertEquals("Unexpected visit specimen row count", countEntry.getValue().getRight().intValue(), deleteMultipleVisitsPage.getVisitSpecimenRowCount(countEntry.getKey())); } } @@ -137,11 +139,14 @@ private DeleteMultipleVisitsPage goToDeleteMultipleVisits() private void verifySpecimenDataRowCount(int expectedRowCount) { - DataRegionTable table = ExecuteQueryPage.beginAt(this,"study", "SpecimenDetail").getDataRegion(); - if (expectedRowCount < 100) - assertEquals("Unexpected number of specimen rows", expectedRowCount, table.getDataRowCount()); - else - assertElementPresent(Locator.paginationText(1, 100, expectedRowCount)); + if (_studyHelper.isSpecimenModulePresent()) + { + DataRegionTable table = ExecuteQueryPage.beginAt(this, "study", "SpecimenDetail").getDataRegion(); + if (expectedRowCount < 100) + assertEquals("Unexpected number of specimen rows", expectedRowCount, table.getDataRowCount()); + else + assertElementPresent(Locator.paginationText(1, 100, expectedRowCount)); + } } private void verifyDatasetRowCount(String datasetName, int expectedRowCount) @@ -155,6 +160,7 @@ private void verifyDatasetRowCount(String datasetName, int expectedRowCount) @Test public void testFailForUndefinedVisitsSpecimen() { + Assume.assumeTrue("Specimen module not present", _studyHelper.isSpecimenModulePresent()); _containerHelper.createSubfolder(getProjectName(), "testFailForUndefinedVisitsSpecimen"); testFailForUndefinedVisits(SPECIMENS_ONLY_FOLDER_ARCHIVE, STUDY_UNDEFINED_VISIT_MSG, 3); } @@ -235,7 +241,7 @@ public void testFailForUndefinedVisitsReload() throws IOException definedVisits = Arrays.asList("301.0 - 391.0", "400.0 - 499.0", "501.0", "601.0", "701.0"); verifyStudyVisits(definedVisits, null); - checkExpectedErrors(6); + checkExpectedErrors(_studyHelper.isSpecimenModulePresent() ? 6 : 5); } private void startFolderImport(boolean failForUndefinedVisits) diff --git a/study/test/src/org/labkey/test/tests/study/TargetStudyTest.java b/study/test/src/org/labkey/test/tests/study/TargetStudyTest.java index cb46d9f2f40..960a3490205 100644 --- a/study/test/src/org/labkey/test/tests/study/TargetStudyTest.java +++ b/study/test/src/org/labkey/test/tests/study/TargetStudyTest.java @@ -233,12 +233,15 @@ protected void linkToStudy() assertEquals(study3OptionText, getSelectedOptionText(table.findCell(5, 0).findElement(AssayConstants.TARGET_STUDY_FIELD_LOCATOR))); log("** Check ptid/visit matches for rows 0-2 and 4, no match for rows 3 and 5"); - assertAttributeContains(table.findCell(0, 1).findElement(By.xpath("i")), "class", "fa fa-check"); - assertAttributeContains(table.findCell(1, 1).findElement(By.xpath("i")), "class", "fa fa-check"); - assertAttributeContains(table.findCell(2, 1).findElement(By.xpath("i")), "class", "fa fa-check"); - assertAttributeContains(table.findCell(3, 1).findElement(By.xpath("i")), "class", "fa fa-times"); - assertAttributeContains(table.findCell(4, 1).findElement(By.xpath("i")), "class", "fa fa-check"); - assertAttributeContains(table.findCell(5, 1).findElement(By.xpath("i")), "class", "fa fa-times"); + if (_studyHelper.isSpecimenModulePresent()) + { + assertAttributeContains(table.findCell(0, 1).findElement(By.xpath("i")), "class", "fa fa-check"); + assertAttributeContains(table.findCell(1, 1).findElement(By.xpath("i")), "class", "fa fa-check"); + assertAttributeContains(table.findCell(2, 1).findElement(By.xpath("i")), "class", "fa fa-check"); + assertAttributeContains(table.findCell(3, 1).findElement(By.xpath("i")), "class", "fa fa-times"); + assertAttributeContains(table.findCell(4, 1).findElement(By.xpath("i")), "class", "fa fa-check"); + assertAttributeContains(table.findCell(5, 1).findElement(By.xpath("i")), "class", "fa fa-times"); + } clickButton("Re-Validate"); assertTextPresent("You must specify a Target Study for all selected rows."); @@ -252,20 +255,23 @@ protected void linkToStudy() log("** Link to studies"); clickButton("Link to Study"); - beginAt("/study/" + TEST_ASSAY_PRJ_SECURITY + "/" + TEST_ASSAY_FLDR_STUDIES + "/" + TEST_ASSAY_FLDR_STUDY1 + "/dataset.view?datasetId=5001"); - DataRegionTable dataset = new DataRegionTable("Dataset", this); - assertEquals(3, dataset.getDataRowCount()); - dataset.setSort("ParticipantId", SortDirection.ASC); - assertEquals(3, dataset.getDataRowCount()); - assertEquals("999320396", dataset.getDataAsText(0, "Participant ID")); - assertEquals("999320396", dataset.getDataAsText(1, "Participant ID")); - assertEquals("999320812", dataset.getDataAsText(2, "Participant ID")); - - beginAt("/study/" + TEST_ASSAY_PRJ_SECURITY + "/" + TEST_ASSAY_FLDR_STUDIES + "/" + TEST_ASSAY_FLDR_STUDY2 + "/dataset.view?datasetId=5001"); - assertEquals(1, dataset.getDataRowCount()); - assertEquals("999320706", dataset.getDataAsText(0, "Participant ID")); + if (_studyHelper.isSpecimenModulePresent()) + { + beginAt("/" + TEST_ASSAY_PRJ_SECURITY + "/" + TEST_ASSAY_FLDR_STUDIES + "/" + TEST_ASSAY_FLDR_STUDY1 + "/study-dataset.view?datasetId=5001"); + DataRegionTable dataset = new DataRegionTable("Dataset", this); + assertEquals(3, dataset.getDataRowCount()); + dataset.setSort("ParticipantId", SortDirection.ASC); + assertEquals(3, dataset.getDataRowCount()); + assertEquals("999320396", dataset.getDataAsText(0, "Participant ID")); + assertEquals("999320396", dataset.getDataAsText(1, "Participant ID")); + assertEquals("999320812", dataset.getDataAsText(2, "Participant ID")); + + beginAt("/" + TEST_ASSAY_PRJ_SECURITY + "/" + TEST_ASSAY_FLDR_STUDIES + "/" + TEST_ASSAY_FLDR_STUDY2 + "/study-dataset.view?datasetId=5001"); + assertEquals(1, dataset.getDataRowCount()); + assertEquals("999320706", dataset.getDataAsText(0, "Participant ID")); + } - beginAt("/study/" + TEST_ASSAY_PRJ_SECURITY + "/" + TEST_ASSAY_FLDR_STUDIES + "/" + TEST_ASSAY_FLDR_STUDY3 + "/dataset.view?datasetId=5001"); + beginAt("/" + TEST_ASSAY_PRJ_SECURITY + "/" + TEST_ASSAY_FLDR_STUDIES + "/" + TEST_ASSAY_FLDR_STUDY3 + "/study-dataset.view?datasetId=5001"); assertEquals(404, getResponseCode()); } diff --git a/study/test/src/org/labkey/test/tests/study/TruncationTest.java b/study/test/src/org/labkey/test/tests/study/TruncationTest.java index 4186faff804..3c68ac22c77 100644 --- a/study/test/src/org/labkey/test/tests/study/TruncationTest.java +++ b/study/test/src/org/labkey/test/tests/study/TruncationTest.java @@ -23,7 +23,6 @@ import org.labkey.test.Locator; import org.labkey.test.TestFileUtils; import org.labkey.test.categories.Daily; -import org.labkey.test.categories.Specimen; import org.labkey.test.pages.DatasetPropertiesPage; import org.labkey.test.pages.ManageDatasetsPage; @@ -34,7 +33,7 @@ import static org.junit.Assert.assertEquals; import static org.labkey.test.util.PermissionsHelper.EDITOR_ROLE; -@Category({Daily.class, Specimen.class}) +@Category({Daily.class}) @BaseWebDriverTest.ClassTimeout(minutes = 6) public class TruncationTest extends BaseWebDriverTest {