Skip to content

Commit 143d3d6

Browse files
committed
drafted impl
1 parent f7204b8 commit 143d3d6

File tree

5 files changed

+142
-28
lines changed

5 files changed

+142
-28
lines changed

sormas-backend/src/main/java/de/symeda/sormas/backend/patch/SinglePatchResult.java renamed to sormas-api/src/main/java/de/symeda/sormas/api/patch/SinglePatchResult.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
package de.symeda.sormas.backend.patch;
1+
package de.symeda.sormas.api.patch;
22

33
import javax.annotation.Nullable;
44
import javax.validation.constraints.NotNull;
55

6-
import de.symeda.sormas.api.patch.DataPatchFailure;
7-
86
public class SinglePatchResult {
97

108
@NotNull

sormas-api/src/main/java/de/symeda/sormas/api/patch/mapping/GroupedFieldsMapper.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package de.symeda.sormas.api.patch.mapping;
22

3-
import java.util.Map;
3+
import java.util.List;
44
import java.util.Set;
55

6+
import javax.validation.constraints.NotEmpty;
67
import javax.validation.constraints.NotNull;
78

9+
import de.symeda.sormas.api.patch.SinglePatchResult;
10+
811
/**
912
* Some complex - fields objects cannot be mapped using the simple 1 key <-> 1 value approach as some fields must be grouped together to
1013
* make sense.
@@ -18,12 +21,15 @@ public interface GroupedFieldsMapper {
1821
* to determine what may or may not be aggregated.
1922
* @return result dictionary: key path and value the result: patched value or failure.
2023
*/
21-
Map<String, ValueMappingResult<Object>> aggregatedPatch(@NotNull GroupedFieldsRequest request);
24+
@NotNull
25+
List<SinglePatchResult> aggregatedPatch(@NotNull GroupedFieldsRequest request);
2226

2327
/**
2428
* Specifies the prefixes that are supported by this grouped mapper.
2529
*
2630
* @return supported prefixes.
2731
*/
32+
@NotNull
33+
@NotEmpty
2834
Set<String> aggregatedPrefixes();
2935
}

