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

Commit 88a4b6a

Browse files
Merge pull request SORMAS-Foundation#3698 from hzi-braunschweig/3610_BAG-export-rest
3610 bag export rest
2 parents 04984a0 + e9f367f commit 88a4b6a

File tree

13 files changed

+373
-162
lines changed

13 files changed

+373
-162
lines changed

sormas-api/src/main/java/de/symeda/sormas/api/user/UserRight.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import static de.symeda.sormas.api.user.UserRole.ADMIN;
2121
import static de.symeda.sormas.api.user.UserRole.ADMIN_SUPERVISOR;
22+
import static de.symeda.sormas.api.user.UserRole.BAG_USER;
2223
import static de.symeda.sormas.api.user.UserRole.CASE_OFFICER;
2324
import static de.symeda.sormas.api.user.UserRole.CASE_SUPERVISOR;
2425
import static de.symeda.sormas.api.user.UserRole.COMMUNITY_INFORMANT;
@@ -1186,16 +1187,7 @@ public enum UserRight {
11861187
POE_SUPERVISOR
11871188
),
11881189
BAG_EXPORT(
1189-
ADMIN,
1190-
NATIONAL_USER,
1191-
NATIONAL_CLINICIAN,
1192-
POE_NATIONAL_USER,
1193-
SURVEILLANCE_SUPERVISOR,
1194-
ADMIN_SUPERVISOR,
1195-
CASE_SUPERVISOR,
1196-
CONTACT_SUPERVISOR,
1197-
POE_SUPERVISOR,
1198-
LAB_USER
1190+
BAG_USER
11991191
),
12001192
SORMAS_TO_SORMAS_SHARE(
12011193
ADMIN,

sormas-api/src/main/java/de/symeda/sormas/api/user/UserRole.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ public enum UserRole
6161
IMPORT_USER(false, false, false, false, JurisdictionLevel.NONE),
6262
REST_EXTERNAL_VISITS_USER(false, false, false, false, JurisdictionLevel.NONE),
6363
REST_USER(false, false, false, false, JurisdictionLevel.NONE),
64-
SORMAS_TO_SORMAS_CLIENT(false, false, false, false, JurisdictionLevel.NATION);
64+
SORMAS_TO_SORMAS_CLIENT(false, false, false, false, JurisdictionLevel.NATION),
65+
BAG_USER(false, false, false, false, JurisdictionLevel.NONE);
6566

6667
/*
6768
* Hint for SonarQube issues:
@@ -92,6 +93,7 @@ public enum UserRole
9293
public static final String _REST_EXTERNAL_VISITS_USER = REST_EXTERNAL_VISITS_USER.name();
9394
public static final String _REST_USER = REST_USER.name();
9495
public static final String _SORMAS_TO_SORMAS_CLIENT = "SORMAS_TO_SORMAS_CLIENT";
96+
public static final String _BAG_USER = "BAG_USER";
9597

9698
private Set<UserRight> defaultUserRights = null;
9799

@@ -220,6 +222,9 @@ public void addAssignableRoles(Collection<UserRole> collection) {
220222
case SORMAS_TO_SORMAS_CLIENT:
221223
collection.add(SORMAS_TO_SORMAS_CLIENT);
222224
break;
225+
case BAG_USER:
226+
collection.add(BAG_USER);
227+
break;
223228
default:
224229
break;
225230
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
* SORMAS® - Surveillance Outbreak Response Management & Analysis System
3+
* Copyright © 2016-2020 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI)
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
* You should have received a copy of the GNU General Public License
13+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
14+
*/
15+
16+
package de.symeda.sormas.api.utils;
17+
18+
import java.io.OutputStream;
19+
import java.io.OutputStreamWriter;
20+
import java.lang.reflect.InvocationTargetException;
21+
import java.lang.reflect.Method;
22+
import java.nio.charset.StandardCharsets;
23+
import java.util.ArrayList;
24+
import java.util.Arrays;
25+
import java.util.Collections;
26+
import java.util.Comparator;
27+
import java.util.HashMap;
28+
import java.util.List;
29+
import java.util.Map;
30+
31+
import org.apache.commons.collections.CollectionUtils;
32+
import org.apache.commons.collections.Predicate;
33+
34+
import com.opencsv.CSVWriter;
35+
36+
import de.symeda.sormas.api.ConfigFacade;
37+
import de.symeda.sormas.api.EntityDto;
38+
import de.symeda.sormas.api.importexport.ExportConfigurationDto;
39+
import de.symeda.sormas.api.importexport.ExportProperty;
40+
import de.symeda.sormas.api.utils.fieldvisibility.checkers.CountryFieldVisibilityChecker;
41+
42+
public class CsvStreamUtils {
43+
44+
public static final int STEP_SIZE = 50;
45+
46+
public static <T> void writeCsvContentToStream(
47+
Class<T> csvRowClass,
48+
SupplierBiFunction<Integer, Integer, List<T>> exportRowsSupplier,
49+
SupplierBiFunction<String, Class<?>, String> propertyIdCaptionSupplier,
50+
ExportConfigurationDto exportConfiguration,
51+
final Predicate redMethodFilter,
52+
ConfigFacade configFacade,
53+
OutputStream out) {
54+
55+
try (
56+
CSVWriter writer = CSVUtils.createCSVWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8.name()), configFacade.getCsvSeparator())) {
57+
58+
// 1. fields in order of declaration - not using Introspector here, because it gives properties in alphabetical order
59+
List<Method> readMethods =
60+
getExportRowClassReadMethods(csvRowClass, exportConfiguration, redMethodFilter, configFacade.getCountryLocale());
61+
62+
// 2. replace entity fields with all the columns of the entity
63+
Map<Method, SubEntityProvider<T>> subEntityProviders = new HashMap<Method, SubEntityProvider<T>>();
64+
for (int i = 0; i < readMethods.size(); i++) {
65+
final Method method = readMethods.get(i);
66+
if (EntityDto.class.isAssignableFrom(method.getReturnType())) {
67+
68+
// allows us to access the sub entity
69+
SubEntityProvider<T> subEntityProvider = new SubEntityProvider<T>() {
70+
71+
@Override
72+
public Object get(T o) {
73+
try {
74+
return method.invoke(o);
75+
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
76+
throw new RuntimeException(e);
77+
}
78+
}
79+
};
80+
81+
// remove entity field
82+
readMethods.remove(i);
83+
84+
// add columns of the entity
85+
List<Method> subReadMethods = getReadMethods(method.getReturnType(), null);
86+
readMethods.addAll(i, subReadMethods);
87+
i--;
88+
89+
for (Method subReadMethod : subReadMethods) {
90+
subEntityProviders.put(subReadMethod, subEntityProvider);
91+
}
92+
}
93+
}
94+
95+
String[] fieldValues = new String[readMethods.size()];
96+
for (int i = 0; i < readMethods.size(); i++) {
97+
final Method method = readMethods.get(i);
98+
// field caption
99+
String propertyId = method.getName().startsWith("get") ? method.getName().substring(3) : method.getName().substring(2);
100+
if (method.isAnnotationPresent(ExportProperty.class)) {
101+
// TODO not sure why we are using the export property name to get the caption here
102+
final ExportProperty exportProperty = method.getAnnotation(ExportProperty.class);
103+
if (!exportProperty.combined()) {
104+
propertyId = exportProperty.value();
105+
}
106+
}
107+
propertyId = Character.toLowerCase(propertyId.charAt(0)) + propertyId.substring(1);
108+
fieldValues[i] = propertyIdCaptionSupplier.apply(propertyId, method.getReturnType());
109+
}
110+
writer.writeNext(fieldValues);
111+
112+
int startIndex = 0;
113+
List<T> exportRows = exportRowsSupplier.apply(startIndex, STEP_SIZE);
114+
while (!exportRows.isEmpty()) {
115+
try {
116+
for (T exportRow : exportRows) {
117+
for (int i = 0; i < readMethods.size(); i++) {
118+
Method method = readMethods.get(i);
119+
SubEntityProvider<T> subEntityProvider = subEntityProviders.get(method);
120+
Object entity = subEntityProvider != null ? subEntityProvider.get(exportRow) : exportRow;
121+
// Sub entity might be null
122+
Object value = entity != null ? method.invoke(entity) : null;
123+
124+
fieldValues[i] = DataHelper.valueToString(value);
125+
}
126+
writer.writeNext(fieldValues);
127+
} ;
128+
} catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) {
129+
throw new RuntimeException(e);
130+
}
131+
132+
writer.flush();
133+
startIndex += STEP_SIZE;
134+
exportRows = exportRowsSupplier.apply(startIndex, STEP_SIZE);
135+
}
136+
} catch (Exception e) {
137+
throw new RuntimeException(e);
138+
}
139+
}
140+
141+
private static <T> List<Method> getExportRowClassReadMethods(
142+
Class<T> exportRowClass,
143+
final ExportConfigurationDto exportConfiguration,
144+
final Predicate redMethodFilter,
145+
String countryLocale) {
146+
final CountryFieldVisibilityChecker countryFieldVisibilityChecker = new CountryFieldVisibilityChecker(countryLocale);
147+
148+
return getReadMethods(exportRowClass, new Predicate() {
149+
150+
@Override
151+
public boolean evaluate(Object o) {
152+
Method m = (Method) o;
153+
return (countryFieldVisibilityChecker.isVisible(m))
154+
&& (redMethodFilter == null || redMethodFilter.evaluate(o))
155+
&& (exportConfiguration == null || exportConfiguration.getProperties().contains(m.getAnnotation(ExportProperty.class).value()));
156+
}
157+
});
158+
}
159+
160+
private static List<Method> getReadMethods(Class<?> clazz, final Predicate filters) {
161+
ArrayList<Method> readMethods = new ArrayList<>(Arrays.asList(clazz.getDeclaredMethods()));
162+
163+
CollectionUtils.filter(readMethods, new Predicate() {
164+
165+
@Override
166+
public boolean evaluate(Object o) {
167+
Method m = (Method) o;
168+
return (m.getName().startsWith("get") || m.getName().startsWith("is"))
169+
&& m.isAnnotationPresent(Order.class)
170+
&& (filters == null || filters.evaluate(o));
171+
}
172+
});
173+
Collections.sort(readMethods, new Comparator<Method>() {
174+
175+
@Override
176+
public int compare(Method m1, Method m2) {
177+
int o1 = m1.getAnnotation(Order.class).value();
178+
int o2 = m2.getAnnotation(Order.class).value();
179+
180+
return o1 - o2;
181+
}
182+
});
183+
184+
return readMethods;
185+
}
186+
187+
public interface SupplierBiFunction<T, U, R> {
188+
189+
R apply(T t, U u);
190+
}
191+
192+
private interface SubEntityProvider<T> {
193+
194+
Object get(T parent);
195+
}
196+
}

