Skip to content

Commit 9a0f4bc

Browse files
committed
🚧: WIP: started groupedFields impl.
TODO: launch unit tests
1 parent 51540d3 commit 9a0f4bc

File tree

8 files changed

+232
-15
lines changed

8 files changed

+232
-15
lines changed

sormas-api/src/main/java/de/symeda/sormas/api/patch/DataPatchFailureCause.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ public enum DataPatchFailureCause {
77
*/
88
INVALID_MULTIPLE_FIELDS_FORMAT,
99

10+
MISSING_MANDATORY_FIELD_FOR_GROUP,
11+
1012
/**
1113
* Alias cannot be mapped to a single physical path.
1214
* Path aliases base on the "Field ID" field from the generated data dictionary can be used to shorten the physical path.

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,30 @@
11
package de.symeda.sormas.api.patch.mapping;
22

3-
import java.util.List;
43
import java.util.Set;
54

65
import javax.validation.constraints.NotEmpty;
76
import javax.validation.constraints.NotNull;
87

9-
import de.symeda.sormas.api.patch.SinglePatchResult;
8+
import de.symeda.sormas.api.EntityDto;
109

1110
/**
1211
* Some complex - fields objects cannot be mapped using the simple 1 key <-> 1 value approach as some fields must be grouped together to
1312
* make sense.
13+
* <p>
14+
* This also allows creating entities that are not accessible from the Mapping context Root (as of 2026-03-05:
15+
* {@link de.symeda.sormas.api.caze.CaseDataDto})
1416
*/
15-
public interface GroupedFieldsMapper {
17+
public interface GroupedFieldsMapper<T extends EntityDto> {
1618

1719
/**
18-
* Will try to
20+
* Attempts to create a response for the mapping.
1921
*
2022
* @param request
2123
* to determine what may or may not be aggregated.
2224
* @return result dictionary: key path and value the result: patched value or failure.
2325
*/
2426
@NotNull
25-
List<SinglePatchResult> aggregatedPatch(@NotNull GroupedFieldsRequest request);
27+
GroupedFieldsResponse<T> aggregatedPatch(@NotNull GroupedFieldsRequest request);
2628

2729
/**
2830
* Specifies the prefixes that are supported by this grouped mapper.

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

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,27 @@
77
import javax.annotation.Nullable;
88
import javax.validation.constraints.NotNull;
99

10+
import de.symeda.sormas.api.Disease;
1011
import de.symeda.sormas.api.Language;
12+
import de.symeda.sormas.api.caze.CaseReferenceDto;
1113
import de.symeda.sormas.api.patch.DataReplacementStrategy;
1214
import de.symeda.sormas.api.patch.EmptyValueBehavior;
15+
import de.symeda.sormas.api.person.PersonReferenceDto;
1316

1417
/**
1518
* Request object to patch more complex objects together will all fields belonging to that object.
1619
*/
1720
public class GroupedFieldsRequest {
1821

22+
@NotNull
23+
private Disease disease;
24+
25+
@NotNull
26+
private CaseReferenceDto caseData;
27+
28+
@NotNull
29+
private PersonReferenceDto person;
30+
1931
private boolean patchedInCaseOfFailures = false;
2032

2133
@NotNull
@@ -113,13 +125,43 @@ public GroupedFieldsRequest setAllowFallbackValues(boolean allowFallbackValues)
113125
return this;
114126
}
115127

128+
public CaseReferenceDto getCaseData() {
129+
return caseData;
130+
}
131+
132+
public GroupedFieldsRequest setCaseData(CaseReferenceDto caseData) {
133+
this.caseData = caseData;
134+
return this;
135+
}
136+
137+
public PersonReferenceDto getPerson() {
138+
return person;
139+
}
140+
141+
public GroupedFieldsRequest setPerson(PersonReferenceDto person) {
142+
this.person = person;
143+
return this;
144+
}
145+
146+
public Disease getDisease() {
147+
return disease;
148+
}
149+
150+
public GroupedFieldsRequest setDisease(Disease disease) {
151+
this.disease = disease;
152+
return this;
153+
}
154+
116155
@Override
117156
public boolean equals(Object o) {
118157
if (o == null || getClass() != o.getClass())
119158
return false;
120159
GroupedFieldsRequest that = (GroupedFieldsRequest) o;
121160
return patchedInCaseOfFailures == that.patchedInCaseOfFailures
122161
&& allowFallbackValues == that.allowFallbackValues
162+
&& disease == that.disease
163+
&& Objects.equals(caseData, that.caseData)
164+
&& Objects.equals(person, that.person)
123165
&& replacementStrategy == that.replacementStrategy
124166
&& emptyValueBehavior == that.emptyValueBehavior
125167
&& Objects.equals(partialPatchDictionary, that.partialPatchDictionary)
@@ -130,6 +172,9 @@ public boolean equals(Object o) {
130172
@Override
131173
public int hashCode() {
132174
return Objects.hash(
175+
disease,
176+
caseData,
177+
person,
133178
patchedInCaseOfFailures,
134179
replacementStrategy,
135180
emptyValueBehavior,
@@ -141,8 +186,9 @@ public int hashCode() {
141186

142187
@Override
143188
public String toString() {
144-
return "GroupedFieldsRequest{" + "patchedInCaseOfFailures=" + patchedInCaseOfFailures + ", replacementStrategy=" + replacementStrategy
145-
+ ", emptyValueBehavior=" + emptyValueBehavior + ", partialPatchDictionary=" + partialPatchDictionary + ", origin='" + origin + '\''
146-
+ ", inputLanguages=" + inputLanguages + ", allowFallbackValues=" + allowFallbackValues + '}';
189+
return "GroupedFieldsRequest{" + "disease=" + disease + ", caseData=" + caseData + ", person=" + person + ", patchedInCaseOfFailures="
190+
+ patchedInCaseOfFailures + ", replacementStrategy=" + replacementStrategy + ", emptyValueBehavior=" + emptyValueBehavior
191+
+ ", partialPatchDictionary=" + partialPatchDictionary + ", origin='" + origin + '\'' + ", inputLanguages=" + inputLanguages
192+
+ ", allowFallbackValues=" + allowFallbackValues + '}';
147193
}
148194
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package de.symeda.sormas.api.patch.mapping;
2+
3+
import java.util.List;
4+
import java.util.Objects;
5+
6+
import javax.annotation.Nullable;
7+
8+
import de.symeda.sormas.api.EntityDto;
9+
import de.symeda.sormas.api.patch.SinglePatchResult;
10+
11+
public class GroupedFieldsResponse<T extends EntityDto> {
12+
13+
/**
14+
* In case of errors this might be null, otherwise should always be present to be stored at the very end of the patching processing.
15+
*/
16+
@Nullable
17+
private T entityDto;
18+
19+
/**
20+
* Actual results from the original {@link GroupedFieldsRequest}.
21+
*/
22+
private List<SinglePatchResult> patchingResults;
23+
24+
@Nullable
25+
public T getEntityDto() {
26+
return entityDto;
27+
}
28+
29+
public GroupedFieldsResponse<T> setEntityDto(@Nullable T entityDto) {
30+
this.entityDto = entityDto;
31+
return this;
32+
}
33+
34+
public List<SinglePatchResult> getPatchingResults() {
35+
return patchingResults;
36+
}
37+
38+
public GroupedFieldsResponse<T> setPatchingResults(List<SinglePatchResult> patchingResults) {
39+
this.patchingResults = patchingResults;
40+
return this;
41+
}
42+
43+
@Override
44+
public boolean equals(Object o) {
45+
if (o == null || getClass() != o.getClass())
46+
return false;
47+
GroupedFieldsResponse<T> that = (GroupedFieldsResponse<T>) o;
48+
return Objects.equals(entityDto, that.entityDto) && Objects.equals(patchingResults, that.patchingResults);
49+
}
50+
51+
@Override
52+
public int hashCode() {
53+
return Objects.hash(entityDto, patchingResults);
54+
}
55+
56+
@Override
57+
public String toString() {
58+
return "GroupedFieldsResponse{" + "entityDto=" + entityDto + ", patchingResults=" + patchingResults + '}';
59+
}
60+
}

sormas-backend/src/main/java/de/symeda/sormas/backend/ReferenceDataValueInstanceProviderImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public class ReferenceDataValueInstanceProviderImpl implements ReferenceDataValu
3838
private Map<Class<? extends ReferenceDto>, Supplier<List<? extends ReferenceDto>>> dictionary;
3939

4040
@PostConstruct
41-
public void init() {
41+
private void init() {
4242
dictionary = Map.ofEntries(
4343
Map.entry(CountryReferenceDto.class, () -> getInstance(CountryFacade.class).getAllActiveAsReference()),
4444
Map.entry(RegionReferenceDto.class, () -> getInstance(RegionFacade.class).getAllActiveAsReference()),
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package de.symeda.sormas.backend.patch;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import java.util.Optional;
6+
import java.util.function.Consumer;
7+
import java.util.function.Function;
8+
9+
import javax.annotation.PostConstruct;
10+
import javax.ejb.EJB;
11+
import javax.enterprise.context.ApplicationScoped;
12+
import javax.validation.constraints.NotNull;
13+
14+
import de.symeda.sormas.api.EntityDto;
15+
import de.symeda.sormas.api.caze.CaseDataDto;
16+
import de.symeda.sormas.api.immunization.ImmunizationDto;
17+
import de.symeda.sormas.api.person.PersonDto;
18+
import de.symeda.sormas.backend.caze.CaseFacadeEjb;
19+
import de.symeda.sormas.backend.immunization.ImmunizationFacadeEjb;
20+
import de.symeda.sormas.backend.person.PersonFacadeEjb;
21+
22+
@ApplicationScoped
23+
public class BusinessDtoFacade {
24+
25+
@EJB
26+
private CaseFacadeEjb.CaseFacadeEjbLocal caseFacade;
27+
28+
@EJB
29+
private PersonFacadeEjb.PersonFacadeEjbLocal personFacade;
30+
31+
@EJB
32+
private ImmunizationFacadeEjb.ImmunizationFacadeEjbLocal immunizationFacade;
33+
34+
private final Map<Class<? extends EntityDto>, Consumer<? extends EntityDto>> dtoSaveDictionary = new HashMap<>();
35+
36+
private final Map<Class<? extends EntityDto>, Function<CaseDataDto, ? extends EntityDto>> dtoRetrieverDictionary = new HashMap<>();
37+
38+
@PostConstruct
39+
private void init() {
40+
registerSaveOperations();
41+
getRegisterFetchOperations();
42+
}
43+
44+
private void getRegisterFetchOperations() {
45+
registerFetch(PersonDto.class, caseDataDto -> personFacade.getByUuid(caseDataDto.getPerson().getUuid()));
46+
}
47+
48+
private <T extends EntityDto> void registerFetch(Class<T> dtoClass, Function<CaseDataDto, T> fct) {
49+
dtoRetrieverDictionary.put(dtoClass, fct);
50+
}
51+
52+
private void registerSaveOperations() {
53+
registerSave(PersonDto.class, personDto -> personFacade.save(personDto));
54+
registerSave(ImmunizationDto.class, immunizationDto -> immunizationFacade.save(immunizationDto));
55+
}
56+
57+
private <T extends EntityDto> void registerSave(Class<T> dtoClass, Consumer<T> consumer) {
58+
dtoSaveDictionary.put(dtoClass, consumer);
59+
}
60+
61+
public CaseDataDto getCaseDataDto(String caseUuid) {
62+
return caseFacade.getByUuid(caseUuid);
63+
}
64+
65+
public <T extends EntityDto> Optional<Function<CaseDataDto, T>> fetch(@NotNull Class<T> entity) {
66+
return Optional.ofNullable((Function<CaseDataDto, T>) dtoRetrieverDictionary.get(entity));
67+
}
68+
69+
public <T extends EntityDto> Optional<Consumer<T>> save(@NotNull EntityDto entityDto) {
70+
return Optional.ofNullable((Consumer<T>) dtoSaveDictionary.get(entityDto));
71+
}
72+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public class DataPatcherImpl implements DataPatcher {
6262

6363
private final static Logger logger = LoggerFactory.getLogger(DataPatcherImpl.class);
6464

65-
private static final Map<PropertyAccessFailure, DataPatchFailureCause> PROPERTY_FAILURE_TO_PATCH_FAILURE = Map.of(
65+
private static final Map<PropertyAccessFailure, DataPatchFailureCause> PROPERTY_FAILURE_TO_DATA_PATCH_FAILURE = Map.of(
6666
PropertyAccessFailure.INVALID_INPUT,
6767
DataPatchFailureCause.TECHNICAL,
6868

@@ -247,7 +247,7 @@ private boolean anyFieldPatchedWithPrefix(Map<String, Object> validPatchDictiona
247247
logger.info("Missing field: [{}] on target: [{}]", relativeFieldName, target);
248248
return singlePatchResult.setFailure(
249249
buildFailure(
250-
PROPERTY_FAILURE_TO_PATCH_FAILURE.getOrDefault(propertyAccessFailure, DataPatchFailureCause.TECHNICAL),
250+
PROPERTY_FAILURE_TO_DATA_PATCH_FAILURE.getOrDefault(propertyAccessFailure, DataPatchFailureCause.TECHNICAL),
251251
untypedTargetValue));
252252
}
253253
Class<?> targetType = nestedPropertyTypeTuple.getFirst();

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

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,63 @@
11
package de.symeda.sormas.backend.patch.mapping.impl.groupedfieldmapper;
22

3-
import java.util.List;
3+
import java.util.Map;
44
import java.util.Set;
55
import java.util.stream.Collectors;
66
import java.util.stream.Stream;
77

88
import javax.enterprise.context.ApplicationScoped;
9+
import javax.inject.Inject;
910

1011
import de.symeda.sormas.api.immunization.ImmunizationDto;
12+
import de.symeda.sormas.api.patch.DataPatchFailure;
13+
import de.symeda.sormas.api.patch.DataPatchFailureCause;
1114
import de.symeda.sormas.api.patch.SinglePatchResult;
1215
import de.symeda.sormas.api.patch.mapping.GroupedFieldsMapper;
1316
import de.symeda.sormas.api.patch.mapping.GroupedFieldsRequest;
17+
import de.symeda.sormas.api.patch.mapping.GroupedFieldsResponse;
18+
import de.symeda.sormas.api.patch.mapping.ValuePatchRequest;
1419
import de.symeda.sormas.api.vaccination.VaccinationDto;
1520
import de.symeda.sormas.backend.patch.PatchFieldHelper;
21+
import de.symeda.sormas.backend.patch.mapping.ValueMapperRegistry;
1622

1723
@ApplicationScoped
18-
public class ImmunizationGroupedFieldMapper implements GroupedFieldsMapper {
24+
public class ImmunizationGroupedFieldMapper implements GroupedFieldsMapper<ImmunizationDto> {
1925

2026
private static final Set<String> SUPPORTED_PREFIXES = Stream.of(ImmunizationDto.I18N_PREFIX, VaccinationDto.I18N_PREFIX)
2127
.map(prefix -> prefix + PatchFieldHelper.PATH_SEPARATOR)
2228
.collect(Collectors.toSet());
2329

30+
@Inject
31+
private ValueMapperRegistry valueMapperRegistry;
32+
2433
@Override
25-
public List<SinglePatchResult> aggregatedPatch(GroupedFieldsRequest request) {
34+
public GroupedFieldsResponse<ImmunizationDto> aggregatedPatch(GroupedFieldsRequest request) {
2635
// TODO: use a field as reference to trigger one or another logic: Immunization.immunizationStatus
2736

37+
Map<String, Object> originalPatchDictionary = request.getPartialPatchDictionary();
38+
39+
ImmunizationDto build = ImmunizationDto.build(request.getPerson());
40+
build.setDisease(request.getDisease());
41+
42+
Object immunizationStatus = originalPatchDictionary.get(ImmunizationDto.IMMUNIZATION_STATUS);
43+
if (immunizationStatus == null) {
44+
return new GroupedFieldsResponse<ImmunizationDto>().setPatchingResults(
45+
originalPatchDictionary.entrySet()
46+
.stream()
47+
.map(
48+
entry -> new SinglePatchResult().setFieldName(entry.getKey())
49+
.setFailure(
50+
new DataPatchFailure().setProvidedFieldValue(entry.getValue())
51+
.setDataPatchFailureCause(DataPatchFailureCause.MISSING_MANDATORY_FIELD_FOR_GROUP)))
52+
.collect(Collectors.toList()));
53+
}
54+
55+
String immunizationStatusString = immunizationStatus.toString();
56+
57+
valueMapperRegistry.map(new ValuePatchRequest<Boolean>().setValue(immunizationStatus).setTargetType(Boolean.class));
58+
59+
// TODO: use (Field ID) Vaccination.vaccineType: to determine if it is a vaccine for the mother.
60+
2861
/*
2962
* Implementation steps:
3063
* - Retrieve value for: 'Immunization.immunizationStatus'
@@ -41,7 +74,9 @@ public List<SinglePatchResult> aggregatedPatch(GroupedFieldsRequest request) {
4174
* - Can use the DataPatcher again to be able to set all single field values: values or exact fields. (focus on values now)
4275
*/
4376

44-
return List.of();
77+
GroupedFieldsResponse<ImmunizationDto> groupedFieldsResponse =
78+
new GroupedFieldsResponse<ImmunizationDto>().setEntityDto(build).setPatchingResults(null);
79+
return groupedFieldsResponse;
4580
}
4681

4782
@Override

0 commit comments

Comments
 (0)