sormas-backend/src/main/java/de/symeda/sormas/backend/patch/DataPatcherImpl.java

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import de.symeda.sormas.backend.feature.FeatureConfigurationFacadeEjb;
4646
import de.symeda.sormas.backend.json.ObjectMapperProvider;
4747
import de.symeda.sormas.backend.patch.mapping.FieldCustomMapperRegistry;
48+
import de.symeda.sormas.backend.patch.mapping.GroupedFieldMapperRegistry;
4849
import de.symeda.sormas.backend.patch.mapping.ValueMapperRegistry;
4950
import de.symeda.sormas.backend.person.PersonFacadeEjb;
5051
import de.symeda.sormas.backend.util.CollectorUtils;
@@ -89,6 +90,9 @@ public class DataPatcherImpl implements DataPatcher {
8990
@EJB
9091
private ConfigFacadeEjb.ConfigFacadeEjbLocal configFacade;
9192

93+
@Inject
94+
private GroupedFieldMapperRegistry groupedFieldMapperRegistry;
95+
9296
public DataPatcherImpl() {
9397
}
9498

@@ -120,14 +124,16 @@ public DataPatchResponse patch(CaseDataPatchRequest request) {
120124
// make this generic for additional "root"-types
121125
Supplier<PersonDto> personSupplier = Suppliers.memoize(() -> getPersonDto(caseData));
122126

123-
Stream<Tuple<String, Tuple<DataPatchFailureCause, Object>>> tupleStream = computeActualDictionary(request);
127+
List<Tuple<String, Tuple<DataPatchFailureCause, Object>>> patchingTuples = computePatchingTuples(request);
124128

125-
// TODO: tupleStream (transform to map ?) must be passed to the group field handler so that it can be handled.
129+
groupedFieldMapperRegistry.aggregatedPatch()
130+
131+
// TODO: patchingTuples (transform to map ?) must be passed to the group field handler so that it can be handled.
126132
// TODO: provide only the valid one as simple object ? Once this is done the actual dictionary must be not contain does anymore
127133

128-
List<SinglePatchResult> results = tupleStream.map(entry -> {
134+
List<de.symeda.sormas.api.patch.SinglePatchResult> results = patchingTuples.stream().map(entry -> {
129135
String fullFieldName = entry.getFirst();
130-
SinglePatchResult singlePatchResult = new SinglePatchResult().setFieldName(fullFieldName);
136+
de.symeda.sormas.api.patch.SinglePatchResult singlePatchResult = new de.symeda.sormas.api.patch.SinglePatchResult().setFieldName(fullFieldName);
131137

132138
Supplier<Object> target = () -> findAppropriateTarget(fullFieldName, caseData, personSupplier);
133139

@@ -142,9 +148,9 @@ public DataPatchResponse patch(CaseDataPatchRequest request) {
142148

143149
}).collect(Collectors.toList());
144150

145-
Map<String, Object> validPatchDictionary = buildDictionaryFor(results, SinglePatchResult::getValue, true);
151+
Map<String, Object> validPatchDictionary = buildDictionaryFor(results, de.symeda.sormas.api.patch.SinglePatchResult::getValue, true);
146152
DataPatchResponse response = new DataPatchResponse().setApplied(false)
147-
.setFailures(buildDictionaryFor(results, SinglePatchResult::getFailure, false))
153+
.setFailures(buildDictionaryFor(results, de.symeda.sormas.api.patch.SinglePatchResult::getFailure, false))
148154
.setValidPatchDictionary(validPatchDictionary);
149155

150156
if (validPatchDictionary.isEmpty() || (!request.isPatchedInCaseOfFailures() && response.hasFailures())) {
@@ -190,27 +196,28 @@ private boolean anyFieldPatchedWithPrefix(Map<String, Object> validPatchDictiona
190196
}
191197

192198
private @NotNull <R> Map<String, R> buildDictionaryFor(
193-
List<SinglePatchResult> results,
194-
Function<SinglePatchResult, R> fct,
199+
List<de.symeda.sormas.api.patch.SinglePatchResult> results,
200+
Function<de.symeda.sormas.api.patch.SinglePatchResult, R> fct,
195201
boolean valueContext) {
196202
return results.stream()
197203
// edge case were target value is null: this is allowed, which makes both fields null.
198204
.filter(
199205
singlePatchResult -> fct.apply(singlePatchResult) != null
200206
|| (valueContext && singlePatchResult.getFailure() == null && singlePatchResult.getValue() == null))
201-
.collect(CollectorUtils.toNullSafeMap(SinglePatchResult::getFieldName, fct));
207+
.collect(CollectorUtils.toNullSafeMap(de.symeda.sormas.api.patch.SinglePatchResult::getFieldName, fct));
202208
}
203209

204210
// TODO: make usable for external use
205-
private @NotNull SinglePatchResult valueMappingResult(
211+
private @NotNull de.symeda.sormas.api.patch.SinglePatchResult valueMappingResult(
206212
Tuple<String, Tuple<DataPatchFailureCause, Object>> entry,
207213
Disease disease,
208214
CaseDataPatchRequest request,
209215
Supplier<Object> targetOpt) {
210216

211217
String fullFieldName = entry.getFirst();
212218

213-
SinglePatchResult singlePatchResult = new SinglePatchResult().setFieldName(fullFieldName);
219+
de.symeda.sormas.api.patch.SinglePatchResult singlePatchResult =
220+
new de.symeda.sormas.api.patch.SinglePatchResult().setFieldName(fullFieldName);
214221

215222
Object target = targetOpt.get();
216223
String relativeFieldName = fullFieldName.substring(fullFieldName.indexOf('.') + 1);
@@ -271,7 +278,8 @@ private DataPatchFailure buildFailure(DataPatchFailureCause fieldDoesNotExist, O
271278
return new DataPatchFailure().setDataPatchFailureCause(fieldDoesNotExist).setProvidedFieldValue(untypedTargetValue);
272279
}
273280

274-
private @NotNull Optional<SinglePatchResult> invalidFieldResult(Tuple<String, Tuple<DataPatchFailureCause, Object>> entry) {
281+
private @NotNull Optional<de.symeda.sormas.api.patch.SinglePatchResult> invalidFieldResult(
282+
Tuple<String, Tuple<DataPatchFailureCause, Object>> entry) {
275283
return Optional.ofNullable(extractFailureCause(entry)).map(invalidFieldFailureCause -> buildFailureFor(entry, invalidFieldFailureCause));
276284
}
277285

@@ -280,7 +288,7 @@ private DataPatchFailureCause extractFailureCause(Tuple<String, Tuple<DataPatchF
280288
}
281289

282290
// TODO: make usable for external use
283-
public Optional<SinglePatchResult> fieldMappingResult(
291+
public Optional<de.symeda.sormas.api.patch.SinglePatchResult> fieldMappingResult(
284292
Tuple<String, Tuple<DataPatchFailureCause, Object>> entry,
285293
Disease disease,
286294
CaseDataPatchRequest request,
@@ -292,7 +300,8 @@ public Optional<SinglePatchResult> fieldMappingResult(
292300

293301
Object untypedTargetValue = extractValue(entry);
294302
if (mapper.isPresent()) {
295-
SinglePatchResult singlePatchResult = new SinglePatchResult().setFieldName(fullFieldName);
303+
de.symeda.sormas.api.patch.SinglePatchResult singlePatchResult =
304+
new de.symeda.sormas.api.patch.SinglePatchResult().setFieldName(fullFieldName);
296305

297306
Optional<DataPatchFailure> dataPatchFailureOpt = mapper.orElseThrow()
298307
.map(
@@ -308,9 +317,12 @@ public Optional<SinglePatchResult> fieldMappingResult(
308317
return Optional.empty();
309318
}
310319

311-
private SinglePatchResult buildFailureFor(Tuple<String, Tuple<DataPatchFailureCause, Object>> entry, DataPatchFailureCause fieldFailureCause) {
320+
private de.symeda.sormas.api.patch.SinglePatchResult buildFailureFor(
321+
Tuple<String, Tuple<DataPatchFailureCause, Object>> entry,
322+
DataPatchFailureCause fieldFailureCause) {
312323

313-
return new SinglePatchResult().setFieldName(entry.getFirst()).setFailure(buildFailure(fieldFailureCause, extractValue(entry)));
324+
return new de.symeda.sormas.api.patch.SinglePatchResult().setFieldName(entry.getFirst())
325+
.setFailure(buildFailure(fieldFailureCause, extractValue(entry)));
314326
}
315327

316328
private Object extractValue(Tuple<String, Tuple<DataPatchFailureCause, Object>> entry) {
@@ -323,7 +335,7 @@ private FieldVisibilityCheckers getFieldVisibilityCheckers(Disease disease) {
323335
.andWithFeatureType(featureConfigurationFacade.getActiveServerFeatureConfigurations());
324336
}
325337

326-
private Stream<Tuple<String, Tuple<DataPatchFailureCause, Object>>> computeActualDictionary(CaseDataPatchRequest request) {
338+
private List<Tuple<String, Tuple<DataPatchFailureCause, Object>>> computePatchingTuples(CaseDataPatchRequest request) {
327339
Predicate<Map.Entry<String, Object>> filterPredicate = buildAdequateDictionaryValuePredicate(request);
328340

329341
return request.getPatchDictionary()
@@ -351,7 +363,8 @@ private Stream<Tuple<String, Tuple<DataPatchFailureCause, Object>>> computeActua
351363

352364
return splitMultipleFieldsPath(entry);
353365
})
354-
.map(tuple -> Tuple.of(tuple.getFirst(), tuple.getSecond()));
366+
.map(tuple -> Tuple.of(tuple.getFirst(), tuple.getSecond()))
367+
.collect(Collectors.toList());
355368
}
356369

357370
private AbstractMap.@NotNull SimpleEntry<String, Object> toMapEntry(String first, Object value) {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package de.symeda.sormas.backend.patch.mapping;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
import java.util.Optional;
6+
import java.util.Set;
7+
import java.util.stream.Collectors;
8+
9+
import javax.annotation.PostConstruct;
10+
import javax.enterprise.context.ApplicationScoped;
11+
import javax.enterprise.inject.Instance;
12+
import javax.inject.Inject;
13+
import javax.validation.constraints.NotNull;
14+
15+
import org.apache.commons.collections4.CollectionUtils;
16+
import org.apache.commons.lang3.StringUtils;
17+
import org.slf4j.Logger;
18+
import org.slf4j.LoggerFactory;
19+
20+
import de.symeda.sormas.api.patch.SinglePatchResult;
21+
import de.symeda.sormas.api.patch.mapping.GroupedFieldsMapper;
22+
import de.symeda.sormas.api.patch.mapping.GroupedFieldsRequest;
23+
import de.symeda.sormas.api.utils.Tuple;
24+
import de.symeda.sormas.backend.util.CollectorUtils;
25+
26+
@ApplicationScoped
27+
public class GroupedFieldMapperRegistry {
28+
29+
private final static Logger logger = LoggerFactory.getLogger(GroupedFieldMapperRegistry.class);
30+
31+
@Inject
32+
private Instance<GroupedFieldsMapper> instances;
33+
34+
private Set<Tuple<String, GroupedFieldsMapper>> prefixMapperTuples;
35+
36+
@PostConstruct
37+
void init() {
38+
Map<String, List<GroupedFieldsMapper>> mappersByPrefixDictionary = instances.stream()
39+
.flatMap(mapper -> mapper.aggregatedPrefixes().stream().map(prefix -> Tuple.of(mapper, prefix)))
40+
.collect(Collectors.groupingBy(Tuple::getSecond, Collectors.mapping(Tuple::getFirst, Collectors.toList())));
41+
42+
Set<Tuple<String, Set<String>>> duplicateMappings = mappersByPrefixDictionary.entrySet()
43+
.stream()
44+
.filter(entry -> CollectionUtils.size(entry.getValue()) > 1)
45+
.map(
46+
entry -> Tuple
47+
.of(entry.getKey(), entry.getValue().stream().map(mapper -> mapper.getClass().getSimpleName()).collect(Collectors.toSet())))
48+
.collect(Collectors.toSet());
49+
50+
if (CollectionUtils.isNotEmpty(duplicateMappings)) {
51+
throw new IllegalStateException(
52+
String.format(
53+
"There are duplicate grouped field mappings: [%s]. \nA prefix can only be handled by a single mapper",
54+
duplicateMappings));
55+
}
56+
57+
prefixMapperTuples =
58+
mappersByPrefixDictionary.entrySet().stream().map(entry -> Tuple.of(entry.getKey(), entry.getValue().get(0))).collect(Collectors.toSet());
59+
}
60+
61+
@NotNull
62+
public List<SinglePatchResult> aggregatedPatch(@NotNull GroupedFieldsRequest request) {
63+
64+
Map<GroupedFieldsMapper, Map<String, Object>> dictionary = request.getPartialPatchDictionary()
65+
.entrySet()
66+
.stream()
67+
.map(
68+
patchKeyValueEntry -> prefixMapperTuples.stream()
69+
.filter(prefixMapperTuple -> StringUtils.startsWith(patchKeyValueEntry.getKey(), prefixMapperTuple.getFirst()))
70+
.findAny()
71+
.map(tuple -> Tuple.of(tuple.getSecond(), patchKeyValueEntry)))
72+
.flatMap(Optional::stream)
73+
.collect(
74+
Collectors.groupingBy(
75+
Tuple::getFirst,
76+
CollectorUtils.toNullSafeMap(tuple -> tuple.getSecond().getKey(), tuple -> tuple.getSecond().getValue())));
77+
78+
return dictionary.entrySet()
79+
.stream()
80+
.flatMap(entry -> entry.getKey().aggregatedPatch(buildCopy(request).setPartialPatchDictionary(entry.getValue())).stream())
81+
.collect(Collectors.toList());
82+
}
83+
84+
private static GroupedFieldsRequest buildCopy(GroupedFieldsRequest request) {
85+
return new GroupedFieldsRequest().setAllowFallbackValues(request.isAllowFallbackValues())
86+
.setEmptyValueBehavior(request.getEmptyValueBehavior())
87+
.setOrigin(request.getOrigin())
88+
.setInputLanguages(request.getInputLanguages())
89+
.setPatchedInCaseOfFailures(request.isPatchedInCaseOfFailures())
90+
.setReplacementStrategy(request.getReplacementStrategy());
91+
}
92+
}

sormas-backend/src/main/java/de/symeda/sormas/backend/patch/mapping/impl/groupedfieldmapper/ImmunizationGroupedFieldMapper.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,25 @@
22

33
import java.util.Map;
44
import java.util.Set;
5+
import java.util.stream.Collectors;
6+
import java.util.stream.Stream;
57

68
import javax.enterprise.context.ApplicationScoped;
79

10+
import de.symeda.sormas.api.immunization.ImmunizationDto;
811
import de.symeda.sormas.api.patch.mapping.GroupedFieldsMapper;
912
import de.symeda.sormas.api.patch.mapping.GroupedFieldsRequest;
1013
import de.symeda.sormas.api.patch.mapping.ValueMappingResult;
14+
import de.symeda.sormas.api.vaccination.VaccinationDto;
15+
import de.symeda.sormas.backend.patch.PatchFieldHelper;
1116

1217
@ApplicationScoped
1318
public class ImmunizationGroupedFieldMapper implements GroupedFieldsMapper {
1419

20+
private static final Set<String> SUPPORTED_PREFIXES = Stream.of(ImmunizationDto.I18N_PREFIX, VaccinationDto.I18N_PREFIX)
21+
.map(prefix -> prefix + PatchFieldHelper.PATH_SEPARATOR)
22+
.collect(Collectors.toSet());
23+
1524
@Override
1625
public Map<String, ValueMappingResult<Object>> aggregatedPatch(GroupedFieldsRequest request) {
1726
// TODO: use a field as reference to trigger one or another logic: Immunization.immunizationStatus
@@ -37,10 +46,6 @@ public Map<String, ValueMappingResult<Object>> aggregatedPatch(GroupedFieldsRequ
3746

3847
@Override
3948
public Set<String> aggregatedPrefixes() {
40-
if (true) {
41-
// TODO: specifiy adequate fields.
42-
throw new IllegalStateException();
43-
}
44-
return Set.of();
49+
return SUPPORTED_PREFIXES;
4550
}
4651
}

0 commit comments

Comments
 (0)