Skip to content

Commit

Permalink
fix: avoid duplicating scanned classes (#674)
Browse files Browse the repository at this point in the history
* fix: avoid duplicating scanned classes + test
* docs: CHANGELOG entry
  • Loading branch information
berezovskyi authored Jan 6, 2025
1 parent cf95cb6 commit ae59caf
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -49,7 +49,7 @@ public class ResourcePackages {
/**
* The RDFs-Classes types mapping.
*/
static final Map<String, List<Class<?>>> TYPES_MAPPINGS = new HashMap<>();
static final Map<String, Set<Class<?>>> TYPES_MAPPINGS = new HashMap<>();

private ResourcePackages() {}

Expand All @@ -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);
List<Class<?>> types = TYPES_MAPPINGS.computeIfAbsent(rdfType, k -> new ArrayList<>());
types.add(rdfClass);
counter ++;
LOGGER.trace("[+] {} -> {}", rdfType, rdfClass);
var types = TYPES_MAPPINGS.computeIfAbsent(rdfType, k -> new HashSet<>());
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);
}
}
Expand Down Expand Up @@ -150,33 +153,34 @@ private static Class<?> getMostConcreteClassOf(List<Class<?>> candidates) {
* inheritance tree) is annotated to be mapped by the same {@code RDF:type}.
*/
public static Optional<Class<?>> 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<Class<?>> candidates = new ArrayList<>();
synchronized(ResourcePackages.class) {
while(rdfTypes.hasNext()) {
Statement statement = rdfTypes.nextStatement();
String typeURI = statement.getObject().asResource().getURI();
List<Class<?>> rdfClasses = TYPES_MAPPINGS.get(typeURI);
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) {
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);
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 (");
sb.append(rdfClasses.toString());
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;
}
Expand All @@ -185,11 +189,11 @@ public static Optional<Class<?>> 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);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
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;
Expand All @@ -9,6 +10,7 @@
import org.eclipse.lyo.oslc4j.provider.jena.resources.Cat;
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;
Expand Down Expand Up @@ -45,7 +47,24 @@ 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());
for (String aPackage : ResourcePackages.SCANNED_PACKAGES) {
log.info("Scanned package: {}", aPackage);
}

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(it -> it.getValue().stream())
.filter(it -> it.getCanonicalName().equals(ChildAnimal.class.getCanonicalName()))
.count());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -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");
}


}

0 comments on commit ae59caf

Please sign in to comment.