Skip to content

Commit c0bb9e4

Browse files
committed
feat: Implement custom schema module to support reflective subtypes
1 parent 475dd26 commit c0bb9e4

13 files changed

+1084
-801
lines changed

pom.xml

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@
6767
</properties>
6868

6969
<dependencies>
70+
<dependency>
71+
<groupId>com.fasterxml</groupId>
72+
<artifactId>classmate</artifactId>
73+
<version>1.7.0</version>
74+
</dependency>
7075
<!-- scope: compile -->
7176
<dependency>
7277
<groupId>com.fasterxml.jackson.core</groupId>
@@ -80,6 +85,16 @@
8085
<groupId>com.fasterxml.jackson.core</groupId>
8186
<artifactId>jackson-databind</artifactId>
8287
</dependency>
88+
<dependency>
89+
<groupId>com.github.victools</groupId>
90+
<artifactId>jsonschema-generator</artifactId>
91+
<version>4.38.0</version>
92+
</dependency>
93+
<dependency>
94+
<groupId>com.github.victools</groupId>
95+
<artifactId>jsonschema-module-jackson</artifactId>
96+
<version>4.38.0</version>
97+
</dependency>
8398
<dependency>
8499
<groupId>jakarta.xml.bind</groupId>
85100
<artifactId>jakarta.xml.bind-api</artifactId>
@@ -96,6 +111,10 @@
96111
<groupId>org.apache.logging.log4j</groupId>
97112
<artifactId>log4j-core</artifactId>
98113
</dependency>
114+
<dependency>
115+
<groupId>org.reflections</groupId>
116+
<artifactId>reflections</artifactId>
117+
</dependency>
99118
<!-- scope: test -->
100119
<dependency>
101120
<groupId>org.junit.jupiter</groupId>
@@ -168,34 +187,6 @@
168187
</compilerArgs>
169188
</configuration>
170189
</plugin>
171-
<!-- Generate JSON schema from Java classes -->
172-
<plugin>
173-
<groupId>com.github.victools</groupId>
174-
<artifactId>jsonschema-maven-plugin</artifactId>
175-
<version>4.38.0</version>
176-
<configuration>
177-
<classNames>de.rub.nds.modifiablevariable.ModifiableVariable</classNames>
178-
<schemaFilePath>src/main/resources</schemaFilePath>
179-
<schemaFileName>{0}.schema.json</schemaFileName>
180-
<schemaVersion>DRAFT_2020_12</schemaVersion>
181-
<modules>
182-
<module>
183-
<name>Jackson</name>
184-
<options>
185-
<option>RESPECT_JSONPROPERTY_REQUIRED</option>
186-
<option>ALWAYS_REF_SUBTYPES</option>
187-
</options>
188-
</module>
189-
</modules>
190-
</configuration>
191-
<executions>
192-
<execution>
193-
<goals>
194-
<goal>generate</goal>
195-
</goals>
196-
</execution>
197-
</executions>
198-
</plugin>
199190
<!-- Execute unit tests -->
200191
<plugin>
201192
<groupId>org.apache.maven.plugins</groupId>
@@ -215,6 +206,29 @@
215206
<skipTests>${skip.surefire.tests}</skipTests>
216207
</configuration>
217208
</plugin>
209+
<!-- Generate JSON schema from Java classes -->
210+
<plugin>
211+
<groupId>org.codehaus.mojo</groupId>
212+
<artifactId>exec-maven-plugin</artifactId>
213+
<version>3.5.1</version>
214+
<executions>
215+
<execution>
216+
<id>generate-json-schema</id>
217+
<goals>
218+
<goal>java</goal>
219+
</goals>
220+
<phase>prepare-package</phase>
221+
<configuration>
222+
<mainClass>de.rub.nds.modifiablevariable.serialization.JsonSchemaCliGenerator</mainClass>
223+
<arguments>
224+
<argument>de.rub.nds.modifiablevariable.ModifiableVariable</argument>
225+
<argument>src/main/resources/ModifiableVariable.schema.json</argument>
226+
<argument>de.rub.nds.modifiablevariable.serialization.ModifiableVariableModule</argument>
227+
</arguments>
228+
</configuration>
229+
</execution>
230+
</executions>
231+
</plugin>
218232
<!-- Build jar file -->
219233
<plugin>
220234
<groupId>org.apache.maven.plugins</groupId>

src/main/java/de/rub/nds/modifiablevariable/ModifiableVariable.java

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,6 @@
88
package de.rub.nds.modifiablevariable;
99

