Skip to content

Commit

Permalink
Merge pull request #72 from kbss-cvut/development
Browse files Browse the repository at this point in the history
[0.15.0] Release
  • Loading branch information
ledsoft authored Aug 26, 2024
2 parents df11fb7 + 3926c4d commit a4aa36e
Show file tree
Hide file tree
Showing 18 changed files with 374 additions and 70 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '11'
java-version: '17'
- name: Build with Maven
run: mvn -B package --file pom.xml
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# JB4JSON-LD Changelog

## 0.15.0 - 2024-08-26
- Support deserializing objects containing only identifier when `ASSUME_TARGET_TYPE` is enabled (Enhancement #69).
- Cache deserialization type map (Enhancement #68).
- Dependency updates: JOPA 2.0.4, test deps.
- **Breaking change:** Set Java 17 as minimum Java version.

## 0.14.3 - 2024-04-16
- Fix missing context entry when serializing empty collection mapped to data/annotation property.

Expand Down
41 changes: 19 additions & 22 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>cz.cvut.kbss.jsonld</groupId>
<artifactId>jb4jsonld</artifactId>
<version>0.14.3</version>
<version>0.15.0</version>
<name>JB4JSON-LD</name>
<description>Java Binding for JSON-LD allows serialization and deserialization of Java POJOs to/from JSON-LD.
This is the core implementation, which has to be integrated with Jackson, Jersey etc.
Expand All @@ -15,15 +15,16 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>

<cz.cvut.kbss.jopa.version>1.2.2</cz.cvut.kbss.jopa.version>
<cz.cvut.kbss.jopa.version>2.0.4</cz.cvut.kbss.jopa.version>

<org.junit.jupiter.version>5.9.2</org.junit.jupiter.version>
<org.mockito.version>4.11.0</org.mockito.version>
<ch.qos.logback.version>1.4.14</ch.qos.logback.version>
<org.eclipse.rdf4j.version>4.3.10</org.eclipse.rdf4j.version>
<org.junit.jupiter.version>5.11.0</org.junit.jupiter.version>
<org.mockito.version>5.12.0</org.mockito.version>
<ch.qos.logback.version>1.5.7</ch.qos.logback.version>
<org.eclipse.rdf4j.version>5.0.2</org.eclipse.rdf4j.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -79,13 +80,13 @@
<dependency>
<groupId>com.apicatalog</groupId>
<artifactId>titanium-json-ld</artifactId>
<version>1.4.0</version>
<version>1.4.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.parsson</groupId>
<artifactId>jakarta.json</artifactId>
<version>1.1.5</version>
<version>1.1.7</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -152,18 +153,14 @@
</executions>
</plugin>
<plugin>
<!-- explicitly define maven-deploy-plugin after other to force exec order -->
<artifactId>maven-deploy-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>deploy</id>
<phase>deploy</phase>
<goals>
<goal>deploy</goal>
</goals>
</execution>
</executions>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.5.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
<autoPublish>true</autoPublish>
</configuration>
</plugin>
</plugins>
</build>
Expand Down
35 changes: 25 additions & 10 deletions src/main/java/cz/cvut/kbss/jsonld/ConfigParam.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ public enum ConfigParam {
/**
* Whether to require an identifier when serializing an object.
* <p>
* If set to {@code true} and no identifier is found (either there is no identifier field or its value is {@code
* null}), an exception will be thrown. If configured to {@code false}, a blank node identifier is generated if no
* id is present.
* If set to {@code true} and no identifier is found (either there is no identifier field or its value is
* {@code null}), an exception will be thrown. If configured to {@code false}, a blank node identifier is generated
* if no id is present.
*/
REQUIRE_ID("requireId"),

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

/**
* Whether to serialize individuals using expanded term definition in context.
*
* <p>
* This basically means that the individual's identifier is provided directly as a string and an expanded term
* definition (consisting of a {@literal @id} and {@literal @type}) is added into the context, specifying that the string is an identifier.
* definition (consisting of a {@literal @id} and {@literal @type}) is added into the context, specifying that the
* string is an identifier.
*/
SERIALIZE_INDIVIDUALS_USING_EXPANDED_DEFINITION("serializeIndividualsUsingExpandedDefinition"),

/**
* Whether to disable the type map cache.
* <p>
* Type map is used to map JSON-LD types to Java classes for deserialization. Since this map is built by scanning
* the classpath (see the {@link #SCAN_PACKAGE} parameter), it is cached by default to avoid repeated scanning of
* the classpath.
* <p>
* If every deserializer instance should get a fresh type map based on the current configuration, disable this
* cache.
*/
SERIALIZE_INDIVIDUALS_USING_EXPANDED_DEFINITION("serializeIndividualsUsingExpandedDefinition");
DISABLE_TYPE_MAP_CACHE("disableTypeMapCache");

private final String name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,31 @@
*/
package cz.cvut.kbss.jsonld.common;

import cz.cvut.kbss.jopa.model.annotations.Id;
import cz.cvut.kbss.jopa.model.annotations.Namespace;
import cz.cvut.kbss.jopa.model.annotations.Namespaces;
import cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty;
import cz.cvut.kbss.jopa.model.annotations.OWLClass;
import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty;
import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty;
import cz.cvut.kbss.jopa.model.annotations.Properties;
import cz.cvut.kbss.jopa.model.annotations.*;
import cz.cvut.kbss.jopa.model.annotations.Types;
import cz.cvut.kbss.jsonld.JsonLd;
import cz.cvut.kbss.jsonld.annotation.JsonLdAttributeOrder;
import cz.cvut.kbss.jsonld.exception.JsonLdSerializationException;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -126,7 +141,7 @@ private static Optional<String> expandIri(String iri, Class<?> declaringClass) {
}
}
return declaringClass.getSuperclass() != null ? expandIri(iri, declaringClass.getSuperclass()) :
Optional.empty();
Optional.empty();
}

private static Optional<String> resolveNamespace(AnnotatedElement annotated, String prefix) {
Expand Down Expand Up @@ -418,6 +433,14 @@ public static String getAttributeIdentifier(Field field) {
throw new JsonLdSerializationException("Field " + field + " is not JSON-LD serializable.");
}

/**
* Gets the identifier field of the specified class.
* <p>
* That is, gets the field annotated with {@link Id}, even inherited.
*
* @param cls Class whose identifier field to retrieve
* @return Matching field, optionally empty
*/
public static Optional<Field> getIdentifierField(Class<?> cls) {
return getMarshallableFields(cls, (f, c) -> f.isAnnotationPresent(Id.class)).stream().findFirst();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import cz.cvut.kbss.jsonld.exception.JsonLdDeserializationException;

import java.util.List;
import java.util.Optional;

/**
* Builds instances from parsed JSON-LD.
Expand Down Expand Up @@ -58,8 +57,8 @@ public interface InstanceBuilder {
* <p>
* The new instance also becomes the currently open object.
* <p>
* This method is intended for creating top level objects or adding objects to collections. Use {@link
* #openObject(String, String, List)} for opening objects as values of attributes.
* This method is intended for creating top level objects or adding objects to collections. Use
* {@link #openObject(String, String, List)} for opening objects as values of attributes.
*
* @param <T> The type of the object to open
* @param id Identifier of the object being open
Expand All @@ -82,8 +81,8 @@ public interface InstanceBuilder {
* <p>
* The new collection also becomes the currently open object.
* <p>
* This method should also verify cardinality, i.e. a collection cannot be set as value of a field mapped by {@code
* property}, if the field is singular.
* This method should also verify cardinality, i.e. a collection cannot be set as value of a field mapped by
* {@code property}, if the field is singular.
* <p>
* This method assumes that the property is mapped, i.e. that {@link #isPropertyMapped(String)} returned true.
*
Expand All @@ -99,8 +98,8 @@ public interface InstanceBuilder {
* <p>
* The new collection also becomes the currently open object.
* <p>
* This method is intended for creating top level collections or nesting collections. Use {@link
* #openCollection(String)} for opening collections as values of attributes.
* This method is intended for creating top level collections or nesting collections. Use
* {@link #openCollection(String)} for opening collections as values of attributes.
*
* @param collectionType Type of the JSON collection to instantiate in Java
* @see #openCollection(String)
Expand Down Expand Up @@ -188,8 +187,8 @@ public interface InstanceBuilder {
* Checks whether the current collection context represents a {@link cz.cvut.kbss.jopa.model.annotations.Properties}
* attribute.
*
* @return {@code true} if the current context is a collection representing a {@link
* cz.cvut.kbss.jopa.model.annotations.Properties} field
* @return {@code true} if the current context is a collection representing a
* {@link cz.cvut.kbss.jopa.model.annotations.Properties} field
*/
boolean isCurrentCollectionProperties();

Expand Down Expand Up @@ -239,11 +238,12 @@ public interface InstanceBuilder {

/**
* Gets the target type of the specified property.
*
* That is, it retrieves the type of the field to which the specified property is mapped. If the target field is a collection,
* the element type is returned (equivalent to {@link #getCurrentCollectionElementType()}).
*
* <p>
* That is, it retrieves the type of the field to which the specified property is mapped. If the target field is a
* collection, the element type is returned (equivalent to {@link #getCurrentCollectionElementType()}).
* <p>
* This method assumes that the property is mapped, i.e. that {@link #isPropertyMapped(String)} returned true.
*
* @param property Property whose target type to resolve
* @return Target type wrapped in an optional
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
*/
public abstract class JsonLdDeserializer implements Configured {

private static final TypeMap TYPE_MAP = new TypeMap();

private final Configuration configuration;

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

private TargetClassResolver initializeTargetClassResolver() {
final TypeMap typeMap = new TypeMap();
final String scanPath = configuration.get(ConfigParam.SCAN_PACKAGE, "");
final TypeMap typeMap = discoverAvailableTypes(scanPath, configuration.is(ConfigParam.DISABLE_TYPE_MAP_CACHE));
return new TargetClassResolver(typeMap,
new TargetClassResolverConfig(
configuration.is(ConfigParam.ASSUME_TARGET_TYPE),
configuration().is(ConfigParam.ENABLE_OPTIMISTIC_TARGET_TYPE_RESOLUTION),
configuration().is(ConfigParam.PREFER_SUPERCLASS)));
}

/**
* Finds potential deserialization target types on the classpath.
*
* @param scanPath Path to scan on classpath
* @return Map of types to Java classes
*/
private static TypeMap discoverAvailableTypes(String scanPath, boolean disableCache) {
final TypeMap map = disableCache ? new TypeMap() : TYPE_MAP;
if (!map.isEmpty()) {
return map;
}
new ClasspathScanner(c -> {
final OWLClass ann = c.getDeclaredAnnotation(OWLClass.class);
if (ann != null) {
typeMap.register(BeanAnnotationProcessor.expandIriIfNecessary(ann.iri(), c), c);
map.register(BeanAnnotationProcessor.expandIriIfNecessary(ann.iri(), c), c);
}
}).processClasses(scanPath);
return new TargetClassResolver(typeMap,
new TargetClassResolverConfig(
configuration.is(ConfigParam.ASSUME_TARGET_TYPE),
configuration().is(ConfigParam.ENABLE_OPTIMISTIC_TARGET_TYPE_RESOLUTION),
configuration().is(ConfigParam.PREFER_SUPERCLASS)));
return map;
}

@Override
Expand All @@ -78,9 +94,9 @@ public Configuration configuration() {
* <p>
* If a deserializer already existed for the type, it is replaced by the new one.
*
* @param type Target type to register the deserializer for
* @param type Target type to register the deserializer for
* @param deserializer Deserializer to register
* @param <T> Target type
* @param <T> Target type
*/
public <T> void registerDeserializer(Class<T> type, ValueDeserializer<T> deserializer) {
Objects.requireNonNull(type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
*/
package cz.cvut.kbss.jsonld.deserialization.expanded;

import cz.cvut.kbss.jsonld.ConfigParam;
import cz.cvut.kbss.jsonld.Configuration;
import cz.cvut.kbss.jsonld.deserialization.DefaultInstanceBuilder;
import cz.cvut.kbss.jsonld.deserialization.DeserializationContext;
import cz.cvut.kbss.jsonld.deserialization.InstanceBuilder;
import cz.cvut.kbss.jsonld.deserialization.JsonLdDeserializer;
import cz.cvut.kbss.jsonld.deserialization.reference.AssumedTypeReferenceReplacer;
import cz.cvut.kbss.jsonld.deserialization.reference.PendingReferenceRegistry;
import cz.cvut.kbss.jsonld.exception.JsonLdDeserializationException;
import jakarta.json.JsonArray;
Expand Down Expand Up @@ -59,6 +61,9 @@ public <T> T deserialize(JsonValue jsonLd, Class<T> resultClass) {
final InstanceBuilder instanceBuilder = new DefaultInstanceBuilder(classResolver, referenceRegistry);
new ObjectDeserializer(instanceBuilder, new DeserializerConfig(configuration(), classResolver, deserializers), resultClass)
.processValue(root);
if (configuration().is(ConfigParam.ASSUME_TARGET_TYPE)) {
new AssumedTypeReferenceReplacer().replacePendingReferencesWithAssumedTypedObjects(referenceRegistry);
}
referenceRegistry.verifyNoUnresolvedReferencesExist();
assert resultClass.isAssignableFrom(instanceBuilder.getCurrentRoot().getClass());
return resultClass.cast(instanceBuilder.getCurrentRoot());
Expand Down
Loading

0 comments on commit a4aa36e

Please sign in to comment.