Skip to content
This repository has been archived by the owner on May 5, 2021. It is now read-only.

Commit

Permalink
Merge pull request SORMAS-Foundation#3698 from hzi-braunschweig/3610_…
Browse files Browse the repository at this point in the history
…BAG-export-rest

3610 bag export rest
  • Loading branch information
leventegal-she authored Dec 10, 2020
2 parents 04984a0 + e9f367f commit 88a4b6a
Show file tree
Hide file tree
Showing 13 changed files with 373 additions and 162 deletions.
12 changes: 2 additions & 10 deletions sormas-api/src/main/java/de/symeda/sormas/api/user/UserRight.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1186,16 +1187,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.NATION);
SORMAS_TO_SORMAS_CLIENT(false, false, false, false, JurisdictionLevel.NATION),
BAG_USER(false, false, false, false, JurisdictionLevel.NONE);

/*
* Hint for SonarQube issues:
Expand Down Expand Up @@ -92,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<UserRight> defaultUserRights = null;

Expand Down Expand Up @@ -220,6 +222,9 @@ public void addAssignableRoles(Collection<UserRole> collection) {
case SORMAS_TO_SORMAS_CLIENT:
collection.add(SORMAS_TO_SORMAS_CLIENT);
break;
case BAG_USER:
collection.add(BAG_USER);
break;
default:
break;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

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 <T> void writeCsvContentToStream(
Class<T> csvRowClass,
SupplierBiFunction<Integer, Integer, List<T>> exportRowsSupplier,
SupplierBiFunction<String, Class<?>, 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<Method> readMethods =
getExportRowClassReadMethods(csvRowClass, exportConfiguration, redMethodFilter, configFacade.getCountryLocale());

// 2. replace entity fields with all the columns of the entity
Map<Method, SubEntityProvider<T>> subEntityProviders = new HashMap<Method, SubEntityProvider<T>>();
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<T> subEntityProvider = new SubEntityProvider<T>() {

@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<Method> 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<T> 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<T> 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 <T> List<Method> getExportRowClassReadMethods(
Class<T> 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<Method> getReadMethods(Class<?> clazz, final Predicate filters) {
ArrayList<Method> 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<Method>() {

@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<T, U, R> {

R apply(T t, U u);
}

private interface SubEntityProvider<T> {

Object get(T parent);
}
}
2 changes: 2 additions & 0 deletions sormas-api/src/main/resources/enum.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,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
Expand All @@ -1144,6 +1145,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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

package de.symeda.sormas.rest;

import java.io.OutputStream;
import java.util.Date;
import java.util.function.Consumer;

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 javax.ws.rs.core.StreamingOutput;

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;

@Path("/bagexport")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Consumes(MediaType.APPLICATION_JSON + "; charset=UTF-8")
@RolesAllowed({
UserRole._BAG_USER })
public class BAGExportResource {

@GET
@Path("/cases")
public Response exportCases(String file) {
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");
}

private Response createFileDownloadResponse(Consumer<OutputStream> writeContent, String fileNamePrefix, String extension) {
StreamingOutput fileStream = writeContent::accept;

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;
}
}
4 changes: 4 additions & 0 deletions sormas-rest/src/main/webapp/WEB-INF/glassfish-web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,8 @@
<role-name>SORMAS_TO_SORMAS_CLIENT</role-name>
<group-name>SORMAS_TO_SORMAS_CLIENT</group-name>
</security-role-mapping>
<security-role-mapping>
<role-name>BAG_USER</role-name>
<group-name>BAG_USER</group-name>
</security-role-mapping>
</glassfish-web-app>
3 changes: 3 additions & 0 deletions sormas-rest/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,8 @@
<security-role>
<role-name>SORMAS_TO_SORMAS_CLIENT</role-name>
</security-role>
<security-role>
<role-name>BAG_USER</role-name>
</security-role>

</web-app>
Original file line number Diff line number Diff line change
Expand Up @@ -334,13 +334,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);

Expand Down
Loading

0 comments on commit 88a4b6a

Please sign in to comment.