Skip to content

Commit eecb03d

Browse files
authored
Add a strict object type resolver builder (#111)
1 parent 1eda3c5 commit eecb03d

File tree

12 files changed

+400
-23
lines changed

12 files changed

+400
-23
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2016-2023 Berry Cloud Ltd. All rights reserved.
3+
*/
4+
5+
package dev.learning.xapi.jackson;
6+
7+
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
8+
import com.fasterxml.jackson.core.JsonParser;
9+
import com.fasterxml.jackson.databind.BeanProperty;
10+
import com.fasterxml.jackson.databind.DeserializationConfig;
11+
import com.fasterxml.jackson.databind.DeserializationContext;
12+
import com.fasterxml.jackson.databind.JavaType;
13+
import com.fasterxml.jackson.databind.jsontype.NamedType;
14+
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
15+
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
16+
import com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer;
17+
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
18+
import com.fasterxml.jackson.databind.util.TokenBuffer;
19+
import dev.learning.xapi.model.ObjectType;
20+
import java.io.IOException;
21+
import java.util.Collection;
22+
23+
/**
24+
* Custom TypeResolverBuilder which accepts only valid strings as type identifiers.
25+
*
26+
* @author István Rátkai (Selindek)
27+
*/
28+
public class StrictObjectTypeResolverBuilder extends StdTypeResolverBuilder {
29+
30+
@Override
31+
public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType,
32+
Collection<NamedType> subtypes) {
33+
final var subTypeValidator = verifyBaseTypeValidity(config, baseType);
34+
final var idRes = idResolver(config, baseType, subTypeValidator, subtypes, false, true);
35+
final var defaultImpl = defineDefaultImpl(config, baseType);
36+
37+
return new StrictObjectTypePropertyDeserializer(baseType, idRes, _typeProperty, _typeIdVisible,
38+
defaultImpl, _includeAs);
39+
40+
}
41+
42+
/**
43+
* Custom {@link AsPropertyTypeDeserializer} which accepts only valid {@link ObjectType} as
44+
* property value.
45+
*/
46+
public static class StrictObjectTypePropertyDeserializer extends AsPropertyTypeDeserializer {
47+
48+
private static final long serialVersionUID = 1139909729567678431L;
49+
50+
public StrictObjectTypePropertyDeserializer(JavaType baseType, TypeIdResolver idRes,
51+
String typeProperty, boolean typeIdVisible, JavaType defaultImpl, As includeAs) {
52+
super(baseType, idRes, typeProperty, typeIdVisible, defaultImpl, includeAs);
53+
}
54+
55+
public StrictObjectTypePropertyDeserializer(AsPropertyTypeDeserializer src,
56+
BeanProperty property) {
57+
super(src, property);
58+
}
59+
60+
@Override
61+
public TypeDeserializer forProperty(BeanProperty prop) {
62+
return (prop == _property) ? this : new StrictObjectTypePropertyDeserializer(this, prop);
63+
}
64+
65+
@Override
66+
protected Object _deserializeTypedForId(JsonParser p, DeserializationContext ctxt,
67+
TokenBuffer tb, String typeId) throws IOException {
68+
69+
// This is the actual custom logic.
70+
if (ObjectType.getByValue(typeId).isEmpty()) {
71+
throw ctxt.instantiationException(null, "Invalid objectType: " + typeId);
72+
}
73+
// Everything else is just unavoidable duplication of the original code.
74+
75+
return super._deserializeTypedForId(p, ctxt, tb, typeId);
76+
}
77+
78+
}
79+
80+
}

xapi-model/src/main/java/dev/learning/xapi/model/Activity.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package dev.learning.xapi.model;
66

