Skip to content

Commit 3a0e2fc

Browse files
author
Alexey Zarakovskiy
committed
Merge pull request #84 in TSI/web-application-java from feature/TSI-3010 to develop
* commit '137e519db5c2d6eec107e7d0ec749f0823e9b51b': TSI-3011 Typo fix (TSI-3010) TSI-3011 Review fix (TSI-3010) TSI-3011 Review fix (TSI-3010) TSI-3011 Added data model extensions to public API (TSI-3010) TSI-3010 Added advanced way to save type information without additional changes from CM side (TSI-3011) TSI-3010 Moved Conditions to webapp & added support of any classes in extension data (TSI-3011)
2 parents 686627b + 137e519 commit 3a0e2fc

25 files changed

Lines changed: 356 additions & 124 deletions

File tree

dxa-framework/dxa-common-api/src/test/java/com/sdl/dxa/caching/wrapper/CacheTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public void shouldDelegateKeyCalculationToConcreteCaches() {
6363
pagesCopyingCache.setKeyGenerator(keyGenerator);
6464
entitiesCache.setKeyGenerator(keyGenerator);
6565
MvcModelData mvcData = new MvcModelData("a", "a", "a", "c", "v", null);
66-
PageModelData pageData = (PageModelData) new PageModelData("1", null, null, null, null, null, null, "/url")
66+
PageModelData pageData = (PageModelData) new PageModelData("1", null, null, null, null, null, "/url")
6767
.setMvcData(mvcData);
6868
EntityModelData entityMvcData = (EntityModelData) new EntityModelData("2", null, null, null, null, mvcData, "1", "/url", null, null, null)
6969
.setMvcData(mvcData);

dxa-framework/dxa-data-model/src/main/java/com/sdl/dxa/api/datamodel/DataModelSpringConfiguration.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import com.fasterxml.jackson.datatype.joda.JodaModule;
1010
import com.sdl.dxa.api.datamodel.json.Polymorphic;
1111
import com.sdl.dxa.api.datamodel.json.PolymorphicObjectMixin;
12-
import com.sdl.dxa.api.datamodel.model.ViewModelData;
1312
import lombok.extern.slf4j.Slf4j;
1413
import org.springframework.context.annotation.Bean;
1514
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
@@ -43,7 +42,7 @@ public ObjectMapper dxaR2ObjectMapper() {
4342

4443
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
4544
scanner.addIncludeFilter(new AnnotationTypeFilter(Polymorphic.class));
46-
scanner.findCandidateComponents(ViewModelData.class.getPackage().getName())
45+
scanner.findCandidateComponents(DataModelSpringConfiguration.class.getPackage().getName())
4746
.forEach(type -> {
4847
try {
4948
Class<?> aClass = forName(type.getBeanClassName(), getDefaultClassLoader());
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.sdl.dxa.api.datamodel.json;
2+
3+
/**
4+
* Basic constants for polymorphic JSON mapping.
5+
*/
6+
public final class Constants {
7+
8+
public static final String DOLLAR_TYPE = "$type";
9+
10+
public static final String LIST_MARKER = "[]";
11+
12+
private Constants() {
13+
}
14+
}

dxa-framework/dxa-data-model/src/main/java/com/sdl/dxa/api/datamodel/json/ModelDataTypeIdResolver.java

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@
66
import com.fasterxml.jackson.databind.JavaType;
77
import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase;
88
import com.fasterxml.jackson.databind.type.TypeFactory;
9+
import com.sdl.dxa.api.datamodel.DataModelSpringConfiguration;
910
import com.sdl.dxa.api.datamodel.model.ViewModelData;
11+
import com.sdl.dxa.api.datamodel.model.util.HandlesTypeInformationForListWrapper;
1012
import com.sdl.dxa.api.datamodel.model.util.ListWrapper;
13+
import com.sdl.dxa.api.datamodel.model.util.UnknownClassesContentModelData;
1114
import lombok.extern.slf4j.Slf4j;
12-
import org.jetbrains.annotations.NotNull;
1315
import org.joda.time.DateTime;
1416
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
1517
import org.springframework.core.type.filter.AnnotationTypeFilter;
1618

17-
import java.io.IOException;
1819
import java.util.Date;
1920
import java.util.HashMap;
2021
import java.util.Map;
22+
import java.util.stream.Collectors;
2123
import java.util.stream.Stream;
2224

2325
import static com.fasterxml.jackson.databind.type.TypeFactory.unknownType;
@@ -39,45 +41,39 @@
3941
@Slf4j
4042
public class ModelDataTypeIdResolver extends TypeIdResolverBase {
4143

42-
private static final String LIST_MARKER = "[]";
43-
4444
private static final Map<String, JavaType> BASIC_MAPPING = new HashMap<>();
4545

4646
static {
4747
Stream.of(Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class,
48-
Double.class, String.class).forEach(aClass -> {
49-
addMapping(aClass.getSimpleName(), aClass);
50-
addMapping(aClass.getSimpleName() + LIST_MARKER, ListWrapper.class, aClass);
51-
});
48+
Double.class, String.class).forEach(aClass -> addMapping(aClass.getSimpleName(), aClass, null));
5249

53-
Stream.of(Date.class, DateTime.class).forEach(aClass -> {
54-
addMapping(aClass.getSimpleName(), String.class);
55-
addMapping(aClass.getSimpleName() + LIST_MARKER, ListWrapper.class, String.class);
56-
});
50+
Stream.of(Date.class, DateTime.class).forEach(aClass -> addMapping(aClass.getSimpleName(), String.class, null));
5751

5852
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
5953
scanner.addIncludeFilter(new AnnotationTypeFilter(JsonTypeName.class));
60-
scanner.findCandidateComponents(ViewModelData.class.getPackage().getName())
54+
scanner.findCandidateComponents(DataModelSpringConfiguration.class.getPackage().getName())
6155
.forEach(type -> {
6256
try {
6357
Class<?> aClass = forName(type.getBeanClassName(), getDefaultClassLoader());
6458
JsonTypeName typeName = aClass.getAnnotation(JsonTypeName.class);
65-
addMapping(defaultIfBlank(typeName.value(), aClass.getSimpleName()), aClass);
59+
addMapping(defaultIfBlank(typeName.value(), aClass.getSimpleName()), aClass, null);
6660
} catch (ClassNotFoundException e) {
6761
log.warn("Class not found while mapping model data to typeIDs. Should never happen.", e);
6862
}
6963
});
70-
}
7164

72-
private static void addMapping(String classId, Class<?> basicClass) {
73-
addMapping(classId, basicClass, null);
65+
// now go through all the mappings to add all additional [] that are not yet added (= no explicit implementation for it)
66+
BASIC_MAPPING.entrySet().stream()
67+
.filter(entry -> !entry.getKey().contains(Constants.LIST_MARKER))
68+
.filter(entry -> !BASIC_MAPPING.containsKey(entry.getKey() + Constants.LIST_MARKER))
69+
.collect(Collectors.toList())
70+
.forEach(entry -> addMapping(entry.getKey() + Constants.LIST_MARKER, ListWrapper.class, entry.getValue().getRawClass()));
7471
}
7572

7673
private static void addMapping(String classId, Class<?> basicClass, Class<?> genericClass) {
7774
JavaType javaType = genericClass == null ?
7875
TypeFactory.defaultInstance().constructSpecializedType(unknownType(), basicClass) :
7976
TypeFactory.defaultInstance().constructParametricType(basicClass, genericClass);
80-
8177
BASIC_MAPPING.put(classId, javaType);
8278
log.trace("Added mapping for polymorphic deserialization {} <-> {}", classId, javaType);
8379
}
@@ -97,22 +93,39 @@ public JsonTypeInfo.Id getMechanism() {
9793
return JsonTypeInfo.Id.CUSTOM;
9894
}
9995

100-
@NotNull
10196
private String getIdFromValue(Object value) {
10297
if (value == null) {
10398
log.warn("Should normally never happen, Jackson should handle nulls");
10499
return "unknown";
105100
}
106101

102+
if (value instanceof UnknownClassesContentModelData || value instanceof ListWrapper.UnknownClassesListWrapper) {
103+
log.debug("Value {} is of an unknown non-core type, we have no idea about the type, let's just skip it, it is in the data map anyway", value);
104+
return null;
105+
}
106+
107107
JsonTypeName annotation = value.getClass().getAnnotation(JsonTypeName.class);
108108
if (annotation != null && !isEmpty(annotation.value())) {
109109
log.trace("Type ID for value '{}' taken from annotation and is '{}'", value, annotation.value());
110110
return annotation.value();
111111
}
112112

113113
if (value instanceof ListWrapper && !((ListWrapper) value).empty()) {
114-
String simpleName = ((ListWrapper) value).get(0).getClass().getSimpleName();
115-
String id = getMappingName(simpleName) + LIST_MARKER;
114+
Object firstValue = ((ListWrapper) value).get(0);
115+
116+
String typeName;
117+
if (firstValue instanceof HandlesTypeInformationForListWrapper) {
118+
log.debug("First value of the list defines a type ID for the list", value);
119+
typeName = ((HandlesTypeInformationForListWrapper) firstValue).getTypeId();
120+
} else if (firstValue.getClass().isAnnotationPresent(JsonTypeName.class)) {
121+
log.debug("First value of the list is annotated with a type ID for the list", value);
122+
typeName = defaultIfBlank(firstValue.getClass().getAnnotation(JsonTypeName.class).value(),
123+
firstValue.getClass().getSimpleName());
124+
} else {
125+
log.debug("First value of the list doesn't know a type ID for the list, using simple name", value);
126+
typeName = firstValue.getClass().getSimpleName();
127+
}
128+
String id = getMappingName(typeName) + Constants.LIST_MARKER;
116129
log.trace("Value is instance of ListWrapper without an explicit implementation, value = '{}', id = '{}'", id);
117130
return id;
118131
}
@@ -127,8 +140,13 @@ private String getMappingName(String simpleName) {
127140
}
128141

129142
@Override
130-
public JavaType typeFromId(DatabindContext context, String id) throws IOException {
143+
public JavaType typeFromId(DatabindContext context, String id) {
131144
JavaType javaType = BASIC_MAPPING.get(id);
145+
if (javaType == null) {
146+
log.debug("Found id = {} which we don't know, create a map or list of maps to just save the data", id);
147+
return TypeFactory.defaultInstance().constructSpecializedType(unknownType(),
148+
id.contains(Constants.LIST_MARKER) ? ListWrapper.UnknownClassesListWrapper.class : UnknownClassesContentModelData.class);
149+
}
132150
log.trace("Type ID '{}' is mapped to '{}'", id, javaType);
133151
return javaType;
134152
}

dxa-framework/dxa-data-model/src/main/java/com/sdl/dxa/api/datamodel/json/Polymorphic.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
import com.sdl.dxa.api.datamodel.DataModelSpringConfiguration;
55

66
/**
7-
* Annotation that marks object as polymorphic.
7+
* Annotation that marks object as polymorphic meaning that it may have subclasses.
88
* Used by {@link ObjectMapper} to set {@link PolymorphicObjectMixin} in {@link DataModelSpringConfiguration}.
9+
*
10+
* @dxa.publicApi
911
*/
1012
public @interface Polymorphic {
1113

dxa-framework/dxa-data-model/src/main/java/com/sdl/dxa/api/datamodel/json/PolymorphicObjectMixin.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
66
import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;
77

8+
import static com.sdl.dxa.api.datamodel.json.Constants.DOLLAR_TYPE;
9+
810
/**
911
* Mix-in for Jackson that makes everything polymorphic.
1012
* <p>To be added to the configuration of {@link ObjectMapper} to make all Java objects polymorphic,
@@ -13,7 +15,7 @@
1315
@SuppressWarnings("unused")
1416
@JsonTypeResolver(ModelDataTypeResolver.class)
1517
@JsonTypeIdResolver(ModelDataTypeIdResolver.class)
16-
@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, include = JsonTypeInfo.As.PROPERTY, property = "$type")
18+
@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, include = JsonTypeInfo.As.PROPERTY, property = DOLLAR_TYPE, visible = true)
1719
public interface PolymorphicObjectMixin {
1820

1921
}

dxa-framework/dxa-data-model/src/main/java/com/sdl/dxa/api/datamodel/model/ContentModelData.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import java.util.HashMap;
99
import java.util.Map;
1010

11+
import static com.sdl.dxa.api.datamodel.json.Constants.DOLLAR_TYPE;
12+
1113
/**
1214
* {@link Map} implemenation to handle DXA polymorphic JSON logic. Is created to be able to address to {@code ContentModelData[]}.
1315
*/
@@ -36,6 +38,24 @@ public ContentModelData(ContentModelData other) {
3638
}
3739
//endregion
3840

41+
@Override
42+
public Object put(String key, Object value) {
43+
return shouldRemoveDollarType() && DOLLAR_TYPE.equals(key) ? null : super.put(key, value);
44+
}
45+
46+
@Override
47+
public void putAll(Map<? extends String, ?> m) {
48+
if (shouldRemoveDollarType()) {
49+
m.remove(DOLLAR_TYPE);
50+
}
51+
super.putAll(m);
52+
}
53+
54+
@Override
55+
public Object putIfAbsent(String key, Object value) {
56+
return shouldRemoveDollarType() && DOLLAR_TYPE.equals(key) ? null : super.putIfAbsent(key, value);
57+
}
58+
3959
@Override
4060
public Object getElement(String identifier) {
4161
return get(identifier);
@@ -60,4 +80,13 @@ public Object getWrappedModel() {
6080
}
6181
};
6282
}
83+
84+
/**
85+
* $type is removed only for unknown classes which are mapped to a a map.
86+
*
87+
* @return true unless overridden
88+
*/
89+
protected boolean shouldRemoveDollarType() {
90+
return true;
91+
}
6392
}

dxa-framework/dxa-data-model/src/main/java/com/sdl/dxa/api/datamodel/model/PageModelData.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.sdl.dxa.api.datamodel.model;
22

33
import com.fasterxml.jackson.annotation.JsonTypeName;
4-
import com.sdl.dxa.api.datamodel.model.condition.Condition;
54
import com.sdl.dxa.api.datamodel.model.util.ModelDataWrapper;
65
import lombok.AllArgsConstructor;
76
import lombok.Data;
@@ -32,8 +31,6 @@ public class PageModelData extends ViewModelData {
3231

3332
private List<RegionModelData> regions;
3433

35-
private List<Condition> conditions;
36-
3734
private String urlPath;
3835

3936
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.sdl.dxa.api.datamodel.model.util;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnore;
4+
import com.sdl.dxa.api.datamodel.json.Polymorphic;
5+
6+
/**
7+
* If you have a parent class and its children, and you have a {@code ListWrapper}&lt;{@code ParentClass}&gt; with
8+
* any children type expected inside the list, then we cannot guess the generic type of the list because generics in Java
9+
* are removed in runtime. If we simply guess the generic type by the first element, we may be wrong.
10+
* <p>To handle this we need this interface. It should be implemented by parent class of the parent-children hierarchy.
11+
* When we need to get a type of the whole list, the first element will be checked for this, and if the parent implements
12+
* this, then we will get a list of parents.</p>
13+
* <p>A parent should also implement {@link Polymorphic}, so we know it is a parent and not a concrete class. By the way,
14+
* it cannot be abstract or interface. Sorry about this.</p>
15+
*
16+
* @dxa.publicApi
17+
*/
18+
@FunctionalInterface
19+
public interface HandlesTypeInformationForListWrapper {
20+
21+
/**
22+
* Returns {@code Type ID} of the implementor. Implement on parent (top level) classes.
23+
*
24+
* @return type id
25+
*/
26+
@JsonIgnore
27+
String getTypeId();
28+
}

dxa-framework/dxa-data-model/src/main/java/com/sdl/dxa/api/datamodel/model/util/ListWrapper.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
import com.sdl.dxa.api.datamodel.model.RichTextData;
99
import lombok.AccessLevel;
1010
import lombok.Data;
11+
import lombok.Getter;
1112
import lombok.NoArgsConstructor;
1213
import lombok.Setter;
1314

1415
import java.util.ArrayList;
1516
import java.util.List;
1617

18+
import static com.sdl.dxa.api.datamodel.json.Constants.DOLLAR_TYPE;
19+
1720
/**
1821
* Wrapper for the polymorphic list JSON representation coming from .NET Template Builder.
1922
* <p>It serializes the list as the following meaning that all elements of a list are of type {@link ContentModelData}</p>
@@ -96,4 +99,20 @@ public RichTextDataListWrapper(List<RichTextData> values) {
9699
super(values);
97100
}
98101
}
102+
103+
/**
104+
* List of unknown entity-level {@link UnknownClassesContentModelData}s.
105+
*/
106+
@NoArgsConstructor(access = AccessLevel.PUBLIC)
107+
public static class UnknownClassesListWrapper extends ListWrapper<UnknownClassesContentModelData> {
108+
109+
@JsonProperty(DOLLAR_TYPE)
110+
@Getter
111+
private String type;
112+
113+
@SuppressWarnings("unused")
114+
public UnknownClassesListWrapper(List<UnknownClassesContentModelData> values) {
115+
super(values);
116+
}
117+
}
99118
}

0 commit comments

Comments
 (0)