Skip to content

Commit 65c6db0

Browse files
committed
[Enhancement #68] Cache type map for deserialization to prevent repeated classpath scanning.
1 parent 6a46ce7 commit 65c6db0

File tree

5 files changed

+95
-21
lines changed

5 files changed

+95
-21
lines changed

src/main/java/cz/cvut/kbss/jsonld/ConfigParam.java

+25-10
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ public enum ConfigParam {
3939
/**
4040
* Whether to require an identifier when serializing an object.
4141
* <p>
42-
* If set to {@code true} and no identifier is found (either there is no identifier field or its value is {@code
43-
* null}), an exception will be thrown. If configured to {@code false}, a blank node identifier is generated if no
44-
* id is present.
42+
* If set to {@code true} and no identifier is found (either there is no identifier field or its value is
43+
* {@code null}), an exception will be thrown. If configured to {@code false}, a blank node identifier is generated
44+
* if no id is present.
4545
*/
4646
REQUIRE_ID("requireId"),
4747

@@ -59,8 +59,8 @@ public enum ConfigParam {
5959
* Enables optimistic target type resolution.
6060
* <p>
6161
* This means that when a an ambiguous target type is encountered during deserialization of an object (i.e.,
62-
* multiple concrete classes match the data type), instead of throwing an {@link
63-
* cz.cvut.kbss.jsonld.exception.AmbiguousTargetTypeException}, one of the classes will be selected for
62+
* multiple concrete classes match the data type), instead of throwing an
63+
* {@link cz.cvut.kbss.jsonld.exception.AmbiguousTargetTypeException}, one of the classes will be selected for
6464
* instantiation.
6565
* <p>
6666
* Note that enabling this behavior should probably be done together with setting {@link #IGNORE_UNKNOWN_PROPERTIES}
@@ -98,19 +98,34 @@ public enum ConfigParam {
9898
/**
9999
* Format string used to serialize and deserialize datetime values.
100100
* <p>
101-
* Note that if {@link #SERIALIZE_DATETIME_AS_MILLIS} is enabled, this parameter has no effect on serialization of datetime.
101+
* Note that if {@link #SERIALIZE_DATETIME_AS_MILLIS} is enabled, this parameter has no effect on serialization of
102+
* datetime.
102103
* <p>
103-
* Also note that this format applies only to full datetime values. Date or time values have to be formatted per-attribute.
104+
* Also note that this format applies only to full datetime values. Date or time values have to be formatted
105+
* per-attribute.
104106
*/
105107
DATE_TIME_FORMAT("datetimeFormat"),
106108

107109
/**
108110
* Whether to serialize individuals using expanded term definition in context.
109-
*
111+
* <p>
110112
* This basically means that the individual's identifier is provided directly as a string and an expanded term
111-
* definition (consisting of a {@literal @id} and {@literal @type}) is added into the context, specifying that the string is an identifier.
113+
* definition (consisting of a {@literal @id} and {@literal @type}) is added into the context, specifying that the
114+
* string is an identifier.
115+
*/
116+
SERIALIZE_INDIVIDUALS_USING_EXPANDED_DEFINITION("serializeIndividualsUsingExpandedDefinition"),
117+
118+
/**
119+
* Whether to disable the type map cache.
120+
* <p>
121+
* Type map is used to map JSON-LD types to Java classes for deserialization. Since this map is built by scanning
122+
* the classpath (see the {@link #SCAN_PACKAGE} parameter), it is cached by default to avoid repeated scanning of
123+
* the classpath.
124+
* <p>
125+
* If every deserializer instance should get a fresh type map based on the current configuration, disable this
126+
* cache.
112127
*/
113-
SERIALIZE_INDIVIDUALS_USING_EXPANDED_DEFINITION("serializeIndividualsUsingExpandedDefinition");
128+
DISABLE_TYPE_MAP_CACHE("disableTypeMapCache");
114129

115130
private final String name;
116131

src/main/java/cz/cvut/kbss/jsonld/deserialization/JsonLdDeserializer.java

+25-9
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
*/
3737
public abstract class JsonLdDeserializer implements Configured {
3838

39+
private static final TypeMap TYPE_MAP = new TypeMap();
40+
3941
private final Configuration configuration;
4042

4143
protected final TargetClassResolver classResolver;
@@ -53,19 +55,33 @@ protected JsonLdDeserializer(Configuration configuration) {
5355
}
5456

5557
private TargetClassResolver initializeTargetClassResolver() {
56-
final TypeMap typeMap = new TypeMap();
5758
final String scanPath = configuration.get(ConfigParam.SCAN_PACKAGE, "");
59+
final TypeMap typeMap = discoverAvailableTypes(scanPath, configuration.is(ConfigParam.DISABLE_TYPE_MAP_CACHE));
60+
return new TargetClassResolver(typeMap,
61+
new TargetClassResolverConfig(
62+
configuration.is(ConfigParam.ASSUME_TARGET_TYPE),
63+
configuration().is(ConfigParam.ENABLE_OPTIMISTIC_TARGET_TYPE_RESOLUTION),
64+
configuration().is(ConfigParam.PREFER_SUPERCLASS)));
65+
}
66+
67+
/**
68+
* Finds potential deserialization target types on the classpath.
69+
*
70+
* @param scanPath Path to scan on classpath
71+
* @return Map of types to Java classes
72+
*/
73+
private static TypeMap discoverAvailableTypes(String scanPath, boolean disableCache) {
74+
final TypeMap map = disableCache ? new TypeMap() : TYPE_MAP;
75+
if (!map.isEmpty()) {
76+
return map;
77+
}
5878
new ClasspathScanner(c -> {
5979
final OWLClass ann = c.getDeclaredAnnotation(OWLClass.class);
6080
if (ann != null) {
61-
typeMap.register(BeanAnnotationProcessor.expandIriIfNecessary(ann.iri(), c), c);
81+
map.register(BeanAnnotationProcessor.expandIriIfNecessary(ann.iri(), c), c);
6282
}
6383
}).processClasses(scanPath);
64-
return new TargetClassResolver(typeMap,
65-
new TargetClassResolverConfig(
66-
configuration.is(ConfigParam.ASSUME_TARGET_TYPE),
67-
configuration().is(ConfigParam.ENABLE_OPTIMISTIC_TARGET_TYPE_RESOLUTION),
68-
configuration().is(ConfigParam.PREFER_SUPERCLASS)));
84+
return map;
6985
}
7086

7187
@Override
@@ -78,9 +94,9 @@ public Configuration configuration() {
7894
* <p>
7995
* If a deserializer already existed for the type, it is replaced by the new one.
8096
*
81-
* @param type Target type to register the deserializer for
97+
* @param type Target type to register the deserializer for
8298
* @param deserializer Deserializer to register
83-
* @param <T> Target type
99+
* @param <T> Target type
84100
*/
85101
public <T> void registerDeserializer(Class<T> type, ValueDeserializer<T> deserializer) {
86102
Objects.requireNonNull(type);

src/main/java/cz/cvut/kbss/jsonld/deserialization/util/TypeMap.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,19 @@
1717
*/
1818
package cz.cvut.kbss.jsonld.deserialization.util;
1919

20-
import java.util.*;
20+
import java.util.Collections;
21+
import java.util.HashMap;
22+
import java.util.HashSet;
23+
import java.util.Map;
24+
import java.util.Set;
2125

2226
/**
2327
* Represents a map of type IRIs to their mapped Java classes.
2428
* <p>
2529
* Used by the deserialization when determining target class from JSON-LD object types.
2630
* <p>
27-
* This class is not synchronized, as it is expected that types will be registered by one thread once and then only queried.
31+
* Read access is not synchronized, as it is expected that types will be registered by one thread once and then only
32+
* queried.
2833
*/
2934
public class TypeMap {
3035

@@ -41,4 +46,8 @@ public synchronized void register(String type, Class<?> cls) {
4146
public Set<Class<?>> get(String type) {
4247
return typeMap.getOrDefault(type, Collections.emptySet());
4348
}
49+
50+
public synchronized boolean isEmpty() {
51+
return typeMap.isEmpty();
52+
}
4453
}

src/test/java/cz/cvut/kbss/jsonld/deserialization/JsonLdDeserializerTest.java

+33
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import static cz.cvut.kbss.jsonld.environment.TestUtil.readAndExpand;
3636
import static org.hamcrest.MatcherAssert.assertThat;
3737
import static org.hamcrest.Matchers.hasItem;
38+
import static org.hamcrest.Matchers.not;
3839
import static org.junit.jupiter.api.Assertions.assertFalse;
3940
import static org.junit.jupiter.api.Assertions.assertThrows;
4041
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -45,6 +46,7 @@ class JsonLdDeserializerTest {
4546
void constructionScansClasspathAndBuildsTypeMap() throws Exception {
4647
final Configuration config = new Configuration();
4748
config.set(ConfigParam.SCAN_PACKAGE, "cz.cvut.kbss.jsonld");
49+
config.set(ConfigParam.DISABLE_TYPE_MAP_CACHE, "true");
4850
final JsonLdDeserializer deserializer = JsonLdDeserializer.createExpandedDeserializer(config);
4951
assertFalse(typeMap(deserializer).get(Vocabulary.STUDY).isEmpty());
5052
assertTrue(typeMap(deserializer).get(Vocabulary.STUDY).contains(Study.class));
@@ -60,6 +62,7 @@ private TypeMap typeMap(JsonLdDeserializer deserializer) throws Exception {
6062
void constructionScansClasspathWithSpecifiedPackageAndBuildsTypeMap() throws Exception {
6163
final Configuration config = new Configuration();
6264
config.set(ConfigParam.SCAN_PACKAGE, "cz.cvut.kbss.jsonld.deserialization");
65+
config.set(ConfigParam.DISABLE_TYPE_MAP_CACHE, "true");
6366
final JsonLdDeserializer deserializer = JsonLdDeserializer.createExpandedDeserializer(config);
6467
assertTrue(typeMap(deserializer).get(Vocabulary.GENERIC_MEMBER).isEmpty());
6568
assertTrue(typeMap(deserializer).get(Vocabulary.AGENT).contains(TestClass.class));
@@ -82,8 +85,38 @@ void deserializationThrowsJsonLdDeserializationExceptionWhenInputIsNotValidJsonL
8285
void constructionExpandsCompactIrisWhenBuildingTypeMap() throws Exception {
8386
final Configuration config = new Configuration();
8487
config.set(ConfigParam.SCAN_PACKAGE, "cz.cvut.kbss.jsonld.environment.model");
88+
config.set(ConfigParam.DISABLE_TYPE_MAP_CACHE, "true");
8589
final JsonLdDeserializer deserializer = JsonLdDeserializer.createExpandedDeserializer(config);
8690
assertFalse(typeMap(deserializer).get(Vocabulary.STUDY).isEmpty());
8791
assertThat(typeMap(deserializer).get(Vocabulary.STUDY), hasItem(StudyWithNamespaces.class));
8892
}
93+
94+
@Test
95+
void constructionCachesTypeMapByDefault() throws Exception {
96+
final Configuration config = new Configuration();
97+
config.set(ConfigParam.SCAN_PACKAGE, "cz.cvut.kbss.jsonld");
98+
final JsonLdDeserializer deserializerOne = JsonLdDeserializer.createExpandedDeserializer(config);
99+
final TypeMap typeMapOne = typeMap(deserializerOne);
100+
assertThat(typeMapOne.get(Vocabulary.STUDY), hasItem(Study.class));
101+
config.set(ConfigParam.SCAN_PACKAGE, "cz.cvut.kbss.jsonld.deserialization");
102+
final JsonLdDeserializer deserializerTwo = JsonLdDeserializer.createExpandedDeserializer(config);
103+
final TypeMap typeMapTwo = typeMap(deserializerTwo);
104+
// If classpath scanning occurred again, the updated scan package would be too specific for Study
105+
assertThat(typeMapTwo.get(Vocabulary.STUDY), hasItem(Study.class));
106+
}
107+
108+
@Test
109+
void constructionBuildsFreshTypeMapWhenTypeMapCacheIsDisabled() throws Exception {
110+
final Configuration config = new Configuration();
111+
config.set(ConfigParam.SCAN_PACKAGE, "cz.cvut.kbss.jsonld");
112+
final JsonLdDeserializer deserializerOne = JsonLdDeserializer.createExpandedDeserializer(config);
113+
final TypeMap typeMapOne = typeMap(deserializerOne);
114+
assertThat(typeMapOne.get(Vocabulary.STUDY), hasItem(Study.class));
115+
config.set(ConfigParam.SCAN_PACKAGE, "cz.cvut.kbss.jsonld.deserialization");
116+
config.set(ConfigParam.DISABLE_TYPE_MAP_CACHE, "true");
117+
final JsonLdDeserializer deserializerTwo = JsonLdDeserializer.createExpandedDeserializer(config);
118+
final TypeMap typeMapTwo = typeMap(deserializerTwo);
119+
// If classpath scanning occurred again, the updated scan package would be too specific for Study
120+
assertThat(typeMapTwo.get(Vocabulary.STUDY), not(hasItem(Study.class)));
121+
}
89122
}

src/test/java/cz/cvut/kbss/jsonld/deserialization/expanded/ExpandedJsonLdDeserializerTest.java

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ private static Map<URI, User> initUsers() {
108108
void setUp() {
109109
final Configuration config = new Configuration();
110110
config.set(ConfigParam.SCAN_PACKAGE, "cz.cvut.kbss.jsonld");
111+
config.set(ConfigParam.DISABLE_TYPE_MAP_CACHE, "true");
111112
this.sut = JsonLdDeserializer.createExpandedDeserializer(config);
112113
}
113114

0 commit comments

Comments
 (0)