66import java .util .List ;
77import java .util .Objects ;
88import java .util .Optional ;
9+ import java .util .Set ;
10+ import java .util .function .Predicate ;
11+ import java .util .stream .Collectors ;
912import life .qbic .application .commons .ApplicationException ;
13+ import life .qbic .application .commons .ApplicationException .ErrorCode ;
14+ import life .qbic .application .commons .ApplicationException .ErrorParameters ;
1015import life .qbic .application .commons .Result ;
1116import life .qbic .logging .api .Logger ;
1217import life .qbic .logging .service .LoggerFactory ;
18+ import life .qbic .projectmanagement .application .sample .SampleInformationService ;
1319import life .qbic .projectmanagement .domain .model .experiment .Experiment ;
1420import life .qbic .projectmanagement .domain .model .experiment .ExperimentId ;
1521import life .qbic .projectmanagement .domain .model .experiment .ExperimentalDesign .AddExperimentalGroupResponse .ResponseCode ;
2430import life .qbic .projectmanagement .domain .repository .ProjectRepository ;
2531import org .springframework .beans .factory .annotation .Autowired ;
2632import 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}
0 commit comments