From c1b6fb841080c92aa45c050c9a3df14b0a936801 Mon Sep 17 00:00:00 2001 From: Lum Date: Mon, 30 Jun 2025 09:17:36 -0700 Subject: [PATCH 1/3] sample type schema support for contextual roles --- .../labkey/api/exp/api/SampleTypeService.java | 9 +++++++++ .../labkey/api/exp/query/SamplesSchema.java | 18 ++++++++++++++++-- .../experiment/api/SampleTypeServiceImpl.java | 9 +++++++++ .../labkey/study/query/SampleDatasetTable.java | 7 ++++++- 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/api/src/org/labkey/api/exp/api/SampleTypeService.java b/api/src/org/labkey/api/exp/api/SampleTypeService.java index 2d3bc26ecff..0fd8a4d9143 100644 --- a/api/src/org/labkey/api/exp/api/SampleTypeService.java +++ b/api/src/org/labkey/api/exp/api/SampleTypeService.java @@ -32,6 +32,8 @@ import org.labkey.api.lists.permissions.ManagePicklistsPermission; import org.labkey.api.qc.DataState; import org.labkey.api.query.BatchValidationException; +import org.labkey.api.query.QuerySchema; +import org.labkey.api.query.UserSchema; import org.labkey.api.query.ValidationException; import org.labkey.api.security.User; import org.labkey.api.security.permissions.DeletePermission; @@ -40,6 +42,7 @@ import org.labkey.api.security.permissions.Permission; import org.labkey.api.security.permissions.SampleWorkflowJobPermission; import org.labkey.api.security.permissions.UpdatePermission; +import org.labkey.api.security.roles.Role; import org.labkey.api.services.ServiceRegistry; import org.labkey.api.util.Pair; @@ -254,4 +257,10 @@ default Map incrementSampleCounts(@Nullable Date counterDate) long getCurrentCount(NameGenerator.EntityCounter counterType, Container container); void ensureMinSampleCount(long newSeqValue, NameGenerator.EntityCounter counterType, Container container) throws ExperimentException; + + /** + * Should only be used to get a local instance of a schema and isn't cached. All other usages should get + * it from the QueryService. + */ + UserSchema getUserSchema(QuerySchema schema, Set contextualRoles); } diff --git a/api/src/org/labkey/api/exp/query/SamplesSchema.java b/api/src/org/labkey/api/exp/query/SamplesSchema.java index 3ee29647527..38604f07207 100644 --- a/api/src/org/labkey/api/exp/query/SamplesSchema.java +++ b/api/src/org/labkey/api/exp/query/SamplesSchema.java @@ -38,6 +38,7 @@ import org.labkey.api.query.QuerySettings; import org.labkey.api.query.QueryView; import org.labkey.api.query.SchemaKey; +import org.labkey.api.query.UserSchema; import org.labkey.api.security.User; import org.labkey.api.security.permissions.ReadPermission; import org.labkey.api.security.roles.ReaderRole; @@ -56,7 +57,7 @@ import java.util.Map; import java.util.Set; -public class SamplesSchema extends AbstractExpSchema +public class SamplesSchema extends AbstractExpSchema implements UserSchema.HasContextualRoles { public static final String SCHEMA_NAME = "samples"; public static final String STUDY_LINKED_SCHEMA_NAME = "~~STUDY.LINKED.SAMPLE~~"; @@ -107,7 +108,7 @@ public SamplesSchema(QuerySchema schema) setDefaultSchema(schema.getDefaultSchema()); } - public SamplesSchema(QuerySchema schema, boolean studyLinkedSamples) + private SamplesSchema(QuerySchema schema, boolean studyLinkedSamples) { this(schema.getUser(), schema.getContainer()); setDefaultSchema(schema.getDefaultSchema()); @@ -126,6 +127,19 @@ public SamplesSchema(User user, Container container) this(SchemaKey.fromParts(SCHEMA_NAME), user, container); } + public SamplesSchema(QuerySchema schema, Set contextualRoles) + { + this(schema.getUser(), schema.getContainer()); + setDefaultSchema(schema.getDefaultSchema()); + this.contextualRoles = contextualRoles; + } + + @Override + public @NotNull Set getContextualRoles() + { + return contextualRoles; + } + @Override public boolean canReadSchema() { diff --git a/experiment/src/org/labkey/experiment/api/SampleTypeServiceImpl.java b/experiment/src/org/labkey/experiment/api/SampleTypeServiceImpl.java index 1a6f7a2bad8..eff11278cd7 100644 --- a/experiment/src/org/labkey/experiment/api/SampleTypeServiceImpl.java +++ b/experiment/src/org/labkey/experiment/api/SampleTypeServiceImpl.java @@ -77,13 +77,16 @@ import org.labkey.api.query.FieldKey; import org.labkey.api.query.MetadataUnavailableException; import org.labkey.api.query.QueryChangeListener; +import org.labkey.api.query.QuerySchema; import org.labkey.api.query.QueryService; import org.labkey.api.query.SchemaKey; import org.labkey.api.query.SimpleValidationError; +import org.labkey.api.query.UserSchema; import org.labkey.api.query.ValidationException; import org.labkey.api.search.SearchService; import org.labkey.api.security.User; import org.labkey.api.security.permissions.ReadPermission; +import org.labkey.api.security.roles.Role; import org.labkey.api.study.Dataset; import org.labkey.api.study.StudyService; import org.labkey.api.study.publish.StudyPublishService; @@ -2271,4 +2274,10 @@ public void refreshSampleTypeMaterializedView(@NotNull ExpSampleType st, SampleC { ExpMaterialTableImpl.refreshMaterializedView(st.getLSID(), reason); } + + @Override + public UserSchema getUserSchema(QuerySchema schema, Set contextualRoles) + { + return new SamplesSchema(schema, contextualRoles); + } } diff --git a/study/src/org/labkey/study/query/SampleDatasetTable.java b/study/src/org/labkey/study/query/SampleDatasetTable.java index 27befce7b03..593e35e3260 100644 --- a/study/src/org/labkey/study/query/SampleDatasetTable.java +++ b/study/src/org/labkey/study/query/SampleDatasetTable.java @@ -10,18 +10,22 @@ import org.labkey.api.data.TableInfo; import org.labkey.api.exp.api.ExpObject; import org.labkey.api.exp.api.ExpSampleType; +import org.labkey.api.exp.api.SampleTypeService; import org.labkey.api.exp.query.ExpMaterialTable; import org.labkey.api.exp.query.SamplesSchema; import org.labkey.api.query.ExprColumn; import org.labkey.api.query.FieldKey; import org.labkey.api.query.QueryService; import org.labkey.api.query.UserSchema; +import org.labkey.api.security.roles.ReaderRole; +import org.labkey.api.security.roles.RoleManager; import org.labkey.study.model.DatasetDefinition; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; public class SampleDatasetTable extends LinkedDatasetTable { @@ -112,7 +116,8 @@ private TableInfo getSamplesTable() return null; _sampleType = sampleType; - UserSchema userSchema = QueryService.get().getUserSchema(_userSchema.getUser(), sampleType.getContainer(), SamplesSchema.SCHEMA_NAME); + UserSchema userSchema = SampleTypeService.get().getUserSchema(_userSchema, Set.of(RoleManager.getRole(ReaderRole.class))); + //QueryService.get().getUserSchema(_userSchema.getUser(), sampleType.getContainer(), SamplesSchema.SCHEMA_NAME); // Hide 'linked' column for Sample Type Datasets if (userSchema instanceof SamplesSchema samplesSchema) From ab86a14ad1b20df3cdc3c9de640e4a02aaa52b39 Mon Sep 17 00:00:00 2001 From: Lum Date: Wed, 2 Jul 2025 13:55:48 -0700 Subject: [PATCH 2/3] Support contextual roles for the plate schema and well table --- .../plate/AssayPlateMetadataService.java | 13 +++++++ .../labkey/api/exp/api/SampleTypeService.java | 9 ----- .../labkey/api/exp/query/SamplesSchema.java | 7 ---- .../plate/AssayPlateMetadataServiceImpl.java | 10 ++++++ .../labkey/assay/plate/query/PlateSchema.java | 27 ++++++++++++++- .../labkey/assay/plate/query/WellTable.java | 5 +++ .../experiment/api/ExpMaterialTableImpl.java | 34 +++++++++++++++++-- .../experiment/api/SampleTypeServiceImpl.java | 29 ++++++++++------ .../study/query/SampleDatasetTable.java | 8 +---- 9 files changed, 105 insertions(+), 37 deletions(-) diff --git a/api/src/org/labkey/api/assay/plate/AssayPlateMetadataService.java b/api/src/org/labkey/api/assay/plate/AssayPlateMetadataService.java index 9b1e30ad979..10dfdfac534 100644 --- a/api/src/org/labkey/api/assay/plate/AssayPlateMetadataService.java +++ b/api/src/org/labkey/api/assay/plate/AssayPlateMetadataService.java @@ -19,13 +19,17 @@ import org.labkey.api.gwt.client.model.GWTDomain; import org.labkey.api.gwt.client.model.GWTPropertyDescriptor; import org.labkey.api.qc.DataLoaderSettings; +import org.labkey.api.query.QuerySchema; +import org.labkey.api.query.UserSchema; import org.labkey.api.query.ValidationException; import org.labkey.api.security.User; +import org.labkey.api.security.roles.Role; import org.labkey.api.services.ServiceRegistry; import org.labkey.vfs.FileLike; import java.util.List; import java.util.Map; +import java.util.Set; public interface AssayPlateMetadataService { @@ -178,4 +182,13 @@ void applyHitSelectionCriteria( TableInfo resultsTable, List runIds ) throws ValidationException; + + /** + * Should only be used to get a local instance of a plate schema where a contextual role might be involved. Schemas created this way are not cached, + * and all other usages should retrieve schemas from the QueryService. + *

+ * This method would be better placed in the PlateService interface, but it would have required moving that (and other) classes into API. + */ + @NotNull + UserSchema getPlateSchema(QuerySchema schema, Set contextualRoles); } diff --git a/api/src/org/labkey/api/exp/api/SampleTypeService.java b/api/src/org/labkey/api/exp/api/SampleTypeService.java index 0fd8a4d9143..2d3bc26ecff 100644 --- a/api/src/org/labkey/api/exp/api/SampleTypeService.java +++ b/api/src/org/labkey/api/exp/api/SampleTypeService.java @@ -32,8 +32,6 @@ import org.labkey.api.lists.permissions.ManagePicklistsPermission; import org.labkey.api.qc.DataState; import org.labkey.api.query.BatchValidationException; -import org.labkey.api.query.QuerySchema; -import org.labkey.api.query.UserSchema; import org.labkey.api.query.ValidationException; import org.labkey.api.security.User; import org.labkey.api.security.permissions.DeletePermission; @@ -42,7 +40,6 @@ import org.labkey.api.security.permissions.Permission; import org.labkey.api.security.permissions.SampleWorkflowJobPermission; import org.labkey.api.security.permissions.UpdatePermission; -import org.labkey.api.security.roles.Role; import org.labkey.api.services.ServiceRegistry; import org.labkey.api.util.Pair; @@ -257,10 +254,4 @@ default Map incrementSampleCounts(@Nullable Date counterDate) long getCurrentCount(NameGenerator.EntityCounter counterType, Container container); void ensureMinSampleCount(long newSeqValue, NameGenerator.EntityCounter counterType, Container container) throws ExperimentException; - - /** - * Should only be used to get a local instance of a schema and isn't cached. All other usages should get - * it from the QueryService. - */ - UserSchema getUserSchema(QuerySchema schema, Set contextualRoles); } diff --git a/api/src/org/labkey/api/exp/query/SamplesSchema.java b/api/src/org/labkey/api/exp/query/SamplesSchema.java index 38604f07207..b3547bcdd47 100644 --- a/api/src/org/labkey/api/exp/query/SamplesSchema.java +++ b/api/src/org/labkey/api/exp/query/SamplesSchema.java @@ -127,13 +127,6 @@ public SamplesSchema(User user, Container container) this(SchemaKey.fromParts(SCHEMA_NAME), user, container); } - public SamplesSchema(QuerySchema schema, Set contextualRoles) - { - this(schema.getUser(), schema.getContainer()); - setDefaultSchema(schema.getDefaultSchema()); - this.contextualRoles = contextualRoles; - } - @Override public @NotNull Set getContextualRoles() { diff --git a/assay/src/org/labkey/assay/plate/AssayPlateMetadataServiceImpl.java b/assay/src/org/labkey/assay/plate/AssayPlateMetadataServiceImpl.java index dfd3b15cc89..912f8185966 100644 --- a/assay/src/org/labkey/assay/plate/AssayPlateMetadataServiceImpl.java +++ b/assay/src/org/labkey/assay/plate/AssayPlateMetadataServiceImpl.java @@ -78,12 +78,15 @@ import org.labkey.api.qc.DataState; import org.labkey.api.query.BatchValidationException; import org.labkey.api.query.FieldKey; +import org.labkey.api.query.QuerySchema; import org.labkey.api.query.QueryService; import org.labkey.api.query.QueryUpdateService; import org.labkey.api.query.RuntimeValidationException; +import org.labkey.api.query.UserSchema; import org.labkey.api.query.ValidationException; import org.labkey.api.reader.DataLoader; import org.labkey.api.security.User; +import org.labkey.api.security.roles.Role; import org.labkey.api.util.FileUtil; import org.labkey.api.util.JunitUtil; import org.labkey.api.util.Pair; @@ -93,6 +96,7 @@ import org.labkey.api.view.ActionURL; import org.labkey.assay.TSVProtocolSchema; import org.labkey.assay.plate.model.WellBean; +import org.labkey.assay.plate.query.PlateSchema; import org.labkey.assay.plate.query.PlateTable; import org.labkey.assay.plate.query.WellTable; import org.labkey.assay.query.AssayDbSchema; @@ -1485,6 +1489,12 @@ public String format(FieldKey fieldKey) return StringUtils.join(parts, " and "); } + @Override + public UserSchema getPlateSchema(QuerySchema querySchema, Set contextualRoles) + { + return new PlateSchema(querySchema, contextualRoles); + } + private static class PlateMetadataImportHelper extends SimpleAssayDataImportHelper { private final Map> _wellPositionMap; // map of plate position to well table diff --git a/assay/src/org/labkey/assay/plate/query/PlateSchema.java b/assay/src/org/labkey/assay/plate/query/PlateSchema.java index 33f0e3bae92..0d874a1e88a 100644 --- a/assay/src/org/labkey/assay/plate/query/PlateSchema.java +++ b/assay/src/org/labkey/assay/plate/query/PlateSchema.java @@ -16,6 +16,7 @@ package org.labkey.assay.plate.query; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.labkey.api.collections.CaseInsensitiveTreeSet; import org.labkey.api.data.Container; @@ -25,13 +26,16 @@ import org.labkey.api.query.DefaultSchema; import org.labkey.api.query.QuerySchema; import org.labkey.api.query.SimpleUserSchema; +import org.labkey.api.query.UserSchema; import org.labkey.api.security.User; +import org.labkey.api.security.permissions.ReadPermission; +import org.labkey.api.security.roles.Role; import org.labkey.assay.query.AssayDbSchema; import java.util.List; import java.util.Set; -public class PlateSchema extends SimpleUserSchema +public class PlateSchema extends SimpleUserSchema implements UserSchema.HasContextualRoles { public static final String SCHEMA_NAME = "plate"; private static final String DESCRIPTION = "Contains data about defined plates"; @@ -48,12 +52,21 @@ public class PlateSchema extends SimpleUserSchema AmountUnitsTable.NAME )); + Set contextualRoles = Set.of(); + public PlateSchema(User user, Container container) { super(SCHEMA_NAME, DESCRIPTION, user, container, AssayDbSchema.getInstance().getSchema(), null, AVAILABLE_TABLES, null); } + public PlateSchema(QuerySchema schema, Set contextualRoles) + { + this(schema.getUser(), schema.getContainer()); + setDefaultSchema(schema.getDefaultSchema()); + this.contextualRoles = contextualRoles; + } + @Override public Set getTableNames() { @@ -85,6 +98,18 @@ public TableInfo createTable(String name, ContainerFilter cf) return null; } + @Override + public @NotNull Set getContextualRoles() + { + return contextualRoles; + } + + @Override + public boolean canReadSchema() + { + return super.canReadSchema() || getContainer().hasPermission(getUser(), ReadPermission.class, contextualRoles) ; + } + public static TableInfo getPlateSetTable(Container container, User user, @Nullable ContainerFilter cf) { PlateSchema plateSchema = new PlateSchema(user, container); diff --git a/assay/src/org/labkey/assay/plate/query/WellTable.java b/assay/src/org/labkey/assay/plate/query/WellTable.java index 1f6f828b898..92a277c303a 100644 --- a/assay/src/org/labkey/assay/plate/query/WellTable.java +++ b/assay/src/org/labkey/assay/plate/query/WellTable.java @@ -48,6 +48,7 @@ import org.labkey.api.security.permissions.DeletePermission; import org.labkey.api.security.permissions.InsertPermission; import org.labkey.api.security.permissions.Permission; +import org.labkey.api.security.permissions.ReadPermission; import org.labkey.api.util.UnexpectedException; import org.labkey.assay.plate.PlateManager; import org.labkey.assay.plate.TsvPlateLayoutHandler; @@ -374,6 +375,10 @@ public boolean hasPermission(@NotNull UserPrincipal user, @NotNull Class implements ExpMaterialTable { @@ -780,8 +802,14 @@ public void addQueryFieldKeys(Set keys) if (InventoryService.get() != null && (st == null || !st.isMedia())) defaultCols.addAll(InventoryService.get().addInventoryStatusColumns(st == null ? null : st.getMetricUnit(), this, getContainer(), _userSchema.getUser())); - UserSchema plateUserSchema = QueryService.get().getUserSchema(_userSchema.getUser(), getContainer(), "plate"); SQLFragment sql; + UserSchema plateUserSchema; + // Issue 53194 : this would be the case for linked to study samples + if (_userSchema instanceof UserSchema.HasContextualRoles samplesSchema && !samplesSchema.getContextualRoles().isEmpty()) + plateUserSchema = AssayPlateMetadataService.get().getPlateSchema(_userSchema, samplesSchema.getContextualRoles()); + else + plateUserSchema = QueryService.get().getUserSchema(_userSchema.getUser(), _userSchema.getContainer(), "plate"); + if (plateUserSchema != null && plateUserSchema.getTable("Well") != null) { String rowIdField = ExprColumn.STR_TABLE_ALIAS + "." + Column.RowId.name(); diff --git a/experiment/src/org/labkey/experiment/api/SampleTypeServiceImpl.java b/experiment/src/org/labkey/experiment/api/SampleTypeServiceImpl.java index eff11278cd7..8068ef0714a 100644 --- a/experiment/src/org/labkey/experiment/api/SampleTypeServiceImpl.java +++ b/experiment/src/org/labkey/experiment/api/SampleTypeServiceImpl.java @@ -32,7 +32,25 @@ import org.labkey.api.audit.TransactionAuditProvider; import org.labkey.api.cache.Cache; import org.labkey.api.cache.CacheManager; -import org.labkey.api.data.*; +import org.labkey.api.data.AuditConfigurable; +import org.labkey.api.data.Container; +import org.labkey.api.data.ContainerFilter; +import org.labkey.api.data.ContainerManager; +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.NameGenerator; +import org.labkey.api.data.Parameter; +import org.labkey.api.data.ParameterMapStatement; +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.TableInfo; +import org.labkey.api.data.TableSelector; import org.labkey.api.data.dialect.SqlDialect; import org.labkey.api.data.measurement.Measurement; import org.labkey.api.defaults.DefaultValueService; @@ -77,16 +95,13 @@ import org.labkey.api.query.FieldKey; import org.labkey.api.query.MetadataUnavailableException; import org.labkey.api.query.QueryChangeListener; -import org.labkey.api.query.QuerySchema; import org.labkey.api.query.QueryService; import org.labkey.api.query.SchemaKey; import org.labkey.api.query.SimpleValidationError; -import org.labkey.api.query.UserSchema; import org.labkey.api.query.ValidationException; import org.labkey.api.search.SearchService; import org.labkey.api.security.User; import org.labkey.api.security.permissions.ReadPermission; -import org.labkey.api.security.roles.Role; import org.labkey.api.study.Dataset; import org.labkey.api.study.StudyService; import org.labkey.api.study.publish.StudyPublishService; @@ -2274,10 +2289,4 @@ public void refreshSampleTypeMaterializedView(@NotNull ExpSampleType st, SampleC { ExpMaterialTableImpl.refreshMaterializedView(st.getLSID(), reason); } - - @Override - public UserSchema getUserSchema(QuerySchema schema, Set contextualRoles) - { - return new SamplesSchema(schema, contextualRoles); - } } diff --git a/study/src/org/labkey/study/query/SampleDatasetTable.java b/study/src/org/labkey/study/query/SampleDatasetTable.java index 593e35e3260..85a6f1b3e96 100644 --- a/study/src/org/labkey/study/query/SampleDatasetTable.java +++ b/study/src/org/labkey/study/query/SampleDatasetTable.java @@ -10,22 +10,18 @@ import org.labkey.api.data.TableInfo; import org.labkey.api.exp.api.ExpObject; import org.labkey.api.exp.api.ExpSampleType; -import org.labkey.api.exp.api.SampleTypeService; import org.labkey.api.exp.query.ExpMaterialTable; import org.labkey.api.exp.query.SamplesSchema; import org.labkey.api.query.ExprColumn; import org.labkey.api.query.FieldKey; import org.labkey.api.query.QueryService; import org.labkey.api.query.UserSchema; -import org.labkey.api.security.roles.ReaderRole; -import org.labkey.api.security.roles.RoleManager; import org.labkey.study.model.DatasetDefinition; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.Set; public class SampleDatasetTable extends LinkedDatasetTable { @@ -116,9 +112,7 @@ private TableInfo getSamplesTable() return null; _sampleType = sampleType; - UserSchema userSchema = SampleTypeService.get().getUserSchema(_userSchema, Set.of(RoleManager.getRole(ReaderRole.class))); - //QueryService.get().getUserSchema(_userSchema.getUser(), sampleType.getContainer(), SamplesSchema.SCHEMA_NAME); - + UserSchema userSchema = QueryService.get().getUserSchema(_userSchema.getUser(), sampleType.getContainer(), SamplesSchema.SCHEMA_NAME); // Hide 'linked' column for Sample Type Datasets if (userSchema instanceof SamplesSchema samplesSchema) { From 55c0cad5d882b029833b35ac59972e3cbcca6ef3 Mon Sep 17 00:00:00 2001 From: Lum Date: Wed, 9 Jul 2025 09:40:28 -0700 Subject: [PATCH 3/3] code review feedback --- .../src/org/labkey/experiment/api/ExpMaterialTableImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/experiment/src/org/labkey/experiment/api/ExpMaterialTableImpl.java b/experiment/src/org/labkey/experiment/api/ExpMaterialTableImpl.java index 342fc69ff22..8e80573a917 100644 --- a/experiment/src/org/labkey/experiment/api/ExpMaterialTableImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpMaterialTableImpl.java @@ -804,7 +804,9 @@ public void addQueryFieldKeys(Set keys) SQLFragment sql; UserSchema plateUserSchema; - // Issue 53194 : this would be the case for linked to study samples + // Issue 53194 : this would be the case for linked to study samples. The contextual role is set up from the study dataset + // for the source sample, we want to allow the plate schema to inherit any contextual roles to allow querying + // against tables in that schema. if (_userSchema instanceof UserSchema.HasContextualRoles samplesSchema && !samplesSchema.getContextualRoles().isEmpty()) plateUserSchema = AssayPlateMetadataService.get().getPlateSchema(_userSchema, samplesSchema.getContextualRoles()); else