Skip to content

Commit 931dbb5

Browse files
authored
Merge pull request #502 from qbicsoftware/development
Release PR
2 parents 5a8559f + 7818dc5 commit 931dbb5

File tree

58 files changed

+1478
-330
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1478
-330
lines changed

application-commons/src/main/java/life/qbic/application/commons/ApplicationException.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ public enum ErrorCode {
144144
NO_SPECIES_DEFINED,
145145
NO_SPECIMEN_DEFINED,
146146
NO_ANALYTE_DEFINED,
147-
DATA_ATTACHED_TO_SAMPLES
147+
DATA_ATTACHED_TO_SAMPLES,
148+
SAMPLES_ATTACHED_TO_EXPERIMENT
148149
;
149150

150151
@Override

finances-infrastructure/pom.xml

+10
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@
2323
<version>0.36.0</version>
2424
<scope>compile</scope>
2525
</dependency>
26+
<dependency>
27+
<groupId>org.springframework.security</groupId>
28+
<artifactId>spring-security-core</artifactId>
29+
</dependency>
30+
<dependency>
31+
<groupId>life.qbic.identity</groupId>
32+
<artifactId>project-management-infrastructure</artifactId>
33+
<version>0.34.0</version>
34+
<scope>compile</scope>
35+
</dependency>
2636
</dependencies>
2737

2838
</project>

finances-infrastructure/src/main/java/life/qbic/finance/infrastructure/SimpleOfferSearchService.java

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import life.qbic.finance.domain.model.OfferId;
88
import life.qbic.finance.domain.model.OfferPreview;
99
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.security.access.prepost.PreAuthorize;
1011
import org.springframework.stereotype.Service;
1112

1213
/**
@@ -23,20 +24,24 @@ public class SimpleOfferSearchService implements OfferSearchService {
2324

2425
private final OfferRepository offerRepository;
2526

27+
2628
@Override
29+
@PreAuthorize("hasAnyAuthority('ROLE_PROJECT_MANAGER', 'ROLE_ADMIN')")
2730
public List<OfferPreview> findByProjectTitleOrOfferId(String projectTitle, String offerId) {
2831
return offerPreviewRepository.findByProjectTitleContainingIgnoreCaseOrOfferIdContainingIgnoreCase(
2932
projectTitle, offerId);
3033
}
3134

3235
@Override
36+
@PreAuthorize("hasAnyAuthority('ROLE_PROJECT_MANAGER', 'ROLE_ADMIN')")
3337
public List<OfferPreview> findByProjectTitleOrOfferId(String projectTitle, String offerId,
3438
int offset, int limit) {
3539
return offerPreviewRepository.findByProjectTitleContainingIgnoreCaseOrOfferIdContainingIgnoreCase(
3640
projectTitle, offerId, new OffsetBasedRequest(offset, limit)).stream().toList();
3741
}
3842

3943
@Override
44+
@PreAuthorize("hasAnyAuthority('ROLE_PROJECT_MANAGER', 'ROLE_ADMIN')")
4045
public Optional<Offer> findByOfferId(String offerId) {
4146
return Optional.ofNullable(offerRepository.findByOfferId(OfferId.from(offerId)));
4247
}

project-management-infrastructure/src/main/java/life/qbic/projectmanagement/infrastructure/project/ProjectRepositoryImpl.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ public class ProjectRepositoryImpl implements ProjectRepository {
5252
private static final Logger log = logger(ProjectRepositoryImpl.class);
5353
private final QbicProjectRepo projectRepo;
5454
private final QbicProjectDataRepo projectDataRepo;
55-
5655
private final ProjectAccessService projectAccessService;
5756

5857
@Autowired
@@ -75,7 +74,7 @@ public void add(Project project) {
7574
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
7675
QbicUserDetails details = (QbicUserDetails) authentication.getPrincipal();
7776
projectAccessService.grant(details.getUserId(), project.getId(),
78-
List.of(READ, WRITE));
77+
List.of(READ, WRITE, ADMINISTRATION));//administration of this project, only
7978
projectAccessService.grantToAuthority(ADMIN.auth(), project.getId(), ADMIN.permissions());
8079
projectAccessService.grantToAuthority(PROJECT_MANAGER.auth(), project.getId(),
8180
PROJECT_MANAGER.permissions());

project-management/src/main/java/life/qbic/projectmanagement/application/DeletionService.java

-14
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,6 @@ public Result<ExperimentId, ResponseCode> deleteAllExperimentalVariables(Experim
7575
return Result.fromValue(id);
7676
}
7777

78-
public Result<ExperimentId, ResponseCode> deleteAllExperimentalGroups(ExperimentId id) {
79-
var queryResult = sampleInformationService.retrieveSamplesForExperiment(id);
80-
if (queryResult.isError()) {
81-
log.debug("experiment (%s) converting %s to %s".formatted(id, queryResult.getError(),
82-
ResponseCode.QUERY_FAILED));
83-
return Result.fromError(ResponseCode.QUERY_FAILED);
84-
}
85-
if (queryResult.isValue() && !queryResult.getValue().isEmpty()) {
86-
return Result.fromError(ResponseCode.SAMPLES_STILL_ATTACHED_TO_EXPERIMENT);
87-
}
88-
experimentInformationService.deleteAllExperimentalGroups(id);
89-
return Result.fromValue(id);
90-
}
91-
9278
@Transactional
9379
public BatchId deleteBatch(ProjectId projectId, BatchId batchId) {
9480
var samples = sampleInformationService.retrieveSamplesForBatch(batchId).stream()

project-management/src/main/java/life/qbic/projectmanagement/application/ExperimentInformationService.java

+98-31
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@
66
import java.util.List;
77
import java.util.Objects;
88
import java.util.Optional;
9+
import java.util.Set;
10+
import java.util.function.Predicate;
11+
import java.util.stream.Collectors;
912
import life.qbic.application.commons.ApplicationException;
13+
import life.qbic.application.commons.ApplicationException.ErrorCode;
14+
import life.qbic.application.commons.ApplicationException.ErrorParameters;
1015
import life.qbic.application.commons.Result;
1116
import life.qbic.logging.api.Logger;
1217
import life.qbic.logging.service.LoggerFactory;
18+
import life.qbic.projectmanagement.application.sample.SampleInformationService;
1319
import life.qbic.projectmanagement.domain.model.experiment.Experiment;
1420
import life.qbic.projectmanagement.domain.model.experiment.ExperimentId;
1521
import life.qbic.projectmanagement.domain.model.experiment.ExperimentalDesign.AddExperimentalGroupResponse.ResponseCode;
@@ -24,6 +30,7 @@
2430
import life.qbic.projectmanagement.domain.repository.ProjectRepository;
2531
import org.springframework.beans.factory.annotation.Autowired;
2632
import org.springframework.stereotype.Service;
33+
import org.springframework.transaction.annotation.Transactional;
2734

2835
/**
2936
* Service that provides an API to query basic experiment information
@@ -36,11 +43,14 @@ public class ExperimentInformationService {
3643
private static final Logger log = LoggerFactory.logger(ExperimentInformationService.class);
3744
private final ExperimentRepository experimentRepository;
3845
private final ProjectRepository projectRepository;
46+
private final SampleInformationService sampleInformationService;
3947

4048
public ExperimentInformationService(@Autowired ExperimentRepository experimentRepository,
41-
@Autowired ProjectRepository projectRepository) {
49+
@Autowired ProjectRepository projectRepository,
50+
@Autowired SampleInformationService sampleInformationService) {
4251
this.experimentRepository = experimentRepository;
4352
this.projectRepository = projectRepository;
53+
this.sampleInformationService = sampleInformationService;
4454
}
4555

4656
public Optional<Experiment> find(ExperimentId experimentId) {
@@ -64,18 +74,35 @@ private Experiment loadExperimentById(ExperimentId experimentId) {
6474
* @param experimentId the Id of the experiment for which to add the species
6575
* @param experimentalGroup the experimental groups to add
6676
*/
67-
public Result<ExperimentalGroup, ResponseCode> addExperimentalGroupToExperiment(
77+
private void addExperimentalGroupToExperiment(
6878
ExperimentId experimentId, ExperimentalGroupDTO experimentalGroup) {
6979
Objects.requireNonNull(experimentalGroup, "experimental group must not be null");
7080
Objects.requireNonNull(experimentId, "experiment id must not be null");
7181

72-
Experiment experiment = loadExperimentById(experimentId);
73-
Result<ExperimentalGroup, ResponseCode> result = experiment.addExperimentalGroup(
74-
experimentalGroup.levels(), experimentalGroup.replicateCount());
75-
if (result.isValue()) {
76-
experimentRepository.update(experiment);
82+
List<VariableLevel> varLevels = experimentalGroup.levels;
83+
if (varLevels.isEmpty()) {
84+
throw new ApplicationException("No experimental variable was selected",
85+
ErrorCode.NO_CONDITION_SELECTED,
86+
ErrorParameters.empty());
7787
}
78-
return result;
88+
89+
Experiment experiment = loadExperimentById(experimentId);
90+
Result<ExperimentalGroup, ResponseCode> result = experiment.addExperimentalGroup(
91+
experimentalGroup.name(), experimentalGroup.levels(), experimentalGroup.replicateCount());
92+
if (result.isValue()) {
93+
experimentRepository.update(experiment);
94+
} else {
95+
ResponseCode responseCode = result.getError();
96+
if (responseCode.equals(ResponseCode.CONDITION_EXISTS)) {
97+
throw new ApplicationException("A group with the variable levels %s already exists.".formatted(varLevels.toString()),
98+
ErrorCode.DUPLICATE_GROUP_SELECTED,
99+
ErrorParameters.empty());
100+
} else {
101+
throw new ApplicationException(
102+
"Could not save one or more experimental groups %s %nReason: %s".formatted(
103+
experimentalGroup.toString(), responseCode));
104+
}
105+
}
79106
}
80107

81108
/**
@@ -88,7 +115,7 @@ public Result<ExperimentalGroup, ResponseCode> addExperimentalGroupToExperiment(
88115
public List<ExperimentalGroupDTO> getExperimentalGroups(ExperimentId experimentId) {
89116
Experiment experiment = loadExperimentById(experimentId);
90117
return experiment.getExperimentalGroups().stream()
91-
.map(it -> new ExperimentalGroupDTO(it.condition().getVariableLevels(), it.sampleSize()))
118+
.map(it -> new ExperimentalGroupDTO(it.id(), it.name(), it.condition().getVariableLevels(), it.sampleSize()))
92119
.toList();
93120
}
94121

@@ -254,42 +281,80 @@ public List<ExperimentalVariable> getVariablesOfExperiment(ExperimentId experime
254281

255282
/**
256283
* Deletes all experimental groups in a given experiment.
257-
* <p>
258-
* This method does not check if samples are already.
259284
*
260285
* @param id the experiment identifier of the experiment the experimental groups are going to be
261286
* deleted.
262287
* @since 1.0.0
263288
*/
264-
public void deleteAllExperimentalGroups(ExperimentId id) {
289+
public void deleteExperimentalGroupsWithIds(ExperimentId id, List<Long> groupIds) {
290+
var queryResult = sampleInformationService.retrieveSamplesForExperiment(id);
291+
if (queryResult.isError()) {
292+
throw new ApplicationException("experiment (%s) converting %s to %s".formatted(id,
293+
queryResult.getError(), DeletionService.ResponseCode.QUERY_FAILED),
294+
ErrorCode.GENERAL,
295+
ErrorParameters.empty());
296+
}
297+
if (queryResult.isValue() && !queryResult.getValue().isEmpty()) {
298+
throw new ApplicationException("Could not edit experimental groups because samples are already registered.",
299+
ErrorCode.SAMPLES_ATTACHED_TO_EXPERIMENT,
300+
ErrorParameters.empty());
301+
}
265302
Experiment experiment = loadExperimentById(id);
266-
experiment.removeAllExperimentalGroups();
303+
experiment.removeExperimentalGroups(groupIds);
267304
experimentRepository.update(experiment);
268305
}
269306

307+
@Transactional
270308
/**
271-
* Adds experimental groups to an experiment
309+
* Updates experimental groups in a given experiment.
310+
*
311+
* Compares the provided list of experimental groups of an experiment with the persistent state.
312+
* Removes groups from the experiment that are not in the new list, adds groups that are not in
313+
* the experiment yet and updates the other groups of the experiment.
272314
*
273-
* @param experimentId the experiment to add the groups to
274-
* @param experimentalGroupDTOS the group information
275-
* @return either the collection of added groups or an appropriate response code
315+
* @param id the experiment identifier of the experiment whose groups should be updated
316+
* @param experimentalGroupDTOS the new list of experimental groups including all updates
317+
* @since 1.0.0
276318
*/
277-
public Result<Collection<ExperimentalGroup>, ResponseCode> addExperimentalGroupsToExperiment(
278-
ExperimentId experimentId, List<ExperimentalGroupDTO> experimentalGroupDTOS) {
279-
Experiment experiment = loadExperimentById(experimentId);
280-
List<ExperimentalGroup> addedGroups = new ArrayList<>();
281-
for (ExperimentalGroupDTO experimentalGroupDTO : experimentalGroupDTOS) {
282-
Result<ExperimentalGroup, ResponseCode> result = experiment.addExperimentalGroup(
283-
experimentalGroupDTO.levels(),
284-
experimentalGroupDTO.replicateCount());
285-
if (result.isError()) {
286-
return Result.fromError(result.getError());
319+
public void updateExperimentalGroupsOfExperiment(ExperimentId experimentId,
320+
List<ExperimentalGroupDTO> experimentalGroupDTOS) {
321+
322+
// check for duplicates
323+
List<List<VariableLevel>> distinctLevels = experimentalGroupDTOS.stream()
324+
.map(ExperimentalGroupDTO::levels).distinct().toList();
325+
if (distinctLevels.size() < experimentalGroupDTOS.size()) {
326+
throw new ApplicationException("Duplicate experimental group was selected",
327+
ErrorCode.DUPLICATE_GROUP_SELECTED,
328+
ErrorParameters.empty());
329+
}
330+
331+
List<ExperimentalGroup> existingGroups = experimentalGroupsFor(experimentId);
332+
List<Long> idsToDelete = getGroupIdsToDelete(existingGroups, experimentalGroupDTOS);
333+
if(!idsToDelete.isEmpty()) {
334+
deleteExperimentalGroupsWithIds(experimentId, idsToDelete);
335+
}
336+
337+
for(ExperimentalGroupDTO group : experimentalGroupDTOS) {
338+
if(group.id() == -1) {
339+
addExperimentalGroupToExperiment(experimentId, group);
287340
} else {
288-
addedGroups.add(result.getValue());
341+
updateExperimentalGroupOfExperiment(experimentId, group);
289342
}
290343
}
291-
experimentRepository.update(experiment);
292-
return Result.fromValue(addedGroups);
344+
}
345+
346+
private void updateExperimentalGroupOfExperiment(ExperimentId experimentId, ExperimentalGroupDTO group) {
347+
Experiment experiment = loadExperimentById(experimentId);
348+
experiment.updateExperimentalGroup(group.id(), group.name(), group.levels(), group.replicateCount());
349+
}
350+
351+
private List<Long> getGroupIdsToDelete(List<ExperimentalGroup> existingGroups,
352+
List<ExperimentalGroupDTO> newGroups) {
353+
Set<Long> newIds = newGroups.stream().map(ExperimentalGroupDTO::id).collect(Collectors.toSet());
354+
return existingGroups.stream()
355+
.map(ExperimentalGroup::id)
356+
.filter(Predicate.not(newIds::contains))
357+
.toList();
293358
}
294359

295360
public void editExperimentInformation(ExperimentId experimentId, String experimentName,
@@ -305,10 +370,12 @@ public void editExperimentInformation(ExperimentId experimentId, String experime
305370
/**
306371
* Information about an experimental group
307372
*
373+
* @param id id, -1 for new groups
374+
* @param name the name of the group - can be empty
308375
* @param levels the levels in the condition of the group
309376
* @param replicateCount the number of biological replicates
310377
*/
311-
public record ExperimentalGroupDTO(List<VariableLevel> levels, int replicateCount) {
378+
public record ExperimentalGroupDTO(long id, String name, List<VariableLevel> levels, int replicateCount) {
312379

313380
}
314381
}

project-management/src/main/java/life/qbic/projectmanagement/domain/model/experiment/Experiment.java

+33-2
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,15 @@ public void removeAllExperimentalVariables() {
171171
experimentalDesign.removeAllExperimentalVariables();
172172
}
173173

174+
/**
175+
* Removes experimental groups with the provided ids from the experiment .
176+
*
177+
* @since 1.0.0
178+
*/
179+
public void removeExperimentalGroups(List<Long> ids) {
180+
ids.forEach(experimentalDesign::removeExperimentalGroup);
181+
}
182+
174183
/**
175184
* Removes all experimental groups in an experiment.
176185
*
@@ -247,14 +256,36 @@ public Result<VariableName, Exception> addVariableToDesign(String variableName,
247256
* <li>If the sample size is not at least 1, the creation will fail with an {@link IllegalArgumentException}
248257
* </ul>
249258
*
259+
* @param groupName the name of this experimental group, which can be empty
260+
* @param variableLevels at least one value for a variable defined in this experiment
261+
* @param sampleSize the number of samples that are expected for this experimental group
262+
* @return
263+
*/
264+
public Result<ExperimentalGroup, ResponseCode> addExperimentalGroup(String groupName,
265+
Collection<VariableLevel> variableLevels,
266+
int sampleSize) {
267+
return experimentalDesign.addExperimentalGroup(groupName, variableLevels, sampleSize);
268+
}
269+
270+
/**
271+
* Updates an experimental group of to the experimental design.
272+
* <p>
273+
* <ul>
274+
* <li>If an experimental group with the same variable levels already exists, the creation will fail with an {@link ConditionExistsException} and no condition is added to the design.
275+
* <li>If the {@link VariableLevel}s belong to variables not specified in this experiment, the creation will fail with an {@link IllegalArgumentException}
276+
* <li>If the sample size is not at least 1, the creation will fail with an {@link IllegalArgumentException}
277+
* </ul>
278+
*
279+
* @param id the unique identifier of the experimental group to update
280+
* @param groupName the name of this experimental group, which can be empty
250281
* @param variableLevels at least one value for a variable defined in this experiment
251282
* @param sampleSize the number of samples that are expected for this experimental group
252283
* @return
253284
*/
254-
public Result<ExperimentalGroup, ResponseCode> addExperimentalGroup(
285+
public Result<ExperimentalGroup, ResponseCode> updateExperimentalGroup(long id, String groupName,
255286
Collection<VariableLevel> variableLevels,
256287
int sampleSize) {
257-
return experimentalDesign.addExperimentalGroup(variableLevels, sampleSize);
288+
return experimentalDesign.updateExperimentalGroup(id, groupName, variableLevels, sampleSize);
258289
}
259290

260291
public List<ExperimentalGroup> getExperimentalGroups() {

0 commit comments

Comments
 (0)