Skip to content

Commit 1ce3db1

Browse files
committed
Merge branch 'develop' into fb_git950
2 parents 929b8b0 + cb2b140 commit 1ce3db1

12 files changed

Lines changed: 205 additions & 25 deletions

File tree

api/src/org/labkey/api/attachments/AttachmentService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,9 @@ static AttachmentService get()
140140

141141
HttpView<?> getFindAttachmentParentsView();
142142

143-
void detectOrphans();
143+
void logOrphanedAttachments();
144+
145+
void deleteOrphanedAttachments();
144146

145147
class DuplicateFilenameException extends IOException implements SkipMothershipLogging
146148
{

api/src/org/labkey/api/data/ContainerManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1944,7 +1944,7 @@ private static boolean delete(final Container c, User user, @Nullable String com
19441944
setContainerTabDeleted(c.getParent(), c.getName(), c.getParent().getFolderType().getName());
19451945
}
19461946

1947-
AttachmentService.get().detectOrphans();
1947+
AttachmentService.get().logOrphanedAttachments();
19481948

19491949
fireDeleteContainer(c, user);
19501950

api/src/org/labkey/api/query/AbstractQueryImportAction.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import org.labkey.api.view.ViewBackgroundInfo;
7070
import org.labkey.api.webdav.WebdavResource;
7171
import org.labkey.api.webdav.WebdavService;
72+
import org.labkey.api.workflow.WorkflowService;
7273
import org.labkey.vfs.FileLike;
7374
import org.springframework.validation.BindException;
7475
import org.springframework.web.multipart.MultipartFile;
@@ -609,6 +610,11 @@ else if (!dataFileDir.exists())
609610
.setAllowLineageColumns(allowLineageColumns())
610611
.setJobDescription(getQueryImportDescription())
611612
.setJobNotificationProvider(getQueryImportJobNotificationProviderName());
613+
if (WorkflowService.get() != null)
614+
{
615+
Map<String, Object> workflowParams = WorkflowService.get().getConfigParameters(getViewContext().getRequest());
616+
importContextBuilder.setWorkflowParams(workflowParams);
617+
}
612618

613619
importContextBuilder.setTransactionDetails(transactionDetails);
614620
QueryImportPipelineJob job = new QueryImportPipelineJob(getQueryImportProviderName(), info, root, importContextBuilder);

api/src/org/labkey/api/query/QueryImportPipelineJob.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.labkey.api.reader.DataLoader;
1717
import org.labkey.api.util.URLHelper;
1818
import org.labkey.api.view.ViewBackgroundInfo;
19+
import org.labkey.api.workflow.WorkflowService;
1920
import org.labkey.vfs.FileLike;
2021

2122
import java.util.HashMap;
@@ -62,6 +63,7 @@ public static class QueryImportAsyncContextBuilder
6263
boolean _allowLineageColumns = false;
6364
Map<AbstractQueryImportAction.Params, Boolean> _optionParamsMap = new HashMap<>();
6465
LookupResolutionType _lookupResolutionType = null;
66+
Map<String, Object> _workflowParams = null;
6567

6668
String _jobDescription;
6769

@@ -212,6 +214,17 @@ public QueryImportAsyncContextBuilder setOptionParamsMap(Map<AbstractQueryImport
212214
return this;
213215
}
214216

217+
public QueryImportAsyncContextBuilder setWorkflowParams(Map<String, Object> workflowParams)
218+
{
219+
_workflowParams = workflowParams;
220+
return this;
221+
}
222+
223+
public Map<String, Object> getWorkflowParams()
224+
{
225+
return _workflowParams;
226+
}
227+
215228
public LookupResolutionType getLookupResolutionType()
216229
{
217230
return _lookupResolutionType;
@@ -311,6 +324,10 @@ public void run()
311324

312325
DataIteratorContext diContext = createDataIteratorContext(ve, getContainer());
313326

327+
if (_importContextBuilder.getWorkflowParams() != null && WorkflowService.get() != null)
328+
{
329+
WorkflowService.get().populateConfigParams(_importContextBuilder.getWorkflowParams(), diContext.getConfigParameters());
330+
}
314331
TransactionAuditProvider.TransactionAuditEvent auditEvent = null;
315332
if (diContext.isCrossTypeImport() || (_importContextBuilder.getAuditBehaviorType() != null && _importContextBuilder.getAuditBehaviorType() != AuditBehaviorType.NONE))
316333
auditEvent = createTransactionAuditEvent(getContainer(), diContext.getInsertOption().auditAction, _importContextBuilder.getTransactionDetails());

api/src/org/labkey/api/workflow/WorkflowService.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
11
package org.labkey.api.workflow;
22

3+
import jakarta.servlet.http.HttpServletRequest;
34
import org.jetbrains.annotations.NotNull;
4-
import org.labkey.api.data.Container;import org.labkey.api.security.User;import org.labkey.api.services.ServiceRegistry;
5+
import org.labkey.api.data.Container;
6+
import org.labkey.api.dataiterator.DataIteratorBuilder;
7+
import org.labkey.api.query.ValidationException;
8+
import org.labkey.api.security.User;
9+
import org.labkey.api.services.ServiceRegistry;
10+
11+
import java.util.Map;
512

613
public interface WorkflowService
714
{
15+
enum WorkflowConfigs
16+
{
17+
ActionId,
18+
JobId,
19+
}
20+
821
enum ActionType
922
{
10-
AssayImport("assay types", "Imported assay data");
23+
AssayImport("assay types", "Imported assay data"),
24+
DeriveSamples("derivation sample type parameters", "Derived samples"),
25+
AliquotSamples("aliquot sample type parameters", "Aliquot samples"),
26+
PoolSamples("pooling sample type parameters", "Pooled samples");
1127

1228
private final String _inputDescription;
1329
private final String _auditMessage;
@@ -39,6 +55,14 @@ static WorkflowService get()
3955
return ServiceRegistry.get().getService(WorkflowService.class);
4056
}
4157

58+
void populateConfigParams(Map<String, Object> provided, Map<Enum, Object> configParameters) throws ValidationException;
59+
60+
void populateConfigParams(HttpServletRequest request, Map<Enum, Object> configParameters) throws ValidationException;
61+
Map<String, Object> getConfigParameters(HttpServletRequest request) throws ValidationException;
4262
void onActionComplete(@NotNull Container container, @NotNull User user, @NotNull Long actionId);
4363
void onActionComplete(@NotNull Container container, @NotNull User user, @NotNull Long taskId, @NotNull ActionType actionType);
64+
65+
DataIteratorBuilder getSampleCreationDataIteratorBuilder(DataIteratorBuilder data, Container container, User user);
66+
67+
DataIteratorBuilder getActionAuditDataIteratorBuilder(DataIteratorBuilder data, Container container, User user);
4468
}

core/module.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Name: Core
22
ModuleClass: org.labkey.core.CoreModule
3-
SchemaVersion: 26.002
3+
SchemaVersion: 26.003
44
Label: Administration and Essential Services
55
Description: The Core module provides central services such as login, \
66
security, administration, folder management, user management, \

core/src/org/labkey/core/CoreUpgradeCode.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public static void migrateAllowedExternalConnectionHosts(ModuleContext context)
7171
if (context.isNewInstall())
7272
return;
7373

74+
// TODO: Remove getExternalSourceHosts() method when this upgrade code is deleted
7475
List<String> hosts = AppProps.getInstance().getExternalSourceHosts();
7576
List<AllowedHost> allowedHosts = hosts.stream()
7677
.map(host -> new AllowedHost(Directive.Connection, host))
@@ -106,4 +107,23 @@ public static void populateAttachmentParentTypeColumn(ModuleContext context)
106107
new SqlExecutor(CoreSchema.getInstance().getSchema()).execute(updateSql);
107108
}
108109
}
109-
}
110+
111+
/**
112+
* This is not invoked from any script yet. We want to make sure our orphaned attachment detection is perfect
113+
* before blanket deleting them.
114+
*/
115+
@DeferredUpgrade // Need to execute this after AttachmentTypes are registered
116+
@SuppressWarnings("unused")
117+
public static void deleteOrphanedAttachments(ModuleContext context)
118+
{
119+
if (context.isNewInstall())
120+
return;
121+
122+
AttachmentService svc = AttachmentService.get();
123+
124+
if (svc != null)
125+
{
126+
svc.deleteOrphanedAttachments();
127+
}
128+
}
129+
}

