|
21 | 21 | import org.apache.commons.io.IOUtils; |
22 | 22 | import org.apache.commons.lang3.StringUtils; |
23 | 23 | import org.apache.commons.lang3.Strings; |
| 24 | +import org.apache.commons.lang3.mutable.MutableInt; |
24 | 25 | import org.apache.logging.log4j.Logger; |
25 | 26 | import org.jetbrains.annotations.NotNull; |
26 | 27 | import org.jetbrains.annotations.Nullable; |
|
45 | 46 | import org.labkey.api.data.ColumnRenderProperties; |
46 | 47 | import org.labkey.api.data.CompareType; |
47 | 48 | import org.labkey.api.data.Container; |
| 49 | +import org.labkey.api.data.ContainerFilter; |
48 | 50 | import org.labkey.api.data.ContainerFilter.AllFolders; |
49 | 51 | import org.labkey.api.data.ContainerManager; |
50 | 52 | import org.labkey.api.data.CoreSchema; |
|
113 | 115 | import org.labkey.api.webdav.WebdavResolver; |
114 | 116 | import org.labkey.api.webdav.WebdavResource; |
115 | 117 | import org.labkey.core.query.AttachmentAuditProvider; |
| 118 | +import org.labkey.core.query.AttachmentAuditProvider.AttachmentAuditEvent; |
116 | 119 | import org.labkey.core.query.CoreQuerySchema; |
117 | 120 | import org.springframework.http.ContentDisposition; |
118 | 121 | import org.springframework.mock.web.MockMultipartFile; |
@@ -203,7 +206,7 @@ public void addAuditEvent(User user, AttachmentParent parent, String filename, S |
203 | 206 | if (parent != null) |
204 | 207 | { |
205 | 208 | 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); |
207 | 210 |
|
208 | 211 | attachmentEvent.setAttachmentParentEntityId(parent.getEntityId()); |
209 | 212 | attachmentEvent.setParentType(parent.getAttachmentParentType().getUniqueName()); |
@@ -1098,7 +1101,7 @@ public int available() |
1098 | 1101 | private record Orphan(String documentName, String parentType){} |
1099 | 1102 |
|
1100 | 1103 | @Override |
1101 | | - public void detectOrphans() |
| 1104 | + public void logOrphanedAttachments() |
1102 | 1105 | { |
1103 | 1106 | // Log orphaned attachments in this server, but in dev mode only, since this is for our testing. Also, we |
1104 | 1107 | // 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() |
1135 | 1138 | } |
1136 | 1139 | } |
1137 | 1140 |
|
| 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 | + |
1138 | 1204 | private CoreSchema coreTables() |
1139 | 1205 | { |
1140 | 1206 | return CoreSchema.getInstance(); |
|
0 commit comments