From ce3544473d0a8e3e85095afdbaba1e0dc836e6a5 Mon Sep 17 00:00:00 2001 From: Andrew Berezovskyi Date: Sat, 4 Jan 2025 15:26:36 +0100 Subject: [PATCH 1/4] fix: avoid duplicating scanned classes --- .../provider/jena/ordfm/ResourcePackages.java | 11 ++++++----- .../provider/jena/ordfm/ResourcePackagesTests.java | 13 +++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/core/oslc4j-core/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackages.java b/core/oslc4j-core/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackages.java index 7fee97f99..87b2380ea 100644 --- a/core/oslc4j-core/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackages.java +++ b/core/oslc4j-core/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackages.java @@ -49,7 +49,7 @@ public class ResourcePackages { /** * The RDFs-Classes types mapping. */ - static final Map>> TYPES_MAPPINGS = new HashMap<>(); + static final Map>> TYPES_MAPPINGS = new HashMap<>(); private ResourcePackages() {} @@ -78,7 +78,7 @@ public static synchronized void mapPackage(Package pkg) { Class rdfClass = Class.forName(classInfo.getName(), true, Thread.currentThread().getContextClassLoader()); String rdfType = TypeFactory.getQualifiedName(rdfClass); - List> types = TYPES_MAPPINGS.computeIfAbsent(rdfType, k -> new ArrayList<>()); + var types = TYPES_MAPPINGS.computeIfAbsent(rdfType, k -> new HashSet<>()); types.add(rdfClass); counter ++; LOGGER.trace("[+] {} -> {}", rdfType, rdfClass); @@ -157,12 +157,13 @@ public static Optional> getClassOf(Resource resource, Class... prefe while(rdfTypes.hasNext()) { Statement statement = rdfTypes.nextStatement(); String typeURI = statement.getObject().asResource().getURI(); - List> rdfClasses = TYPES_MAPPINGS.get(typeURI); + var rdfClasses = TYPES_MAPPINGS.get(typeURI); if (rdfClasses == null) { LOGGER.trace("[-] Unmapped class(es) for RDF:type {}", typeURI); } else if (rdfClasses.size() == 1) { - candidates.add(rdfClasses.get(0)); - LOGGER.trace("[+] Candidate class {} found for RDF:type {}", rdfClasses.get(0).getName(), typeURI); + var candidate = rdfClasses.stream().findFirst().get(); + candidates.add(candidate); + LOGGER.trace("[+] Candidate class {} found for RDF:type {}", candidate.getName(), typeURI); } else if (preferredTypes.length == 0) { StringBuilder sb = new StringBuilder(); sb.append("'preferredTypes' argument is required when more than one class ("); diff --git a/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackagesTests.java b/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackagesTests.java index 02ece529b..f01dac46d 100644 --- a/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackagesTests.java +++ b/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackagesTests.java @@ -7,6 +7,7 @@ import org.apache.jena.rdf.model.ResourceFactory; import org.apache.jena.vocabulary.RDF; import org.eclipse.lyo.oslc4j.provider.jena.resources.Cat; +import org.eclipse.lyo.oslc4j.provider.jena.resources.Dog; import org.eclipse.lyo.oslc4j.provider.jena.resources.Pet; import org.eclipse.lyo.oslc4j.provider.jena.resources.WildDog; import org.junit.After; @@ -48,6 +49,18 @@ public void testMapPackage() { Assert.assertEquals(7, ResourcePackages.TYPES_MAPPINGS.keySet().size()); } + @Test + public void testMapPackageTwice() { + ResourcePackages.mapPackage(Pet.class.getPackage()); + ResourcePackages.mapPackage(Dog.class.getPackage()); + for (String aPackage : ResourcePackages.SCANNED_PACKAGES) { + log.info("Scanned package: {}", aPackage); + } + + Assert.assertEquals(1, ResourcePackages.SCANNED_PACKAGES.size()); + Assert.assertEquals(7, ResourcePackages.TYPES_MAPPINGS.keySet().size()); + } + @Test public void testGetClassOf_noMapping() { Assert.assertEquals(false, ResourcePackages.getClassOf(resource).isPresent()); From e6e6674ae9036d69d6ef87367990c9df3a676b04 Mon Sep 17 00:00:00 2001 From: Andrew Berezovskyi Date: Sat, 4 Jan 2025 15:55:41 +0100 Subject: [PATCH 2/4] fix: avoid duplicating scanned classes + test --- .../provider/jena/ordfm/ResourcePackages.java | 35 +++++++++--------- .../jena/ordfm/ResourcePackagesTests.java | 17 ++++++--- .../jena/resources/child/ChildAnimal.java | 36 +++++++++++++++++++ 3 files changed, 67 insertions(+), 21 deletions(-) create mode 100644 core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/resources/child/ChildAnimal.java diff --git a/core/oslc4j-core/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackages.java b/core/oslc4j-core/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackages.java index 87b2380ea..3e0010494 100644 --- a/core/oslc4j-core/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackages.java +++ b/core/oslc4j-core/src/main/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackages.java @@ -39,7 +39,7 @@ public class ResourcePackages { /** * The logger of this class. */ - private static final Logger LOGGER = LoggerFactory.getLogger(ResourcePackages.class); + private static final Logger log = LoggerFactory.getLogger(ResourcePackages.class); /** * The set of scanned packages. @@ -62,33 +62,36 @@ private ResourcePackages() {} public static synchronized void mapPackage(Package pkg) { String packageName = pkg.getName(); if (SCANNED_PACKAGES.contains(packageName)) { - LOGGER.trace("> package {} already scanned", packageName); + log.trace("> package {} already scanned", packageName); } else { int counter = 0; - LOGGER.trace("> scanning package {}", packageName); + log.trace("> scanning package {}", packageName); ClassGraph classGraph = new ClassGraph().acceptPackages(pkg.getName()); classGraph = classGraph.enableClassInfo().enableAnnotationInfo(); try (ScanResult scanResult = classGraph.scan()) { ClassInfoList classInforList = scanResult.getClassesWithAnnotation(OslcResourceShape.class.getName()); for (ClassInfo classInfo : classInforList) { if (classInfo.isAbstract()) { - LOGGER.trace("[-] Abstract class: {}", classInfo.getName()); + log.trace("[-] Abstract class: {}", classInfo.getName()); } else { try { Class rdfClass = Class.forName(classInfo.getName(), true, Thread.currentThread().getContextClassLoader()); String rdfType = TypeFactory.getQualifiedName(rdfClass); var types = TYPES_MAPPINGS.computeIfAbsent(rdfType, k -> new HashSet<>()); - types.add(rdfClass); - counter ++; - LOGGER.trace("[+] {} -> {}", rdfType, rdfClass); + if(types.add(rdfClass)) { + counter ++; + log.trace("[+] {} -> {}", rdfType, rdfClass); + } else { + log.trace("[.] {} already registered", rdfClass.getName()); + } } catch (ClassNotFoundException ex) { - LOGGER.trace("[-] Unexpected missing class: {}", classInfo.getName()); + log.trace("[-] Unexpected missing class: {}", classInfo.getName()); } } } } - LOGGER.debug("< {} RDF classes found in package {}", counter, packageName); + log.debug("< {} RDF classes found in package {}", counter, packageName); SCANNED_PACKAGES.add(packageName); } } @@ -150,7 +153,7 @@ private static Class getMostConcreteClassOf(List> candidates) { * inheritance tree) is annotated to be mapped by the same {@code RDF:type}. */ public static Optional> getClassOf(Resource resource, Class... preferredTypes) { - LOGGER.trace("> resolving class for resource {}", resource.getURI()); + log.trace("> resolving class for resource {}", resource.getURI()); StmtIterator rdfTypes = resource.listProperties(RDF.type); List> candidates = new ArrayList<>(); synchronized(ResourcePackages.class) { @@ -159,11 +162,11 @@ public static Optional> getClassOf(Resource resource, Class... prefe String typeURI = statement.getObject().asResource().getURI(); var rdfClasses = TYPES_MAPPINGS.get(typeURI); if (rdfClasses == null) { - LOGGER.trace("[-] Unmapped class(es) for RDF:type {}", typeURI); + log.trace("[-] Unmapped class(es) for RDF:type {}", typeURI); } else if (rdfClasses.size() == 1) { var candidate = rdfClasses.stream().findFirst().get(); candidates.add(candidate); - LOGGER.trace("[+] Candidate class {} found for RDF:type {}", candidate.getName(), typeURI); + log.trace("[+] Candidate class {} found for RDF:type {}", candidate.getName(), typeURI); } else if (preferredTypes.length == 0) { StringBuilder sb = new StringBuilder(); sb.append("'preferredTypes' argument is required when more than one class ("); @@ -171,13 +174,13 @@ public static Optional> getClassOf(Resource resource, Class... prefe sb.append(") are mapped to the same RDF:type ("); sb.append(typeURI); sb.append(")"); - LOGGER.debug(sb.toString()); + log.debug(sb.toString()); throw new IllegalArgumentException(sb.toString()); } else { for(Class preferredType : preferredTypes) { if (rdfClasses.contains(preferredType)) { candidates.add(preferredType); - LOGGER.trace("[+] Preferred candidate class {} found for RDF:type {}", + log.trace("[+] Preferred candidate class {} found for RDF:type {}", preferredType.getName(), typeURI); break; } @@ -186,11 +189,11 @@ public static Optional> getClassOf(Resource resource, Class... prefe } } if (candidates.isEmpty()) { - LOGGER.debug("< Unmapped class for resource {}", resource.getURI()); + log.debug("< Unmapped class for resource {}", resource.getURI()); return Optional.empty(); } else { Class mappedClass = (candidates.size() == 1 ? candidates.get(0) : getMostConcreteClassOf(candidates)); - LOGGER.debug("< Mapped class {} for resource {}", mappedClass.getName(), resource.getURI()); + log.debug("< Mapped class {} for resource {}", mappedClass.getName(), resource.getURI()); return Optional.of(mappedClass); } } diff --git a/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackagesTests.java b/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackagesTests.java index f01dac46d..ccf65933d 100644 --- a/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackagesTests.java +++ b/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackagesTests.java @@ -1,15 +1,16 @@ package org.eclipse.lyo.oslc4j.provider.jena.ordfm; import java.util.Optional; + import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.ResourceFactory; import org.apache.jena.vocabulary.RDF; import org.eclipse.lyo.oslc4j.provider.jena.resources.Cat; -import org.eclipse.lyo.oslc4j.provider.jena.resources.Dog; import org.eclipse.lyo.oslc4j.provider.jena.resources.Pet; import org.eclipse.lyo.oslc4j.provider.jena.resources.WildDog; +import org.eclipse.lyo.oslc4j.provider.jena.resources.child.ChildAnimal; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -46,19 +47,25 @@ public void testMapPackage() { } Assert.assertEquals(1, ResourcePackages.SCANNED_PACKAGES.size()); - Assert.assertEquals(7, ResourcePackages.TYPES_MAPPINGS.keySet().size()); + Assert.assertEquals(8, ResourcePackages.TYPES_MAPPINGS.keySet().size()); } @Test public void testMapPackageTwice() { + ResourcePackages.mapPackage(ChildAnimal.class.getPackage()); ResourcePackages.mapPackage(Pet.class.getPackage()); - ResourcePackages.mapPackage(Dog.class.getPackage()); + // parent package for (String aPackage : ResourcePackages.SCANNED_PACKAGES) { log.info("Scanned package: {}", aPackage); } - Assert.assertEquals(1, ResourcePackages.SCANNED_PACKAGES.size()); - Assert.assertEquals(7, ResourcePackages.TYPES_MAPPINGS.keySet().size()); + Assert.assertEquals(2, ResourcePackages.SCANNED_PACKAGES.size()); + Assert.assertEquals(8, ResourcePackages.TYPES_MAPPINGS.keySet().size()); + // ensure the ChildAnimal class is mapped once + Assert.assertEquals(1, ResourcePackages.TYPES_MAPPINGS.entrySet().stream() + .flatMap(it1 -> it1.getValue().stream()) + .filter(it -> it.getCanonicalName().equals(ChildAnimal.class.getCanonicalName())) + .count()); } @Test diff --git a/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/resources/child/ChildAnimal.java b/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/resources/child/ChildAnimal.java new file mode 100644 index 000000000..84077c8df --- /dev/null +++ b/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/resources/child/ChildAnimal.java @@ -0,0 +1,36 @@ +package org.eclipse.lyo.oslc4j.provider.jena.resources.child; + +import org.eclipse.lyo.oslc4j.core.annotation.OslcNamespace; +import org.eclipse.lyo.oslc4j.core.annotation.OslcPropertyDefinition; +import org.eclipse.lyo.oslc4j.core.annotation.OslcResourceShape; +import org.eclipse.lyo.oslc4j.core.model.AbstractResource; +import org.eclipse.lyo.oslc4j.provider.jena.resources.Pet; + +/** + * A concrete base class for Pet implementations. + * @author rherrera + */ +@OslcNamespace("http://locahost:7001/vocabulary/") +@OslcResourceShape(title = "AbstractTypesTest2") +public class ChildAnimal extends AbstractResource implements Pet { + + private String name; + + @Override + @OslcPropertyDefinition("http://locahost:7001/vocabulary/name") + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public void eat() { + System.out.println("Eating like an animal"); + } + + +} From a778c5a33a5b9b5fd051a8f499be72cf2885ec55 Mon Sep 17 00:00:00 2001 From: Andrew Berezovskyi Date: Sat, 4 Jan 2025 15:57:51 +0100 Subject: [PATCH 3/4] docs: CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6377b02c7..afff05859 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ This release does not introduce deprecations. ### Fixed - Client now picks the correct ResponseInfo object when an OSLC Query response contains multiple ResponseInfo objects. +- Lyo object-graph mapping (OGM) framework no longer registers duplicate classes when doing recursive scans. ## [6.0.0] From 64881ae37b798b53c4de5d8c1a06bf989020b6bc Mon Sep 17 00:00:00 2001 From: Andrew Berezovskyi Date: Sat, 4 Jan 2025 16:00:36 +0100 Subject: [PATCH 4/4] chore: cleanup --- .../lyo/oslc4j/provider/jena/ordfm/ResourcePackagesTests.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackagesTests.java b/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackagesTests.java index ccf65933d..47acdb620 100644 --- a/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackagesTests.java +++ b/core/oslc4j-jena-provider/src/test/java/org/eclipse/lyo/oslc4j/provider/jena/ordfm/ResourcePackagesTests.java @@ -54,7 +54,6 @@ public void testMapPackage() { public void testMapPackageTwice() { ResourcePackages.mapPackage(ChildAnimal.class.getPackage()); ResourcePackages.mapPackage(Pet.class.getPackage()); - // parent package for (String aPackage : ResourcePackages.SCANNED_PACKAGES) { log.info("Scanned package: {}", aPackage); } @@ -63,7 +62,7 @@ public void testMapPackageTwice() { Assert.assertEquals(8, ResourcePackages.TYPES_MAPPINGS.keySet().size()); // ensure the ChildAnimal class is mapped once Assert.assertEquals(1, ResourcePackages.TYPES_MAPPINGS.entrySet().stream() - .flatMap(it1 -> it1.getValue().stream()) + .flatMap(it -> it.getValue().stream()) .filter(it -> it.getCanonicalName().equals(ChildAnimal.class.getCanonicalName())) .count()); }