sormas-api/src/main/resources/enum.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,7 @@ UserRole.REST_EXTERNAL_VISITS_USER = External Visits User
11191119
UserRole.REST_USER = ReST User
11201120
UserRole.SORMAS_TO_SORMAS_CLIENT = Sormas to Sormas Client
11211121
UserRole.ADMIN_SUPERVISOR = Admin Surveillance Supervisor
1122+
UserRole.BAG_USER = BAG User
11221123
UserRole.Short.ADMIN = Admin
11231124
UserRole.Short.CASE_OFFICER = CaseOff
11241125
UserRole.Short.CASE_SUPERVISOR = Clinician
@@ -1144,6 +1145,7 @@ UserRole.Short.SURVEILLANCE_OFFICER = SurvOff
11441145
UserRole.Short.REST_EXTERNAL_VISITS_USER = ExtVis
11451146
UserRole.Short.REST_USER = ReST
11461147
UserRole.Short.SORMAS_TO_SORMAS_CLIENT = SormasToSormas
1148+
UserRole.Short.BAG_USER = BAG
11471149

11481150
# Vaccination
11491151
Vaccination.UNKNOWN = Unknown
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* SORMAS® - Surveillance Outbreak Response Management & Analysis System
3+
* Copyright © 2016-2020 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI)
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
* You should have received a copy of the GNU General Public License
13+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
14+
*/
15+
16+
package de.symeda.sormas.rest;
17+
18+
import java.io.OutputStream;
19+
import java.util.Date;
20+
import java.util.function.Consumer;
21+
22+
import javax.annotation.security.RolesAllowed;
23+
import javax.ws.rs.Consumes;
24+
import javax.ws.rs.GET;
25+
import javax.ws.rs.Path;
26+
import javax.ws.rs.Produces;
27+
import javax.ws.rs.core.MediaType;
28+
import javax.ws.rs.core.Response;
29+
import javax.ws.rs.core.StreamingOutput;
30+
31+
import de.symeda.sormas.api.FacadeProvider;
32+
import de.symeda.sormas.api.bagexport.BAGExportCaseDto;
33+
import de.symeda.sormas.api.bagexport.BAGExportContactDto;
34+
import de.symeda.sormas.api.user.UserRole;
35+
import de.symeda.sormas.api.utils.CsvStreamUtils;
36+
import de.symeda.sormas.api.utils.DateHelper;
37+
38+
@Path("/bagexport")
39+
@Produces(MediaType.APPLICATION_OCTET_STREAM)
40+
@Consumes(MediaType.APPLICATION_JSON + "; charset=UTF-8")
41+
@RolesAllowed({
42+
UserRole._BAG_USER })
43+
public class BAGExportResource {
44+
45+
@GET
46+
@Path("/cases")
47+
public Response exportCases(String file) {
48+
return createFileDownloadResponse(
49+
output -> CsvStreamUtils.writeCsvContentToStream(
50+
BAGExportCaseDto.class,
51+
(from, to) -> FacadeProvider.getBAGExportFacade().getCaseExportList(from, to),
52+
(propertyId, type) -> propertyId,
53+
null,
54+
null,
55+
FacadeProvider.getConfigFacade(),
56+
output),
57+
"sormas_BAG_cases_",
58+
"csv");
59+
}
60+
61+
@GET
62+
@Path("/contacts")
63+
public Response exportContacts(String file) {
64+
return createFileDownloadResponse(
65+
output -> CsvStreamUtils.writeCsvContentToStream(
66+
BAGExportContactDto.class,
67+
(from, to) -> FacadeProvider.getBAGExportFacade().getContactExportList(from, to),
68+
(propertyId, type) -> propertyId,
69+
null,
70+
null,
71+
FacadeProvider.getConfigFacade(),
72+
output),
73+
"sormas_BAG_contacts_",
74+
"csv");
75+
}
76+
77+
private Response createFileDownloadResponse(Consumer<OutputStream> writeContent, String fileNamePrefix, String extension) {
78+
StreamingOutput fileStream = writeContent::accept;
79+
80+
Response.ResponseBuilder response = Response.ok(fileStream);
81+
response.header("Content-Disposition", "attachment;filename=" + createFileNameWithCurrentDate(fileNamePrefix, extension));
82+
83+
return response.build();
84+
}
85+
86+
private String createFileNameWithCurrentDate(String fileNamePrefix, String extension) {
87+
return fileNamePrefix + DateHelper.formatDateForExport(new Date()) + "." + extension;
88+
}
89+
}