7+
import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;
8+
import dev.learning.xapi.jackson.StrictObjectTypeResolverBuilder;
79
import dev.learning.xapi.model.validation.constraints.ValidActivityDefinition;
810
import jakarta.validation.Valid;
911
import jakarta.validation.constraints.NotNull;
@@ -27,6 +29,7 @@
2729
@Builder
2830
@AllArgsConstructor
2931
@EqualsAndHashCode(exclude = "definition")
32+
@JsonTypeResolver(StrictObjectTypeResolverBuilder.class)
3033
public class Activity implements StatementObject, SubStatementObject {
3134

3235
/**

xapi-model/src/main/java/dev/learning/xapi/model/Actor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import com.fasterxml.jackson.annotation.JsonSubTypes;
1111
import com.fasterxml.jackson.annotation.JsonTypeInfo;
1212
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
13+
import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;
14+
import dev.learning.xapi.jackson.StrictObjectTypeResolverBuilder;
1315
import dev.learning.xapi.model.validation.constraints.Mbox;
1416
import jakarta.validation.Valid;
1517
import java.net.URI;
@@ -39,6 +41,7 @@
3941
@JsonSubTypes.Type(value = Agent.class, name = "Person"),
4042
@JsonSubTypes.Type(value = Group.class, name = "Group")})
4143
@JsonInclude(Include.NON_EMPTY)
44+
@JsonTypeResolver(StrictObjectTypeResolverBuilder.class)
4245
public abstract class Actor implements StatementObject, SubStatementObject {
4346

4447
/**

xapi-model/src/main/java/dev/learning/xapi/model/Agent.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
package dev.learning.xapi.model;
66

77
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
8+
import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;
9+
import dev.learning.xapi.jackson.StrictObjectTypeResolverBuilder;
810
import lombok.EqualsAndHashCode;
911
import lombok.Getter;
1012
import lombok.NoArgsConstructor;
@@ -24,6 +26,7 @@
2426
@EqualsAndHashCode(callSuper = true)
2527
@ToString(callSuper = true)
2628
@JsonIgnoreProperties(value = {"firstName", "lastName"})
29+
@JsonTypeResolver(StrictObjectTypeResolverBuilder.class)
2730
public class Agent extends Actor {
2831

2932
// **Warning** do not add fields that are not required by the xAPI specification.

xapi-model/src/main/java/dev/learning/xapi/model/Attachment.java

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
import java.net.URI;
1313
import java.nio.charset.StandardCharsets;
1414
import java.security.MessageDigest;
15-
import java.security.NoSuchAlgorithmException;
1615
import java.util.Locale;
1716
import lombok.Builder;
1817
import lombok.EqualsAndHashCode;
18+
import lombok.SneakyThrows;
1919
import lombok.Value;
2020
import lombok.With;
2121

@@ -201,23 +201,19 @@ public Builder content(String content) {
201201

202202
}
203203

204+
@SneakyThrows
204205
private static String sha256Hex(byte[] data) {
205-
try {
206-
final var digest = MessageDigest.getInstance("SHA-256");
207-
final var hash = digest.digest(data);
208-
final var hexString = new StringBuilder(2 * hash.length);
209-
for (final byte element : hash) {
210-
final var hex = Integer.toHexString(0xff & element);
211-
if (hex.length() == 1) {
212-
hexString.append('0');
213-
}
214-
hexString.append(hex);
206+
final var digest = MessageDigest.getInstance("SHA-256");
207+
final var hash = digest.digest(data);
208+
final var hexString = new StringBuilder(2 * hash.length);
209+
for (final byte element : hash) {
210+
final var hex = Integer.toHexString(0xff & element);
211+
if (hex.length() == 1) {
212+
hexString.append('0');
215213
}
216-
return hexString.toString();
217-
} catch (final NoSuchAlgorithmException e) {
218-
throw new IllegalArgumentException(e);
214+
hexString.append(hex);
219215
}
220-
216+
return hexString.toString();
221217
}
222218
}
223219

xapi-model/src/main/java/dev/learning/xapi/model/Group.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
import com.fasterxml.jackson.annotation.JsonFormat;
88
import com.fasterxml.jackson.annotation.JsonIgnore;
9+
import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;
10+
import dev.learning.xapi.jackson.StrictObjectTypeResolverBuilder;
911
import dev.learning.xapi.model.validation.constraints.ValidActor;
1012
import jakarta.validation.Valid;
1113
import java.util.ArrayList;
@@ -29,6 +31,7 @@
2931
@AllArgsConstructor
3032
@EqualsAndHashCode(callSuper = true)
3133
@ToString(callSuper = true)
34+
@JsonTypeResolver(StrictObjectTypeResolverBuilder.class)
3235
public class Group extends Actor {
3336

3437
/**

xapi-model/src/main/java/dev/learning/xapi/model/ObjectType.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
package dev.learning.xapi.model;
66

77
import com.fasterxml.jackson.annotation.JsonProperty;
8+
import java.util.Arrays;
9+
import java.util.Optional;
810

911
/**
1012
* This enumeration class represents all valid xAPI object types.
@@ -17,13 +19,13 @@ public enum ObjectType {
1719
* Activity object type.
1820
*/
1921
@JsonProperty("Activity")
20-
ACTIVITY,
22+
ACTIVITY("Activity"),
2123

2224
/**
2325
* Agent object type.
2426
*/
2527
@JsonProperty("Agent")
26-
AGENT,
28+
AGENT("Agent"),
2729

2830
/**
2931
* Person object type.
@@ -36,25 +38,35 @@ public enum ObjectType {
3638
* Statements to 1.0.0</a>
3739
*/
3840
@JsonProperty("Person")
39-
PERSON,
41+
PERSON("Person"),
4042

4143
/**
4244
* Group object type.
4345
*/
4446
@JsonProperty("Group")
45-
GROUP,
47+
GROUP("Group"),
4648

4749
/**
4850
* SubStatement object type.
4951
*/
5052
@JsonProperty("SubStatement")
51-
SUBSTATEMENT,
53+
SUBSTATEMENT("SubStatement"),
5254

5355
/**
5456
* StatementRef object type.
5557
*/
5658
@JsonProperty("StatementRef")
57-
STATEMENTREF;
59+
STATEMENTREF("StatementRef");
60+
61+
private String value;
62+
63+
ObjectType(String value) {
64+
this.value = value;
65+
}
66+
67+
public static Optional<ObjectType> getByValue(String url) {
68+
return Arrays.stream(ObjectType.values()).filter(env -> env.value.equals(url)).findFirst();
69+
}
5870

5971
// **Warning** do not add fields that are not required by the xAPI specification.
6072
}

