Skip to content

Commit 07bf44e

Browse files
committed
🚧 Started impl for CustomizableFields.
CODE does not yet compile, issue with generics. TODO: - Use Mapping - Fix compile - Add tests for mappers - Add save in patcher - Allow display in partial retrieval
1 parent 3d2ce49 commit 07bf44e

10 files changed

Lines changed: 467 additions & 6 deletions

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ public DataPatchResponse patch(CaseDataPatchRequest request) {
137137
}
138138

139139
saveDTOsIfAppropriate(entityCache);
140+
// TODO: once entities are saved, if appropriate save customizable fields.
140141

141142
logger.debug("dataPatchResponse: [{}]", response);
142143

@@ -421,7 +422,11 @@ private Predicate<Map.Entry<PatchField, Object>> buildEmptyValuePredicate() {
421422
};
422423
}
423424

424-
private static final class SingleFieldPatchResult {
425+
/**
426+
*
427+
*/
428+
// TODO: Extract & Rename
429+
public static final class SingleFieldPatchResult {
425430

426431
final PatchField field;
427432
final DataPatchFailureCause failureCause;

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

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

33
import java.util.Arrays;
4-
import java.util.HashSet;
4+
import java.util.Collection;
55
import java.util.Optional;
66
import java.util.Set;
77
import java.util.stream.Collectors;
@@ -27,13 +27,14 @@ public class PatchFieldHelper {
2727

2828
public static final String PATH_SEPARATOR = ".";
2929
public static final String DUPLICATE_MARKER = "_duplicate_";
30+
public static final String PATCH_FORBIDDEN_FIELDS_CONFIG_KEY = "PATCH_FORBIDDEN_FIELDS";
31+
public static final String CUSTOM_PREFIX = "Custom";
32+
private static final Collection<String> CUSTOM_PREFIX_COLLECTION = Set.of(CUSTOM_PREFIX);
3033

3134
private static final String OPENING_PARENTHESIS = "(";
3235
private static final String CLOSING_PARENTHESIS = ")";
3336
private static final String PIPE = "|";
3437

35-
public static final String PATCH_FORBIDDEN_FIELDS_CONFIG_KEY = "PATCH_FORBIDDEN_FIELDS";
36-
3738
@Inject
3839
private PathAliasHelper pathAliasHelper;
3940

@@ -122,10 +123,11 @@ private Set<String> resolveConfiguredForbiddenFields() {
122123

123124
private boolean startsWithAllowedPrefix(String path) {
124125
return pathStartsWithAllowedPrefix(path, pathAliasHelper.supportedPrefixes())
125-
|| pathStartsWithAllowedPrefix(path, businessDtoFacade.fetchablePrefixes());
126+
|| pathStartsWithAllowedPrefix(path, businessDtoFacade.fetchablePrefixes())
127+
|| pathStartsWithAllowedPrefix(path, CUSTOM_PREFIX_COLLECTION);
126128
}
127129

128-
private static boolean pathStartsWithAllowedPrefix(String path, Set<String> prefixes) {
130+
private static boolean pathStartsWithAllowedPrefix(String path, Collection<String> prefixes) {
129131
return prefixes.stream().anyMatch(path::startsWith);
130132
}
131133

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package de.symeda.sormas.backend.patch.customizablefield;
2+
3+
import de.symeda.sormas.api.caze.CaseDataDto;
4+
import de.symeda.sormas.api.customizablefield.CustomizableFieldContext;
5+
import de.symeda.sormas.api.epidata.EpiDataDto;
6+
import de.symeda.sormas.api.exposure.ExposureDto;
7+
8+
/**
9+
* To be able to use the same naming convention as for non-customizable fields.
10+
*/
11+
public enum CustomizableFieldContextPatchMapping {
12+
13+
CASE(CustomizableFieldContext.CASE, CaseDataDto.I18N_PREFIX),
14+
EPIDATA(CustomizableFieldContext.EPIDATA, EpiDataDto.I18N_PREFIX),
15+
EXPOSURE(CustomizableFieldContext.EXPOSURE, ExposureDto.I18N_PREFIX);
16+
17+
private final CustomizableFieldContext customizableFieldContext;
18+
private final String patchName;
19+
20+
CustomizableFieldContextPatchMapping(CustomizableFieldContext customizableFieldContext, String patchName) {
21+
this.customizableFieldContext = customizableFieldContext;
22+
this.patchName = patchName;
23+
}
24+
25+
public CustomizableFieldContext getCustomizableFieldContext() {
26+
return customizableFieldContext;
27+
}
28+
29+
public String getPatchName() {
30+
return patchName;
31+
}
32+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package de.symeda.sormas.backend.patch.customizablefield;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
6+
import javax.ejb.EJB;
7+
import javax.enterprise.context.ApplicationScoped;
8+
import javax.validation.constraints.NotNull;
9+
10+
import de.symeda.sormas.api.caze.CaseDataDto;
11+
import de.symeda.sormas.api.customizablefield.CustomizableFieldContext;
12+
import de.symeda.sormas.api.customizablefield.CustomizableFieldMetadataDto;
13+
import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto;
14+
import de.symeda.sormas.api.patch.CaseDataPatchRequest;
15+
import de.symeda.sormas.api.patch.SinglePatchResult;
16+
import de.symeda.sormas.api.utils.Tuple;
17+
import de.symeda.sormas.backend.customizablefield.CustomizableFieldValueFacadeEjb;
18+
import de.symeda.sormas.backend.patch.DataPatcherImpl;
19+
20+
/**
21+
* Wrapper arround {@link de.symeda.sormas.api.customizablefield.CustomizableFieldValueFacade} to be able to patch customizable field
22+
* values.
23+
*/
24+
@ApplicationScoped
25+
public class CustomizableFieldDataPatcher {
26+
27+
@EJB
28+
private CustomizableFieldValueFacadeEjb.CustomizableFieldValueFacadeEjbLocal customizableFieldValueFacade;
29+
30+
public List<Tuple<SinglePatchResult, CustomizableFieldValueDto>> patch(Request request) {
31+
CaseDataPatchRequest caseDataPatchRequest = request.getCaseDataPatchRequest();
32+
33+
Map<CustomizableFieldMetadataDto, CustomizableFieldValueDto> valuesForEntity =
34+
customizableFieldValueFacade.getValuesForEntity(caseDataPatchRequest.getCaseUuid(), CustomizableFieldContext.CASE);
35+
36+
return List.of();
37+
}
38+
39+
public void save(List<CustomizableFieldValueDto> values) {
40+
values.forEach(customizableFieldValueFacade::save);
41+
}
42+
43+
public static final class Request {
44+
45+
@NotNull
46+
private CaseDataPatchRequest caseDataPatchRequest;
47+
48+
@NotNull
49+
private List<DataPatcherImpl.SingleFieldPatchResult> patchingTuples;
50+
51+
@NotNull
52+
private CaseDataDto caseDataDto;
53+
54+
public CaseDataPatchRequest getCaseDataPatchRequest() {
55+
return caseDataPatchRequest;
56+
}
57+
58+
public Request setCaseDataPatchRequest(CaseDataPatchRequest caseDataPatchRequest) {
59+
this.caseDataPatchRequest = caseDataPatchRequest;
60+
return this;
61+
}
62+
63+
public List<DataPatcherImpl.SingleFieldPatchResult> getPatchingTuples() {
64+
return patchingTuples;
65+
}
66+
67+
public Request setPatchingTuples(List<DataPatcherImpl.SingleFieldPatchResult> patchingTuples) {
68+
this.patchingTuples = patchingTuples;
69+
return this;
70+
}
71+
72+
public CaseDataDto getCaseDataDto() {
73+
return caseDataDto;
74+
}
75+
76+
public Request setCaseDataDto(CaseDataDto caseDataDto) {
77+
this.caseDataDto = caseDataDto;
78+
return this;
79+
}
80+
}
81+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package de.symeda.sormas.backend.patch.customizablefield;
2+
3+
import java.util.function.BiConsumer;
4+
5+
import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto;
6+
7+
/**
8+
* Typed Bi-Consumer to ease reading.
9+
*
10+
* @param <T>
11+
* targetType.
12+
*/
13+
@FunctionalInterface
14+
public interface CustomizableFieldSetter<T> extends BiConsumer<CustomizableFieldValueDto, T> {
15+
16+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package de.symeda.sormas.backend.patch.customizablefield;
2+
3+
import java.util.Objects;
4+
5+
import javax.validation.constraints.NotNull;
6+
7+
import de.symeda.sormas.api.customizablefield.CustomizableFieldType;
8+
import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto;
9+
10+
public class CustomizableFieldValuePatchRequest {
11+
12+
@NotNull
13+
private Object value;
14+
15+
@NotNull
16+
private CustomizableFieldType targetType;
17+
18+
/**
19+
* WARNING: will be mutated with {@link #getValue()}
20+
*/
21+
@NotNull
22+
private CustomizableFieldValueDto customizableFieldValueDto;
23+
24+
public Object getValue() {
25+
return value;
26+
}
27+
28+
public CustomizableFieldValuePatchRequest setValue(Object value) {
29+
this.value = value;
30+
return this;
31+
}
32+
33+
public CustomizableFieldType getTargetType() {
34+
return targetType;
35+
}
36+
37+
public CustomizableFieldValuePatchRequest setTargetType(CustomizableFieldType targetType) {
38+
this.targetType = targetType;
39+
return this;
40+
}
41+
42+
public CustomizableFieldValueDto getCustomizableFieldValueDto() {
43+
return customizableFieldValueDto;
44+
}
45+
46+
public CustomizableFieldValuePatchRequest setCustomizableFieldValueDto(CustomizableFieldValueDto customizableFieldValueDto) {
47+
this.customizableFieldValueDto = customizableFieldValueDto;
48+
return this;
49+
}
50+
51+
@Override
52+
public boolean equals(Object o) {
53+
if (o == null || getClass() != o.getClass())
54+
return false;
55+
CustomizableFieldValuePatchRequest that = (CustomizableFieldValuePatchRequest) o;
56+
return Objects.equals(value, that.value)
57+
&& targetType == that.targetType
58+
&& Objects.equals(customizableFieldValueDto, that.customizableFieldValueDto);
59+
}
60+
61+
@Override
62+
public int hashCode() {
63+
return Objects.hash(value, targetType, customizableFieldValueDto);
64+
}
65+
66+
@Override
67+
public String toString() {
68+
return "CustomizableFieldValuePatchRequest{" + "value=" + value + ", targetType=" + targetType + ", customizableFieldValueDto="
69+
+ customizableFieldValueDto + '}';
70+
}
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package de.symeda.sormas.backend.patch.customizablefield.mappers;
2+
3+
import java.util.Set;
4+
5+
import javax.validation.constraints.NotNull;
6+
7+
import de.symeda.sormas.api.customizablefield.CustomizableFieldType;
8+
import de.symeda.sormas.api.customizablefield.CustomizableFieldValueDto;
9+
import de.symeda.sormas.api.patch.DataPatchFailureCause;
10+
import de.symeda.sormas.api.patch.mapping.ValueMappingResult;
11+
import de.symeda.sormas.api.utils.Tuple;
12+
import de.symeda.sormas.backend.patch.customizablefield.CustomizableFieldSetter;
13+
import de.symeda.sormas.backend.patch.customizablefield.CustomizableFieldValuePatchRequest;
14+
15+
/**
16+
* Contract to specify how a type must be mapped into a value, NOT field specific.
17+
*/
18+
public interface CustomizableFieldValuePatchMapper extends Comparable<CustomizableFieldValuePatchMapper> {
19+
20+
int HIGH_PRECEDENCE = Integer.MIN_VALUE;
21+
22+
int LOW_PRECEDENCE = Integer.MAX_VALUE;
23+
24+
/**
25+
* Can be used to add it to the default precedences values and a keep some "space between" the implementations ordering.
26+
*/
27+
int ORDER_CHUNK = 20;
28+
29+
/**
30+
*
31+
* @param request
32+
* to specif how the value should be mapped.
33+
* @return actual value
34+
* @throws RuntimeException
35+
* in case of the value couldn't be mapped.
36+
*/
37+
@NotNull
38+
ValueMappingResult<CustomizableFieldValueDto> map(CustomizableFieldValuePatchRequest request);
39+
40+
@NotNull
41+
Set<CustomizableFieldType> getSupportedTypes();
42+
43+
/**
44+
* Specifies if the targetType is supported by this class.
45+
*
46+
* @param targetType
47+
* can be a child class.
48+
* @return true if the class will be able to perform some action with this type.
49+
*/
50+
default boolean supports(@NotNull CustomizableFieldType targetType) {
51+
return getSupportedTypes().contains(targetType);
52+
}
53+
54+
/**
55+
* Allows you to override default mappers.
56+
* {@link #HIGH_PRECEDENCE} means this mapper will be used (among) first.
57+
* {@link #LOW_PRECEDENCE} means this mapper will be used (among) last.
58+
*
59+
* @return defaults to LOW_PRECEDENCE
60+
*/
61+
default int getOrder() {
62+
return LOW_PRECEDENCE;
63+
}
64+
65+
@Override
66+
default int compareTo(CustomizableFieldValuePatchMapper o) {
67+
return Integer.compare(this.getOrder(), o.getOrder());
68+
}
69+
70+
/**
71+
* Creates a new typed instance with the error or the new value to specify.
72+
*
73+
* @param initialResult
74+
* @param dto
75+
* @return
76+
*/
77+
static ValueMappingResult<CustomizableFieldValueDto> buildMappingResultFrom(
78+
@NotNull ValueMappingResult<?> initialResult,
79+
CustomizableFieldValueDto dto) {
80+
81+
DataPatchFailureCause dataPatchFailureCause = initialResult.getDataPatchFailureCause();
82+
if (dataPatchFailureCause != null) {
83+
return ValueMappingResult.withCause(dataPatchFailureCause);
84+
}
85+
86+
return ValueMappingResult.withData(dto);
87+
}
88+
89+
static <T> Tuple<Class<?>, CustomizableFieldSetter<?>> buildTuple(Class<T> clazz, CustomizableFieldSetter<T> setter) {
90+
return Tuple.of(clazz, setter);
91+
}
92+
}

0 commit comments

Comments
 (0)