1010
import com.fasterxml.jackson.annotation.*;
11-
import de.rub.nds.modifiablevariable.biginteger.ModifiableBigInteger;
12-
import de.rub.nds.modifiablevariable.bool.ModifiableBoolean;
13-
import de.rub.nds.modifiablevariable.bytearray.ModifiableByteArray;
14-
import de.rub.nds.modifiablevariable.integer.ModifiableInteger;
15-
import de.rub.nds.modifiablevariable.length.ModifiableLengthField;
16-
import de.rub.nds.modifiablevariable.longint.ModifiableLong;
17-
import de.rub.nds.modifiablevariable.singlebyte.ModifiableByte;
18-
import de.rub.nds.modifiablevariable.string.ModifiableString;
1911
import jakarta.xml.bind.annotation.*;
2012
import java.io.Serializable;
2113
import java.util.LinkedList;
@@ -40,17 +32,10 @@
4032
*/
4133
@XmlTransient
4234
@XmlAccessorType(XmlAccessType.FIELD)
43-
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@type")
44-
@JsonSubTypes({
45-
@JsonSubTypes.Type(name = "BigInteger", value = ModifiableBigInteger.class),
46-
@JsonSubTypes.Type(name = "Boolean", value = ModifiableBoolean.class),
47-
@JsonSubTypes.Type(name = "ByteArray", value = ModifiableByteArray.class),
48-
@JsonSubTypes.Type(name = "Integer", value = ModifiableInteger.class),
49-
@JsonSubTypes.Type(name = "LengthField", value = ModifiableLengthField.class),
50-
@JsonSubTypes.Type(name = "Long", value = ModifiableLong.class),
51-
@JsonSubTypes.Type(name = "Byte", value = ModifiableByte.class),
52-
@JsonSubTypes.Type(name = "String", value = ModifiableString.class),
53-
})
35+
@JsonTypeInfo(
36+
use = JsonTypeInfo.Id.SIMPLE_NAME,
37+
include = JsonTypeInfo.As.PROPERTY,
38+
property = "@type")
5439
@JsonAutoDetect(
5540
fieldVisibility = JsonAutoDetect.Visibility.ANY,
5641
getterVisibility = JsonAutoDetect.Visibility.NONE,

src/main/java/de/rub/nds/modifiablevariable/VariableModification.java

Lines changed: 4 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,11 @@
1010
import static de.rub.nds.modifiablevariable.util.StringUtil.backslashEscapeString;
1111

1212
import com.fasterxml.jackson.annotation.JsonAutoDetect;
13-
import com.fasterxml.jackson.annotation.JsonSubTypes;
1413
import com.fasterxml.jackson.annotation.JsonTypeInfo;
1514
import de.rub.nds.modifiablevariable.biginteger.*;
16-
import de.rub.nds.modifiablevariable.bool.BooleanExplicitValueModification;
17-
import de.rub.nds.modifiablevariable.bool.BooleanToggleModification;
1815
import de.rub.nds.modifiablevariable.bytearray.*;
1916
import de.rub.nds.modifiablevariable.integer.*;
2017
import de.rub.nds.modifiablevariable.longint.*;
21-
import de.rub.nds.modifiablevariable.singlebyte.ByteAddModification;
22-
import de.rub.nds.modifiablevariable.singlebyte.ByteExplicitValueModification;
23-
import de.rub.nds.modifiablevariable.singlebyte.ByteSubtractModification;
24-
import de.rub.nds.modifiablevariable.singlebyte.ByteXorModification;
2518
import de.rub.nds.modifiablevariable.string.*;
2619
import de.rub.nds.modifiablevariable.util.ArrayConverter;
2720
import jakarta.xml.bind.annotation.XmlAccessType;
@@ -59,67 +52,10 @@
5952
*/
6053
@XmlTransient
6154
@XmlAccessorType(XmlAccessType.FIELD)
62-
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@type")
63-
@JsonSubTypes({
64-
@JsonSubTypes.Type(name = "BigIntegerAdd", value = BigIntegerAddModification.class),
65-
@JsonSubTypes.Type(
66-
name = "BigIntegerExplicitValue",
67-
value = BigIntegerExplicitValueModification.class),
68-
@JsonSubTypes.Type(name = "BigIntegerMultiply", value = BigIntegerMultiplyModification.class),
69-
@JsonSubTypes.Type(name = "BigIntegerShiftLeft", value = BigIntegerShiftLeftModification.class),
70-
@JsonSubTypes.Type(
71-
name = "BigIntegerShiftRight",
72-
value = BigIntegerShiftRightModification.class),
73-
@JsonSubTypes.Type(name = "BigIntegerSubtract", value = BigIntegerSubtractModification.class),
74-
@JsonSubTypes.Type(name = "BigIntegerXor", value = BigIntegerXorModification.class),
75-
@JsonSubTypes.Type(
76-
name = "BooleanExplicitValue",
77-
value = BooleanExplicitValueModification.class),
78-
@JsonSubTypes.Type(name = "BooleanToggle", value = BooleanToggleModification.class),
79-
@JsonSubTypes.Type(
80-
name = "ByteArrayAppendValue",
81-
value = ByteArrayAppendValueModification.class),
82-
@JsonSubTypes.Type(name = "ByteArrayDelete", value = ByteArrayDeleteModification.class),
83-
@JsonSubTypes.Type(name = "ByteArrayDuplicate", value = ByteArrayDuplicateModification.class),
84-
@JsonSubTypes.Type(
85-
name = "ByteArrayExplicitValue",
86-
value = ByteArrayExplicitValueModification.class),
87-
@JsonSubTypes.Type(
88-
name = "ByteArrayInsertValue",
89-
value = ByteArrayInsertValueModification.class),
90-
@JsonSubTypes.Type(
91-
name = "ByteArrayPrependValue",
92-
value = ByteArrayPrependValueModification.class),
93-
@JsonSubTypes.Type(name = "ByteArrayShuffle", value = ByteArrayShuffleModification.class),
94-
@JsonSubTypes.Type(name = "ByteArrayXor", value = ByteArrayXorModification.class),
95-
@JsonSubTypes.Type(name = "IntegerAdd", value = IntegerAddModification.class),
96-
@JsonSubTypes.Type(
97-
name = "IntegerExplicitValue",
98-
value = IntegerExplicitValueModification.class),
99-
@JsonSubTypes.Type(name = "IntegerMultiply", value = IntegerMultiplyModification.class),
100-
@JsonSubTypes.Type(name = "IntegerShiftLeft", value = IntegerShiftLeftModification.class),
101-
@JsonSubTypes.Type(name = "IntegerShiftRight", value = IntegerShiftRightModification.class),
102-
@JsonSubTypes.Type(name = "IntegerSubtract", value = IntegerSubtractModification.class),
103-
@JsonSubTypes.Type(name = "IntegerSwapEndian", value = IntegerSwapEndianModification.class),
104-
@JsonSubTypes.Type(name = "IntegerXor", value = IntegerXorModification.class),
105-
@JsonSubTypes.Type(name = "LongAdd", value = LongAddModification.class),
106-
@JsonSubTypes.Type(name = "LongExplicitValue", value = LongExplicitValueModification.class),
107-
@JsonSubTypes.Type(name = "LongMultiply", value = LongMultiplyModification.class),
108-
@JsonSubTypes.Type(name = "LongShiftLeft", value = LongShiftLeftModification.class),
109-
@JsonSubTypes.Type(name = "LongShiftRight", value = LongShiftRightModification.class),
110-
@JsonSubTypes.Type(name = "LongSubtract", value = LongSubtractModification.class),
111-
@JsonSubTypes.Type(name = "LongSwapEndian", value = LongSwapEndianModification.class),
112-
@JsonSubTypes.Type(name = "LongXor", value = LongXorModification.class),
113-
@JsonSubTypes.Type(name = "ByteAdd", value = ByteAddModification.class),
114-
@JsonSubTypes.Type(name = "ByteExplicitValue", value = ByteExplicitValueModification.class),
115-
@JsonSubTypes.Type(name = "ByteSubtract", value = ByteSubtractModification.class),
116-
@JsonSubTypes.Type(name = "ByteXor", value = ByteXorModification.class),
117-
@JsonSubTypes.Type(name = "StringAppendValue", value = StringAppendValueModification.class),
118-
@JsonSubTypes.Type(name = "StringDelete", value = StringDeleteModification.class),
119-
@JsonSubTypes.Type(name = "StringExplicitValue", value = StringExplicitValueModification.class),
120-
@JsonSubTypes.Type(name = "StringInsertValue", value = StringInsertValueModification.class),
121-
@JsonSubTypes.Type(name = "StringPrependValue", value = StringPrependValueModification.class),
122-
})
55+
@JsonTypeInfo(
56+
use = JsonTypeInfo.Id.SIMPLE_NAME,
57+
include = JsonTypeInfo.As.PROPERTY,
58+
property = "@type")
12359
@JsonAutoDetect(
12460
fieldVisibility = JsonAutoDetect.Visibility.ANY,
12561
getterVisibility = JsonAutoDetect.Visibility.NONE,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* ModifiableVariable - A Variable Concept for Runtime Modifications
3+
*
4+
* Ruhr University Bochum, Paderborn University, Technology Innovation Institute, and Hackmanit GmbH
5+
*
6+
* Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
7+
*/
8+
package de.rub.nds.modifiablevariable.serialization;
9+
10+
import com.fasterxml.jackson.databind.JsonNode;
11+
import com.fasterxml.jackson.databind.Module;
12+
import com.fasterxml.jackson.databind.ObjectMapper;
13+
import com.github.victools.jsonschema.generator.*;
14+
import java.io.File;
15+
import java.lang.reflect.InvocationTargetException;
16+
17+
public class JsonSchemaCliGenerator {
18+
19+
/**
20+
* A command line tool to generate a JSON schema for a given class using Jackson and victools.
21+
*
22+
* @param args args[0] is the fully qualified class name for which to generate the schema,
23+
* args[1] is the output file path, and any additional arguments are class names of
24+
* additional Jackson modules to register.
25+
*/
26+
public static void main(String[] args) {
27+
ObjectMapper mapper = new ObjectMapper();
28+
29+
// Register additional modules (if any) passed as command line arguments
30+
for (int i = 2; i < args.length; i++) {
31+
try {
32+
mapper.registerModule(
33+
(Module) Class.forName(args[i]).getConstructor().newInstance());
34+
} catch (InstantiationException
35+
| IllegalAccessException
36+
| InvocationTargetException
37+
| NoSuchMethodException
38+
| ClassNotFoundException e) {
39+
throw new RuntimeException(e);
40+
}
41+
}
42+
43+
// Prepare the schema generator
44+
SchemaGeneratorConfigBuilder builder =
45+
new SchemaGeneratorConfigBuilder(
46+
mapper, SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON);
47+
builder.with(new ReflectiveJacksonSchemaModule());
48+
SchemaGeneratorConfig config = builder.build();
49+
SchemaGenerator generator = new SchemaGenerator(config);
50+
JsonNode jsonSchema;
51+
52+
// Generate the schema for the specified class
53+
try {
54+
jsonSchema = generator.generateSchema(Class.forName(args[0]));
55+
} catch (ClassNotFoundException e) {
56+
throw new RuntimeException(e);
57+
}
58+
59+
// Output the generated schema to the specified file
60+
File outputFile = new File(args[1]);
61+
//noinspection AssertWithSideEffects
62+
assert outputFile.exists() || outputFile.mkdirs();
63+
try {
64+
mapper.writerWithDefaultPrettyPrinter().writeValue(outputFile, jsonSchema);
65+
} catch (Exception e) {
66+
throw new RuntimeException(
67+
"Failed to write JSON schema to file: " + outputFile.getAbsolutePath(), e);
68+
}
69+
}
70+
}

src/main/java/de/rub/nds/modifiablevariable/util/ModifiableVariableModule.java renamed to src/main/java/de/rub/nds/modifiablevariable/serialization/ModifiableVariableModule.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*
66
* Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
77
*/
8-
package de.rub.nds.modifiablevariable.util;
8+
package de.rub.nds.modifiablevariable.serialization;
99

1010
import com.fasterxml.jackson.core.JsonGenerator;
1111
import com.fasterxml.jackson.core.JsonParser;
@@ -15,7 +15,11 @@
1515
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
1616
import com.fasterxml.jackson.databind.module.SimpleModule;
1717
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
18+
import de.rub.nds.modifiablevariable.ModifiableVariable;
19+
import de.rub.nds.modifiablevariable.VariableModification;
20+
import de.rub.nds.modifiablevariable.util.ArrayConverter;
1821
import java.io.IOException;
22+
import org.reflections.Reflections;
1923

2024
/**
2125
* A Jackson module for the ModifiableVariable library. It registers serializers and deserializers
@@ -32,6 +36,10 @@ public ModifiableVariableModule() {
3236
super(MODULE_NAME, new Version(1, 0, 0, null, "de.rub.nds", "modifiable-variable"));
3337
addSerializer(byte[].class, new UnformattedByteArraySerializer());
3438
addDeserializer(byte[].class, new UnformattedByteArrayDeserializer());
39+
// Subtypes
40+
Reflections reflections = new Reflections("de.rub.nds.modifiablevariable");
41+
reflections.getSubTypesOf(ModifiableVariable.class).forEach(this::registerSubtypes);
42+
reflections.getSubTypesOf(VariableModification.class).forEach(this::registerSubtypes);
3543
}
3644

3745
public static class UnformattedByteArraySerializer extends StdSerializer<byte[]> {

0 commit comments

Comments
 (0)