xapi-model/src/main/java/dev/learning/xapi/model/StatementObject.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import com.fasterxml.jackson.annotation.JsonSubTypes;
1010
import com.fasterxml.jackson.annotation.JsonTypeInfo;
1111
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
12+
import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;
13+
import dev.learning.xapi.jackson.StrictObjectTypeResolverBuilder;
1214

1315
/**
1416
* This interface represents the xAPI statement object.
@@ -26,6 +28,7 @@
2628
@JsonSubTypes.Type(value = Group.class, name = "Group"),
2729
@JsonSubTypes.Type(value = SubStatement.class, name = "SubStatement"),
2830
@JsonSubTypes.Type(value = StatementReference.class, name = "StatementRef")})
31+
@JsonTypeResolver(StrictObjectTypeResolverBuilder.class)
2932
public interface StatementObject {
3033

3134
}

xapi-model/src/main/java/dev/learning/xapi/model/StatementReference.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package dev.learning.xapi.model;
66

7+
import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;
8+
import dev.learning.xapi.jackson.StrictObjectTypeResolverBuilder;
79
import dev.learning.xapi.model.validation.constraints.Variant;
810
import jakarta.validation.constraints.NotNull;
911
import java.util.UUID;
@@ -21,6 +23,7 @@
2123
*/
2224
@Value
2325
@Builder
26+
@JsonTypeResolver(StrictObjectTypeResolverBuilder.class)
2427
public class StatementReference implements StatementObject, SubStatementObject {
2528

2629
/**

xapi-model/src/main/java/dev/learning/xapi/model/SubStatement.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package dev.learning.xapi.model;
66

7+
import com.fasterxml.jackson.databind.annotation.JsonTypeResolver;
8+
import dev.learning.xapi.jackson.StrictObjectTypeResolverBuilder;
79
import dev.learning.xapi.model.validation.constraints.ValidActor;
810
import dev.learning.xapi.model.validation.constraints.ValidStatementPlatform;
911
import dev.learning.xapi.model.validation.constraints.ValidStatementRevision;
@@ -30,6 +32,7 @@
3032
@ValidStatementPlatform
3133
@ValidStatementRevision
3234
@EqualsAndHashCode(exclude = {"timestamp", "attachments"})
35+
@JsonTypeResolver(StrictObjectTypeResolverBuilder.class)
3336
public class SubStatement implements StatementObject, CoreStatement {
3437

3538
/**
@@ -171,7 +174,7 @@ public Builder verb(Verb verb) {
171174
*/
172175
public Builder addAttachment(Consumer<Attachment.Builder> attachment) {
173176

174-
final Attachment.Builder builder = Attachment.builder();
177+
final var builder = Attachment.builder();
175178

176179
attachment.accept(builder);
177180

0 commit comments

Comments
 (0)