sormas-rest/src/main/webapp/WEB-INF/glassfish-web.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,8 @@
5050
<role-name>SORMAS_TO_SORMAS_CLIENT</role-name>
5151
<group-name>SORMAS_TO_SORMAS_CLIENT</group-name>
5252
</security-role-mapping>
53+
<security-role-mapping>
54+
<role-name>BAG_USER</role-name>
55+
<group-name>BAG_USER</group-name>
56+
</security-role-mapping>
5357
</glassfish-web-app>

sormas-rest/src/main/webapp/WEB-INF/web.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,8 @@
3131
<security-role>
3232
<role-name>SORMAS_TO_SORMAS_CLIENT</role-name>
3333
</security-role>
34+
<security-role>
35+
<role-name>BAG_USER</role-name>
36+
</security-role>
3437

3538
</web-app>

sormas-ui/src/main/java/de/symeda/sormas/ui/caze/CasesView.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -334,13 +334,7 @@ private void addCommonCasesOverviewToolbar() {
334334
BAGExportCaseDto.class,
335335
null,
336336
(Integer start, Integer max) -> FacadeProvider.getBAGExportFacade().getCaseExportList(start, max),
337-
(propertyId, type) -> {
338-
String caption = I18nProperties.findPrefixCaption(propertyId);
339-
if (Date.class.isAssignableFrom(type)) {
340-
caption += " (" + DateFormatHelper.getDateFormatPattern() + ")";
341-
}
342-
return caption;
343-
},
337+
(propertyId, type) -> propertyId,
344338
createFileNameWithCurrentDate("sormas_BAG_cases_", ".csv"),
345339
null);
346340

0 commit comments

Comments
 (0)