core/src/org/labkey/core/attachment/AttachmentServiceImpl.java

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.apache.commons.io.IOUtils;
2222
import org.apache.commons.lang3.StringUtils;
2323
import org.apache.commons.lang3.Strings;
24+
import org.apache.commons.lang3.mutable.MutableInt;
2425
import org.apache.logging.log4j.Logger;
2526
import org.jetbrains.annotations.NotNull;
2627
import org.jetbrains.annotations.Nullable;
@@ -45,6 +46,7 @@
4546
import org.labkey.api.data.ColumnRenderProperties;
4647
import org.labkey.api.data.CompareType;
4748
import org.labkey.api.data.Container;
49+
import org.labkey.api.data.ContainerFilter;
4850
import org.labkey.api.data.ContainerFilter.AllFolders;
4951
import org.labkey.api.data.ContainerManager;
5052
import org.labkey.api.data.CoreSchema;
@@ -113,6 +115,7 @@
113115
import org.labkey.api.webdav.WebdavResolver;
114116
import org.labkey.api.webdav.WebdavResource;
115117
import org.labkey.core.query.AttachmentAuditProvider;
118+
import org.labkey.core.query.AttachmentAuditProvider.AttachmentAuditEvent;
116119
import org.labkey.core.query.CoreQuerySchema;
117120
import org.springframework.http.ContentDisposition;
118121
import org.springframework.mock.web.MockMultipartFile;
@@ -203,7 +206,7 @@ public void addAuditEvent(User user, AttachmentParent parent, String filename, S
203206
if (parent != null)
204207
{
205208
Container c = ContainerManager.getForId(parent.getContainerId());
206-
AttachmentAuditProvider.AttachmentAuditEvent attachmentEvent = new AttachmentAuditProvider.AttachmentAuditEvent(c == null ? ContainerManager.getRoot() : c, comment);
209+
AttachmentAuditEvent attachmentEvent = new AttachmentAuditEvent(c == null ? ContainerManager.getRoot() : c, comment);
207210

208211
attachmentEvent.setAttachmentParentEntityId(parent.getEntityId());
209212
attachmentEvent.setParentType(parent.getAttachmentParentType().getUniqueName());
@@ -1098,7 +1101,7 @@ public int available()
10981101
private record Orphan(String documentName, String parentType){}
10991102

11001103
@Override
1101-
public void detectOrphans()
1104+
public void logOrphanedAttachments()
11021105
{
11031106
// Log orphaned attachments in this server, but in dev mode only, since this is for our testing. Also, we
11041107
// don't yet offer a way to delete orphaned attachments via the UI, so it's not helpful to inform admins.
@@ -1135,6 +1138,69 @@ public void detectOrphans()
11351138
}
11361139
}
11371140

1141+
record OrphanedAttachment(String container, String parent, String parentType, String documentName)
1142+
{
1143+
AttachmentParent getAttachmentParent()
1144+
{
1145+
return new AttachmentParent()
1146+
{
1147+
@Override
1148+
public String getEntityId()
1149+
{
1150+
return parent;
1151+
}
1152+
1153+
@Override
1154+
public String getContainerId()
1155+
{
1156+
return container;
1157+
}
1158+
1159+
@Override
1160+
public @NotNull AttachmentParentType getAttachmentParentType()
1161+
{
1162+
// Attempt to resolve the parent type. This will get written to the audit log.
1163+
AttachmentParentType type = ATTACHMENT_TYPE_MAP.get(parentType());
1164+
return type != null ? type : AttachmentParentType.UNKNOWN;
1165+
}
1166+
};
1167+
}
1168+
}
1169+
1170+
@Override
1171+
public void deleteOrphanedAttachments()
1172+
{
1173+
// TroubleShooterRole provides ability to read the Documents table. deleteAttachments() does not check perms.
1174+
User user = ElevatedUser.getElevatedUser(User.getSearchUser(), TroubleshooterRole.class);
1175+
UserSchema core = DefaultSchema.get(user, ContainerManager.getRoot()).getUserSchema(CoreQuerySchema.NAME);
1176+
if (core != null)
1177+
{
1178+
// Use "unsafe everything" container filter because it's possible that orphaned attachments have a container
1179+
// that no longer exists.
1180+
TableInfo documents = core.getTable(CoreQuerySchema.DOCUMENTS_TABLE_NAME, ContainerFilter.getUnsafeEverythingFilter());
1181+
if (null != documents)
1182+
{
1183+
SimpleFilter filter = new SimpleFilter(FieldKey.fromParts("Orphaned"), true);
1184+
MutableInt count = new MutableInt(0);
1185+
new TableSelector(documents, new CsvSet("Container, Parent, ParentType, DocumentName"), filter, null).forEach(OrphanedAttachment.class, orphan -> {
1186+
LOG.info("Deleting orphaned attachment: {}", orphan);
1187+
try
1188+
{
1189+
deleteAttachment(orphan.getAttachmentParent(), orphan.documentName(), user);
1190+
count.increment();
1191+
}
1192+
catch (Exception e)
1193+
{
1194+
LOG.error("Exception while deleting orphaned attachment: {}", orphan, e);
1195+
}
1196+
});
1197+
AttachmentAuditEvent event = new AttachmentAuditEvent(ContainerManager.getRoot(), "Deleted " + StringUtilsLabKey.pluralize(count.intValue(), "orphaned attachment"));
1198+
event.setAttachment("All orphaned attachments");
1199+
AuditLogService.get().addEvent(user, event);
1200+
}
1201+
}
1202+
}
1203+
11381204
private CoreSchema coreTables()
11391205
{
11401206
return CoreSchema.getInstance();

experiment/src/org/labkey/experiment/api/ExpDataClassType.java

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.labkey.experiment.api;
1717

18+
import org.apache.logging.log4j.Logger;
1819
import org.jetbrains.annotations.NotNull;
1920
import org.labkey.api.attachments.AttachmentParentType;
2021
import org.labkey.api.data.Container;
@@ -28,15 +29,19 @@
2829
import org.labkey.api.exp.api.ExperimentService;
2930
import org.labkey.api.exp.property.Domain;
3031
import org.labkey.api.exp.property.PropertyService;
32+
import org.labkey.api.exp.query.DataClassUserSchema;
33+
import org.labkey.api.security.User;
3134
import org.labkey.api.util.PageFlowUtil;
3235
import org.labkey.api.util.Pair;
36+
import org.labkey.api.util.logging.LogHelper;
3337

3438
import java.util.LinkedList;
3539
import java.util.List;
3640

3741
public class ExpDataClassType implements AttachmentParentType
3842
{
3943
private static final AttachmentParentType INSTANCE = new ExpDataClassType();
44+
private static final Logger LOG = LogHelper.getLogger(ExpDataClassType.class, "Issues selecting entityIds");
4045

4146
private ExpDataClassType()
4247
{
@@ -74,22 +79,36 @@ public static AttachmentParentType get()
7479
String lsid = rs.getString("LSID");
7580
Domain domain = PropertyService.get().getDomain(c, lsid);
7681

77-
// Add a select for the ObjectIds in this ExpDataClass if the domain includes an attachment column. ExpDataClass attachments
78-
// use the LSID's ObjectId as the attachment parent EntityId, so we need to use a SQL expression to extract it.
79-
if (null != domain && domain.getProperties().stream().anyMatch(p -> p.getPropertyType() == PropertyType.ATTACHMENT))
80-
selectStatements.add(
81-
new SQLFragment("\n SELECT ")
82-
.append(expressionToExtractObjectId)
83-
.append(" AS EntityId, ")
84-
.append(dialect.concatenate(
85-
new SQLFragment("?", domain.getName()),
86-
new SQLFragment("':'"),
87-
new SQLFragment("Name")
88-
))
89-
.append(" AS Description FROM expdataclass.")
90-
.append(domain.getStorageTableName())
91-
.append(" WHERE ").append(where)
92-
);
82+
if (null != domain)
83+
{
84+
// Enumerate columns on the data class TableInfo since it includes the vocabulary domain columns.
85+
// For example, Compound has a built-in Structure2D attachment column supplied by a vocabulary domain.
86+
TableInfo dataClassTable = new DataClassUserSchema(c, User.getSearchUser()).getTable(domain.getName());
87+
88+
if (dataClassTable == null)
89+
{
90+
LOG.warn("DataClass table not found for {}", domain.getName());
91+
}
92+
else if (dataClassTable.getColumns().stream().anyMatch(col -> col.getPropertyType() == PropertyType.ATTACHMENT))
93+
{
94+
// Add a select for the ObjectIds in this ExpDataClass if the table includes an attachment column.
95+
// ExpDataClass attachments use the LSID's ObjectId as the attachment parent EntityId, so we need
96+
// to use a SQL expression to extract it.
97+
selectStatements.add(
98+
new SQLFragment("\n SELECT ")
99+
.append(expressionToExtractObjectId)
100+
.append(" AS EntityId, ")
101+
.append(dialect.concatenate(
102+
new SQLFragment("?", domain.getName()),
103+
new SQLFragment("':'"),
104+
new SQLFragment("Name")
105+
))
106+
.append(" AS Description FROM expdataclass.")
107+
.append(domain.getStorageTableName())
108+
.append(" WHERE ").append(where)
109+
);
110+
}
111+
}
93112
});
94113

95114
return selectStatements.isEmpty() ?

experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
import org.labkey.api.util.StringUtilsLabKey;
109109
import org.labkey.api.util.logging.LogHelper;
110110
import org.labkey.api.view.UnauthorizedException;
111+
import org.labkey.api.workflow.WorkflowService;
111112
import org.labkey.experiment.ExpDataIterators;
112113
import org.labkey.experiment.SampleTypeAuditProvider;
113114

@@ -416,6 +417,15 @@ public DataIteratorBuilder createImportDIB(User user, Container container, DataI
416417

417418
if (sampleType.getAutoLinkTargetContainer() != null && StudyPublishService.get() != null && !context.getInsertOption().updateOnly/* TODO support link to study on update? */)
418419
dib = LoggingDataIterator.wrap(new ExpDataIterators.AutoLinkToStudyDataIteratorBuilder(dib, getSchema(), userSchema.getContainer(), userSchema.getUser(), sampleType));
420+
WorkflowService workService = WorkflowService.get();
421+
if (workService != null)
422+
{
423+
if (context.getConfigParameter(WorkflowService.WorkflowConfigs.ActionId) != null)
424+
{
425+
dib = workService.getSampleCreationDataIteratorBuilder(dib, userSchema.getContainer(), userSchema.getUser());
426+
dib = workService.getActionAuditDataIteratorBuilder(dib, userSchema.getContainer(), userSchema.getUser());
427+
}
428+
}
419429
}
420430
return dib;
421431
}

0 commit comments

Comments
 (0)