Skip to content

Commit 8c9823d

Browse files
authored
Merge pull request #91 from kbss-cvut/development
[0.16.0] Release
2 parents 1bdfcc9 + 5030be7 commit 8c9823d

27 files changed

+641
-83
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# JB4JSON-LD Changelog
22

3+
## 0.16.0 - 2025-10-31
4+
- Support providing custom `ClassLoader` for target type discovery (GH-88).
5+
- Add annotation `@JsonLdType` which can be used as a replacement for `@OWLClass` and does not require type IRI for abstract classes (GH-89).
6+
- Add support for deserializing collections where attributes of elements reference instances further in the collection (GH-90).
7+
- Thanks to @yarisvt for contributing the PRs.
8+
39
## 0.15.4 - 2025-08-11
410
- Fix an issue with deserializing typed literals into `@Properties` (Bug #84).
511

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ public class User {
103103
| `datetimeFormat` | | Format in which datetime values are serialized (and expected for deserialization). Default is undefined, meaning that the ISO 8601 format is used. |
104104
| `serializeIndividualsUsingExpandedDefinition` | `false` | Whether individuals should be serialized as string with expanded term definition in context (consisting of `@id` and `@type`) Relevant only for context-based serializer. |
105105
| `disableTypeMapCache` | `false` | Disables type map cache. Type map is built for deserialization by scanning the classpath. |
106-
106+
| `classLoader` | `null` | Use a custom ClassLoader for loading the target classes. |
107+
| `postponeUnresolvedReferencesCheck` | `false` | Whether to postpone the unresolved references check after deserialization to the cleanup method. |
107108
See `cz.cvut.kbss.jsonld.ConfigParam`.
108109

109110
## Documentation

pom.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>cz.cvut.kbss.jsonld</groupId>
88
<artifactId>jb4jsonld</artifactId>
9-
<version>0.15.4</version>
9+
<version>0.16.0</version>
1010
<name>JB4JSON-LD</name>
1111
<description>Java Binding for JSON-LD allows serialization and deserialization of Java POJOs to/from JSON-LD.
1212
This is the core implementation, which has to be integrated with Jackson, Jersey etc.
@@ -19,12 +19,12 @@
1919
<maven.compiler.source>${java.version}</maven.compiler.source>
2020
<maven.compiler.target>${java.version}</maven.compiler.target>
2121

22-
<cz.cvut.kbss.jopa.version>2.4.4</cz.cvut.kbss.jopa.version>
22+
<cz.cvut.kbss.jopa.version>2.6.4</cz.cvut.kbss.jopa.version>
2323

2424
<org.junit.jupiter.version>5.11.0</org.junit.jupiter.version>
2525
<org.mockito.version>5.12.0</org.mockito.version>
26-
<ch.qos.logback.version>1.5.18</ch.qos.logback.version>
27-
<org.eclipse.rdf4j.version>5.1.3</org.eclipse.rdf4j.version>
26+
<ch.qos.logback.version>1.5.20</ch.qos.logback.version>
27+
<org.eclipse.rdf4j.version>5.1.6</org.eclipse.rdf4j.version>
2828
</properties>
2929

3030
<dependencies>

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,20 @@ public enum ConfigParam {
125125
* If every deserializer instance should get a fresh type map based on the current configuration, disable this
126126
* cache.
127127
*/
128-
DISABLE_TYPE_MAP_CACHE("disableTypeMapCache");
128+
DISABLE_TYPE_MAP_CACHE("disableTypeMapCache"),
129+
130+
/**
131+
* Set custom classLoader used for resolving types.
132+
*/
133+
CLASS_LOADER("classLoader"),
134+
135+
/**
136+
* Whether to postpone the unresolved references check after deserialization to the cleanup method.
137+
* <p>
138+
* When a JSON-LD is used with @list in it, it refers to objects later in the same JSON-LD.
139+
* In this case, the unresolved references can only be checked after deserializing all entries.
140+
*/
141+
POSTPONE_UNRESOLVED_REFERENCES_CHECK("postponeUnresolvedReferencesCheck");
129142

130143
private final String name;
131144

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
*/
2727
public class Configuration {
2828

29-
private final Map<String, String> config = new HashMap<>();
29+
private final Map<String, Object> config = new HashMap<>();
3030

3131
public Configuration() {
3232
}
@@ -42,15 +42,33 @@ public String get(ConfigParam param) {
4242
}
4343

4444
public String get(String param) {
45-
return config.get(param);
45+
return config.containsKey(param) ? config.get(param).toString() : null;
4646
}
4747

4848
public String get(ConfigParam param, String defaultValue) {
4949
Objects.requireNonNull(param);
50-
return config.getOrDefault(param.getName(), defaultValue);
50+
return get(param.getName(), defaultValue);
5151
}
5252

5353
public String get(String param, String defaultValue) {
54+
return config.getOrDefault(param, defaultValue).toString();
55+
}
56+
57+
public Object getObject(ConfigParam param) {
58+
Objects.requireNonNull(param);
59+
return getObject(param.getName());
60+
}
61+
62+
public Object getObject(String param) {
63+
return config.get(param);
64+
}
65+
66+
public Object getObject(ConfigParam param, Object defaultValue) {
67+
Objects.requireNonNull(param);
68+
return getObject(param.getName(), defaultValue);
69+
}
70+
71+
public Object getObject(String param, Object defaultValue) {
5472
return config.getOrDefault(param, defaultValue);
5573
}
5674

@@ -64,12 +82,12 @@ public boolean is(String param) {
6482
return Boolean.parseBoolean(value);
6583
}
6684

67-
public void set(ConfigParam param, String value) {
85+
public void set(ConfigParam param, Object value) {
6886
Objects.requireNonNull(param);
6987
config.put(param.getName(), value);
7088
}
7189

72-
public void set(String param, String value) {
90+
public void set(String param, Object value) {
7391
Objects.requireNonNull(param);
7492
config.put(param, value);
7593
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* JB4JSON-LD
3+
* Copyright (C) 2025 Czech Technical University in Prague
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 3.0 of the License, or (at your option) any later version.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library.
17+
*/
18+
package cz.cvut.kbss.jsonld.annotation;
19+
20+
import java.lang.annotation.Documented;
21+
import java.lang.annotation.ElementType;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
/**
27+
* Specifies that the class is an entity class and maps to an ontological class (RDFS or OWL).
28+
* <p>
29+
* This annotation is applied to the entity class, if the IRI is empty, then it must be applied to an abstract class.
30+
*/
31+
@Documented
32+
@Retention(RetentionPolicy.RUNTIME)
33+
@Target(ElementType.TYPE)
34+
public @interface JsonLdType {
35+
36+
/**
37+
* IRI of the ontological class. When empty, the annotated class must be abstract.
38+
*
39+
* @return IRI of the referenced class
40+
*/
41+
String iri() default "";
42+
}

src/main/java/cz/cvut/kbss/jsonld/common/BeanAnnotationProcessor.java

Lines changed: 98 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import cz.cvut.kbss.jopa.model.annotations.Types;
2929
import cz.cvut.kbss.jsonld.JsonLd;
3030
import cz.cvut.kbss.jsonld.annotation.JsonLdAttributeOrder;
31+
import cz.cvut.kbss.jsonld.annotation.JsonLdType;
3132
import cz.cvut.kbss.jsonld.exception.JsonLdSerializationException;
3233

3334
import java.lang.reflect.AnnotatedElement;
@@ -69,30 +70,101 @@ public static void setPropertyAccessResolver(PropertyAccessResolver resolver) {
6970
propertyAccessResolver = Objects.requireNonNull(resolver);
7071
}
7172

73+
/**
74+
* Checks whether the specified class is annotated with the {@link OWLClass} or
75+
* {@link JsonLdType} annotation. If it's annotated with {@link JsonLdType} the iri
76+
* is not empty, and it's not an abstract class.
77+
*
78+
* @param cls The class to examine
79+
* @return Whether it is annotated with {@link OWLClass} or {@link JsonLdType}.
80+
*/
81+
public static boolean isInstantiableMappedType(Class<?> cls) {
82+
return isOwlClassEntity(cls) || isJsonLdTypeEntity(cls);
83+
}
84+
85+
/**
86+
* Checks whether the specified class is annotated with the {@link OWLClass} or
87+
* {@link JsonLdType} annotation. If it's annotated with {@link JsonLdType} the IRI
88+
* may be empty and it may be an abstract class;
89+
*
90+
* @param cls The class to examine
91+
* @return Whether it is annotated with {@link OWLClass} or {@link JsonLdType}.
92+
*/
93+
public static boolean isMappedType(Class<?> cls) {
94+
return isInstantiableMappedType(cls) || isJsonLdTypeAbstractEntity(cls);
95+
}
96+
7297
/**
7398
* Checks whether the specified class is annotated with the {@link OWLClass} annotation.
7499
*
75100
* @param cls The class to examine
76101
* @return Whether it is annotated with {@link OWLClass}
77102
*/
78-
public static boolean isOwlClassEntity(Class<?> cls) {
103+
protected static boolean isOwlClassEntity(Class<?> cls) {
79104
return cls != null && cls.getDeclaredAnnotation(OWLClass.class) != null;
80105
}
81106

107+
/**
108+
* Checks whether the specified class is annotated with the {@link JsonLdType} annotation.
109+
* Additionally, iri should also be provided and the class should not be abstract.
110+
*
111+
* @param cls The class to examine
112+
* @return Whether it is annotated with {@link JsonLdType} and iri is provided
113+
*/
114+
protected static boolean isJsonLdTypeEntity(Class<?> cls) {
115+
if (cls == null) {
116+
return false;
117+
}
118+
if (Modifier.isAbstract(cls.getModifiers())) {
119+
return false;
120+
}
121+
JsonLdType jsonLdType = cls.getDeclaredAnnotation(JsonLdType.class);
122+
if (jsonLdType == null) {
123+
return false;
124+
}
125+
return jsonLdType.iri() != null && !jsonLdType.iri().isEmpty();
126+
}
127+
128+
/**
129+
* Checks whether the specified class is annotated with the {@link JsonLdType} annotation.
130+
* Additionally, iri should not be provided and the class should be abstract.
131+
*
132+
* @param cls The class to examine
133+
* @return Whether it is annotated with {@link JsonLdType} and iri is not provided
134+
*/
135+
protected static boolean isJsonLdTypeAbstractEntity(Class<?> cls) {
136+
if (cls == null) {
137+
return false;
138+
}
139+
if (!Modifier.isAbstract(cls.getModifiers())) {
140+
return false;
141+
}
142+
JsonLdType jsonLdType = cls.getDeclaredAnnotation(JsonLdType.class);
143+
if (jsonLdType == null) {
144+
return false;
145+
}
146+
return jsonLdType.iri() != null && jsonLdType.iri().isEmpty();
147+
}
148+
82149
/**
83-
* Gets IRI of the OWL class represented by the specified Java class.
150+
* Gets IRI of the JsonLdType/OWLClass represented by the specified Java class.
84151
*
85152
* @param cls Java class to examine
86153
* @return Represented ontological class
87-
* @throws IllegalArgumentException If the specified class is not mapped by {@link OWLClass}
154+
* @throws IllegalArgumentException If the specified class is not mapped by {@link JsonLdType} {@link OWLClass}
88155
*/
89-
public static String getOwlClass(Class<?> cls) {
156+
public static String getJsonLdTypeOrOwlClass(Class<?> cls) {
90157
Objects.requireNonNull(cls);
91158
final OWLClass owlClass = cls.getDeclaredAnnotation(OWLClass.class);
92-
if (owlClass == null) {
93-
throw new IllegalArgumentException(cls + " is not an OWL class entity.");
159+
if (owlClass != null) {
160+
return expandIriIfNecessary(owlClass.iri(), cls);
161+
}
162+
final JsonLdType jsonLdType = cls.getDeclaredAnnotation(JsonLdType.class);
163+
if (jsonLdType != null) {
164+
return expandIriIfNecessary(jsonLdType.iri(), cls);
165+
94166
}
95-
return expandIriIfNecessary(owlClass.iri(), cls);
167+
throw new IllegalArgumentException(cls + " is not an JsonLdType/OWLClass entity.");
96168
}
97169

98170
/**
@@ -164,34 +236,41 @@ private static Optional<String> resolveNamespace(AnnotatedElement annotated, Str
164236
}
165237

166238
/**
167-
* Resolves ontological types of the specified object, as specified by the {@link OWLClass} annotation.
239+
* Resolves ontological types of the specified object, as specified by the {@link JsonLdType}
240+
* and {@link OWLClass} annotation.
168241
*
169242
* @param object The object to resolve
170-
* @return Resolved OWL types or an empty set if the object's class is not annotated with {@link OWLClass}
171-
* @see #getOwlClasses(Class)
243+
* @return Resolved JsonLdType/OwlClass types or an empty set if the object's class is not annotated
244+
* with {@link JsonLdType} or {@link OWLClass}
245+
* @see #getJsonLdTypeOrOwlClasses(Class)
172246
*/
173-
public static Set<String> getOwlClasses(Object object) {
247+
public static Set<String> getJsonLdTypeOrOwlClasses(Object object) {
174248
Objects.requireNonNull(object);
175249
final Class<?> cls = object.getClass();
176-
return getOwlClasses(cls);
250+
return getJsonLdTypeOrOwlClasses(cls);
177251
}
178252

179253
/**
180-
* Resolves a transitive closure of ontological types of the specified class, as specified by the {@link OWLClass}
181-
* annotation.
254+
* Resolves a transitive closure of ontological types of the specified class, as specified by the {@link JsonLdType}
255+
* and {@link OWLClass} annotation.
182256
* <p>
183257
* I.e. the result contains also types mapped by the class's ancestors.
184258
*
185259
* @param cls The class to process
186260
* @return Set of mapped ontological classes (possibly empty)
187261
*/
188-
public static Set<String> getOwlClasses(Class<?> cls) {
262+
public static Set<String> getJsonLdTypeOrOwlClasses(Class<?> cls) {
189263
final Set<String> classes = new HashSet<>();
190264
getAncestors(cls).forEach(c -> {
191-
final OWLClass owlClass = c.getDeclaredAnnotation(OWLClass.class);
192-
if (owlClass != null) {
193-
classes.add(expandIriIfNecessary(owlClass.iri(), c));
194-
}
265+
final JsonLdType jsonLdType = c.getDeclaredAnnotation(JsonLdType.class);
266+
if (jsonLdType != null) {
267+
classes.add(expandIriIfNecessary(jsonLdType.iri(), c));
268+
} else {
269+
final OWLClass owlClass = c.getDeclaredAnnotation(OWLClass.class);
270+
if (owlClass != null) {
271+
classes.add(expandIriIfNecessary(owlClass.iri(), c));
272+
}
273+
}
195274
});
196275
return classes;
197276
}

src/main/java/cz/cvut/kbss/jsonld/common/Configured.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,10 @@ public interface Configured {
3030
* @return Configuration
3131
*/
3232
Configuration configuration();
33+
34+
/**
35+
* Updates the configuration for this JSON-LD processor.
36+
* @param configuration The {@link Configuration}.
37+
*/
38+
void updateConfiguration(Configuration configuration);
3339
}

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
public class DefaultInstanceBuilder implements InstanceBuilder {
4040

4141
// Identifiers to instances
42-
private final Map<String, Object> knownInstances = new HashMap<>();
42+
private final Map<String, Object> knownInstances;
4343
private final Stack<InstanceContext> openInstances = new Stack<>();
4444

4545
private final TargetClassResolver classResolver;
@@ -52,6 +52,15 @@ public DefaultInstanceBuilder(TargetClassResolver classResolver,
5252
PendingReferenceRegistry pendingReferenceRegistry) {
5353
this.classResolver = classResolver;
5454
this.pendingReferenceRegistry = pendingReferenceRegistry;
55+
this.knownInstances = new HashMap<>();
56+
}
57+
58+
public DefaultInstanceBuilder(TargetClassResolver classResolver,
59+
PendingReferenceRegistry pendingReferenceRegistry,
60+
Map<String, Object> knownInstances) {
61+
this.classResolver = classResolver;
62+
this.pendingReferenceRegistry = pendingReferenceRegistry;
63+
this.knownInstances = knownInstances;
5564
}
5665

5766
@Override
@@ -83,7 +92,7 @@ public void openObject(String id, String property, List<String> types) {
8392
private InstanceContext<?> openObjectForProperty(String id, List<String> types, Field targetField) {
8493
final Class<?> type = targetField.getType();
8594
final Class<?> targetClass = classResolver.getTargetClass(type, types);
86-
assert BeanAnnotationProcessor.isOwlClassEntity(targetClass);
95+
assert BeanAnnotationProcessor.isInstantiableMappedType(targetClass);
8796
if (knownInstances.containsKey(id)) {
8897
return reopenExistingInstance(id, targetClass);
8998
} else {

0 commit comments

Comments
 (0)