Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: avoid duplicating scanned classes #674

Merged
merged 4 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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<>();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ben81 please note that the map is not "org.eclipse.lyo.oslc4j.provider.jena.resources.child.ChildAnimal" -> [ChildAnimal.class] but instead "http://locahost:7001/vocabulary/AbstractTypesTest2"->[ChildAnimal.class]. It can happen that you annotate multiple classes with the same @OslcNamespace("http://locahost:7001/vocabulary/")/@OslcResourceShape(title = "AbstractTypesTest2") annotation pair and a collection on the map value is designed to handle this.


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");
}


}
Loading