Skip to content

Commit 31ab9fb

Browse files
committed
Working example for sample registration
1 parent 3378786 commit 31ab9fb

5 files changed

Lines changed: 129 additions & 46 deletions

File tree

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import life.qbic.projectmanagement.domain.model.sample.Sample;
3232
import life.qbic.projectmanagement.domain.model.sample.SampleId;
3333
import org.springframework.beans.factory.annotation.Autowired;
34+
import org.springframework.beans.factory.annotation.Qualifier;
3435
import org.springframework.lang.NonNull;
3536
import org.springframework.security.core.context.SecurityContext;
3637
import org.springframework.security.core.context.SecurityContextHolder;
@@ -67,7 +68,7 @@ public AsyncProjectServiceImpl(
6768
@Autowired SampleInformationService sampleInfoService,
6869
@Autowired Scheduler scheduler,
6970
@Autowired DigitalObjectFactory digitalObjectFactory,
70-
@Autowired TemplateService templateService
71+
@Autowired @Qualifier("templateServiceV2") TemplateService templateService
7172
) {
7273
this.projectService = Objects.requireNonNull(projectService);
7374
this.sampleInfoService = Objects.requireNonNull(sampleInfoService);

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
*
2727
* @since <version tag>
2828
*/
29-
@Service
29+
@Service("templateServiceV2")
3030
public class TemplateService {
3131

3232
private final ExperimentInformationService experimentService;
@@ -45,9 +45,8 @@ public TemplateService(ExperimentInformationService experimentService,
4545
}
4646

4747
/**
48-
* Creates a {@link XSSFWorkbook} that contains a template
49-
* {@link org.apache.poi.xssf.usermodel.XSSFSheet} that can be used to register one or more sample
50-
* batches for an experiment.
48+
* Creates a {@link DigitalObject} that contains a template in the provided {@link MimeType} that
49+
* can be used to register one or more sample batches for an experiment.
5150
* <p>
5251
* The workbook contains two sheets. The first one is for the user input and contains cell with
5352
* data-validation, e.g. a list of available conditions in the experiment.
@@ -66,7 +65,8 @@ public TemplateService(ExperimentInformationService experimentService,
6665
* template shall be generated
6766
* @param experimentId the experiment id of the experiment to create the template for
6867
* @return a pre-configured template workbook
69-
* @throws NoSuchExperimentException if no experiment with the provided id can be found.
68+
* @throws NoSuchExperimentException if no experiment with the provided id can be found.
69+
* @throws UnsupportedMimeTypeException if there is no support for the requested {@link MimeType}
7070
* @since 1.5.0
7171
*/
7272
@PreAuthorize(

user-interface/src/main/java/life/qbic/datamanager/files/export/sample/TemplateService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.apache.poi.ss.usermodel.Workbook;
1818
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
1919
import org.springframework.beans.factory.annotation.Autowired;
20+
import org.springframework.beans.factory.annotation.Qualifier;
2021
import org.springframework.security.access.prepost.PreAuthorize;
2122
import org.springframework.stereotype.Service;
2223

user-interface/src/main/java/life/qbic/datamanager/views/projects/project/samples/SampleInformationMain.java

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616
import com.vaadin.flow.spring.annotation.SpringComponent;
1717
import com.vaadin.flow.spring.annotation.UIScope;
1818
import jakarta.annotation.security.PermitAll;
19+
import java.io.IOException;
1920
import java.io.Serial;
2021
import java.time.LocalDate;
2122
import java.util.ArrayList;
2223
import java.util.concurrent.CompletableFuture;
2324
import java.util.concurrent.TimeUnit;
2425
import life.qbic.application.commons.ApplicationException;
2526
import life.qbic.application.commons.FileNameFormatter;
26-
import life.qbic.datamanager.files.export.download.WorkbookDownloadStreamProvider;
27+
import life.qbic.datamanager.files.export.download.ByteArrayDownloadStreamProvider;
2728
import life.qbic.datamanager.files.export.sample.TemplateService;
2829
import life.qbic.datamanager.views.AppRoutes.ProjectRoutes;
2930
import life.qbic.datamanager.views.Context;
@@ -44,6 +45,8 @@
4445
import life.qbic.projectmanagement.application.ProjectInformationService;
4546
import life.qbic.projectmanagement.application.ProjectOverview;
4647
import life.qbic.projectmanagement.application.api.AsyncProjectService;
48+
import life.qbic.projectmanagement.application.api.AsyncProjectService.AccessDeniedException;
49+
import life.qbic.projectmanagement.application.api.fair.DigitalObject;
4750
import life.qbic.projectmanagement.application.confounding.ConfoundingVariableService.ExperimentReference;
4851
import life.qbic.projectmanagement.application.experiment.ExperimentInformationService;
4952
import life.qbic.projectmanagement.application.sample.SampleInformationService;
@@ -55,8 +58,8 @@
5558
import life.qbic.projectmanagement.domain.model.project.Project;
5659
import life.qbic.projectmanagement.domain.model.project.ProjectId;
5760
import life.qbic.projectmanagement.domain.model.sample.Sample;
58-
import org.apache.poi.ss.usermodel.Workbook;
5961
import org.springframework.beans.factory.annotation.Autowired;
62+
import org.springframework.util.MimeType;
6063

6164
/**
6265
* Sample Information Main Component
@@ -74,6 +77,8 @@ public class SampleInformationMain extends Main implements BeforeEnterObserver {
7477

7578
public static final String PROJECT_ID_ROUTE_PARAMETER = "projectId";
7679
public static final String EXPERIMENT_ID_ROUTE_PARAMETER = "experimentId";
80+
private static final MimeType OPEN_XML = MimeType.valueOf(
81+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
7782
@Serial
7883
private static final long serialVersionUID = 3778218989387044758L;
7984
private static final Logger log = LoggerFactory.logger(SampleInformationMain.class);
@@ -96,6 +101,7 @@ public class SampleInformationMain extends Main implements BeforeEnterObserver {
96101
private final transient TemplateService templateService;
97102
private final transient SampleRegistrationServiceV2 sampleRegistrationServiceV2;
98103
private final transient AsyncProjectService asyncProjectService;
104+
private final MessageSourceNotificationFactory messageSourceNotificationFactory;
99105
private transient Context context;
100106

101107
public SampleInformationMain(@Autowired ExperimentInformationService experimentInformationService,
@@ -108,7 +114,9 @@ public SampleInformationMain(@Autowired ExperimentInformationService experimentI
108114
CancelConfirmationDialogFactory cancelConfirmationDialogFactory,
109115
MessageSourceNotificationFactory notificationFactory,
110116
SampleValidationService sampleValidationService,
111-
TemplateService templateService, SampleRegistrationServiceV2 sampleRegistrationServiceV2) {
117+
TemplateService templateService,
118+
SampleRegistrationServiceV2 sampleRegistrationServiceV2,
119+
MessageSourceNotificationFactory messageSourceNotificationFactory) {
112120
this.downloadComponent = new DownloadComponent();
113121

114122
this.experimentInformationService = requireNonNull(experimentInformationService,
@@ -155,6 +163,7 @@ public SampleInformationMain(@Autowired ExperimentInformationService experimentI
155163
sampleDetailsComponent.getClass().getSimpleName(),
156164
System.identityHashCode(sampleDetailsComponent)));
157165
add(downloadComponent);
166+
this.messageSourceNotificationFactory = messageSourceNotificationFactory;
158167
}
159168

160169
private static boolean noExperimentGroupsInExperiment(Experiment experiment) {
@@ -195,21 +204,52 @@ private void downloadSampleMetadata() {
195204
.map(ProjectOverview::projectCode).orElseThrow();
196205
var experimentName = experimentInformationService.find(projectId.value(), experimentId)
197206
.map(Experiment::getName).orElseThrow();
198-
downloadComponent.trigger(new WorkbookDownloadStreamProvider() {
199-
@Override
200-
public String getFilename() {
201-
return FileNameFormatter.formatWithTimestampedContext(LocalDate.now(), projectCode,
202-
experimentName,
203-
"sample information",
204-
"xlsx");
205-
}
206207

207-
@Override
208-
public Workbook getWorkbook() {
209-
return templateService.sampleBatchInformationXLSXTemplate(projectId.value(),
210-
experimentId.value());
211-
}
212-
});
208+
asyncProjectService.sampleRegistrationTemplate(projectId.value(), experimentId.value(),
209+
OPEN_XML).doOnSuccess(resource ->
210+
triggerDownload(resource,
211+
FileNameFormatter.formatWithTimestampedContext(LocalDate.now(), projectCode,
212+
experimentName,
213+
"sample information",
214+
"xlsx")
215+
)).doOnError(this::handleError).subscribe();
216+
}
217+
218+
private void handleError(Throwable throwable) {
219+
switch (throwable) {
220+
case AccessDeniedException ignored:
221+
handleAccessDeniedError();
222+
return;
223+
default:
224+
handleUnexpectedError(throwable);
225+
}
226+
}
227+
228+
private void handleUnexpectedError(Throwable throwable) {
229+
throw new ApplicationException("We are sorry, an unexpected error occurred.", throwable);
230+
}
231+
232+
private void handleAccessDeniedError() {
233+
getUI().ifPresent(ui -> ui.access(() -> messageSourceNotificationFactory.toast("access.denied.message", new Object[]{}, getLocale()).open()));
234+
}
235+
236+
private void triggerDownload(DigitalObject resource, String filename) {
237+
getUI().ifPresent(
238+
ui -> ui.access(() -> downloadComponent.trigger(new ByteArrayDownloadStreamProvider() {
239+
@Override
240+
public byte[] getBytes() {
241+
try (var content = resource.content()) {
242+
return content.readAllBytes();
243+
} catch (IOException e) {
244+
throw new RuntimeException(e);
245+
}
246+
}
247+
248+
@Override
249+
public String getFilename() {
250+
return filename;
251+
}
252+
})));
213253
}
214254

215255
private void onRegisterBatchClicked() {
@@ -225,7 +265,7 @@ private void onRegisterBatchClicked() {
225265
ProjectOverview projectOverview = projectInformationService.findOverview(projectId)
226266
.orElseThrow();
227267
RegisterSampleBatchDialog registerSampleBatchDialog = new RegisterSampleBatchDialog(
228-
sampleValidationService, templateService, experimentId.value(),
268+
sampleValidationService, asyncProjectService, messageSourceNotificationFactory, experimentId.value(),
229269
projectId.value(), projectOverview.projectCode());
230270
UI ui = UI.getCurrent();
231271
registerSampleBatchDialog.addConfirmListener(event -> {

user-interface/src/main/java/life/qbic/datamanager/views/projects/project/samples/registration/batch/RegisterSampleBatchDialog.java

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,45 @@
1313
import com.vaadin.flow.component.textfield.TextField;
1414
import com.vaadin.flow.shared.Registration;
1515
import java.io.IOException;
16+
import java.time.LocalDate;
1617
import java.util.ArrayList;
1718
import java.util.Collections;
1819
import java.util.List;
1920
import java.util.Map;
21+
import java.util.Objects;
2022
import java.util.concurrent.CompletableFuture;
2123
import java.util.concurrent.TimeUnit;
2224
import java.util.function.Function;
2325
import java.util.stream.Collectors;
2426
import life.qbic.application.commons.ApplicationException;
25-
import life.qbic.datamanager.download.DownloadContentProvider.XLSXDownloadContentProvider;
26-
import life.qbic.datamanager.download.DownloadProvider;
2727
import life.qbic.application.commons.FileNameFormatter;
28-
import life.qbic.datamanager.files.export.sample.TemplateService;
28+
import life.qbic.datamanager.files.export.download.ByteArrayDownloadStreamProvider;
2929
import life.qbic.datamanager.files.parsing.MetadataParser.ParsingException;
3030
import life.qbic.datamanager.files.parsing.ParsingResult;
3131
import life.qbic.datamanager.files.parsing.SampleInformationExtractor;
3232
import life.qbic.datamanager.files.parsing.SampleInformationExtractor.SampleInformationForNewSample;
3333
import life.qbic.datamanager.files.parsing.xlsx.XLSXParser;
3434
import life.qbic.datamanager.views.general.WizardDialogWindow;
35+
import life.qbic.datamanager.views.general.download.DownloadComponent;
3536
import life.qbic.datamanager.views.general.upload.UploadWithDisplay;
3637
import life.qbic.datamanager.views.general.upload.UploadWithDisplay.FileType;
3738
import life.qbic.datamanager.views.general.upload.UploadWithDisplay.SucceededEvent;
3839
import life.qbic.datamanager.views.general.upload.UploadWithDisplay.UploadedData;
40+
import life.qbic.datamanager.views.notifications.MessageSourceNotificationFactory;
3941
import life.qbic.logging.api.Logger;
4042
import life.qbic.logging.service.LoggerFactory;
4143
import life.qbic.projectmanagement.application.ValidationResult;
4244
import life.qbic.projectmanagement.application.ValidationResultWithPayload;
45+
import life.qbic.projectmanagement.application.api.AsyncProjectService;
46+
import life.qbic.projectmanagement.application.api.AsyncProjectService.AccessDeniedException;
4347
import life.qbic.projectmanagement.application.api.SampleMetadata;
48+
import life.qbic.projectmanagement.application.api.fair.DigitalObject;
4449
import life.qbic.projectmanagement.application.sample.SampleValidationService;
45-
import org.apache.poi.ss.usermodel.Workbook;
50+
import org.springframework.util.MimeType;
4651

4752
public class RegisterSampleBatchDialog extends WizardDialogWindow {
48-
53+
private static final MimeType OPEN_XML = MimeType.valueOf(
54+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
4955
private final List<SampleMetadata> validatedSampleMetadata;
5056
private final TextField batchNameField;
5157
private static final Logger log = LoggerFactory.logger(RegisterSampleBatchDialog.class);
@@ -55,17 +61,22 @@ public class RegisterSampleBatchDialog extends WizardDialogWindow {
5561
private final Div succeededView;
5662
private static final int MAX_FILE_SIZE = 25 * 1024 * 1024;
5763
private final UploadWithDisplay uploadWithDisplay;
64+
private transient final MessageSourceNotificationFactory messageFactory;
65+
private final DownloadComponent downloadComponent;
5866

5967
private void setValidatedSampleMetadata(List<SampleMetadata> validatedSampleMetadata) {
6068
this.validatedSampleMetadata.clear();
6169
this.validatedSampleMetadata.addAll(validatedSampleMetadata);
6270
}
6371

6472
public RegisterSampleBatchDialog(SampleValidationService sampleValidationService,
65-
TemplateService templateService,
73+
AsyncProjectService service, MessageSourceNotificationFactory messageFactory,
6674
String experimentId,
6775
String projectId,
6876
String projectCode) {
77+
this.messageFactory = Objects.requireNonNull(messageFactory);
78+
this.downloadComponent = new DownloadComponent();
79+
6980

7081
setHeaderTitle("Register Sample Batch");
7182
setConfirmButtonLabel("Register");
@@ -86,7 +97,7 @@ public RegisterSampleBatchDialog(SampleValidationService sampleValidationService
8697
batchNameField.setErrorMessage("Please provide a name for your batch.");
8798
batchNameField.setPlaceholder("Please enter a name for your batch");
8899

89-
Div downloadMetadataSection = setupDownloadMetadataSection(templateService, experimentId,
100+
Div downloadMetadataSection = setupDownloadMetadataSection(service, experimentId,
90101
projectId, projectCode);
91102

92103
validatedSampleMetadata = new ArrayList<>();
@@ -110,7 +121,25 @@ public RegisterSampleBatchDialog(SampleValidationService sampleValidationService
110121
inProgressView.setVisible(false);
111122
failedView.setVisible(false);
112123
succeededView.setVisible(false);
113-
add(initialView, inProgressView, failedView, succeededView);
124+
add(initialView, inProgressView, failedView, succeededView, downloadComponent);
125+
}
126+
127+
private void handleError(Throwable throwable) {
128+
switch (throwable) {
129+
case AccessDeniedException ignored:
130+
handleAccessDeniedError();
131+
return;
132+
default:
133+
handleUnexpectedError(throwable);
134+
}
135+
}
136+
137+
private void handleUnexpectedError(Throwable throwable) {
138+
throw new ApplicationException("We are sorry, an unexpected error occurred.", throwable);
139+
}
140+
141+
private void handleAccessDeniedError() {
142+
getUI().ifPresent(ui -> ui.access(() -> messageFactory.toast("access.denied.message", new Object[]{}, getLocale()).open()));
114143
}
115144

116145
private void onUploadSucceeded(SampleValidationService sampleValidationService,
@@ -209,24 +238,36 @@ private void onUploadSucceeded(SampleValidationService sampleValidationService,
209238

210239
}
211240

212-
private Div setupDownloadMetadataSection(TemplateService templateService, String experimentId,
241+
private void triggerDownload(DigitalObject resource, String filename) {
242+
getUI().ifPresent(
243+
ui -> ui.access(() -> downloadComponent.trigger(new ByteArrayDownloadStreamProvider() {
244+
@Override
245+
public byte[] getBytes() {
246+
try (var content = resource.content()) {
247+
return content.readAllBytes();
248+
} catch (IOException e) {
249+
throw new RuntimeException(e);
250+
}
251+
}
252+
253+
@Override
254+
public String getFilename() {
255+
return filename;
256+
}
257+
})));
258+
}
259+
260+
private Div setupDownloadMetadataSection(AsyncProjectService service, String experimentId,
213261
String projectId, String projectCode) {
214262
Button downloadTemplate = new Button("Download metadata template");
215263
downloadTemplate.addClassName("download-metadata-button");
216264
downloadTemplate.addClickListener(buttonClickEvent -> {
217-
try (Workbook workbook = templateService.sampleBatchRegistrationXLSXTemplate(
218-
projectId,
219-
experimentId)) {
220-
var filename = FileNameFormatter.formatWithVersion(
221-
projectCode + "_sample metadata registration template",
222-
1, "xlsx");
223-
var downloadProvider = new DownloadProvider(
224-
new XLSXDownloadContentProvider(filename, workbook));
225-
add(downloadProvider);
226-
downloadProvider.trigger();
227-
} catch (IOException e) {
228-
throw new ApplicationException(e.getMessage(), e);
229-
}
265+
service.sampleRegistrationTemplate(projectId, experimentId,
266+
OPEN_XML).doOnSuccess(resource ->
267+
triggerDownload(resource,
268+
FileNameFormatter.formatWithTimestampedSimple(LocalDate.now(), projectCode, " sample metadata template",
269+
"xlsx")
270+
)).doOnError(this::handleError).subscribe();
230271
});
231272
Div text = new Div();
232273
text.addClassName("download-metadata-text");

0 commit comments

Comments
 (0)