Skip to content

Commit

Permalink
Merge pull request #2668 from pierrehenri-dauvergne/shanoir-issue#2660
Browse files Browse the repository at this point in the history
shanoir-issue#2658: delete dataset
  • Loading branch information
michaelkain authored Feb 27, 2025
2 parents 3cbf007 + bf8acfd commit 91dbc32
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.shanoir.ng.dataset.service;

import org.shanoir.ng.dataset.model.Dataset;
import org.shanoir.ng.datasetfile.DatasetFile;
import org.shanoir.ng.shared.exception.ShanoirException;
import org.springframework.security.access.prepost.PreAuthorize;

import java.util.List;

public interface DatasetAsyncService {


// No PreAuthorize here since it's always called after a security check
void deleteDatasetFilesFromDiskAndPacsAsync(List<DatasetFile> datasetFiles, boolean isDicom, Long datasetId) throws ShanoirException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.shanoir.ng.dataset.service;

import org.apache.commons.io.FileUtils;
import org.shanoir.ng.dataset.model.Dataset;
import org.shanoir.ng.dataset.model.DatasetExpression;
import org.shanoir.ng.dataset.model.DatasetExpressionFormat;
import org.shanoir.ng.datasetfile.DatasetFile;
import org.shanoir.ng.dicom.web.service.DICOMWebService;
import org.shanoir.ng.shared.event.ShanoirEvent;
import org.shanoir.ng.shared.event.ShanoirEventService;
import org.shanoir.ng.shared.event.ShanoirEventType;
import org.shanoir.ng.shared.exception.AccessDeniedException;
import org.shanoir.ng.shared.exception.ShanoirException;
import org.shanoir.ng.utils.KeycloakUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriUtils;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

@Service
public class DatasetAsyncServiceImpl implements DatasetAsyncService {

@Autowired
private DICOMWebService dicomWebService;
@Autowired
ShanoirEventService eventService;

private static final Logger LOG = LoggerFactory.getLogger(DatasetAsyncService.class);

public void deleteDatasetFilesFromDiskAndPacs(List<DatasetFile> datasetFiles, boolean isDicom, Long datasetId) throws ShanoirException {
deleteDatasetFilesFromDiskAndPacsAsync(datasetFiles, isDicom, datasetId);
}

@Override
@Async
public void deleteDatasetFilesFromDiskAndPacsAsync(List<DatasetFile> datasetFiles, boolean isDicom, Long datasetId) throws ShanoirException {
ShanoirEvent event = null;
event = new ShanoirEvent(
ShanoirEventType.DELETE_DATASET_EVENT,
String.valueOf(datasetId),
KeycloakUtil.getTokenUserId(),
"Delete dataset with id :" + datasetId,
ShanoirEvent.IN_PROGRESS,
0f,
null);

eventService.publishEvent(event);

for (DatasetFile file : datasetFiles) {
// DICOM
if (isDicom && file.isPacs()) {
dicomWebService.rejectDatasetFromPacs(file.getPath());
float progress = event.getProgress();
progress += 1f / datasetFiles.size();
event.setProgress(progress);
eventService.publishEvent(event);
// NIfTI
} else if (!file.isPacs()) {
try {
URL url = new URL(file.getPath().replaceAll("%20", " "));
File srcFile = new File(UriUtils.decode(url.getPath(), "UTF-8"));
FileUtils.deleteQuietly(srcFile);
} catch (MalformedURLException e) {
throw new ShanoirException("Error while deleting dataset file.", e);
}
}
}

event.setMessage("Dataset " + datasetId + " deleted.");
event.setProgress(1f);
event.setStatus(ShanoirEvent.SUCCESS);
eventService.publishEvent(event);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ public interface DatasetService {

void deleteByIdCascade(Long id) throws EntityNotFoundException, ShanoirException, SolrServerException, IOException, RestServiceException;


@PreAuthorize("hasRole('ADMIN') or (hasRole('EXPERT') and @datasetSecurityService.hasRightOnDataset(#dataset.getId(), 'CAN_ADMINISTRATE'))")
void deleteDatasetFilesFromDiskAndPacs(Dataset dataset) throws ShanoirException;

/**
* Delete several datasets.
*
Expand Down Expand Up @@ -143,9 +147,6 @@ public interface DatasetService {
@PostAuthorize("hasRole('ADMIN') or @datasetSecurityService.filterDatasetList(returnObject, 'CAN_SEE_ALL')")
List<Dataset> findByStudycard(Long studycardId);

@PreAuthorize("hasRole('ADMIN') or (hasRole('EXPERT') and @datasetSecurityService.hasRightOnDataset(#dataset.getId(), 'CAN_ADMINISTRATE'))")
void deleteDatasetFromDiskAndPacs(Dataset dataset) throws ShanoirException;

boolean existsById(Long id);

@PreAuthorize("hasRole('ADMIN') or (hasAnyRole('EXPERT','USER') and @datasetSecurityService.hasRightOnDataset(#dataset.id, 'CAN_SEE_ALL'))")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.util.UriUtils;
Expand Down Expand Up @@ -89,8 +90,8 @@ public class DatasetServiceImpl implements DatasetService {
@Autowired
private SolrService solrService;

@Autowired
private DICOMWebService dicomWebService;
@Value("${dcm4chee-arc.dicom.web}")
private boolean dicomWeb;

@Autowired
private DatasetPropertyService propertyService;
Expand All @@ -101,15 +102,15 @@ public class DatasetServiceImpl implements DatasetService {
@Autowired
private ObjectMapper objectMapper;

@Value("${dcm4chee-arc.dicom.web}")
private boolean dicomWeb;

@Autowired
private DatasetProcessingService processingService;

@Autowired
private ProcessingResourceService processingResourceService;

@Autowired
private DatasetAsyncService datasetAsyncService;

@Autowired
DatasetExpressionRepository datasetExpressionRepository;

Expand All @@ -124,9 +125,6 @@ private void delete(Dataset entity) throws ShanoirException, SolrServerException
processingResourceService.deleteByDatasetId(id);
propertyService.deleteByDatasetId(id);
repository.deleteById(id);

shanoirEventService.publishEvent(new ShanoirEvent(ShanoirEventType.DELETE_DATASET_EVENT, id.toString(), KeycloakUtil.getTokenUserId(), "", ShanoirEvent.SUCCESS, entity.getStudyId()));

}

/**
Expand All @@ -152,7 +150,7 @@ public void deleteById(final Long id) throws ShanoirException, SolrServerExcepti
}
long startTime = System.currentTimeMillis();
delete(dataset);
deleteDatasetFromDiskAndPacs(dataset);
deleteDatasetFilesFromDiskAndPacs(dataset);
long endTime = System.currentTimeMillis();
long elapsedTime = endTime - startTime;
LOG.info("Dataset deletion time: " + elapsedTime + " milliseconds");
Expand Down Expand Up @@ -182,28 +180,15 @@ public void deleteByIdCascade(final Long id) throws ShanoirException, SolrServer
delete(dataset);
}

@Override
public void deleteDatasetFromDiskAndPacs(Dataset dataset) throws ShanoirException {
if (!dicomWeb) {
return;
}
public void deleteDatasetFilesFromDiskAndPacs(Dataset dataset) throws ShanoirException {
if (!dicomWeb) {
return;
}
Long id = dataset.getId();
for (DatasetExpression expression : dataset.getDatasetExpressions()) {
boolean isDicom = DatasetExpressionFormat.DICOM.equals(expression.getDatasetExpressionFormat());
for (DatasetFile file : expression.getDatasetFiles()) {
// DICOM
if (isDicom && file.isPacs()) {
dicomWebService.rejectDatasetFromPacs(file.getPath());
// NIfTI
} else if (!file.isPacs()) {
try {
URL url = new URL(file.getPath().replaceAll("%20", " "));
File srcFile = new File(UriUtils.decode(url.getPath(), "UTF-8"));
FileUtils.deleteQuietly(srcFile);
} catch (MalformedURLException e) {
throw new ShanoirException("Error while deleting dataset file.", e);
}
}
}
List<DatasetFile> datasetFiles = expression.getDatasetFiles();
datasetAsyncService.deleteDatasetFilesFromDiskAndPacsAsync(datasetFiles, isDicom, id);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ List<DatasetAcquisitionDatasetsDTO> datasetAcquisitionsToDatasetAcquisitionDatas
DatasetAcquisitionDatasetsDTO datasetAcquisitionToDatasetAcquisitionDatasetsDTO(
DatasetAcquisition datasetAcquisition);

@Mappings({ @Mapping(target = "source", ignore = true), @Mapping(target = "copies", ignore = true) })
DatasetAcquisition datasetAcquisitionDatasetsDTOToDatasetAcquisition(DatasetAcquisitionDatasetsDTO dto);

@ObjectFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@
import org.mockito.Mockito;
import org.shanoir.ng.dataset.modality.MrDataset;
import org.shanoir.ng.dataset.model.Dataset;
import org.shanoir.ng.dataset.model.DatasetExpression;
import org.shanoir.ng.dataset.model.DatasetExpressionFormat;
import org.shanoir.ng.dataset.repository.DatasetRepository;
import org.shanoir.ng.dataset.service.DatasetAsyncService;
import org.shanoir.ng.dataset.service.DatasetService;
import org.shanoir.ng.datasetacquisition.model.DatasetAcquisition;
import org.shanoir.ng.datasetacquisition.repository.DatasetAcquisitionRepository;
import org.shanoir.ng.datasetfile.DatasetFile;
import org.shanoir.ng.dicom.web.StudyInstanceUIDHandler;
import org.shanoir.ng.examination.model.Examination;
import org.shanoir.ng.examination.repository.ExaminationRepository;
Expand All @@ -46,6 +50,7 @@
import org.springframework.security.test.context.support.WithAnonymousUser;
import org.springframework.test.context.ActiveProfiles;

import java.io.File;
import java.util.*;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -67,9 +72,12 @@ public class DatasetServiceSecurityTest {
private static final long LOGGED_USER_ID = 2L;
private static final String LOGGED_USER_USERNAME = "logged";
private static final long ENTITY_ID = 1L;

@Autowired
private DatasetService service;

@Autowired
private DatasetAsyncService asyncService;

@MockBean
private DatasetRepository datasetRepository;
Expand Down Expand Up @@ -231,23 +239,25 @@ private void testDelete(String role) throws ShanoirException {
//deleteDatasetFromPacs(Dataset)
//deleteByIdIn(List<Long>)
//deleteById(Long)

if ("ROLE_USER".equals(role)) {
given(rightsService.hasRightOnStudy(1L, "CAN_ADMINISTRATE")).willReturn(true);
given(rightsService.hasRightOnStudies(Utils.toSet(1L), "CAN_ADMINISTRATE")).willReturn(Utils.toSet(1L));
assertAccessDenied(service::deleteDatasetFromDiskAndPacs, mockDataset(1L, 1L, 1L, 1L, 1L));
assertAccessDenied(service::deleteById, 1L);
assertAccessDenied(service::deleteByIdIn, Utils.toList(1L, 2L, 3L, 4L));
assertAccessDenied(service::deleteByIdIn, Utils.toList(1L, 3L));
assertAccessDenied(service::deleteDatasetFilesFromDiskAndPacs, mockDataset(1L));

} else if ("ROLE_EXPERT".equals(role)) {
given(rightsService.hasRightOnStudy(1L, "CAN_ADMINISTRATE")).willReturn(false);
given(rightsService.hasRightOnStudies(Utils.toSet(1L), "CAN_ADMINISTRATE")).willReturn(Utils.toSet());
assertAccessDenied(service::deleteDatasetFromDiskAndPacs, mockDataset(1L, 1L, 1L, 1L, 1L));
assertAccessDenied(service::deleteDatasetFilesFromDiskAndPacs, mockDataset(1L));
assertAccessDenied(service::deleteById, 1L);
assertAccessDenied(service::deleteByIdIn, Utils.toList(1L, 2L, 3L, 4L));
assertAccessDenied(service::deleteByIdIn, Utils.toList(1L, 3L));
given(rightsService.hasRightOnStudy(1L, "CAN_ADMINISTRATE")).willReturn(true);
given(rightsService.hasRightOnStudies(Utils.toSet(1L), "CAN_ADMINISTRATE")).willReturn(Utils.toSet(1L));
assertAccessAuthorized(service::deleteDatasetFromDiskAndPacs, mockDataset(1L, 1L, 1L, 1L, 1L));
assertAccessAuthorized(service::deleteDatasetFilesFromDiskAndPacs, mockDataset(1L));
assertAccessAuthorized(service::deleteById, 1L);
assertAccessDenied(service::deleteByIdIn, Utils.toList(1L, 2L, 3L, 4L));
assertAccessDenied(service::deleteByIdIn, Utils.toList(1L, 3L));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@
<ng-template [ngSwitchCase]="'deleteExamination.event'">
<i class="fa-solid fa-trash"></i>
</ng-template>
<ng-template [ngSwitchCase]="'deleteDataset.event'">
<i class="fa-solid fa-trash"></i>
</ng-template>
<ng-template ngSwitchDefault>
<i class="fa-regular fa-clock"></i>
</ng-template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ public void addEvent(ShanoirEvent event) {
|| ShanoirEventType.COPY_DATASET_EVENT.equals(event.getEventType())
|| ShanoirEventType.CHECK_QUALITY_EVENT.equals(event.getEventType())
|| ShanoirEventType.DOWNLOAD_STATISTICS_EVENT.equals(event.getEventType())
|| ShanoirEventType.DELETE_EXAMINATION_EVENT.equals(event.getEventType())) {
|| ShanoirEventType.DELETE_EXAMINATION_EVENT.equals(event.getEventType())
|| ShanoirEventType.DELETE_DATASET_EVENT.equals(event.getEventType())) {
sendSseEventsToUI(saved);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ public ResponseEntity<List<ShanoirEventLight>> findTasks() {
ShanoirEventType.CHECK_QUALITY_EVENT,
ShanoirEventType.SOLR_INDEX_ALL_EVENT,
ShanoirEventType.DOWNLOAD_STATISTICS_EVENT,
ShanoirEventType.DELETE_EXAMINATION_EVENT);
ShanoirEventType.DELETE_EXAMINATION_EVENT,
ShanoirEventType.DELETE_DATASET_EVENT);
// Order by last update date
Comparator<ShanoirEventLight> comparator = new Comparator<ShanoirEventLight>() {
@Override
Expand Down

0 comments on commit 91dbc32

Please sign in to comment.