From 33e06eb03d8765e98d337e14a5468f1f1fd60c38 Mon Sep 17 00:00:00 2001 From: lgal Date: Thu, 3 Dec 2020 10:52:04 +0200 Subject: [PATCH 1/3] #3610 WIP rest endpoint for BAG export cases --- .../de/symeda/sormas/api/user/UserRole.java | 3 +- .../sormas/api/utils/CsvStreamUtils.java | 196 ++++++++++++++++++ .../symeda/sormas/rest/BAGExportResource.java | 67 ++++++ .../src/main/webapp/WEB-INF/glassfish-web.xml | 4 + sormas-rest/src/main/webapp/WEB-INF/web.xml | 3 + .../symeda/sormas/ui/utils/DownloadUtil.java | 165 +++------------ 6 files changed, 303 insertions(+), 135 deletions(-) create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/utils/CsvStreamUtils.java create mode 100644 sormas-rest/src/main/java/de/symeda/sormas/rest/BAGExportResource.java diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRole.java b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRole.java index 8ca0ebdd3fb..d4c1650bc81 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRole.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRole.java @@ -61,7 +61,8 @@ public enum UserRole IMPORT_USER(false, false, false, false, JurisdictionLevel.NONE), REST_EXTERNAL_VISITS_USER(false, false, false, false, JurisdictionLevel.NONE), REST_USER(false, false, false, false, JurisdictionLevel.NONE), - SORMAS_TO_SORMAS_CLIENT(false, false, false, false, JurisdictionLevel.NONE); + SORMAS_TO_SORMAS_CLIENT(false, false, false, false, JurisdictionLevel.NONE), + BAG_USER(false, false, false, false, JurisdictionLevel.NONE); /* * Hint for SonarQube issues: diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/utils/CsvStreamUtils.java b/sormas-api/src/main/java/de/symeda/sormas/api/utils/CsvStreamUtils.java new file mode 100644 index 00000000000..75cb239ca1e --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/utils/CsvStreamUtils.java @@ -0,0 +1,196 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2020 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.symeda.sormas.api.utils; + +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.Predicate; + +import com.opencsv.CSVWriter; + +import de.symeda.sormas.api.ConfigFacade; +import de.symeda.sormas.api.EntityDto; +import de.symeda.sormas.api.importexport.ExportConfigurationDto; +import de.symeda.sormas.api.importexport.ExportProperty; +import de.symeda.sormas.api.utils.fieldvisibility.checkers.CountryFieldVisibilityChecker; + +public class CsvStreamUtils { + + public static final int STEP_SIZE = 50; + + public static void writeCsvContentToStream( + Class csvRowClass, + SupplierBiFunction> exportRowsSupplier, + SupplierBiFunction, String> propertyIdCaptionSupplier, + ExportConfigurationDto exportConfiguration, + final Predicate redMethodFilter, + ConfigFacade configFacade, + OutputStream out) { + + try ( + CSVWriter writer = CSVUtils.createCSVWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8.name()), configFacade.getCsvSeparator())) { + + // 1. fields in order of declaration - not using Introspector here, because it gives properties in alphabetical order + List readMethods = + getExportRowClassReadMethods(csvRowClass, exportConfiguration, redMethodFilter, configFacade.getCountryLocale()); + + // 2. replace entity fields with all the columns of the entity + Map> subEntityProviders = new HashMap>(); + for (int i = 0; i < readMethods.size(); i++) { + final Method method = readMethods.get(i); + if (EntityDto.class.isAssignableFrom(method.getReturnType())) { + + // allows us to access the sub entity + SubEntityProvider subEntityProvider = new SubEntityProvider() { + + @Override + public Object get(T o) { + try { + return method.invoke(o); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + }; + + // remove entity field + readMethods.remove(i); + + // add columns of the entity + List subReadMethods = getReadMethods(method.getReturnType(), null); + readMethods.addAll(i, subReadMethods); + i--; + + for (Method subReadMethod : subReadMethods) { + subEntityProviders.put(subReadMethod, subEntityProvider); + } + } + } + + String[] fieldValues = new String[readMethods.size()]; + for (int i = 0; i < readMethods.size(); i++) { + final Method method = readMethods.get(i); + // field caption + String propertyId = method.getName().startsWith("get") ? method.getName().substring(3) : method.getName().substring(2); + if (method.isAnnotationPresent(ExportProperty.class)) { + // TODO not sure why we are using the export property name to get the caption here + final ExportProperty exportProperty = method.getAnnotation(ExportProperty.class); + if (!exportProperty.combined()) { + propertyId = exportProperty.value(); + } + } + propertyId = Character.toLowerCase(propertyId.charAt(0)) + propertyId.substring(1); + fieldValues[i] = propertyIdCaptionSupplier.apply(propertyId, method.getReturnType()); + } + writer.writeNext(fieldValues); + + int startIndex = 0; + List exportRows = exportRowsSupplier.apply(startIndex, STEP_SIZE); + while (!exportRows.isEmpty()) { + try { + for (T exportRow : exportRows) { + for (int i = 0; i < readMethods.size(); i++) { + Method method = readMethods.get(i); + SubEntityProvider subEntityProvider = subEntityProviders.get(method); + Object entity = subEntityProvider != null ? subEntityProvider.get(exportRow) : exportRow; + // Sub entity might be null + Object value = entity != null ? method.invoke(entity) : null; + + fieldValues[i] = DataHelper.valueToString(value); + } + writer.writeNext(fieldValues); + } ; + } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) { + throw new RuntimeException(e); + } + + writer.flush(); + startIndex += STEP_SIZE; + exportRows = exportRowsSupplier.apply(startIndex, STEP_SIZE); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static List getExportRowClassReadMethods( + Class exportRowClass, + final ExportConfigurationDto exportConfiguration, + final Predicate redMethodFilter, + String countryLocale) { + final CountryFieldVisibilityChecker countryFieldVisibilityChecker = new CountryFieldVisibilityChecker(countryLocale); + + return getReadMethods(exportRowClass, new Predicate() { + + @Override + public boolean evaluate(Object o) { + Method m = (Method) o; + return (countryFieldVisibilityChecker.isVisible(m)) + && (redMethodFilter == null || redMethodFilter.evaluate(o)) + && (exportConfiguration == null || exportConfiguration.getProperties().contains(m.getAnnotation(ExportProperty.class).value())); + } + }); + } + + private static List getReadMethods(Class clazz, final Predicate filters) { + ArrayList readMethods = new ArrayList<>(Arrays.asList(clazz.getDeclaredMethods())); + + CollectionUtils.filter(readMethods, new Predicate() { + + @Override + public boolean evaluate(Object o) { + Method m = (Method) o; + return (m.getName().startsWith("get") || m.getName().startsWith("is")) + && m.isAnnotationPresent(Order.class) + && (filters == null || filters.evaluate(o)); + } + }); + Collections.sort(readMethods, new Comparator() { + + @Override + public int compare(Method m1, Method m2) { + int o1 = m1.getAnnotation(Order.class).value(); + int o2 = m2.getAnnotation(Order.class).value(); + + return o1 - o2; + } + }); + + return readMethods; + } + + public interface SupplierBiFunction { + + R apply(T t, U u); + } + + private interface SubEntityProvider { + + Object get(T parent); + } +} diff --git a/sormas-rest/src/main/java/de/symeda/sormas/rest/BAGExportResource.java b/sormas-rest/src/main/java/de/symeda/sormas/rest/BAGExportResource.java new file mode 100644 index 00000000000..24dc0dcd9ed --- /dev/null +++ b/sormas-rest/src/main/java/de/symeda/sormas/rest/BAGExportResource.java @@ -0,0 +1,67 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2020 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.symeda.sormas.rest; + +import java.io.ByteArrayOutputStream; +import java.util.Date; + +import javax.annotation.security.RolesAllowed; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.bagexport.BAGExportCaseDto; +import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.utils.CsvStreamUtils; +import de.symeda.sormas.api.utils.DateFormatHelper; + +@Path("/bagexport") +@Produces(MediaType.APPLICATION_OCTET_STREAM) +@Consumes(MediaType.APPLICATION_JSON + "; charset=UTF-8") +@RolesAllowed({ + "BAG_USER" }) +public class BAGExportResource { + + @GET + @Path("/cases") + public Response exportCases(String file) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + CsvStreamUtils.writeCsvContentToStream( + BAGExportCaseDto.class, + (from, to) -> FacadeProvider.getBAGExportFacade().getCaseExportList(from, to), + (propertyId, type) -> { + String caption = I18nProperties.findPrefixCaption(propertyId); + if (Date.class.isAssignableFrom(type)) { + caption += " (" + DateFormatHelper.getDateFormatPattern() + ")"; + } + return caption; + }, + null, + null, + FacadeProvider.getConfigFacade(), + baos); + + Response.ResponseBuilder response = Response.ok(baos); + response.header("Content-Disposition", "attachment;filename=test.csv"); + + return response.build(); + } +} diff --git a/sormas-rest/src/main/webapp/WEB-INF/glassfish-web.xml b/sormas-rest/src/main/webapp/WEB-INF/glassfish-web.xml index 5f58c423f35..88c0f8ef156 100644 --- a/sormas-rest/src/main/webapp/WEB-INF/glassfish-web.xml +++ b/sormas-rest/src/main/webapp/WEB-INF/glassfish-web.xml @@ -50,4 +50,8 @@ SORMAS_TO_SORMAS_CLIENT SORMAS_TO_SORMAS_CLIENT + + BAG_USER + BAG_USER + \ No newline at end of file diff --git a/sormas-rest/src/main/webapp/WEB-INF/web.xml b/sormas-rest/src/main/webapp/WEB-INF/web.xml index de495d74879..cffae890061 100644 --- a/sormas-rest/src/main/webapp/WEB-INF/web.xml +++ b/sormas-rest/src/main/webapp/WEB-INF/web.xml @@ -31,5 +31,8 @@ SORMAS_TO_SORMAS_CLIENT + + BAG_USER + diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/DownloadUtil.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/DownloadUtil.java index e5c50eccf8e..64ccf8d062f 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/DownloadUtil.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/DownloadUtil.java @@ -30,24 +30,18 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UncheckedIOException; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; -import java.util.function.BiFunction; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -72,9 +66,7 @@ import com.vaadin.v7.ui.Grid.Column; import de.symeda.sormas.api.AgeGroup; -import de.symeda.sormas.api.EntityDto; import de.symeda.sormas.api.FacadeProvider; -import de.symeda.sormas.api.Language; import de.symeda.sormas.api.caze.CaseCriteria; import de.symeda.sormas.api.caze.CaseExportType; import de.symeda.sormas.api.clinicalcourse.ClinicalVisitDto; @@ -86,7 +78,6 @@ import de.symeda.sormas.api.i18n.Strings; import de.symeda.sormas.api.importexport.DatabaseTable; import de.symeda.sormas.api.importexport.ExportConfigurationDto; -import de.symeda.sormas.api.importexport.ExportProperty; import de.symeda.sormas.api.importexport.ExportTarget; import de.symeda.sormas.api.infrastructure.PopulationDataDto; import de.symeda.sormas.api.person.PersonDto; @@ -97,11 +88,10 @@ import de.symeda.sormas.api.therapy.TreatmentDto; import de.symeda.sormas.api.therapy.TreatmentExportDto; import de.symeda.sormas.api.utils.CSVUtils; +import de.symeda.sormas.api.utils.CsvStreamUtils; import de.symeda.sormas.api.utils.DataHelper; import de.symeda.sormas.api.utils.DateHelper; import de.symeda.sormas.api.utils.ExportErrorException; -import de.symeda.sormas.api.utils.Order; -import de.symeda.sormas.api.utils.fieldvisibility.checkers.CountryFieldVisibilityChecker; import de.symeda.sormas.api.visit.VisitDto; import de.symeda.sormas.api.visit.VisitExportType; import de.symeda.sormas.api.visit.VisitSummaryExportDto; @@ -536,138 +526,45 @@ public static StreamResource createVisitsExportStreamResource(ContactCriteria co public static StreamResource createCsvExportStreamResource( Class exportRowClass, Enum exportType, - BiFunction> exportRowsSupplier, - BiFunction, String> propertyIdCaptionFunction, + CsvStreamUtils.SupplierBiFunction> exportRowsSupplier, + CsvStreamUtils.SupplierBiFunction, String> propertyIdCaptionFunction, String exportFileName, ExportConfigurationDto exportConfiguration) { - StreamResource extendedStreamResource = new StreamResource(() -> { - - return new DelayedInputStream((out) -> { - try (CSVWriter writer = CSVUtils.createCSVWriter( - new OutputStreamWriter(out, StandardCharsets.UTF_8.name()), - FacadeProvider.getConfigFacade().getCsvSeparator())) { - - // 1. fields in order of declaration - not using Introspector here, because it gives properties in alphabetical order - List readMethods = getExportRowClassReadMethods(exportRowClass, exportType, exportConfiguration); - - // 2. replace entity fields with all the columns of the entity - Map> subEntityProviders = new HashMap>(); - for (int i = 0; i < readMethods.size(); i++) { - Method method = readMethods.get(i); - if (EntityDto.class.isAssignableFrom(method.getReturnType())) { - - // allows us to access the sub entity - Function subEntityProvider = o -> { - try { - return method.invoke(o); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new RuntimeException(e); - } - }; - - // remove entity field - readMethods.remove(i); - - // add columns of the entity - List subReadMethods = Arrays.stream(method.getReturnType().getDeclaredMethods()) - .filter(m -> (m.getName().startsWith("get") || m.getName().startsWith("is")) && m.isAnnotationPresent(Order.class)) - .sorted(Comparator.comparingInt(a2 -> a2.getAnnotationsByType(Order.class)[0].value())) - .collect(Collectors.toList()); - readMethods.addAll(i, subReadMethods); - i--; - - for (Method subReadMethod : subReadMethods) { - subEntityProviders.put(subReadMethod, subEntityProvider); - } - } - } - - String[] fieldValues = new String[readMethods.size()]; - for (int i = 0; i < readMethods.size(); i++) { - final Method method = readMethods.get(i); - // field caption - String propertyId = method.getName().startsWith("get") ? method.getName().substring(3) : method.getName().substring(2); - if (method.isAnnotationPresent(ExportProperty.class)) { - // TODO not sure why we are using the export property name to get the caption here - final ExportProperty exportProperty = method.getAnnotation(ExportProperty.class); - if (!exportProperty.combined()) { - propertyId = exportProperty.value(); - } - } - propertyId = Character.toLowerCase(propertyId.charAt(0)) + propertyId.substring(1); - fieldValues[i] = propertyIdCaptionFunction.apply(propertyId, method.getReturnType()); - } - writer.writeNext(fieldValues); - - int startIndex = 0; - List exportRows = exportRowsSupplier.apply(startIndex, DETAILED_EXPORT_STEP_SIZE); - Language userLanguage = I18nProperties.getUserLanguage(); - while (!exportRows.isEmpty()) { - try { - for (T exportRow : exportRows) { - for (int i = 0; i < readMethods.size(); i++) { - Method method = readMethods.get(i); - Function subEntityProvider = subEntityProviders.getOrDefault(method, null); - Object entity = subEntityProvider != null ? subEntityProvider.apply(exportRow) : exportRow; - // Sub entity might be null - Object value = entity != null ? method.invoke(entity) : null; - - fieldValues[i] = DataHelper.valueToString(value); - } - writer.writeNext(fieldValues); - } ; - } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) { - throw new RuntimeException(e); - } + StreamResource extendedStreamResource = new StreamResource(() -> new DelayedInputStream((out) -> { + try { + CsvStreamUtils.writeCsvContentToStream( + exportRowClass, + exportRowsSupplier, + propertyIdCaptionFunction, + exportConfiguration, + (o) -> exportType == null || hasExportTarget(exportType, (Method) o), + FacadeProvider.getConfigFacade(), + out); + } catch (Exception e) { + LoggerFactory.getLogger(DownloadUtil.class).error(e.getMessage(), e); + + throw e; + } - writer.flush(); - startIndex += DETAILED_EXPORT_STEP_SIZE; - exportRows = exportRowsSupplier.apply(startIndex, DETAILED_EXPORT_STEP_SIZE); - } - } catch (Exception e) { - LoggerFactory.getLogger(DownloadUtil.class).error(e.getMessage(), e); - throw new RuntimeException(e); - } - }, - e -> { - // TODO This currently requires the user to click the "Export" button again or reload the page - // as the UI - // is not automatically updated; this should be changed once Vaadin push is enabled (see #516) - VaadinSession.getCurrent() - .access( - () -> new Notification( - I18nProperties.getString(Strings.headingExportFailed), - I18nProperties.getString(Strings.messageExportFailed), - Type.ERROR_MESSAGE, - false).show(Page.getCurrent())); - }); - }, exportFileName); + }, + e -> { + // TODO This currently requires the user to click the "Export" button again or reload the page + // as the UI + // is not automatically updated; this should be changed once Vaadin push is enabled (see #516) + VaadinSession.getCurrent() + .access( + () -> new Notification( + I18nProperties.getString(Strings.headingExportFailed), + I18nProperties.getString(Strings.messageExportFailed), + Type.ERROR_MESSAGE, + false).show(Page.getCurrent())); + }), exportFileName); extendedStreamResource.setMIMEType("text/csv"); extendedStreamResource.setCacheTime(0); return extendedStreamResource; } - private static List getExportRowClassReadMethods( - Class exportRowClass, - Enum exportType, - ExportConfigurationDto exportConfiguration) { - CountryFieldVisibilityChecker countryFieldVisibilityChecker = - new CountryFieldVisibilityChecker(FacadeProvider.getConfigFacade().getCountryLocale()); - - List methods = Stream.of(exportRowClass.getDeclaredMethods()) - .filter( - m -> (m.getName().startsWith("get") || m.getName().startsWith("is")) - && m.isAnnotationPresent(Order.class) - && (countryFieldVisibilityChecker.isVisible(m)) - && (exportType == null || hasExportTarget(exportType, m)) - && (exportConfiguration == null || exportConfiguration.getProperties().contains(m.getAnnotation(ExportProperty.class).value()))) - .sorted(Comparator.comparingInt(a -> a.getAnnotationsByType(Order.class)[0].value())) - .collect(Collectors.toList()); - - return methods; - } - @SuppressWarnings("rawtypes") private static boolean hasExportTarget(Enum exportType, Method m) { From a4dc0617e8514d60cd2337d4d713c5f2b4698644 Mon Sep 17 00:00:00 2001 From: lgal Date: Fri, 4 Dec 2020 11:45:49 +0200 Subject: [PATCH 2/3] #3610 rest endpoint for BAG export contacts --- .../de/symeda/sormas/api/user/UserRight.java | 12 +--- sormas-api/src/main/resources/enum.properties | 2 + .../backend/bagexport/BAGExportFacadeEjb.java | 5 ++ .../symeda/sormas/rest/BAGExportResource.java | 61 +++++++++++++------ .../de/symeda/sormas/ui/caze/CasesView.java | 10 +-- .../sormas/ui/contact/ContactsView.java | 8 +-- .../symeda/sormas/ui/user/UserEditForm.java | 3 +- .../symeda/sormas/ui/user/UserUiHelper.java | 36 +++++++++++ .../de/symeda/sormas/ui/user/UsersView.java | 2 +- 9 files changed, 91 insertions(+), 48 deletions(-) create mode 100644 sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserUiHelper.java diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRight.java b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRight.java index b465d721841..d8c02c55bbe 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRight.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRight.java @@ -19,6 +19,7 @@ import static de.symeda.sormas.api.user.UserRole.ADMIN; import static de.symeda.sormas.api.user.UserRole.ADMIN_SUPERVISOR; +import static de.symeda.sormas.api.user.UserRole.BAG_USER; import static de.symeda.sormas.api.user.UserRole.CASE_OFFICER; import static de.symeda.sormas.api.user.UserRole.CASE_SUPERVISOR; import static de.symeda.sormas.api.user.UserRole.COMMUNITY_INFORMANT; @@ -1182,16 +1183,7 @@ public enum UserRight { POE_SUPERVISOR ), BAG_EXPORT( - ADMIN, - NATIONAL_USER, - NATIONAL_CLINICIAN, - POE_NATIONAL_USER, - SURVEILLANCE_SUPERVISOR, - ADMIN_SUPERVISOR, - CASE_SUPERVISOR, - CONTACT_SUPERVISOR, - POE_SUPERVISOR, - LAB_USER + BAG_USER ), SORMAS_TO_SORMAS_SHARE( ADMIN, diff --git a/sormas-api/src/main/resources/enum.properties b/sormas-api/src/main/resources/enum.properties index 960839ab8d5..a4a4ba2eb97 100644 --- a/sormas-api/src/main/resources/enum.properties +++ b/sormas-api/src/main/resources/enum.properties @@ -1104,6 +1104,7 @@ UserRole.REST_EXTERNAL_VISITS_USER = External Visits User UserRole.REST_USER = ReST User UserRole.SORMAS_TO_SORMAS_CLIENT = Sormas to Sormas Client UserRole.ADMIN_SUPERVISOR = Admin Surveillance Supervisor +UserRole.BAG_USER = BAG User UserRole.Short.ADMIN = Admin UserRole.Short.CASE_OFFICER = CaseOff UserRole.Short.CASE_SUPERVISOR = Clinician @@ -1129,6 +1130,7 @@ UserRole.Short.SURVEILLANCE_OFFICER = SurvOff UserRole.Short.REST_EXTERNAL_VISITS_USER = ExtVis UserRole.Short.REST_USER = ReST UserRole.Short.SORMAS_TO_SORMAS_CLIENT = SormasToSormas +UserRole.Short.BAG_USER = BAG # Vaccination Vaccination.UNKNOWN = Unknown diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/bagexport/BAGExportFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/bagexport/BAGExportFacadeEjb.java index 21346471ab2..9e338963293 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/bagexport/BAGExportFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/bagexport/BAGExportFacadeEjb.java @@ -25,6 +25,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import javax.ejb.EJB; import javax.ejb.LocalBean; import javax.ejb.Stateless; import javax.persistence.EntityManager; @@ -44,6 +45,7 @@ import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.api.utils.YesNoUnknown; import de.symeda.sormas.backend.caze.Case; +import de.symeda.sormas.backend.common.ConfigFacadeEjb.ConfigFacadeEjbLocal; import de.symeda.sormas.backend.contact.Contact; import de.symeda.sormas.backend.contact.ContactJoins; import de.symeda.sormas.backend.location.Location; @@ -62,6 +64,9 @@ public class BAGExportFacadeEjb implements BAGExportFacade { @PersistenceContext(unitName = ModelConstants.PERSISTENCE_UNIT_NAME) private EntityManager em; + @EJB + private ConfigFacadeEjbLocal configFacade; + @Override public List getCaseExportList(int first, int max) { CriteriaBuilder cb = em.getCriteriaBuilder(); diff --git a/sormas-rest/src/main/java/de/symeda/sormas/rest/BAGExportResource.java b/sormas-rest/src/main/java/de/symeda/sormas/rest/BAGExportResource.java index 24dc0dcd9ed..81f30cb13e8 100644 --- a/sormas-rest/src/main/java/de/symeda/sormas/rest/BAGExportResource.java +++ b/sormas-rest/src/main/java/de/symeda/sormas/rest/BAGExportResource.java @@ -15,8 +15,9 @@ package de.symeda.sormas.rest; -import java.io.ByteArrayOutputStream; +import java.io.OutputStream; import java.util.Date; +import java.util.function.Consumer; import javax.annotation.security.RolesAllowed; import javax.ws.rs.Consumes; @@ -25,12 +26,13 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.StreamingOutput; import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.bagexport.BAGExportCaseDto; -import de.symeda.sormas.api.i18n.I18nProperties; +import de.symeda.sormas.api.bagexport.BAGExportContactDto; import de.symeda.sormas.api.utils.CsvStreamUtils; -import de.symeda.sormas.api.utils.DateFormatHelper; +import de.symeda.sormas.api.utils.DateHelper; @Path("/bagexport") @Produces(MediaType.APPLICATION_OCTET_STREAM) @@ -42,26 +44,45 @@ public class BAGExportResource { @GET @Path("/cases") public Response exportCases(String file) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); + return createFileDownloadResponse( + output -> CsvStreamUtils.writeCsvContentToStream( + BAGExportCaseDto.class, + (from, to) -> FacadeProvider.getBAGExportFacade().getCaseExportList(from, to), + (propertyId, type) -> propertyId, + null, + null, + FacadeProvider.getConfigFacade(), + output), + "sormas_BAG_cases_", + "csv"); + } + + @GET + @Path("/contacts") + public Response exportContacts(String file) { + return createFileDownloadResponse( + output -> CsvStreamUtils.writeCsvContentToStream( + BAGExportContactDto.class, + (from, to) -> FacadeProvider.getBAGExportFacade().getContactExportList(from, to), + (propertyId, type) -> propertyId, + null, + null, + FacadeProvider.getConfigFacade(), + output), + "sormas_BAG_contacts_", + "csv"); + } - CsvStreamUtils.writeCsvContentToStream( - BAGExportCaseDto.class, - (from, to) -> FacadeProvider.getBAGExportFacade().getCaseExportList(from, to), - (propertyId, type) -> { - String caption = I18nProperties.findPrefixCaption(propertyId); - if (Date.class.isAssignableFrom(type)) { - caption += " (" + DateFormatHelper.getDateFormatPattern() + ")"; - } - return caption; - }, - null, - null, - FacadeProvider.getConfigFacade(), - baos); + private Response createFileDownloadResponse(Consumer writeContent, String fileNamePrefix, String extension) { + StreamingOutput fileStream = writeContent::accept; - Response.ResponseBuilder response = Response.ok(baos); - response.header("Content-Disposition", "attachment;filename=test.csv"); + Response.ResponseBuilder response = Response.ok(fileStream); + response.header("Content-Disposition", "attachment;filename=" + createFileNameWithCurrentDate(fileNamePrefix, extension)); return response.build(); } + + private String createFileNameWithCurrentDate(String fileNamePrefix, String extension) { + return fileNamePrefix + DateHelper.formatDateForExport(new Date()) + "." + extension; + } } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CasesView.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CasesView.java index 1cbfa60426a..0b62bbb32d3 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CasesView.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CasesView.java @@ -25,7 +25,6 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -import de.symeda.sormas.api.utils.DataHelper; import org.vaadin.hene.popupbutton.PopupButton; import com.vaadin.icons.VaadinIcons; @@ -68,6 +67,7 @@ import de.symeda.sormas.api.sample.SampleDto; import de.symeda.sormas.api.sample.SampleExportDto; import de.symeda.sormas.api.user.UserRight; +import de.symeda.sormas.api.utils.DataHelper; import de.symeda.sormas.api.utils.DateHelper; import de.symeda.sormas.ui.ControllerProvider; import de.symeda.sormas.ui.SearchSpecificLayout; @@ -332,13 +332,7 @@ private void addCommonCasesOverviewToolbar() { BAGExportCaseDto.class, null, (Integer start, Integer max) -> FacadeProvider.getBAGExportFacade().getCaseExportList(start, max), - (propertyId, type) -> { - String caption = I18nProperties.findPrefixCaption(propertyId); - if (Date.class.isAssignableFrom(type)) { - caption += " (" + DateFormatHelper.getDateFormatPattern() + ")"; - } - return caption; - }, + (propertyId, type) -> propertyId, createFileNameWithCurrentDate("sormas_BAG_cases_", ".csv"), null); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactsView.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactsView.java index f2b3333bcd0..aff3459898c 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactsView.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/contact/ContactsView.java @@ -280,13 +280,7 @@ public ContactsView() { BAGExportContactDto.class, null, (Integer start, Integer max) -> FacadeProvider.getBAGExportFacade().getContactExportList(start, max), - (propertyId, type) -> { - String caption = I18nProperties.findPrefixCaption(propertyId); - if (Date.class.isAssignableFrom(type)) { - caption += " (" + DateFormatHelper.getDateFormatPattern() + ")"; - } - return caption; - }, + (propertyId, type) -> propertyId, createFileNameWithCurrentDate("sormas_BAG_contacts_", ".csv"), null); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserEditForm.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserEditForm.java index 3b240e0d613..c14f427363f 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserEditForm.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserEditForm.java @@ -46,7 +46,6 @@ import de.symeda.sormas.api.utils.fieldaccess.UiFieldAccessCheckers; import de.symeda.sormas.api.utils.fieldvisibility.FieldVisibilityCheckers; import de.symeda.sormas.ui.ControllerProvider; -import de.symeda.sormas.ui.UserProvider; import de.symeda.sormas.ui.location.LocationEditForm; import de.symeda.sormas.ui.utils.AbstractEditForm; import de.symeda.sormas.ui.utils.CssStyles; @@ -132,7 +131,7 @@ protected void addFields() { addField(UserDto.USER_ROLES, OptionGroup.class).addValidator(new UserRolesValidator()); OptionGroup userRoles = (OptionGroup) getFieldGroup().getField(UserDto.USER_ROLES); userRoles.setMultiSelect(true); - userRoles.addItems(UserRole.getAssignableRoles(UserProvider.getCurrent().getUserRoles())); + userRoles.addItems(UserUiHelper.getAssignableRoles()); ComboBox region = addInfrastructureField(UserDto.REGION); ComboBox community = addInfrastructureField(UserDto.COMMUNITY); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserUiHelper.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserUiHelper.java new file mode 100644 index 00000000000..9d741ae2c85 --- /dev/null +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserUiHelper.java @@ -0,0 +1,36 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2020 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.symeda.sormas.ui.user; + +import java.util.Set; + +import de.symeda.sormas.api.CountryHelper; +import de.symeda.sormas.api.FacadeProvider; +import de.symeda.sormas.api.user.UserRole; +import de.symeda.sormas.ui.UserProvider; + +public class UserUiHelper { + + public static Set getAssignableRoles() { + Set allRoles = UserRole.getAssignableRoles(UserProvider.getCurrent().getUserRoles()); + + if (!FacadeProvider.getConfigFacade().isConfiguredCountry(CountryHelper.COUNTRY_CODE_SWITZERLAND)) { + allRoles.remove(UserRole.BAG_USER); + } + + return allRoles; + } +} diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UsersView.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UsersView.java index 667c5aabe8d..9aa2a29aecb 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UsersView.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UsersView.java @@ -136,7 +136,7 @@ public HorizontalLayout createFilterBar() { userRolesFilter.setId(UserDto.USER_ROLES); userRolesFilter.setWidth(200, Unit.PIXELS); userRolesFilter.setInputPrompt(I18nProperties.getPrefixCaption(UserDto.I18N_PREFIX, UserDto.USER_ROLES)); - userRolesFilter.addItems(UserRole.getAssignableRoles(UserProvider.getCurrent().getUserRoles())); + userRolesFilter.addItems(UserUiHelper.getAssignableRoles()); userRolesFilter.addValueChangeListener(e -> { criteria.userRole((UserRole) e.getProperty().getValue()); navigateTo(criteria); From de8379ee46be66dede3bf5b87ae9d864d8ddd5e3 Mon Sep 17 00:00:00 2001 From: lgal Date: Wed, 9 Dec 2020 12:51:19 +0200 Subject: [PATCH 3/3] #3610 added constant for BAG_USER role + removed unused EJB --- .../src/main/java/de/symeda/sormas/api/user/UserRole.java | 4 ++++ .../symeda/sormas/backend/bagexport/BAGExportFacadeEjb.java | 5 ----- .../main/java/de/symeda/sormas/rest/BAGExportResource.java | 3 ++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRole.java b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRole.java index d4c1650bc81..d3f0f836f67 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRole.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserRole.java @@ -93,6 +93,7 @@ public enum UserRole public static final String _REST_EXTERNAL_VISITS_USER = REST_EXTERNAL_VISITS_USER.name(); public static final String _REST_USER = REST_USER.name(); public static final String _SORMAS_TO_SORMAS_CLIENT = "SORMAS_TO_SORMAS_CLIENT"; + public static final String _BAG_USER = "BAG_USER"; private Set defaultUserRights = null; @@ -221,6 +222,9 @@ public void addAssignableRoles(Collection collection) { case SORMAS_TO_SORMAS_CLIENT: collection.add(SORMAS_TO_SORMAS_CLIENT); break; + case BAG_USER: + collection.add(BAG_USER); + break; default: break; } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/bagexport/BAGExportFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/bagexport/BAGExportFacadeEjb.java index 9e338963293..21346471ab2 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/bagexport/BAGExportFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/bagexport/BAGExportFacadeEjb.java @@ -25,7 +25,6 @@ import java.util.Optional; import java.util.stream.Collectors; -import javax.ejb.EJB; import javax.ejb.LocalBean; import javax.ejb.Stateless; import javax.persistence.EntityManager; @@ -45,7 +44,6 @@ import de.symeda.sormas.api.sample.PathogenTestResultType; import de.symeda.sormas.api.utils.YesNoUnknown; import de.symeda.sormas.backend.caze.Case; -import de.symeda.sormas.backend.common.ConfigFacadeEjb.ConfigFacadeEjbLocal; import de.symeda.sormas.backend.contact.Contact; import de.symeda.sormas.backend.contact.ContactJoins; import de.symeda.sormas.backend.location.Location; @@ -64,9 +62,6 @@ public class BAGExportFacadeEjb implements BAGExportFacade { @PersistenceContext(unitName = ModelConstants.PERSISTENCE_UNIT_NAME) private EntityManager em; - @EJB - private ConfigFacadeEjbLocal configFacade; - @Override public List getCaseExportList(int first, int max) { CriteriaBuilder cb = em.getCriteriaBuilder(); diff --git a/sormas-rest/src/main/java/de/symeda/sormas/rest/BAGExportResource.java b/sormas-rest/src/main/java/de/symeda/sormas/rest/BAGExportResource.java index 81f30cb13e8..88623c65fc1 100644 --- a/sormas-rest/src/main/java/de/symeda/sormas/rest/BAGExportResource.java +++ b/sormas-rest/src/main/java/de/symeda/sormas/rest/BAGExportResource.java @@ -31,6 +31,7 @@ import de.symeda.sormas.api.FacadeProvider; import de.symeda.sormas.api.bagexport.BAGExportCaseDto; import de.symeda.sormas.api.bagexport.BAGExportContactDto; +import de.symeda.sormas.api.user.UserRole; import de.symeda.sormas.api.utils.CsvStreamUtils; import de.symeda.sormas.api.utils.DateHelper; @@ -38,7 +39,7 @@ @Produces(MediaType.APPLICATION_OCTET_STREAM) @Consumes(MediaType.APPLICATION_JSON + "; charset=UTF-8") @RolesAllowed({ - "BAG_USER" }) + UserRole._BAG_USER }) public class BAGExportResource { @GET