Skip to content

Commit bf9963d

Browse files
KochTobisven1103
andauthored
Api for creating, updating experiment (#1043)
* Add interface for experiment update * Add method for experiment description * Add confounding variables record * Merge interfaces and fix security context handling * Add experimental groups * Re-order methods * Add JavaDoc * Make List and Sets unmodifiable As records should be immutable, we prevent accidental modification of lists and sets contained in those records Co-authored-by: Sven F. <[email protected]> --------- Co-authored-by: KochTobi <[email protected]> Co-authored-by: Sven F. <[email protected]>
1 parent 35daeb2 commit bf9963d

File tree

2 files changed

+258
-19
lines changed

2 files changed

+258
-19
lines changed

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

Lines changed: 207 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package life.qbic.projectmanagement.application.api;
22

3+
import java.util.List;
34
import static java.util.Objects.nonNull;
4-
55
import java.util.Optional;
6+
import java.util.Set;
67
import java.util.UUID;
8+
import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ConfoundingVariableInformation;
9+
import org.springframework.lang.Nullable;
710
import reactor.core.publisher.Mono;
811

912
/**
@@ -29,10 +32,10 @@ public interface AsyncProjectService {
2932
* The method implementation must be non-blocking.
3033
* <p>
3134
* The implementing class must ensure to be able to process all implementing classes of the
32-
* {@link UpdateRequestBody} interface contained in the request.
35+
* {@link ProjectUpdateRequestBody} interface contained in the request.
3336
* <p>
3437
* The implementing class must also ensure to only return responses with classes implementing the
35-
* {@link UpdateResponseBody} interface.
38+
* {@link ProjectUpdateResponseBody} interface.
3639
*
3740
* @param request the request to update a project
3841
* @return a {@link Mono<ProjectUpdateResponse>} object publishing an
@@ -47,6 +50,31 @@ Mono<ProjectUpdateResponse> update(
4750
throws UnknownRequestException, RequestFailedException, AccessDeniedException;
4851

4952

53+
/**
54+
* Submits an experiment update request and returns a reactive
55+
* {@link Mono< ExperimentUpdateResponse >} object immediately.
56+
* <p>
57+
* The method is non-blocking.
58+
* <p>
59+
* The implementing class must ensure to be able to process all implementing classes of the
60+
* {@link ProjectUpdateRequestBody} interface contained in the request.
61+
* <p>
62+
* The implementing class must also ensure to only return responses with classes implementing the
63+
* {@link ProjectUpdateResponseBody} interface.
64+
*
65+
* @param request the request to update a project
66+
* @return a {@link Mono<ProjectUpdateResponse>} object publishing an
67+
* {@link ProjectUpdateResponse} on success.
68+
* @throws UnknownRequestException if an unknown request has been used in the service call
69+
* @throws RequestFailedException if the request was not successfully executed
70+
* @throws AccessDeniedException if the user has insufficient rights
71+
* @since 1.9.0
72+
*/
73+
Mono<ExperimentUpdateResponse> update(ExperimentUpdateRequest request)
74+
throws RequestFailedException, AccessDeniedException;
75+
76+
77+
5078
/**
5179
* Submits a project creation request and returns a {@link Mono<ProjectCreationResponse>}
5280
* immediately.
@@ -71,7 +99,8 @@ Mono<ProjectCreationResponse> create(ProjectCreationRequest request)
7199
*
72100
* @since 1.9.0
73101
*/
74-
sealed interface UpdateRequestBody permits FundingInformation, ProjectContacts, ProjectDesign {
102+
sealed interface ProjectUpdateRequestBody permits FundingInformation, ProjectContacts,
103+
ProjectDesign {
75104

76105
}
77106

@@ -81,17 +110,29 @@ sealed interface UpdateRequestBody permits FundingInformation, ProjectContacts,
81110
*
82111
* @since 1.9.0
83112
*/
84-
sealed interface UpdateResponseBody permits FundingInformation, ProjectContacts, ProjectDesign {
113+
sealed interface ProjectUpdateResponseBody permits FundingInformation, ProjectContacts,
114+
ProjectDesign {
115+
116+
}
117+
118+
sealed interface ExperimentUpdateRequestBody permits ConfoundingVariables, ExperimentDescription,
119+
ExperimentalGroups, ExperimentalVariables {
85120

86121
}
87122

123+
sealed interface ExperimentUpdateResponseBody permits ConfoundingVariables, ExperimentDescription,
124+
ExperimentalGroups, ExperimentalVariables {
125+
126+
}
127+
128+
88129
/**
89130
* Cacheable requests provide a unique identifier so cache implementations can unambiguously
90131
* manage the requests.
91132
*
92133
* @since 1.9.0
93134
*/
94-
sealed interface CacheableRequest permits ProjectUpdateRequest {
135+
sealed interface CacheableRequest permits ProjectUpdateRequest, ExperimentUpdateRequest {
95136

96137
/**
97138
* Returns an ID that is unique to the request.
@@ -104,15 +145,15 @@ sealed interface CacheableRequest permits ProjectUpdateRequest {
104145
}
105146

106147
/**
107-
* Container for passing information in an {@link UpdateRequestBody} or
108-
* {@link UpdateResponseBody}.
148+
* Container for passing information in an {@link ProjectUpdateRequestBody} or
149+
* {@link ProjectUpdateResponseBody}.
109150
*
110151
* @param title the title of the project
111152
* @param objective the objective of the project
112153
* @since 1.9.0
113154
*/
114-
record ProjectDesign(String title, String objective) implements UpdateRequestBody,
115-
UpdateResponseBody {
155+
record ProjectDesign(String title, String objective) implements ProjectUpdateRequestBody,
156+
ProjectUpdateResponseBody {
116157

117158
}
118159

@@ -125,8 +166,8 @@ record ProjectDesign(String title, String objective) implements UpdateRequestBod
125166
* @since 1.9.0
126167
*/
127168
record ProjectContacts(ProjectContact investigator, ProjectContact manager,
128-
ProjectContact responsible) implements UpdateRequestBody,
129-
UpdateResponseBody {
169+
ProjectContact responsible) implements ProjectUpdateRequestBody,
170+
ProjectUpdateResponseBody {
130171

131172
}
132173

@@ -148,8 +189,156 @@ record ProjectContact(String fullName, String email) {
148189
* @param grantId the grant ID
149190
* @since 1.9.0
150191
*/
151-
record FundingInformation(String grant, String grantId) implements UpdateRequestBody,
152-
UpdateResponseBody {
192+
record FundingInformation(String grant, String grantId) implements ProjectUpdateRequestBody,
193+
ProjectUpdateResponseBody {
194+
195+
}
196+
197+
/**
198+
* Contains information on one experimental variables
199+
*
200+
* @param name the name of the variable
201+
* @param levels possible levels of the variable
202+
* @param unit the unit of the experimental variable. Can be null if no unit is set
203+
* @since 1.9.0
204+
*/
205+
record ExperimentalVariable(String name, Set<String> levels, @Nullable String unit) {
206+
207+
public ExperimentalVariable {
208+
levels = Set.copyOf(levels);
209+
}
210+
}
211+
212+
/**
213+
* Container of experimental variables. Can be used in {@link #update(ExperimentUpdateRequest)}.
214+
* @param experimentalVariables the list of experimental variables
215+
* @since 1.9.0
216+
*/
217+
record ExperimentalVariables(
218+
List<ExperimentalVariable> experimentalVariables) implements
219+
ExperimentUpdateRequestBody,
220+
ExperimentUpdateResponseBody {
221+
222+
public ExperimentalVariables {
223+
experimentalVariables = List.copyOf(experimentalVariables);
224+
}
225+
}
226+
227+
/**
228+
* A level of an experimental variable
229+
*
230+
* @param variableName the name of the variable
231+
* @param levelValue the value of the level
232+
* @param unit the unit for the value of the level. Can be null if no unit is set
233+
* @since 1.9.0
234+
*/
235+
record VariableLevel(String variableName, String levelValue, @Nullable String unit) {
236+
237+
}
238+
239+
/**
240+
* Information about an experimental group
241+
*
242+
* @param groupId the identifier of the group
243+
* @param name the name of the eperimental group can be empty but is not expected to be
244+
* null
245+
* @param sampleSize the number of samples in this experimental group
246+
* @param levels the experimental variable levels making up the condition for the samples in
247+
* this group.
248+
* @since 1.9.0
249+
*/
250+
record ExperimentalGroup(@Nullable Long groupId, String name, int sampleSize,
251+
Set<VariableLevel> levels) {
252+
253+
public ExperimentalGroup {
254+
levels = Set.copyOf(levels);
255+
}
256+
}
257+
258+
/**
259+
* A container for experimental groups. Can be used in {@link #update(ExperimentUpdateRequest)}
260+
* @param experimentalGroups the list of experimental groups
261+
* @since 1.9.0
262+
*/
263+
record ExperimentalGroups(List<ExperimentalGroup> experimentalGroups) implements
264+
ExperimentUpdateRequestBody,
265+
ExperimentUpdateResponseBody {
266+
267+
public ExperimentalGroups {
268+
experimentalGroups = List.copyOf(experimentalGroups);
269+
}
270+
}
271+
272+
/**
273+
* A container describing the experiment
274+
*
275+
* @param experimentName the name of the experiment
276+
* @param species a set of species for the experiment. Expected textual representations
277+
* containing CURIEs.
278+
* @param specimen a set of specimen for the eperiment. Expected textual representations
279+
* containing CURIEs.
280+
* @param analytes a set of analytes for the eperiment.Expected textual representations
281+
* containing CURIEs.
282+
* @since 1.9.0
283+
*/
284+
record ExperimentDescription(String experimentName, Set<String> species, Set<String> specimen,
285+
Set<String> analytes) implements ExperimentUpdateRequestBody,
286+
ExperimentUpdateResponseBody {
287+
288+
public ExperimentDescription {
289+
species = Set.copyOf(species);
290+
specimen = Set.copyOf(specimen);
291+
analytes = Set.copyOf(analytes);
292+
}
293+
}
294+
295+
/**
296+
* A list of confounding variable information. Can be used in {@link #update(ExperimentUpdateRequest)}
297+
* @param confoundingVariables the variable information
298+
*/
299+
record ConfoundingVariables(List<ConfoundingVariableInformation> confoundingVariables) implements
300+
ExperimentUpdateRequestBody, ExperimentUpdateResponseBody {
301+
302+
public ConfoundingVariables {
303+
confoundingVariables = List.copyOf(confoundingVariables);
304+
}
305+
}
306+
307+
308+
/**
309+
* A service request to update an experiment
310+
* @param projectId the project's identifier. The project containing the experiment.
311+
* @param experimentId the experiment's identifier
312+
* @param body the request body containing information on what was updated
313+
* @param requestId The identifier of the request. Please use {@link #ExperimentUpdateRequest(String, String, ExperimentUpdateRequestBody)} if it is not determined yet.
314+
* @since 1.9.0
315+
*/
316+
record ExperimentUpdateRequest(String projectId, String experimentId,
317+
ExperimentUpdateRequestBody body,
318+
String requestId) implements CacheableRequest {
319+
320+
/**
321+
* A service request to update an experiment
322+
* @param projectId the project's identifier. The project containing the experiment.
323+
* @param experimentId the experiment's identifier
324+
* @param body the request body containing information on what was updated
325+
* @since 1.9.0
326+
*/
327+
public ExperimentUpdateRequest(String projectId, String experimentId,
328+
ExperimentUpdateRequestBody body) {
329+
this(projectId, experimentId, body, UUID.randomUUID().toString());
330+
}
331+
}
332+
333+
/**
334+
* A service response from a {@link ExperimentUpdateRequest}
335+
* @param experimentId the experiment's identifier
336+
* @param body information about the update
337+
* @param requestId the identifier of the original request to which this is a response.
338+
* @since 1.9.0
339+
*/
340+
record ExperimentUpdateResponse(String experimentId, ExperimentUpdateResponseBody body,
341+
String requestId) {
153342

154343
}
155344

@@ -185,10 +374,11 @@ record ProjectCreationResponse(String projectId) {
185374
* @param requestBody the information to be updated.
186375
* @since 1.9.0
187376
*/
188-
record ProjectUpdateRequest(String projectId, UpdateRequestBody requestBody, String id) implements
377+
record ProjectUpdateRequest(String projectId, ProjectUpdateRequestBody requestBody,
378+
String id) implements
189379
CacheableRequest {
190380

191-
public ProjectUpdateRequest(String projectId, UpdateRequestBody requestBody) {
381+
public ProjectUpdateRequest(String projectId, ProjectUpdateRequestBody requestBody) {
192382
this(projectId, requestBody, UUID.randomUUID().toString());
193383
}
194384

@@ -205,7 +395,7 @@ public String requestId() {
205395
* @param responseBody the information that was updated.
206396
* @since 1.9.0
207397
*/
208-
record ProjectUpdateResponse(String projectId, UpdateResponseBody responseBody,
398+
record ProjectUpdateResponse(String projectId, ProjectUpdateResponseBody responseBody,
209399
String requestId) {
210400

211401
public ProjectUpdateResponse {

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

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ public Mono<ProjectUpdateResponse> update(@NonNull ProjectUpdateRequest request)
5252
SecurityContext securityContext = SecurityContextHolder.getContext();
5353
return response
5454
.transform(original -> writeSecurityContext(original, securityContext))
55-
.retryWhen(Retry.maxInARow(5)
56-
.doBeforeRetry(retrySignal -> log.warn("Update failed (" + retrySignal + ")")));
55+
.retryWhen(defaultRetryStrategy());
5756
}
5857

5958
@Override
@@ -63,10 +62,60 @@ public Mono<ProjectCreationResponse> create(ProjectCreationRequest request)
6362
throw new RuntimeException("not implemented");
6463
}
6564

65+
66+
@Override
67+
public Mono<ExperimentUpdateResponse> update(
68+
ExperimentUpdateRequest request) {
69+
Mono<ExperimentUpdateResponse> response = switch (request.body()) {
70+
case ExperimentalVariables experimentalVariables ->
71+
updateExperimentalVariables(request.projectId(), request.experimentId(),
72+
experimentalVariables);
73+
case ExperimentDescription experimentDescription ->
74+
updateExperimentDescription(request.projectId(), request.experimentId(),
75+
experimentDescription);
76+
77+
case ConfoundingVariables confoundingVariables ->
78+
updateConfoundingVariables(request.projectId(), request.experimentId(),
79+
confoundingVariables);
80+
case ExperimentalGroups experimentalGroups -> unknownRequest();
81+
};
82+
83+
SecurityContext securityContext = SecurityContextHolder.getContext();
84+
return response
85+
.transform(original -> writeSecurityContext(original, securityContext))
86+
.retryWhen(defaultRetryStrategy());
87+
}
88+
89+
private static Retry defaultRetryStrategy() {
90+
return Retry.maxInARow(5)
91+
.doBeforeRetry(retrySignal -> log.warn("Operation failed (" + retrySignal + ")"));
92+
}
93+
6694
private <T> Mono<T> unknownRequest() {
6795
return Mono.error(() -> new UnknownRequestException("Invalid request body"));
6896
}
6997

98+
private Mono<ExperimentUpdateResponse> updateConfoundingVariables(String projectId,
99+
String experimentId,
100+
ConfoundingVariables confoundingVariables) {
101+
//TODO implement
102+
throw new RuntimeException("Not implemented");
103+
}
104+
105+
private Mono<ExperimentUpdateResponse> updateExperimentDescription(String projectId,
106+
String experimentId,
107+
ExperimentDescription experimentDescription) {
108+
//TODO implement
109+
throw new RuntimeException("Not implemented");
110+
}
111+
112+
private Mono<ExperimentUpdateResponse> updateExperimentalVariables(String projectId,
113+
String experimentId, ExperimentalVariables experimentalVariables) {
114+
//TODO implement
115+
throw new RuntimeException("Not implemented");
116+
}
117+
118+
70119
private Mono<ProjectUpdateResponse> updateProjectDesign(String projectId, ProjectDesign design, String requestId) {
71120
return applySecurityContext(
72121
Mono.<ProjectUpdateResponse>create(sink -> {

0 commit comments

Comments
 (0)