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

Commit 33e06eb

Browse files
lgallgal
authored andcommitted
SORMAS-Foundation#3610 WIP rest endpoint for BAG export cases
1 parent eca4173 commit 33e06eb

6 files changed

Lines changed: 303 additions & 135 deletions

File tree

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

Lines changed: 2 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.NONE);
64+
SORMAS_TO_SORMAS_CLIENT(false, false, false, false, JurisdictionLevel.NONE),
65+
BAG_USER(false, false, false, false, JurisdictionLevel.NONE);
6566

6667
/*
6768
* Hint for SonarQube issues:
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+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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.ByteArrayOutputStream;
19+
import java.util.Date;
20+
21+
import javax.annotation.security.RolesAllowed;
22+
import javax.ws.rs.Consumes;
23+
import javax.ws.rs.GET;
24+
import javax.ws.rs.Path;
25+
import javax.ws.rs.Produces;
26+
import javax.ws.rs.core.MediaType;
27+
import javax.ws.rs.core.Response;
28+
29+
import de.symeda.sormas.api.FacadeProvider;
30+
import de.symeda.sormas.api.bagexport.BAGExportCaseDto;
31+
import de.symeda.sormas.api.i18n.I18nProperties;
32+
import de.symeda.sormas.api.utils.CsvStreamUtils;
33+
import de.symeda.sormas.api.utils.DateFormatHelper;
34+
35+
@Path("/bagexport")
36+
@Produces(MediaType.APPLICATION_OCTET_STREAM)
37+
@Consumes(MediaType.APPLICATION_JSON + "; charset=UTF-8")
38+
@RolesAllowed({
39+
"BAG_USER" })
40+
public class BAGExportResource {
41+
42+
@GET
43+
@Path("/cases")
44+
public Response exportCases(String file) {
45+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
46+
47+
CsvStreamUtils.writeCsvContentToStream(
48+
BAGExportCaseDto.class,
49+
(from, to) -> FacadeProvider.getBAGExportFacade().getCaseExportList(from, to),
50+
(propertyId, type) -> {
51+
String caption = I18nProperties.findPrefixCaption(propertyId);
52+
if (Date.class.isAssignableFrom(type)) {
53+
caption += " (" + DateFormatHelper.getDateFormatPattern() + ")";
54+
}
55+
return caption;
56+
},
57+
null,
58+
null,
59+
FacadeProvider.getConfigFacade(),
60+
baos);
61+
62+
Response.ResponseBuilder response = Response.ok(baos);
63+
response.header("Content-Disposition", "attachment;filename=test.csv");
64+
65+
return response.build();
66+
}
67+
}

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>

0 commit comments

Comments
 (0)